24#ifdef _MODEL_PYBIND11_
25 pybind11::scoped_interpreter interpreter;
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");
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";
51 for(
const auto& face : region.second->faces())
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)
57 faceIDs=std::to_string(face.first);
62 require(found,
"No Y periodic seed found");
69 const double length=3671.9477120460206;
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";
81 spec.slipSystemIDs={0};
82 spec.loopRadii={1.e-7};
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");
93 std::vector<std::shared_ptr<model::DislocationNetwork<3>::LoopType>> originals;
94 for(
const auto& entry : network.
loops())
96 const auto loop=entry.second.lock();
99 originals.push_back(loop);
102 for(
const auto& original : originals)
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())
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));
116 using V=Eigen::Vector3d;
117 if(
argc==3 && !partial && !physicalOnly)
120 std::ifstream stream(
argv[2]);
121 require(
bool(stream),
"Cannot read saved configuration");
128 const V center(-0.5*length,0,0);
132 const auto node=entry.second.lock();
134 node->trySet_P(center+(physicalOnly ? 1.0 : 60.0)*(node->get_P()-center));
140 size_t sharedPhysical=0;
144 const auto node=entry.second.lock();
145 for(
const auto* ln : node->loopNodes())
147 if(ln->isPhysicalBoundaryNode() && (physicalOnly || node->isBoundaryNode()) && node->loopNodes().size()>1)
151 if(ln->isPeriodicSubdivision())
157 require(physicalOnly || sharedPhysical>0,
"No shared physical boundary node");
159 require(base.periodicLatticeBasis.cols()==(physicalOnly ? 0 : (partial ? 1 : 3)),
"Incorrect active periodic rank");
162 const auto segment=entry.second.lock();
163 segment->createQuadraturePoints(
false);
165 for(
auto& point : segment->quadraturePoints())
167 point.velocity=(1.0+0.15*std::sin(point.r.sum()/length))*
V(0.1,0.2,0.3);
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");
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());
183 const auto segment=entry.second.lock();
184 if(!segment->isBoundarySegment())
186 segment->addToGlobalAssembly(triplets,force);
189 for(
const auto& entry : triplets)
191 mass(entry.row())+=0.5*entry.value();
192 mass(entry.col())+=0.5*entry.value();
194 Eigen::VectorXd expected=Eigen::VectorXd::Zero(velocities.size());
195 for(Eigen::Index
k=0;
k<mass.size();++
k)
197 if(std::abs(mass(
k))>FLT_EPSILON)
199 expected(
k)=force(
k)/mass(
k);
202 require((velocities-expected).norm()<1.e-10,
"Nonperiodic solve changed unconstrained velocities");
206 const Eigen::VectorXd predicted=Eigen::VectorXd::LinSpaced(velocities.size(),0.1,1.0);
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);
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());
218 climb.segment<3>(3*
index)=scalar(
index)*entry.second.lock()->climbDirection();
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");
227 "Vector climb velocity is incompatible");
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");
234#ifdef _MODEL_PYBIND11_
237 std::set<size_t> connected;
240 const auto link=entry.second.lock();
241 if(!link->quadraturePoints().empty())
243 connected.insert(link->source->sID);
244 connected.insert(link->sink->sID);
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";
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;
261 Eigen::VectorXd raw=Eigen::VectorXd::Zero(velocities.size());
265 if(connected.count(entry.first))
267 raw.segment<3>(3*
index).setConstant(1.0/3.228757859764734e01);
272 "Python bridge pinned boundary nodes or reordered velocities");
275 entry.second.lock()->quadraturePoints().clear();
280 size_t movingPhysical=0;
281 size_t movingSharedPhysical=0;
284 const auto node=entry.second.lock();
285 const V velocity=velocities.segment<3>(3*node->networkID());
287 bool subdivision=!node->loopNodes().empty();
288 for(
const auto* ln : node->loopNodes())
290 physical=physical || ln->isPhysicalBoundaryNode();
291 subdivision=subdivision && ln->isPeriodicSubdivision();
293 if(physical && velocity.norm()>1.e-8)
296 if(node->loopNodes().size()>1)
298 ++movingSharedPhysical;
300 require(velocity.dot(
V(0.1,0.2,0.3))>0,
301 "Physical endpoint lost response to mobile segments");
305 for(
const auto* ln : node->loopNodes())
307 const auto prev=ln->periodicPrev();
308 const auto next=ln->periodicNext();
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");
319 node->set_V(velocity,
true);
322 for(
const auto& normal : node->physicalBoundaryNormals())
324 require(node->get_V().dot(normal)>=-1.e-8,
325 "Trial velocity allows inward detachment from a physical face");
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";
335 catch(
const std::exception& error)
337 std::cerr << error.what() <<
'\n';