10namespace fs=std::filesystem;
11void require(
bool ok,
const std::string& message)
15 throw std::runtime_error(message);
18std::string
read(
const fs::path& path)
20 std::ifstream stream(path);
21 return {std::istreambuf_iterator<char>(stream),std::istreambuf_iterator<char>()};
25 const std::regex pattern(
"(^|\\n)\\s*"+
key+
"\\s*=[^;]*;");
27 text=std::regex_replace(
text,pattern,
"$1"+
key+
"="+value+
";");
29fs::path
fixture(
const fs::path& root,
int rank,
int reactions,
bool source,
const std::string& name=
"unitCube24.msh")
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");
41 const int species[4]={-1,1,2,3};
46 map+=std::to_string(species[
i])+
" "+std::to_string(species[
j])+
" "+((reactions==2 &&
i==0 &&
j==1)?
"1\n" :
"0\n");
49 parameter(material,
"reactionPrefactorMap",map);
52 parameter(material,
"mobileSpeciesCascadeFractions",
"1 1 0 0");
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();
63 for(
const auto& entry : region.second->faces())
65 if(entry.second->periodicFacePair.second)
67 for(
int d=0;d<rank;++d)
69 if(entry.second->periodicFacePair.first.normalized().cross(F.col(d).normalized()).norm()<1.e-8)
71 seeds.insert(entry.first);
79 std::ofstream
poly(folder/
"inputFiles/polycrystal.txt");
80 poly<<std::setprecision(17)<<
"materialFile=material.txt;\nmeshFile="<<
"mesh.msh"<<
";\nabsoluteTemperature=600;\nF=";
85 poly<<
";\nX0=0 0 0;\nperiodicFaceIDs=";
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)
94 poly<<
"C2G"<<g<<
"=1 0 0 0 1 0 0 0 1;\n";
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)
102 dc<<
";\nEwaldLengthFactor=0;\n";
106void periodic(
const Trial& field,
double relative=1.e-10)
108 const auto& fe=field.fe();
109 for(
const auto& pair : fe.periodicNodePairs())
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())
115 jump-=field.periodicNodalOffset().segment(
count*pair.sink,
count)-field.periodicNodalOffset().segment(
count*pair.source,
count);
117 require(jump.norm()<=relative*std::max(1.e-40,field.dofVector().norm()),
"Periodic field mismatch");
122 const auto folder=
fixture(root,rank,0,
false);
128 require(base.periodicLatticeBasis.cols()==rank,
"Wrong active rank");
130 Eigen::VectorXd target(
u->gSize()),offset=Eigen::VectorXd::Zero(
u->gSize());
131 for(
const auto& node : base.fe->nodes())
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);
139 u->setPeriodicNodalOffset(offset);
140 for(
auto&
bc :
u->dirichletConditions())
142 bc.second=target(
bc.first);
144 Eigen::VectorXd legacy;
152 require((
u->dofVector()-target).norm()<1.e-8*target.norm(),
"Manufactured diffusion solution changed");
155 require((
u->dofVector()-legacy).norm()<1.e-9*target.norm(),
"Nonperiodic result differs from legacy solver");
159 for(
auto&
bc :
u->dirichletConditions())
164 require(fem.
mSolver->factorizationCount()==
count,
"Dirichlet RHS change refactorized diffusion");
168 for(
const auto& pair : base.fe->periodicNodePairs())
170 if(
u->dirichletConditions().count(4*pair.source) &&
u->dirichletConditions().count(4*pair.sink))
172 const auto before=
u->dofVector().eval();
173 u->dirichletConditions()[4*pair.sink]+=1.e-4;
179 catch(
const std::runtime_error&)
183 require(rejected && (
u->dofVector()-before).norm()==0.0,
"Failed boundary update modified concentration");
189 fs::remove_all(folder);
191void closed(
const fs::path& root,
int reaction,
bool source)
193 const auto folder=
fixture(root,3,reaction,source);
201 u->setConstant(Eigen::Vector4d::Constant(1.e-7));
202 const auto before=
u->dofVector().eval();
203 if(reaction==0 && source)
210 catch(
const std::runtime_error& error)
212 rejected=std::string(error.what()).find(
"no steady state")!=std::string::npos;
214 require(rejected && (
u->dofVector()-before).norm()==0.0,
"Closed source-only solve was not rejected transactionally");
220 Eigen::Vector4d expected=Eigen::Vector4d::Constant(1.e-7);
223 expected=-parameters.
R1cd.fullPivLu().solve(parameters.
G);
227 expected(0)=expected(1)=std::sqrt(-parameters.
G(0)/parameters.
R2[0](0,1));
229 for(
const auto& node : base.fe->nodes())
231 require((
u->dofVector().segment<4>(4*node.gID)-expected).norm()<2.e-6*expected.norm(),
"Closed reaction balance or conserved mean is wrong");
235 require((fem.
immobileClusters->dofVector()-immobile).norm()==0.0,
"Rate solve overwrote immobile concentrations");
239 require(fem.
iSolver->factorizationCount()==
count,
"Immobile mass factorization not reused");
243 config.
cdMatrix().resize(base.fe->nodeSize(),12);
244 config.
cdMatrix().leftCols(4)=
u->dofVector().reshaped(4,base.fe->nodeSize()).transpose();
246 std::ofstream unused;
249 config.
cdMatrix()(base.fe->periodicNodePairs().front().sink,4)+=1.0;
255 catch(
const std::runtime_error&)
259 require(rejected && (fem.
immobileClusters->dofVector()-state).norm()==0.0,
"Inconsistent restart was not rejected transactionally");
262 fs::remove_all(folder);
264void sinks(
const fs::path& root,
const std::string& name,
int rank)
266 const auto folder=
fixture(root,rank,0,
false,name);
270 auto physics=std::make_shared<ClusterDynamics<3>>(container);
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())
278 for(
const auto* element : node)
280 const auto bary=element->simplex.pos2bary(node.P0);
281 for(
int face=0;face<4;++face)
283 if(std::abs(bary(face))>1.e-8)
287 const auto& simplex=element->simplex.child(face);
288 if(simplex.isRegionBoundarySimplex())
290 expected=internal=
true;
292 if(simplex.isBoundarySimplex())
294 const auto found=base.mesh.periodicSimplexPairs().find(&simplex);
295 if(found==base.mesh.periodicSimplexPairs().end())
299 else if(found->second.partner->parents().begin()->second->region->regionID!=element->simplex.region->regionID)
301 expected=periodicGB=
true;
306 require(
bool(fem.sinkNormals.count(node.gID))==expected,
"Boundary sink classification mismatch");
308 require(internal && periodicGB,
"Fixture must exercise internal and periodic grain boundaries");
311 require(!fem.mobileClusters->dirichletConditions().empty(),
"Periodic polycrystal lost GB concentrations");
313 fs::remove_all(folder);
320 for(
int rank=0;rank<3;++rank)
329 sinks(
argv[1],
"polycrystalCube_10_periodicXY.msh",2);
330 std::cout<<
"PASS: ClusterDynamics periodic diffusion, reactions, sinks, rates and restart\n";
332 catch(
const std::exception& error)
334 std::cerr<<error.what()<<
'\n';
const CDMatrixType & cdMatrix() const
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...
size_t InstanceCount< Derived >::count
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
void solveImmobileClusters()
const std::vector< Eigen::Matrix< double, mSize, mSize > > R2
const Eigen::Matrix< double, mSize, 1 > G
const Eigen::Matrix< double, mSize, mSize > R1cd
const BaseContainerType & microstructures() const
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)