34void exercise(
const std::filesystem::path& root,
int rank)
36 auto mesh=std::make_shared<SimplicialMesh<3>>((root/
"Library/Meshes/bicrystal_48.msh").string(),Eigen::Matrix3d::Identity(),Eigen::Vector3d(2,3,4),std::set<int>{-1});
38 for(
int d=0;d<rank;++d)
42 for(
const auto& face : region.second->faces())
44 const auto& pair=face.second->periodicFacePair;
45 if(pair.second && pair.first.cross(
mesh->periodicBasis().col(d)).norm()<1.e-8)
47 seeds.insert(face.first);
53 mesh->identifyParallelFaces(seeds);
55 auto fe=std::make_shared<FE>(
mesh);
56 auto u=fe->trial<3>();
57 using Trial=
typename decltype(
u)::element_type;
59 Eigen::Matrix<double,6,6> C=Eigen::Matrix<double,6,6>::Identity();
60 C.topLeftCorner<3,3>().array()+=1.0;
62 auto stiffness=(
test(strain),C*strain)*dV;
63 auto penalty=std::make_shared<GlobalRigidMotionPenalty<Trial>>(
u,2.0);
64 auto form=stiffness+penalty;
65 const Eigen::MatrixXd F=penalty->lowRankPenaltyRows();
66 const Eigen::MatrixXd R=penalty->rigidModes();
67 const int modes=rank==0 ? 6 : (rank==1 ? 4 : 3);
68 require(F.rows()==modes,
"Wrong admissible rigid-mode count");
69 Eigen::SparseMatrix<double> sparse(
u->gSize(),
u->gSize());
70 auto triplets=stiffness->globalTriplets();
71 sparse.setFromTriplets(triplets.begin(),triplets.end());
72 const Eigen::MatrixXd K=sparse;
73 require((K*R).norm()<1.e-10*K.norm()*R.norm(),
"Rigid mode has elastic strain energy");
74 require((F*R).fullPivLu().rank()==modes,
"Penalty leaves a rigid mode free");
76 std::map<size_t,size_t> columns;
77 for(
size_t n=0;n<fe->nodeSize();++n)
79 columns.emplace(fe->periodicRepresentative(n),columns.size());
81 Eigen::MatrixXd T=Eigen::MatrixXd::Zero(
u->gSize(),3*columns.size());
82 Eigen::VectorXd g=Eigen::VectorXd::Zero(
u->gSize());
83 Eigen::Matrix3d H=Eigen::Matrix3d::Zero();
85 u->setPeriodicGradient(H);
86 for(
size_t n=0;n<fe->nodeSize();++n)
88 T.block<3,3>(3*n,3*columns.at(fe->periodicRepresentative(n))).setIdentity();
89 g.segment<3>(3*n)=H*fe->periodicShift(n);
91 Eigen::VectorXd
q(T.cols());
92 for(Eigen::Index
i=0;
i<
q.size();++
i)
94 q(
i)=std::sin(0.31*
i);
96 Eigen::VectorXd target=T*
q+g;
97 target-=R*(F*R).fullPivLu().solve(F*target);
98 require((F*target).norm()<1.e-10,
"Deformation has nonzero global rigid moment");
99 require((K*target).norm()>1.e-4,
"Test deformation is trivial");
100 auto rhs=std::make_shared<Load<Trial>>(
Load<Trial>{
u,K*target});
102 problem->solveWithMasterSlaveConstraints();
103 require((
u->dofVector()-target).norm()<1.e-7*target.norm(),
"Global penalty altered deformation");
104 const Eigen::MatrixXd
A=K+F.transpose()*F;
105 const Eigen::MatrixXd reduced=T.transpose()*
A*T;
106 const Eigen::VectorXd
b=T.transpose()*(rhs->value-
A*g);
107 const Eigen::VectorXd expected=T*reduced.ldlt().solve(
b)+g;
108 require((
u->dofVector()-expected).norm()<1.e-7*expected.norm(),
"Bordered solve differs from explicit global penalty");
109 require((problem->reducedForceVector()-
b).norm()<1.e-9*std::max(1.0,
b.norm()),
"Affine penalty lifting is wrong");
110 require(problem->solverMatrix().rows()==T.cols()+modes,
"Penalty was not kept low rank");
112 require(problem->solveWithMasterSlaveConstraints().reusedFactorization,
"Changing RHS refactorized stiffness");
113 auto strong=std::make_shared<GlobalRigidMotionPenalty<Trial>>(
u,200.0);
115 const Eigen::VectorXd solution=
u->dofVector();
116 other->solveWithMasterSlaveConstraints();
117 require((
u->dofVector()-solution).norm()<1.e-7*solution.norm(),
"Penalty strength changed deformation");
120 for(
const auto& face : physical)
122 require(!
mesh->periodicSimplexPairs().count(&face.first->simplex.child(face.second)),
"Periodic face received free-surface traction");
124 require((rank==0 && physical.size()==external.size()) || (rank>0 && physical.size()<external.size()),
"Physical traction selection is wrong");
126 for(
size_t i=0;
i<3;++
i)
128 u->dirichletConditions()[
i]=solution(
i);
130 require(penalty->rigidModes().cols()==(rank==0 ? 3 : (rank==1 ? 1 : 0)),
"Point constraints removed the wrong rigid modes");
132 for(
size_t i=0;
i<
u->gSize();++
i)
134 u->dirichletConditions()[
i]=solution(
i);
136 require(penalty->lowRankPenaltyRows().rows()==0,
"Fixed field still penalizes rigid modes");
137 problem->solveWithMasterSlaveConstraints();
138 require((
u->dofVector()-solution).norm()<1.e-10,
"Fixed solution changed");
151void physicsIntegration(
const std::filesystem::path& root,
int rank,
bool rotated,
int mode,
bool withSource=
false,
bool withPin=
false,
bool irregular=
false)
153 using V=Eigen::Matrix<double,6,1>;
154 using M=Eigen::Matrix<double,6,6>;
155 const std::string meshName=irregular ?
"polycrystalCube_10_periodicXY.msh" :
"bicrystal_48.msh";
156 const Eigen::Matrix3d
Q=rotated ? Eigen::AngleAxisd(0.63,Eigen::Vector3d(1,2,3).normalized()).toRotationMatrix() : Eigen::Matrix3d::Identity();
163 for(
const auto& face : region.second->faces())
165 const auto& pair=face.second->periodicFacePair;
166 for(
int i=0;
i<rank;++
i)
168 const int axis=
i==0 ? 1 : (
i==1 ? 0 : 2);
169 if(pair.second && pair.first.normalized().cross(
Q.col(
axis)).norm()<1.e-8)
171 seeds.insert(face.first);
178 const auto folder=std::filesystem::temp_directory_path()/(
"elastic-mixed-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().
count()));
179 const auto input=folder/
"inputFiles";
180 std::filesystem::create_directories(input);
181 std::filesystem::create_directories(folder/
"config");
182 std::filesystem::create_directories(folder/
"F");
183 std::filesystem::copy_file(root/
"Library/Materials/W.txt",input/
"W.txt");
184 std::filesystem::copy_file(root/
"Library/Meshes"/meshName,input/
"mesh.msh");
185 V strain0,strainRate,stress0,stressRate,
alpha;
186 strain0<<0.002,0.001,-0.0005,0.0007,0.0003,-0.0004;
187 strainRate=0.1*strain0;
188 stress0<<0.001,-0.0004,0.0007,0.0003,-0.0002,0.0004;
189 stressRate=0.2*stress0;
196 alpha.setConstant(1.e20);
200 alpha<<0,1.e20,2,0.5,1.e20,0;
205 strainRate.setZero();
208 stressRate.setZero();
212 const auto writeLoading=[&](
bool fem,
const V& ratios,
const V& stresses,
const V& rates)
214 std::ofstream
out(input/
"ElasticDeformation.txt");
215 out<<std::setprecision(17)<<
"useElasticDeformationFEM="<<fem<<
";\ninertiaReliefPenaltyFactor=0.001;\nExternalStrain0="<<strain0.transpose()<<
";\nExternalStrainRate="<<strainRate.transpose()<<
";\nExternalStress0="<<stresses.transpose()<<
";\nExternalStressRate="<<rates.transpose()<<
";\nstiffnessRatio="<<ratios.transpose()<<
";\n";
218 std::ofstream dc(input/
"DefectiveCrystal.txt");
219 dc<<
"physics=ElasticDeformation;\nuseFEM=1;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=";
220 for(
int i=0;
i<rank;++
i)
224 dc<<
";\nEwaldLengthFactor=0;\n";
225 const auto matrixText=[](
const Eigen::Matrix3d&
matrix)
227 std::ostringstream
result;
228 result<<std::setprecision(17);
229 for(
int row=0;row<3;++row)
231 for(
int col=0;col<3;++col)
238 std::ofstream
poly(input/
"polycrystal.txt");
239 poly<<
"materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\nC2G1="<<matrixText(
Q)<<
";\nC2G2="<<matrixText(
Q)<<
";\nF="<<matrixText(3671.9477120460206*
Q)<<
";\nX0=0 0 0;\nperiodicFaceIDs=";
240 for(
int seed : seeds)
247 for(
int grain=3;grain<=10;++grain)
249 poly<<
"C2G"<<grain<<
"="<<matrixText(
Q)<<
";\n";
252 writeLoading(
true,
alpha,stress0,stressRate);
256 require(base.fe!=
nullptr,
"Explicit FEM request was disabled in a periodic domain");
257 require(base.periodicLatticeBasis.cols()==rank,
"Unexpected active periodic rank");
260 std::unique_ptr<DefectiveCrystalBase<3>> sourceBase;
261 std::unique_ptr<MicrostructureContainer<3>> sourceContainer;
268 const size_t node=base.fe->periodicNodePairs().front().sink;
271 u->dirichletConditions()[3*node+
c]=0.0;
274 physics.
zDot=Eigen::VectorXd::Zero(
u->gSize());
276 V eigenstrain=V::Zero();
277 V sourceStress=V::Zero();
280 sourceStress<<0.0002,0.0003,-0.0001,0.0002,0,-0.0001;
281 writeLoading(
false,V::Zero(),sourceStress,V::Zero());
283 sourceBase=std::make_unique<DefectiveCrystalBase<3>>(folder.string());
284 sourceContainer=std::make_unique<MicrostructureContainer<3>>(*sourceBase);
285 auto source=std::make_shared<UniformSource>(*sourceContainer);
286 eigenstrain<<0.0004,0.0001,0,0.0002,0,0;
287 source->plastic=base.voigtTraits.v2m(eigenstrain,
true);
288 source->uniformLoadController->gs=eigenstrain;
290 writeLoading(
true,
alpha,stress0,stressRate);
293 for(
double time : {7.0,12.0,12.0})
295 base.simulationParameters.totalTime=time;
298 sourceBase->simulationParameters.totalTime=time;
301 for(
const auto&
bc :
u->dirichletConditions())
303 require(std::abs(
u->dofVector()(
bc.first)-
bc.second)<1.e-9,
"Mixed elastic controller moved a pinned node");
306 V target=stress0+time*stressRate+C*eigenstrain;
311 system.row(
i).setZero();
313 target(
i)=strain0(
i)+time*strainRate(
i);
317 const double device=
alpha(
i)*C(
i,
i);
319 target(
i)+=device*(strain0(
i)+time*strainRate(
i));
322 const V expectedTotal=system.fullPivLu().solve(target);
323 const V offset=eigenstrain+C.ldlt().solve(sourceStress);
324 const V expected=expectedTotal-offset;
325 require((physics.
femLoadController->averageStrain()-expected).norm()<1.e-9,
"Mixed FEM average strain differs from uniform controller");
326 require((base.voigtTraits.m2v(physics.
averageStress(),
false)+sourceStress-C*(expectedTotal-eigenstrain)).norm()<1.e-9,
"Mixed FEM average stress differs from uniform controller");
327 const Eigen::Matrix3d expectedStrain=base.voigtTraits.v2m(expected,
true);
328 const double length=(base.mesh.xMax()-base.mesh.xMin()).norm();
329 Eigen::MatrixXd coordinates(base.fe->nodeSize(),4),values(base.fe->nodeSize(),3);
330 for(
const auto& node : base.fe->nodes())
332 coordinates.block<1,3>(node.gID,0)=node.P0.transpose()/length;
333 coordinates(node.gID,3)=1.0;
334 values.row(node.gID)=
u->dofVector().segment<3>(3*node.gID).transpose();
336 const Eigen::MatrixXd fit=coordinates.colPivHouseholderQr().solve(values);
337 const Eigen::Matrix3d expectedGradient=fit.topRows(3).transpose()/length;
338 require((0.5*(expectedGradient+expectedGradient.transpose())-expectedStrain).norm()<1.e-9,
"Mixed loading produced incorrect homogeneous strain");
341 require((expectedGradient-expectedGradient.transpose()).norm()<1.e-9,
"Global stabilization introduced mean rotation");
342 Eigen::Vector3d centroid=Eigen::Vector3d::Zero();
344 for(
const auto& entry : base.fe->elements())
346 centroid+=entry.second.simplex.vol0*entry.second.simplex.center();
347 volume+=entry.second.simplex.vol0;
350 const double meanDisplacement=(expectedGradient*centroid+fit.row(3).transpose()).norm();
351 const double displacementScale=std::max(1.0,values.norm()/std::sqrt(
double(values.rows())));
354 require(meanDisplacement<1.e-6*displacementScale,
"Mean affine displacement is not zero");
357 require(std::abs(expectedGradient(2,0))+std::abs(expectedGradient(2,1))<1.e-9,
"Horizontal plane has a spurious u3 gradient");
360 require((coordinates*fit-values).norm()<2.e-7*std::max(1.0,values.norm()),
"Mixed loading introduced nonuniform deformation");
361 Eigen::Matrix3d projection=Eigen::Matrix3d::Zero();
364 const auto&
B=base.periodicLatticeBasis;
365 projection=
B*(
B.transpose()*
B).inverse()*
B.transpose();
367 require((
u->periodicGradient()-expectedGradient*projection).norm()<1.e-8,
"Solved affine gradient used inactive periodic directions");
368 for(
const auto& pair : base.fe->periodicNodePairs())
370 const Eigen::Vector3d jump=
u->dofVector().segment<3>(3*pair.sink)-
u->dofVector().segment<3>(3*pair.source);
371 require((jump-
u->periodicGradient()*pair.shift).norm()<1.e-8,
"Solved periodic jump is inconsistent");
385 writeLoading(
true,finiteAlpha,stress0,stressRate);
387 finite.
zDot=Eigen::VectorXd::Zero(
u->gSize());
390 reference.
gs=eigenstrain;
391 const V offset=eigenstrain+C.ldlt().solve(sourceStress);
392 require((finite.
femLoadController->averageStrain()+offset-reference.
grad(base.simulationParameters.totalTime)).norm()<1.e-9,
"Finite FEM device stiffness disagrees with UniformController");
395 std::filesystem::remove_all(folder);