Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testBicrystalBoundarySolver.cpp
Go to the documentation of this file.
4#include <PyGlideSolver.h>
7#include <filesystem>
8#include <fstream>
9#include <iomanip>
10#include <chrono>
11#include <iostream>
12#include <set>
13
14void require(bool condition, const char* message)
15{
16 if(!condition)
17 {
18 throw std::runtime_error(message);
19 }
20}
21
22int main(int argc, char** argv)
23{
24#ifdef _MODEL_PYBIND11_
25 pybind11::scoped_interpreter interpreter;
26#endif
27 try
28 {
29 require(argc == 2 || argc == 3, "Expected repository root");
30 const std::filesystem::path root(argv[1]);
31 const auto folder = std::filesystem::temp_directory_path() /
32 ("bicrystal-boundary-solver-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
33 const auto input = folder / "inputFiles";
34 std::filesystem::create_directories(input);
35 std::filesystem::create_directories(folder / "F");
36 std::filesystem::copy_file(root / "Library/Materials/W.txt", input / "W.txt");
37
38 std::filesystem::copy_file(root / "Library/Meshes/bicrystal_48.msh", input / "mesh.msh");
39 std::filesystem::copy_file(root / "Library/DislocationDynamics/DD.txt", input / "DD.txt");
40 std::filesystem::create_directories(folder / "config");
41 const std::string mode=argc==3 ? argv[2] : "full";
42 const bool partial=mode=="partial";
43 const bool physicalOnly=mode=="physical";
44 std::string faceIDs="-1";
45 if(partial)
46 {
47 model::SimplicialMesh<3> mesh((input/"mesh.msh").string(),Eigen::Matrix3d::Identity(),Eigen::Vector3d::Zero(),{-1});
48 bool found=false;
49 for(const auto& region : mesh.regions())
50 {
51 for(const auto& face : region.second->faces())
52 {
53 const auto& pair=face.second->periodicFacePair;
54 if(face.second->isExternal() && pair.second &&
55 pair.first.normalized().cross(Eigen::Vector3d::UnitY()).norm()<1.e-8)
56 {
57 faceIDs=std::to_string(face.first);
58 found=true;
59 }
60 }
61 }
62 require(found,"No Y periodic seed found");
63 model::StaticID<model::Polyhedron<3,3-1>>::force_count(0);
64 }
65 if(physicalOnly)
66 {
67 faceIDs="";
68 }
69 const double length=3671.9477120460206;
70 {
71 std::ofstream f(input / "DefectiveCrystal.txt");
72 f << "physics=DislocationDynamics;\nuseFEM=0;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=" << (partial ? "0" : physicalOnly ? "" : "0 0 0") << ";\nEwaldLengthFactor=0;\n";
73 std::ofstream p(input / "polycrystal.txt");
74 p << std::setprecision(17) << "materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\nC2G1=1 0 0 0 1 0 0 0 1;\n"
75 << "C2G2=0.8 0 0.6 0 1 0 -0.6 0 0.8;\nF=" << length << " 0 0 0 " << length << " 0 0 0 " << length
76 << ";\nX0=0 0 0;\nperiodicFaceIDs=" << faceIDs << ";\n";
77 }
78 model::DefectiveCrystalBase<3> base(folder.string());
79 model::MicrostructureGenerator generator(base);
81 spec.slipSystemIDs={0};
82 spec.loopRadii={1.e-7};
83 spec.glideSteps={50};
84 spec.loopCenters.resize(1,3);
85 spec.loopCenters << -0.5*length,0,0;
86 generator.addPrismaticLoopIndividual(spec);
87 require(generator.config().loops().size()==7,"Expected complete prismatic construction");
89 model::DislocationNetwork<3> network(container);
90 network.initializeConfiguration(generator.config(),std::ofstream(),std::ofstream());
91 // Duplicate the glide loops using the same network nodes. This makes
92 // the surviving mobile branches genuine shared junction segments.
93 std::vector<std::shared_ptr<model::DislocationNetwork<3>::LoopType>> originals;
94 for(const auto& entry : network.loops())
95 {
96 const auto loop=entry.second.lock();
98 {
99 originals.push_back(loop);
100 }
101 }
102 for(const auto& original : originals)
103 {
104 const auto duplicate=network.loops().create(original->burgers(),original->glidePlane);
105 std::vector<std::shared_ptr<model::DislocationNetwork<3>::LoopNodeType>> nodes;
106 for(const auto* link : original->linkSequence())
107 {
108 const auto* node=link->source.get();
109 nodes.push_back(network.loopNodes().create(duplicate,node->networkNode,node->get_P(),
110 node->periodicPlanePatch(),node->periodicPlaneEdge));
111 }
112 network.insertLoop(duplicate,nodes);
113 }
114 network.alphaLineTension=1.0;
115 require(!network.stochasticForceGenerator,"Regression requires deterministic mobility");
116 using V=Eigen::Vector3d;
117 if(argc==3 && !partial && !physicalOnly)
118 {
119 model::DefectiveCrystalConfigIO<3> saved(folder.string());
120 std::ifstream stream(argv[2]);
121 require(bool(stream),"Cannot read saved configuration");
122 saved.readTxtStream(stream);
123 network.setConfiguration(saved);
124 network.moveNodes(10.0);
125 }
126 else
127 {
128 const V center(-0.5*length,0,0);
129 network.trialNodeMotion=true;
130 for(const auto& entry : network.networkNodes())
131 {
132 const auto node=entry.second.lock();
133 // Retain bulk branches while reaching both physical and periodic faces.
134 node->trySet_P(center+(physicalOnly ? 1.0 : 60.0)*(node->get_P()-center));
135 }
136 network.updateBoundaryNodes();
137 network.trialNodeMotion=false;
138 network.updateGeometry();
139 }
140 size_t sharedPhysical=0;
141 size_t periodic=0;
142 for(const auto& entry : network.networkNodes())
143 {
144 const auto node=entry.second.lock();
145 for(const auto* ln : node->loopNodes())
146 {
147 if(ln->isPhysicalBoundaryNode() && (physicalOnly || node->isBoundaryNode()) && node->loopNodes().size()>1)
148 {
149 ++sharedPhysical;
150 }
151 if(ln->isPeriodicSubdivision())
152 {
153 ++periodic;
154 }
155 }
156 }
157 require(physicalOnly || sharedPhysical>0,"No shared physical boundary node");
158 require(physicalOnly ? periodic==0 : periodic>0,"Unexpected same-grain periodic crossing count");
159 require(base.periodicLatticeBasis.cols()==(physicalOnly ? 0 : (partial ? 1 : 3)),"Incorrect active periodic rank");
160 for(const auto& entry : network.networkLinks())
161 {
162 const auto segment=entry.second.lock();
163 segment->createQuadraturePoints(false);
164 // A spatially varying prescribed velocity exposes missing interpolation constraints.
165 for(auto& point : segment->quadraturePoints())
166 {
167 point.velocity=(1.0+0.15*std::sin(point.r.sum()/length))*V(0.1,0.2,0.3);
168 }
169 }
170 const Eigen::VectorXd velocities=network.glideSolver->getNodeVelocities();
171 require(velocities.size()==static_cast<Eigen::Index>(3*network.networkNodes().size()),"Incorrect solver output size");
172 require(velocities.allFinite(),"Nonfinite constrained velocity");
173 require(velocities.norm()>0,"Solver test has no driven nodes");
174 if(physicalOnly)
175 {
176 // Independent reference: original unconstrained lumping must be
177 // unchanged when no periodic subdivisions exist.
178 std::deque<Eigen::Triplet<double>> triplets;
179 Eigen::VectorXd force=Eigen::VectorXd::Zero(velocities.size());
180 Eigen::VectorXd mass=Eigen::VectorXd::Zero(velocities.size());
181 for(const auto& entry : network.networkLinks())
182 {
183 const auto segment=entry.second.lock();
184 if(!segment->isBoundarySegment())
185 {
186 segment->addToGlobalAssembly(triplets,force);
187 }
188 }
189 for(const auto& entry : triplets)
190 {
191 mass(entry.row())+=0.5*entry.value();
192 mass(entry.col())+=0.5*entry.value();
193 }
194 Eigen::VectorXd expected=Eigen::VectorXd::Zero(velocities.size());
195 for(Eigen::Index k=0;k<mass.size();++k)
196 {
197 if(std::abs(mass(k))>FLT_EPSILON)
198 {
199 expected(k)=force(k)/mass(k);
200 }
201 }
202 require((velocities-expected).norm()<1.e-10,"Nonperiodic solve changed unconstrained velocities");
203 }
204
205 // Both alternate solvers consume the same actual bicrystal topology.
206 const Eigen::VectorXd predicted=Eigen::VectorXd::LinSpaced(velocities.size(),0.1,1.0);
207 const auto compatible=model::compatiblePredictedVelocities<3>(network,predicted);
208 const auto climbMap=model::periodicClimbVelocityMap<3>(network);
209 const Eigen::VectorXd mass=Eigen::VectorXd::LinSpaced(network.networkNodes().size(),1.0,2.0);
210 const Eigen::VectorXd force=Eigen::VectorXd::LinSpaced(mass.size(),0.2,0.7);
211 const auto scalar=model::solveConstrainedVelocityDiagonal(mass,force,climbMap);
212 require((climbMap.transpose()*(mass.cwiseProduct(scalar)-force)).norm()<1.e-9,
213 "Reduced climb balance is not satisfied");
214 Eigen::VectorXd climb=Eigen::VectorXd::Zero(velocities.size());
215 size_t index=0;
216 for(const auto& entry : network.networkNodes())
217 {
218 climb.segment<3>(3*index)=scalar(index)*entry.second.lock()->climbDirection();
219 ++index;
220 }
222 model::forEachPeriodicVelocityRelation(network,[&](size_t n,size_t a,size_t b,double wa,double wb)
223 {
224 require((compatible.segment<3>(3*n)-wa*compatible.segment<3>(3*a)-wb*compatible.segment<3>(3*b)).norm()<1.e-9,
225 "Predicted periodic junction velocity is incompatible");
226 require((climb.segment<3>(3*n)-wa*climb.segment<3>(3*a)-wb*climb.segment<3>(3*b)).norm()<1.e-9,
227 "Vector climb velocity is incompatible");
228 });
229 if(physicalOnly)
230 {
231 require((compatible-predicted).norm()==0.0,"Python projection changed nonperiodic predictions");
232 require((scalar-force.cwiseQuotient(mass)).norm()<1.e-12,"Nonperiodic climb balance changed");
233 }
234#ifdef _MODEL_PYBIND11_
235 // Exercise the complete Python bridge with a deterministic model, including
236 // re-insertion of disconnected nodes and actual periodic projection.
237 std::set<size_t> connected;
238 for(const auto& entry : network.networkLinks())
239 {
240 const auto link=entry.second.lock();
241 if(!link->quadraturePoints().empty())
242 {
243 connected.insert(link->source->sID);
244 connected.insert(link->sink->sID);
245 }
246 }
247 std::ofstream(input/"periodic_solver_stub.py")
248 <<"import numpy as np\nclass PIGNN:\n def MLmobility(self, *args):\n return np.ones(("
249 <<connected.size()<<", 3))\n";
250 {
251 std::ifstream file(input/"DD.txt");
252 std::string text((std::istreambuf_iterator<char>(file)),{});
253 const auto begin=text.find("pyModuleName=");
254 const auto end=text.find(';',begin);
255 require(begin!=std::string::npos && end!=std::string::npos,"Missing Python module setting");
256 text.replace(begin,end-begin,"pyModuleName=periodic_solver_stub.py");
257 std::ofstream(input/"DD.txt")<<text;
258 }
259 network.outputQuadraturePoints=false;
260 model::PyGlideSolver<3> pythonSolver(network);
261 Eigen::VectorXd raw=Eigen::VectorXd::Zero(velocities.size());
262 index=0;
263 for(const auto& entry : network.networkNodes())
264 {
265 if(connected.count(entry.first))
266 {
267 raw.segment<3>(3*index).setConstant(1.0/3.228757859764734e01);
268 }
269 ++index;
270 }
271 require((pythonSolver.getNodeVelocities()-model::compatiblePredictedVelocities<3>(network,raw)).norm()<1.e-10,
272 "Python bridge pinned boundary nodes or reordered velocities");
273 for(const auto& entry : network.networkLinks())
274 {
275 entry.second.lock()->quadraturePoints().clear();
276 }
277 require(pythonSolver.getNodeVelocities().isZero(0.0),"Empty quadrature network should remain stationary");
278#endif
279
280 size_t movingPhysical=0;
281 size_t movingSharedPhysical=0;
282 for(const auto& entry : network.networkNodes())
283 {
284 const auto node=entry.second.lock();
285 const V velocity=velocities.segment<3>(3*node->networkID());
286 bool physical=false;
287 bool subdivision=!node->loopNodes().empty();
288 for(const auto* ln : node->loopNodes())
289 {
290 physical=physical || ln->isPhysicalBoundaryNode();
291 subdivision=subdivision && ln->isPeriodicSubdivision();
292 }
293 if(physical && velocity.norm()>1.e-8)
294 {
295 ++movingPhysical;
296 if(node->loopNodes().size()>1)
297 {
298 ++movingSharedPhysical;
299 }
300 require(velocity.dot(V(0.1,0.2,0.3))>0,
301 "Physical endpoint lost response to mobile segments");
302 }
303 if(subdivision)
304 {
305 for(const auto* ln : node->loopNodes())
306 {
307 const auto prev=ln->periodicPrev();
308 const auto next=ln->periodicNext();
309 if(prev && next)
310 {
311 const double a=(ln->get_P()-prev->get_P()).norm();
312 const double b=(ln->get_P()-next->get_P()).norm();
313 const V expected=(b*velocities.segment<3>(3*prev->networkNode->networkID())
314 +a*velocities.segment<3>(3*next->networkNode->networkID()))/(a+b);
315 require((velocity-expected).norm()<1.e-8,"Periodic endpoint interpolation changed");
316 }
317 }
318 }
319 node->set_V(velocity,true);
320 if(physical)
321 {
322 for(const auto& normal : node->physicalBoundaryNormals())
323 {
324 require(node->get_V().dot(normal)>=-1.e-8,
325 "Trial velocity allows inward detachment from a physical face");
326 }
327 }
328 }
329 require(physicalOnly || movingPhysical>0,"Physical nodes were all pinned by the solver");
330 require(physicalOnly || movingSharedPhysical>0,"Shared physical junctions lost motion from mobile segments");
331 std::filesystem::remove_all(folder);
332 std::cout << "PASS: bicrystal physical junctions and periodic crossings in glide solver\n";
333 return 0;
334 }
335 catch(const std::exception& error)
336 {
337 std::cerr << error.what() << '\n';
338 return 1;
339 }
340}
Eigen::Vector3d V
void updateBoundaryNodes(const std::function< void()> &checkpoint={})
std::shared_ptr< DislocationGlideSolverBase< dim > > glideSolver
std::shared_ptr< StochasticForceGenerator > stochasticForceGenerator
void setConfiguration(const DefectiveCrystalConfigIO< dim > &)
void moveNodes(const double &dt_in)
void initializeConfiguration(const DefectiveCrystalConfigIO< dim > &configIO, const std::ofstream &f_file, const std::ofstream &F_labels) override
const LoopContainerType & loops() const
Definition LoopNetwork.h:98
const NetworkNodeContainerType & networkNodes() const
const LoopNodeContainerType & loopNodes() const
NetworkLinkContainerType & networkLinks()
void insertLoop(const std::shared_ptr< LoopType > &loop, const std::vector< std::shared_ptr< LoopNodeType > > nodes)
Eigen::VectorXd getNodeVelocities() const override
A class template that implements a counter of the number of instances of Derived type that are create...
Definition StaticID.h:29
function int_R syms a b c u real f
Definition int_R.m:4
a
Definition int_R.m:14
b
Definition int_R.m:16
Eigen::VectorXd interpolateClimbSubdivisionVelocities(const Network &network, const Eigen::VectorXd &nodal)
Eigen::VectorXd compatiblePredictedVelocities(const Network &network, const Eigen::VectorXd &prediction)
Eigen::VectorXd solveConstrainedVelocityDiagonal(const Eigen::VectorXd &diagonal, const Eigen::VectorXd &force, const VelocityMatrix &Z)
void forEachPeriodicVelocityRelation(const Network &network, Callback callback)
VelocityMatrix periodicClimbVelocityMap(const Network &network)
int regions
Definition triangle.cpp:696
SharedPtrType create(const Args &...args)
void require(bool condition, const char *message)
void periodic(const Trial &field, double relative=1.e-10)
std::string text(Config &config)
void climb(const fs::path &root, int rank)
int end[2]
void argv
int index
void b int argc
vertex p[3]
int k