Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testClusterPeriodic.cpp
Go to the documentation of this file.
1#include <ClusterDynamics.h>
3#include <filesystem>
4#include <fstream>
5#include <regex>
6#include <chrono>
7#include <iostream>
8#include <iomanip>
9using namespace model;
10namespace fs=std::filesystem;
11void require(bool ok,const std::string& message)
12{
13 if(!ok)
14 {
15 throw std::runtime_error(message);
16 }
17}
18std::string read(const fs::path& path)
19{
20 std::ifstream stream(path);
21 return {std::istreambuf_iterator<char>(stream),std::istreambuf_iterator<char>()};
22}
23void parameter(std::string& text,const std::string& key,const std::string& value)
24{
25 const std::regex pattern("(^|\\n)\\s*"+key+"\\s*=[^;]*;");
26 require(std::regex_search(text,pattern),"Missing parameter "+key);
27 text=std::regex_replace(text,pattern,"$1"+key+"="+value+";");
28}
29fs::path fixture(const fs::path& root,int rank,int reactions,bool source,const std::string& name="unitCube24.msh")
30{
31 const auto folder=fs::temp_directory_path()/("cluster-periodic-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
32 fs::create_directories(folder/"inputFiles");
33 fs::create_directories(folder/"config");
34 fs::create_directories(folder/"F");
35 fs::copy_file(root/"Library/Meshes"/name,folder/"inputFiles/mesh.msh");
36 auto material=read(root/"Library/Materials/Zr4_Fitted.txt");
37 parameter(material,"computeReactions",reactions? "1" : "0");
38 parameter(material,"doseRate_dpaPerSec",source? "1e-7" : "0");
39 parameter(material,"otherSinks_SI",reactions==1? "1e14 1e14 1e14 1e14" : "0 0 0 0");
40 std::string map;
41 const int species[4]={-1,1,2,3};
42 for(int i=0;i<4;++i)
43 {
44 for(int j=i;j<4;++j)
45 {
46 map+=std::to_string(species[i])+" "+std::to_string(species[j])+" "+((reactions==2 && i==0 && j==1)? "1\n" : "0\n");
47 }
48 }
49 parameter(material,"reactionPrefactorMap",map);
50 if(reactions==2)
51 {
52 parameter(material,"mobileSpeciesCascadeFractions","1 1 0 0");
53 }
54 std::ofstream(folder/"inputFiles/material.txt")<<material;
55 std::ofstream(folder/"inputFiles/ClusterDynamics.txt")<<"useClusterDynamicsFEM=1;\n";
56 const Eigen::Matrix3d F=(Eigen::Matrix3d()<<100,50,0,0,50*std::sqrt(3.0),0,0,0,100*std::sqrt(8.0/3.0)).finished();
57 StaticID<Polyhedron<3,3-1>>::force_count(0);
58 std::set<int> seeds;
59 {
60 SimplicialMesh<3> mesh((root/"Library/Meshes"/name).string(),F,Eigen::Vector3d::Zero(),{-1});
61 for(const auto& region : mesh.regions())
62 {
63 for(const auto& entry : region.second->faces())
64 {
65 if(entry.second->periodicFacePair.second)
66 {
67 for(int d=0;d<rank;++d)
68 {
69 if(entry.second->periodicFacePair.first.normalized().cross(F.col(d).normalized()).norm()<1.e-8)
70 {
71 seeds.insert(entry.first);
72 }
73 }
74 }
75 }
76 }
77 }
78 StaticID<Polyhedron<3,3-1>>::force_count(0);
79 std::ofstream poly(folder/"inputFiles/polycrystal.txt");
80 poly<<std::setprecision(17)<<"materialFile=material.txt;\nmeshFile="<<"mesh.msh"<<";\nabsoluteTemperature=600;\nF=";
81 for(int i=0;i<3;++i)
82 {
83 poly<<F.row(i)<<"\n";
84 }
85 poly<<";\nX0=0 0 0;\nperiodicFaceIDs=";
86 for(int seed : seeds)
87 {
88 poly<<seed<<" ";
89 }
90 poly<<";\n";
91 const int grains=name=="bicrystal_48.msh"? 2 : (name.find("polycrystal")!=std::string::npos? 10 : 1);
92 for(int g=1;g<=grains;++g)
93 {
94 poly<<"C2G"<<g<<"=1 0 0 0 1 0 0 0 1;\n";
95 }
96 std::ofstream dc(folder/"inputFiles/DefectiveCrystal.txt");
97 dc<<"physics=ClusterDynamics;\nuseFEM=1;\nNsteps=1;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=";
98 for(int d=0;d<rank;++d)
99 {
100 dc<<"0 ";
101 }
102 dc<<";\nEwaldLengthFactor=0;\n";
103 return folder;
104}
105template<class Trial>
106void periodic(const Trial& field,double relative=1.e-10)
107{
108 const auto& fe=field.fe();
109 for(const auto& pair : fe.periodicNodePairs())
110 {
111 constexpr int count=Trial::dofPerNode;
112 Eigen::VectorXd jump=field.dofVector().segment(count*pair.sink,count)-field.dofVector().segment(count*pair.source,count);
113 if(field.periodicNodalOffset().size())
114 {
115 jump-=field.periodicNodalOffset().segment(count*pair.sink,count)-field.periodicNodalOffset().segment(count*pair.source,count);
116 }
117 require(jump.norm()<=relative*std::max(1.e-40,field.dofVector().norm()),"Periodic field mismatch");
118 }
119}
120void diffusion(const fs::path& root,int rank)
121{
122 const auto folder=fixture(root,rank,0,false);
123 {
124 DefectiveCrystalBase<3> base(folder.string());
125 ClusterDynamicsParameters<3> parameters(base);
126 ClusterDynamicsFEM<3> fem(base,parameters);
127 fem.initializeSolver();
128 require(base.periodicLatticeBasis.cols()==rank,"Wrong active rank");
129 auto u=fem.mobileClusters;
130 Eigen::VectorXd target(u->gSize()),offset=Eigen::VectorXd::Zero(u->gSize());
131 for(const auto& node : base.fe->nodes())
132 {
133 for(int c=0;c<4;++c)
134 {
135 offset(4*node.gID+c)=rank>0? 1.e-9*(c+1)*node.P0.x() : 0.0;
136 target(4*node.gID+c)=1.e-6*(c+1)+1.e-8*(c+1)*node.P0.z()+offset(4*node.gID+c);
137 }
138 }
139 u->setPeriodicNodalOffset(offset);
140 for(auto& bc : u->dirichletConditions())
141 {
142 bc.second=target(bc.first);
143 }
144 Eigen::VectorXd legacy;
145 if(rank==0)
146 {
147 FixedDirichletSolver<Eigen::SparseLU<Eigen::SparseMatrix<double>>,Eigen::BiCGSTAB<Eigen::SparseMatrix<double>>> solver(true,1.e-10);
148 solver.compute(fem.mBWF);
149 legacy=solver.solve(fem.cascadeGlobalProduction);
150 }
151 fem.solveMobileClusters(true);
152 require((u->dofVector()-target).norm()<1.e-8*target.norm(),"Manufactured diffusion solution changed");
153 if(rank==0)
154 {
155 require((u->dofVector()-legacy).norm()<1.e-9*target.norm(),"Nonperiodic result differs from legacy solver");
156 }
157 periodic(*u);
158 const auto count=fem.mSolver->factorizationCount();
159 for(auto& bc : u->dirichletConditions())
160 {
161 bc.second+=1.e-6;
162 }
163 fem.solveMobileClusters(true);
164 require(fem.mSolver->factorizationCount()==count,"Dirichlet RHS change refactorized diffusion");
165 if(rank>0)
166 {
167 // Two constrained copies must not silently prescribe incompatible values.
168 for(const auto& pair : base.fe->periodicNodePairs())
169 {
170 if(u->dirichletConditions().count(4*pair.source) && u->dirichletConditions().count(4*pair.sink))
171 {
172 const auto before=u->dofVector().eval();
173 u->dirichletConditions()[4*pair.sink]+=1.e-4;
174 bool rejected=false;
175 try
176 {
177 fem.solveMobileClusters(true);
178 }
179 catch(const std::runtime_error&)
180 {
181 rejected=true;
182 }
183 require(rejected && (u->dofVector()-before).norm()==0.0,"Failed boundary update modified concentration");
184 break;
185 }
186 }
187 }
188 }
189 fs::remove_all(folder);
190}
191void closed(const fs::path& root,int reaction,bool source)
192{
193 const auto folder=fixture(root,3,reaction,source);
194 {
195 DefectiveCrystalBase<3> base(folder.string());
196 ClusterDynamicsParameters<3> parameters(base);
197 ClusterDynamicsFEM<3> fem(base,parameters);
198 fem.initializeSolver();
199 require(fem.sinkNormals.empty(),"Same-grain periodic seams became sinks");
200 auto u=fem.mobileClusters;
201 u->setConstant(Eigen::Vector4d::Constant(1.e-7));
202 const auto before=u->dofVector().eval();
203 if(reaction==0 && source)
204 {
205 bool rejected=false;
206 try
207 {
208 fem.solve(true);
209 }
210 catch(const std::runtime_error& error)
211 {
212 rejected=std::string(error.what()).find("no steady state")!=std::string::npos;
213 }
214 require(rejected && (u->dofVector()-before).norm()==0.0,"Closed source-only solve was not rejected transactionally");
215 }
216 else
217 {
218 fem.solve(true);
219 periodic(*u);
220 Eigen::Vector4d expected=Eigen::Vector4d::Constant(1.e-7);
221 if(reaction==1)
222 {
223 expected=-parameters.R1cd.fullPivLu().solve(parameters.G);
224 }
225 if(reaction==2)
226 {
227 expected(0)=expected(1)=std::sqrt(-parameters.G(0)/parameters.R2[0](0,1));
228 }
229 for(const auto& node : base.fe->nodes())
230 {
231 require((u->dofVector().segment<4>(4*node.gID)-expected).norm()<2.e-6*expected.norm(),"Closed reaction balance or conserved mean is wrong");
232 }
233 const auto immobile=fem.immobileClusters->dofVector().eval();
235 require((fem.immobileClusters->dofVector()-immobile).norm()==0.0,"Rate solve overwrote immobile concentrations");
237 const auto count=fem.iSolver->factorizationCount();
239 require(fem.iSolver->factorizationCount()==count,"Immobile mass factorization not reused");
240 fem.updateImmobileClusters(0.01);
242 DefectiveCrystalConfigIO<3> config((folder/"config").string());
243 config.cdMatrix().resize(base.fe->nodeSize(),12);
244 config.cdMatrix().leftCols(4)=u->dofVector().reshaped(4,base.fe->nodeSize()).transpose();
245 config.cdMatrix().rightCols(8)=fem.immobileClusters->dofVector().reshaped(8,base.fe->nodeSize()).transpose();
246 std::ofstream unused;
247 fem.initializeConfiguration(config,unused,unused);
248 const auto state=fem.immobileClusters->dofVector().eval();
249 config.cdMatrix()(base.fe->periodicNodePairs().front().sink,4)+=1.0;
250 bool rejected=false;
251 try
252 {
253 fem.initializeConfiguration(config,unused,unused);
254 }
255 catch(const std::runtime_error&)
256 {
257 rejected=true;
258 }
259 require(rejected && (fem.immobileClusters->dofVector()-state).norm()==0.0,"Inconsistent restart was not rejected transactionally");
260 }
261 }
262 fs::remove_all(folder);
263}
264void sinks(const fs::path& root,const std::string& name,int rank)
265{
266 const auto folder=fixture(root,rank,0,false,name);
267 {
268 DefectiveCrystalBase<3> base(folder.string());
269 MicrostructureContainer<3> container(base);
270 auto physics=std::make_shared<ClusterDynamics<3>>(container);
271 container.microstructures().push_back(physics);
272 auto& fem=*physics->clusterDynamicsFEM;
273 require(!fem.sinkNormals.empty(),"GB and free-surface sinks missing");
274 bool internal=false,periodicGB=false;
275 for(const auto& node : base.fe->nodes())
276 {
277 bool expected=false;
278 for(const auto* element : node)
279 {
280 const auto bary=element->simplex.pos2bary(node.P0);
281 for(int face=0;face<4;++face)
282 {
283 if(std::abs(bary(face))>1.e-8)
284 {
285 continue;
286 }
287 const auto& simplex=element->simplex.child(face);
288 if(simplex.isRegionBoundarySimplex())
289 {
290 expected=internal=true;
291 }
292 if(simplex.isBoundarySimplex())
293 {
294 const auto found=base.mesh.periodicSimplexPairs().find(&simplex);
295 if(found==base.mesh.periodicSimplexPairs().end())
296 {
297 expected=true;
298 }
299 else if(found->second.partner->parents().begin()->second->region->regionID!=element->simplex.region->regionID)
300 {
301 expected=periodicGB=true;
302 }
303 }
304 }
305 }
306 require(bool(fem.sinkNormals.count(node.gID))==expected,"Boundary sink classification mismatch");
307 }
308 require(internal && periodicGB,"Fixture must exercise internal and periodic grain boundaries");
309 physics->solve();
310 periodic(*fem.mobileClusters);
311 require(!fem.mobileClusters->dirichletConditions().empty(),"Periodic polycrystal lost GB concentrations");
312 }
313 fs::remove_all(folder);
314}
315int main(int argc,char** argv)
316{
317 try
318 {
319 require(argc==2,"Expected repository path");
320 for(int rank=0;rank<3;++rank)
321 {
322 diffusion(argv[1],rank);
323 }
324 closed(argv[1],0,false);
325 closed(argv[1],0,true);
326 closed(argv[1],1,true);
327 closed(argv[1],2,true);
328 sinks(argv[1],"bicrystal_48.msh",3);
329 sinks(argv[1],"polycrystalCube_10_periodicXY.msh",2);
330 std::cout<<"PASS: ClusterDynamics periodic diffusion, reactions, sinks, rates and restart\n";
331 }
332 catch(const std::exception& error)
333 {
334 std::cerr<<error.what()<<'\n';
335 return 1;
336 }
337}
void compute(const std::shared_ptr< BilinearWeakFormType > &bWF)
Eigen::VectorXd solve(const Eigen::VectorXd &b) const
A class template that implements a counter of the number of instances of Derived type that are create...
Definition StaticID.h:29
c
Definition int_R.m:15
size_t InstanceCount< Derived >::count
int regions
Definition triangle.cpp:696
void solve(const bool hasDiscreteLoops)
std::shared_ptr< MobileBilinearWeakFormType > mBWF
std::shared_ptr< ImmobileRateTrialType > immobileRateTrial
std::shared_ptr< Problem > mSolver
void updateImmobileClusters(const double dt)
std::shared_ptr< ImmobileTrialType > immobileClusters
const std::map< size_t, std::vector< VectorDim > > sinkNormals
std::shared_ptr< MobileTrialType > mobileClusters
void initializeConfiguration(const DefectiveCrystalConfigIO< dim > &configIO, const std::ofstream &f_file, const std::ofstream &F_labels)
const Eigen::VectorXd cascadeGlobalProduction
void solveMobileClusters(const bool hasDiscreteLoops)
std::shared_ptr< Problem > iSolver
const std::vector< Eigen::Matrix< double, mSize, mSize > > R2
const Eigen::Matrix< double, mSize, 1 > G
const Eigen::Matrix< double, mSize, mSize > R1cd
void closed(const fs::path &root, int reaction, bool source)
void periodic(const Trial &field, double relative=1.e-10)
void require(bool ok, const std::string &message)
std::string read(const fs::path &path)
fs::path fixture(const fs::path &root, int rank, int reactions, bool source, const std::string &name="unitCube24.msh")
void parameter(std::string &text, const std::string &key, const std::string &value)
void sinks(const fs::path &root, const std::string &name, int rank)
void diffusion(const fs::path &root, int rank)
std::string text(Config &config)
int j
void argv
REAL u[4]
void b int argc
int i
newbad key
b poly
REAL bc[4]