MDStressLab++
Loading...
Searching...
No Matches
testIdealGas.cpp

Demonstrates kinetic Cauchy stress for an ideal gas with nonzero bulk velocity. The test verifies that MDStressLab subtracts the continuum velocity before forming the kinetic stress and writes the Cauchy-only momentum and mass density fields. It compares MethodSphere, MethodLdadConstant, and MethodLdadTrigonometric to the instantaneous ideal-gas pressure and to the expected mean mass density.

Full code:

1#include "BoxConfiguration.h"
2#include "Grid.h"
3#include "MethodLdad.h"
4#include "MethodSphere.h"
5#include "Stress.h"
6#include "calculateStress.h"
7#include "typedef.h"
8#include <cmath>
9#include <fstream>
10#include <iostream>
11#include <tuple>
12
13namespace {
14void validateVoxelGridFile(const std::string& filename,
15 const std::string& expectedGridCellsHeader,
16 const int expectedNumberOfGridPoints,
17 const int expectedDimension)
18{
19 std::ifstream file(filename);
20 if (!file) MY_ERROR("ERROR: " + filename + " could not be opened for reading.");
21
22 std::string line;
23 bool foundGridSize= false;
24 bool foundGridCells= false;
25 bool foundDimension= false;
26 int dataLineCount= 0;
27 while (std::getline(file,line))
28 {
29 if (line == "ITEM: DIMENSION")
30 {
31 int dimension;
32 if (!(file >> dimension))
33 MY_ERROR("ERROR: Could not read voxel grid dimension from " + filename);
34 file.ignore(32767,'\n');
35 if (dimension != expectedDimension)
36 MY_ERROR("ERROR: Voxel grid dimension does not match expected dimension in " + filename);
37 foundDimension= true;
38 }
39 else if (line == "ITEM: GRID SIZE nx ny nz")
40 {
41 int nx,ny,nz;
42 if (!(file >> nx >> ny >> nz))
43 MY_ERROR("ERROR: Could not read voxel grid dimensions from " + filename);
44 file.ignore(32767,'\n');
45 if (nx*ny*nz != expectedNumberOfGridPoints)
46 MY_ERROR("ERROR: Voxel grid dimensions do not match expected grid size in " + filename);
47 foundGridSize= true;
48 }
49 else if (line == expectedGridCellsHeader)
50 {
51 foundGridCells= true;
52 break;
53 }
54 }
55
56 if (!foundDimension)
57 MY_ERROR("ERROR: Missing voxel grid dimension header in " + filename);
58 if (!foundGridSize)
59 MY_ERROR("ERROR: Missing voxel grid size header in " + filename);
60 if (!foundGridCells)
61 MY_ERROR("ERROR: Missing expected voxel grid cells header in " + filename);
62
63 while (std::getline(file,line))
64 if (!line.empty())
65 ++dataLineCount;
66
67 if (dataLineCount != expectedNumberOfGridPoints)
68 MY_ERROR("ERROR: Voxel grid data line count does not match grid size in " + filename);
69}
70
71template<typename TMethod>
72void validateIdealGasStress(const Stress<TMethod,Cauchy>& kineticStress,
73 const Matrix3d& expectedStress,
74 const Matrix3d& rawVelocityStress,
75 const double& expectedMassDensity,
76 const double& meanStressTolerance,
77 const double& pointwiseStressTolerance,
78 const double& densityTolerance)
79{
80 Matrix3d meanStress= Matrix3d::Zero();
81 Matrix3d maxAbsDeviation= Matrix3d::Zero();
82 double meanMassDensity= 0.0;
83
84 for (int i_grid=0; i_grid<kineticStress.field.size(); ++i_grid)
85 {
86 const auto& stress= kineticStress.field[i_grid];
87 meanStress+= stress;
88 maxAbsDeviation= maxAbsDeviation.cwiseMax((stress-expectedStress).cwiseAbs());
89 meanMassDensity+= kineticStress.massDensityField[i_grid];
90
91 if (kineticStress.massDensityField[i_grid] < -epsilon)
92 MY_ERROR("Mass density should be nonnegative.");
93
94 if (kineticStress.massDensityField[i_grid] > epsilon)
95 {
96 Vector3d expectedVelocity=
97 kineticStress.momentumDensityField[i_grid]/kineticStress.massDensityField[i_grid];
98 if ((kineticStress.velocityField[i_grid]-expectedVelocity).norm() > 1e-12)
99 MY_ERROR("Continuum velocity does not equal momentum density divided by mass density.");
100 }
101 else
102 {
103 if (kineticStress.velocityField[i_grid].norm() > 1e-12)
104 MY_ERROR("Continuum velocity should be zero where mass density is zero.");
105 }
106 }
107
108 const double numberOfGridPoints= static_cast<double>(kineticStress.field.size());
109 meanStress/= numberOfGridPoints;
110 meanMassDensity/= numberOfGridPoints;
111
112 if ((rawVelocityStress-expectedStress).cwiseAbs().maxCoeff() < 10.0*meanStressTolerance)
113 MY_ERROR("Bulk velocity is too small to distinguish raw-velocity stress from relative-velocity stress.");
114
115 if (std::abs(meanMassDensity-expectedMassDensity) > densityTolerance)
116 {
117 std::cout << "Expected mass density = " << expectedMassDensity << " amu/A^3" << std::endl;
118 std::cout << "Mean computed mass density = " << meanMassDensity << " amu/A^3" << std::endl;
119 MY_ERROR("Ideal gas mass density mean does not match the normalized-kernel value.");
120 }
121
122 if ((meanStress-expectedStress).cwiseAbs().maxCoeff() > meanStressTolerance)
123 {
124 std::cout << "Expected stress:\n" << expectedStress << std::endl;
125 std::cout << "Mean computed stress:\n" << meanStress << std::endl;
126 MY_ERROR("Ideal gas kinetic stress mean does not match the instantaneous ideal gas value.");
127 }
128
129 if (maxAbsDeviation.diagonal().maxCoeff() > pointwiseStressTolerance)
130 {
131 std::cout << "Expected stress:\n" << expectedStress << std::endl;
132 std::cout << "Maximum pointwise absolute deviation:\n" << maxAbsDeviation << std::endl;
133 MY_ERROR("Ideal gas kinetic stress field is too far from the expected uniform field.");
134 }
135
136 std::cout << kineticStress.name << " mean mass density = "
137 << meanMassDensity << " amu/A^3" << std::endl;
138 std::cout << kineticStress.name << " mean computed stress:\n" << meanStress << std::endl;
139}
140}
141
142int main()
143{
144 const std::string configFileName= "idealGas.lmp";
145 std::ifstream file(configFileName);
146 if(!file) MY_ERROR("ERROR: idealGas.lmp could not be opened for reading.");
147
148 int numberOfParticles= 0;
149 std::string line;
150 while (std::getline(file,line))
151 {
152 std::string loweredLine= line;
153 std::transform(loweredLine.begin(),loweredLine.end(),loweredLine.begin(),::tolower);
154 if (loweredLine.find("atoms") != std::string::npos && (std::stringstream(line) >> numberOfParticles))
155 break;
156 }
157 if (numberOfParticles <= 0) MY_ERROR("ERROR: Could not read number of particles.");
158
159 BoxConfiguration body{numberOfParticles,false};
160 body.readLMP(configFileName,Current);
161 body.pbc= Vector3i(1,1,1);
162
163 const double volume= body.box.determinant();
164 const double boltzmannConstantEvPerK= 8.617333262145e-5;
165 double totalMass= 0.0;
166 Vector3d totalMomentum= Vector3d::Zero();
167 for (int i=0; i<body.numberOfParticles; ++i)
168 {
169 Vector3d velocity= body.velocities.row(i);
170 totalMass+= body.masses(i);
171 totalMomentum+= body.masses(i)*velocity;
172 }
173 Vector3d averageVelocity= totalMomentum/totalMass;
174
175 Matrix3d expectedStress= Matrix3d::Zero();
176 Matrix3d rawVelocityStress= Matrix3d::Zero();
177 double thermalKineticEnergyFactor= 0.0;
178 for (int i=0; i<body.numberOfParticles; ++i)
179 {
180 Vector3d velocity= body.velocities.row(i);
181 Vector3d relativeVelocity= velocity - averageVelocity;
183 body.masses(i)*relativeVelocity.transpose()*relativeVelocity/volume;
185 body.masses(i)*velocity.transpose()*velocity/volume;
186 thermalKineticEnergyFactor+= body.masses(i)*relativeVelocity.squaredNorm();
187 }
188 const double instantaneousTemperature=
189 amuAngstromSquaredPerPicosecondSquaredToEv*thermalKineticEnergyFactor/
190 (3.0*body.numberOfParticles*boltzmannConstantEvPerK);
191
192 const int nx= 12;
193 const int ny= 12;
194 const int nz= 12;
195 const Vector3d lowerLimit(0.0,0.0,0.0);
196 const Vector3d upperLimit(60.0,60.0,60.0);
197 Grid<Current> grid(lowerLimit,upperLimit,nx,ny,nz);
198 MethodSphere virial(20.0,"virial");
199 Matrix3d ldadVectors;
200 ldadVectors << 20.0, 0.0, 0.0,
201 0.0, 20.0, 0.0,
202 0.0, 0.0, 20.0;
203 MethodLdadConstant ldadConstant(ldadVectors);
204 MethodLdadTrigonometric ldadTrigonometric(ldadVectors);
205
206 Stress<MethodSphere,Cauchy> kineticStressSphere("idealGasKineticSphere",virial,&grid);
207 Stress<MethodLdadConstant,Cauchy> kineticStressLdadConstant("idealGasKineticLdadConstant",ldadConstant,&grid);
208 Stress<MethodLdadTrigonometric,Cauchy> kineticStressLdadTrigonometric("idealGasKineticLdadTrigonometric",ldadTrigonometric,&grid);
209
210 calculateKineticStress(body,std::tie(kineticStressSphere,
211 kineticStressLdadConstant,
212 kineticStressLdadTrigonometric));
213 kineticStressSphere.write();
214 kineticStressLdadConstant.write();
215 kineticStressLdadTrigonometric.write();
216 kineticStressSphere.write_voxel_grid(nx,ny,nz,lowerLimit,upperLimit);
217 kineticStressLdadConstant.write_voxel_grid(nx,ny,nz,lowerLimit,upperLimit);
218 kineticStressLdadTrigonometric.write_voxel_grid(nx,ny,nz,lowerLimit,upperLimit);
219
220 const int numberOfGridPoints= nx*ny*nz;
221 const int voxelGridDimension= 3;
222 validateVoxelGridFile("idealGasKineticSphere.voxel_grid_stress",
223 "ITEM: GRID CELLS SXX SYY SZZ SYZ SXZ SXY",
224 numberOfGridPoints,
225 voxelGridDimension);
226 validateVoxelGridFile("idealGasKineticSphere.voxel_grid_momentum_density",
227 "ITEM: GRID CELLS PX PY PZ",
228 numberOfGridPoints,
229 voxelGridDimension);
230 validateVoxelGridFile("idealGasKineticSphere.voxel_grid_mass_density",
231 "ITEM: GRID CELLS RHO",
232 numberOfGridPoints,
233 voxelGridDimension);
234 validateVoxelGridFile("idealGasKineticLdadConstant.voxel_grid_stress",
235 "ITEM: GRID CELLS SXX SYY SZZ SYZ SXZ SXY",
236 numberOfGridPoints,
237 voxelGridDimension);
238 validateVoxelGridFile("idealGasKineticLdadConstant.voxel_grid_momentum_density",
239 "ITEM: GRID CELLS PX PY PZ",
240 numberOfGridPoints,
241 voxelGridDimension);
242 validateVoxelGridFile("idealGasKineticLdadConstant.voxel_grid_mass_density",
243 "ITEM: GRID CELLS RHO",
244 numberOfGridPoints,
245 voxelGridDimension);
246 validateVoxelGridFile("idealGasKineticLdadTrigonometric.voxel_grid_stress",
247 "ITEM: GRID CELLS SXX SYY SZZ SYZ SXZ SXY",
248 numberOfGridPoints,
249 voxelGridDimension);
250 validateVoxelGridFile("idealGasKineticLdadTrigonometric.voxel_grid_momentum_density",
251 "ITEM: GRID CELLS PX PY PZ",
252 numberOfGridPoints,
253 voxelGridDimension);
254 validateVoxelGridFile("idealGasKineticLdadTrigonometric.voxel_grid_mass_density",
255 "ITEM: GRID CELLS RHO",
256 numberOfGridPoints,
257 voxelGridDimension);
258
259 const double pressure= -expectedStress.trace()/3.0;
260 const double analyticalPressure=
261 body.numberOfParticles*boltzmannConstantEvPerK*instantaneousTemperature/volume;
262 const double expectedMassDensity= totalMass/volume;
263 const double meanTolerance= 0.20*pressure;
264 const double pointwiseTolerance= 0.75*pressure;
265 const double densityTolerance= 0.20*expectedMassDensity;
266
267 validateIdealGasStress(kineticStressSphere,expectedStress,rawVelocityStress,
268 expectedMassDensity,meanTolerance,pointwiseTolerance,densityTolerance);
269 validateIdealGasStress(kineticStressLdadConstant,expectedStress,rawVelocityStress,
270 expectedMassDensity,meanTolerance,pointwiseTolerance,densityTolerance);
271 validateIdealGasStress(kineticStressLdadTrigonometric,expectedStress,rawVelocityStress,
272 expectedMassDensity,meanTolerance,pointwiseTolerance,densityTolerance);
273
274 std::cout << "Mass-weighted average velocity = " << averageVelocity << " A/ps" << std::endl;
275 std::cout << "Instantaneous ideal-gas temperature = " << instantaneousTemperature << " K" << std::endl;
276 std::cout << "Instantaneous ideal-gas pressure from stress = " << pressure << " eV/A^3" << std::endl;
277 std::cout << "Analytical ideal-gas pressure NkBT/V = " << analyticalPressure << " eV/A^3" << std::endl;
278 std::cout << "Raw-velocity stress before subtracting bulk motion:\n" << rawVelocityStress << std::endl;
279 std::cout << "Expected stress:\n" << expectedStress << std::endl;
280
281 return 0;
282}
int calculateKineticStress(const BoxConfiguration &body, std::tuple<> cauchyStress)
Represents a particle configuration including simulation box information.
void readLMP(const std::string &, const ConfigType &configType)
Reads a configuration from a LAMMPS data file.
Definition Grid.h:38
Lattice-dependent averaging-domain method.
Definition MethodLdad.h:39
Implements radially symmetric weighting functions (Hardy, Virial) and its associated bond function fo...
Three-dimensional stress field on a grid.
Definition Stress.h:42
std::vector< Matrix3d > field
A three-dimensional stress field.
Definition Stress.h:47
std::string name
The prefix of the filename that will be outputted when the stress field is written.
Definition Stress.h:88
std::vector< Vector3d > momentumDensityField
Cauchy-grid momentum density .
Definition Stress.h:54
std::vector< Vector3d > velocityField
Internal Cauchy-grid continuum velocity.
Definition Stress.h:70
std::vector< double > massDensityField
Cauchy-grid mass density .
Definition Stress.h:61
int main()
const double amuAngstromSquaredPerPicosecondSquaredToEv
Definition typedef.h:74
#define MY_ERROR(message)
Definition typedef.h:17
Eigen::Matrix< double, DIM, DIM, Eigen::RowMajor > Matrix3d
Definition typedef.h:56
Eigen::Matrix< double, 1, DIM, Eigen::RowMajor > Vector3d
Definition typedef.h:60
Eigen::Matrix< int, 1, DIM, Eigen::RowMajor > Vector3i
Definition typedef.h:61
@ Current
Definition typedef.h:70
const double epsilon
Definition typedef.h:73