Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
DislocationCrossSlip.cpp
Go to the documentation of this file.
1/* This file is part of MoDELib, the Mechanics Of Defects Evolution Library.
2 *
3 *
4 * MoDELib is distributed without any warranty under the
5 * GNU General Public License (GPL) v2 <http://www.gnu.org/licenses/>.
6 */
7
8#ifndef model_DislocationCrossSlip_cpp_
9#define model_DislocationCrossSlip_cpp_
10
12#include <numbers>
13#include <memory>
14#include <chrono>
15#ifdef _OPENMP
16#include <omp.h>
17#endif
18
20#include <CrossSlipModels.h>
21#include <OneSidedMotion.h>
22
23#include <DislocationNetwork.h>
24
25namespace model
26{
27 template <int dim>
28 std::shared_ptr<BaseCrossSlipModel<dim>> DislocationCrossSlip<dim>::getModel(const PolycrystallineMaterialBase& material,const DefectiveCrystalTraitsIO& traitsIO)
29 {
30 const int crossSlipModel(TextFileParser(traitsIO.inputFilesFolder+"/DD.txt").readScalar<int>("crossSlipModel",true));
31 switch (crossSlipModel)
32 {
33 case 0:
34 {// no cross-slip
35 return std::shared_ptr<BaseCrossSlipModel<dim>>(nullptr);
36 break;
37 }
38
39 case 1:
40 {// deterministic cross-slip model based on max glide PK force
41 return std::shared_ptr<BaseCrossSlipModel<dim>>(new AthermalCrossSlipModel<dim>(traitsIO));
42 break;
43 }
44
45 case 2:
46 {
47 if(material.crystalStructure=="HEX")
48 {
49 return std::shared_ptr<BaseCrossSlipModel<dim>>(new EscaigCrossSlipModelHEX<dim>(material,traitsIO));
50 }
51 else
52 {
53 throw std::runtime_error("Unknown cross-slip model "+std::to_string(crossSlipModel)+" for crystal structure "+material.crystalStructure);
54 return std::shared_ptr<BaseCrossSlipModel<dim>>(nullptr);
55 }
56 break;
57 }
58
59 default:
60 {
61 throw std::runtime_error("Unknown cross-slip model "+std::to_string(crossSlipModel));
62 return std::shared_ptr<BaseCrossSlipModel<dim>>(nullptr);
63 break;
64 }
65 }
66 }
67
68 template <int dim>
70 /* init */ DN(DN_in)
71 /* init */,verboseCrossSlip(TextFileParser(DN.ddBase.simulationParameters.traitsIO.inputFilesFolder+"/DD.txt").readScalar<int>("verboseCrossSlip",true))
72 {
73 }
74
75 template <int dim>
77 {
78
79 if(DN.crossSlipModel)
80 {
81 std::cout<<"CrossSlip: finding branches "<<std::flush;
82 const auto t0= std::chrono::steady_clock::now();
83 csNodes.clear();
84 for(const auto& loopIter : DN.loops())
85 {
86 const auto loop(loopIter.second.lock());
87 loop->crossSlipBranches(csNodes,dt);
88 }
89 std::cout<<csNodes.size()<<magentaColor<<" ["<<(std::chrono::duration<double>(std::chrono::steady_clock::now()-t0)).count()<<" sec]"<<defaultColor<<std::endl;
90 }
91 }
92
93 template <int dim>
95 {
96 if(!DN.crossSlipModel || !DN.glideSolver || DN.isClimbStep())
97 {
98 return;
99 }
100 auto phaseStart=std::chrono::steady_clock::now();
101 const auto reportPhase=[&](const char* phase)
102 {
103 const auto now=std::chrono::steady_clock::now();
104 std::cout<<"CrossSlip: "<<phase<<" ["<<std::chrono::duration<double>(now-phaseStart).count()<<" sec]"<<std::endl;
105 phaseStart=std::chrono::steady_clock::now();
106 };
107 CrossSlipBranchContainerType alignedBranches;
108 std::set<size_t> reservedNodes;
109 for(const auto& branch : csNodes)
110 {
111 if(branch.first.size()<(branch.thermal? 2 : 3))
112 {
113 continue;
114 }
115 const auto loop=branch.first.back()->loop();
116 if(!loop->glidePlane)
117 {
118 continue;
119 }
120 const auto& system=loop->grain.slipSystems().at(branch.second);
121 VectorDim midpoint=VectorDim::Zero();
122 double branchLength=0.0;
123 for(size_t k=1;k<branch.first.size();++k)
124 {
125 const auto& a=branch.first[k-1]->get_P();
126 const auto& b=branch.first[k]->get_P();
127 const double length=(b-a).norm();
128 midpoint+=0.5*length*(a+b);
129 branchLength+=length;
130 }
131 if(branchLength<=FLT_EPSILON)
132 {
133 continue;
134 }
135 midpoint/=branchLength;
136 const long spacing=std::max(1L,static_cast<long>(20.0/system->n.planeSpacing()));
137 const long height=std::lround(double(system->n.closestPlaneIndexOfPoint(midpoint))/spacing)*spacing;
138 const VectorDim planePoint=height*system->n.planeSpacing()*system->unitNormal;
139 PlanePlaneIntersection<dim> intersection(loop->glidePlane->P,loop->glidePlane->unitNormal,
140 planePoint,system->unitNormal);
141 // Prepare each NETWORK displacement once. No positions or links
142 // change until every occurrence and every affected loop is checked.
143 std::map<size_t,VectorDim> targets;
144 bool valid=true;
145 std::set<LoopType*> affected;
146 for(const auto& node : branch.first)
147 {
148 const auto net=node->networkNode;
149 const VectorDim delta=intersection.P+(node->get_P()-intersection.P).dot(intersection.d)*intersection.d-node->get_P();
150 const VectorDim target=net->get_P()+delta;
151 const auto prior=targets.find(net->sID);
152 if(reservedNodes.count(net->sID) || (prior!=targets.end() && (prior->second-target).norm()>FLT_EPSILON))
153 {
154 valid=false;
155 break;
156 }
158 for(const auto* plane : net->glidePlanes())
159 {
160 normals.push_back(plane->unitNormal);
161 }
162 for(int row=0;row<DN.nodalVelocityConstraints.rows();++row)
163 {
164 normals.push_back(DN.nodalVelocityConstraints.row(row));
165 }
166 if(!target.allFinite() || !net->isMovableTo(target) ||
167 (oneSidedMotion<dim>(delta,normals,net->physicalBoundaryNormals())-delta).norm()>FLT_EPSILON)
168 {
169 valid=false;
170 break;
171 }
172 for(const auto* occurrence : net->loopNodes())
173 {
174 affected.insert(occurrence->loop().get());
175 }
176 targets.emplace(net->sID,target);
177 }
178 for(const auto* affectedLoop : affected)
179 {
180 if(!valid)
181 {
182 break;
183 }
184 std::vector<std::pair<VectorDim,const LoopNodeType* const>> polygon;
185 for(const auto* link : affectedLoop->linkSequence())
186 {
187 const auto& node=link->source;
188 if(!node->isPeriodicSubdivision())
189 {
190 VectorDim position=node->get_P();
191 const auto target=targets.find(node->networkNode->sID);
192 if(target!=targets.end())
193 {
194 position+=target->second-node->networkNode->get_P();
195 }
196 polygon.emplace_back(position,node.get());
197 }
198 }
199 const auto contour=affectedLoop->periodicGlidePlane->polygonPatchIntersection(polygon,true);
200 std::vector<double> coverage(polygon.size(),0.0);
201 for(size_t k=0;k<contour.size();++k)
202 {
203 const auto& metadata=std::get<6>(contour[k]);
204 if(metadata.incomingOriginalSegment)
205 {
206 coverage.at(metadata.incomingOriginalSegment->sourceIndex)+=
207 (std::get<0>(contour[k])-std::get<0>(contour[(k+contour.size()-1)%contour.size()])).norm();
208 }
209 }
210 if(contour.empty())
211 {
212 valid=false;
213 }
214 for(size_t k=0;k<polygon.size();++k)
215 {
216 const double length=(polygon[(k+1)%polygon.size()].first-polygon[k].first).norm();
217 if(std::abs(length-coverage[k])>1.e-8*std::max(1.0,length))
218 {
219 valid=false;
220 }
221 }
222 }
223 if(branch.thermal)
224 {
225 double alignedLength=0.0;
226 for(size_t k=1;k<branch.first.size();++k)
227 {
228 alignedLength+=std::abs((branch.first[k]->get_P()-branch.first[k-1]->get_P()).dot(intersection.d));
229 }
230 if(alignedLength+FLT_EPSILON<branch.thermal->length)
231 {
232 valid=false;
233 }
234 }
235 if(!valid)
236 {
237 continue;
238 }
239 for(const auto& [id,target] : targets)
240 {
241 const auto net=DN.networkNodes().get(id);
242 const VectorDim delta=target-net->get_P();
243 for(auto* occurrence : net->loopNodes())
244 {
245 static_cast<typename LoopNodeType::SplineNodeType&>(*occurrence).set_P(occurrence->get_P()+delta);
246 }
247 // Network positions are committed only by the boundary walk,
248 // which maps same-grain periodic exits back into the RVE.
249 reservedNodes.insert(id);
250 }
251 alignedBranches.push_back(branch);
252 }
253 csNodes.clear();
254 reportPhase("alignment validation and commit");
255 std::cout<<"CrossSlip: "<<alignedBranches.size()<<" aligned branches"<<std::endl;
256 if(alignedBranches.empty())
257 {
258 return;
259 }
260 DN.updateBoundaryNodes();
261 reportPhase("boundary update");
262 std::vector<std::shared_ptr<LoopType>> newLoops;
263 for(auto& branch : alignedBranches)
264 {
265 // Reconciliation can replace a physical-face occurrence while
266 // preserving its network node. Resolve live occurrences before
267 // following prev/next connectivity.
268 for(auto& anchor : branch.first)
269 {
270 const auto parent=anchor->loop();
271 std::shared_ptr<LoopNodeType> live;
272 for(const auto* link : parent->linkSequence())
273 {
274 if(link->source->networkNode==anchor->networkNode &&
275 (link->source->get_P()-anchor->get_P()).norm()<FLT_EPSILON)
276 {
277 live=link->source;
278 break;
279 }
280 }
281 if(!live)
282 {
283 throw std::runtime_error("Cross-slip alignment lost a branch anchor");
284 }
285 anchor=live;
286 }
287 const auto parent=branch.first.back()->loop();
288 if(branch.thermal)
289 {
290 // Split only after successful transactional alignment. Walk live
291 // loop connectivity, including periodic seam connectors; they
292 // carry zero physical length and retain their original patches.
293 const auto first=branch.first.front();
294 const auto last=branch.first.back();
295 double total=0.0;
296 auto* cursor=first.get();
297 size_t walked=0;
298 while(cursor!=last.get())
299 {
300 if(!cursor || !cursor->next.first || ++walked>parent->loopLinks().size())
301 {
302 throw std::runtime_error("Thermal branch lost connectivity");
303 }
304 total+=(cursor->next.first->get_P()-cursor->get_P()).norm();
305 cursor=cursor->next.first;
306 }
307 const double length=std::min(branch.thermal->length,total);
308 const double center=std::clamp(branch.thermal->centerFraction*total,0.5*length,total-0.5*length);
309 std::deque<std::shared_ptr<LoopNodeType>> nucleus;
310 for(const double distance : {center-0.5*length,center,center+0.5*length})
311 {
312 double offset=0.0;
313 cursor=first.get();
314 walked=0;
315 std::shared_ptr<LoopNodeType> selected;
316 while(cursor!=last.get())
317 {
318 if(!cursor || !cursor->next.first || ++walked>parent->loopLinks().size())
319 {
320 throw std::runtime_error("Thermal nucleus lost connectivity");
321 }
322 const auto* link=cursor->next.second;
323 const auto source=link->source;
324 const auto sink=link->sink;
325 const double chord=(sink->get_P()-source->get_P()).norm();
326 if(distance<=offset+FLT_EPSILON)
327 {
328 selected=source;
329 break;
330 }
331 if(chord>FLT_EPSILON && distance<offset+chord-FLT_EPSILON)
332 {
333 const double u=(distance-offset)/chord;
334 const auto a=source->networkNode;
335 const auto b=sink->networkNode;
336 const VectorDim position=(1.0-u)*a->get_P()+u*b->get_P();
337 const auto net=DN.networkNodes().create(position,(1.0-u)*a->get_V()+u*b->get_V(),
338 (1.0-u)*a->climbVelocityScalar+u*b->climbVelocityScalar,
339 (1.0-u)*a->velocityReduction()+u*b->velocityReduction());
340 selected=DN.loopNodes().create(parent,net,link);
341 DN.expandLoopLink(*link,selected);
342 break;
343 }
344 offset+=chord;
345 cursor=sink.get();
346 }
347 nucleus.push_back(selected? selected : last);
348 }
349 branch.first=std::move(nucleus);
350 }
351 const auto& system=parent->grain.slipSystems().at(branch.second);
352 const auto anchor=branch.first.back();
353 const VectorDim originShift=anchor->networkNode->get_P()-anchor->get_P();
354 const auto plane=DN.ddBase.dislocationDynamics().glidePlaneFactory.getFromKey(
355 GlidePlaneKey<dim>(anchor->networkNode->get_P(),system->n));
356 const auto seed=DN.loops().create(system->s.cartesian(),plane);
357 std::deque<std::shared_ptr<LoopNodeType>> nodes;
358 auto* current=anchor.get();
359 size_t visited=0;
360 while(true)
361 {
362 if(!current || ++visited>parent->loopLinks().size())
363 {
364 throw std::runtime_error("Cross-slip branch lost its loop connectivity");
365 }
366 // Transfer the actual patch translation, not an alternating
367 // edge counter. Physical faces never imply a patch transition.
368 const VectorDim shift=current->periodicPlanePatch()->shift-originShift;
369 const auto patch=seed->periodicGlidePlane->getPatch(shift);
370 std::pair<std::shared_ptr<PeriodicPlaneEdge<dim>>,std::shared_ptr<PeriodicPlaneEdge<dim>>> edges;
371 if(current->periodicPlaneEdge.first)
372 {
373 edges.first=patch->edgeOnFaces(current->periodicPlaneEdge.first->meshIntersection->faces);
374 if(!edges.first)
375 {
376 throw std::runtime_error("Cross-slip patch missing first boundary face");
377 }
378 }
379 if(current->periodicPlaneEdge.second)
380 {
381 edges.second=patch->edgeOnFaces(current->periodicPlaneEdge.second->meshIntersection->faces);
382 if(!edges.second)
383 {
384 throw std::runtime_error("Cross-slip patch missing second boundary face");
385 }
386 }
387 const VectorDim position=current->networkNode->get_P()-shift;
388 nodes.emplace_back(DN.loopNodes().create(seed,current->networkNode,position,patch,edges));
389 if(current==branch.first.front().get())
390 {
391 break;
392 }
393 if(current!=anchor.get())
394 {
395 nodes.emplace_front(DN.loopNodes().create(seed,DN.networkNodes().clone(current->networkNode->sID),position,patch,edges));
396 }
397 current=current->prev.first;
398 }
399 DN.insertLoop(seed,nodes);
400 newLoops.push_back(seed);
401 }
402 reportPhase("seed insertion");
403 // Seeds deliberately have zero area. Do not clip them before the
404 // first physical motion; the ordinary moveNodes walk handles that.
405 for(const auto& entry : DN.loops())
406 {
407 entry.second.lock()->updateGeometry();
408 }
409 reportPhase("loop geometry");
410 // Recompute all fields: alignment also changed the parent/junctions.
411 for(const auto& entry : DN.networkLinks())
412 {
413 entry.second.lock()->createQuadraturePoints(false);
414 }
415 reportPhase("quadrature creation");
416 {
417 // Topology is fixed here. Release all shared references before
418 // returning to any operation that can change network connectivity.
419 std::vector<std::shared_ptr<NetworkLinkType>> links;
420 links.reserve(DN.networkLinks().size());
421 for(const auto& entry : DN.networkLinks())
422 {
423 links.push_back(entry.second.lock());
424 }
425#ifdef _OPENMP
426#pragma omp parallel for
427#endif
428 for(size_t k=0;k<links.size();++k)
429 {
430 links[k]->updateQuadraturePoints(false);
431 }
432 }
433 reportPhase("field refresh");
434 if(DN.glideSolver)
435 {
436 const Eigen::VectorXd velocities=DN.glideSolver->getNodeVelocities();
437 if(velocities.size()!=static_cast<Eigen::Index>(dim*DN.networkNodes().size()))
438 {
439 throw std::runtime_error("Cross-slip velocity solve returned wrong size");
440 }
441 size_t k=0;
442 for(const auto& entry : DN.networkNodes())
443 {
444 entry.second.lock()->set_V(velocities.template segment<dim>(dim*k),false);
445 ++k;
446 }
447 }
448 reportPhase("velocity solve and storage");
449 for(const auto& entry : DN.loops())
450 {
451 entry.second.lock()->updateRates();
452 }
453 reportPhase("rate update");
454 csNodes.clear();
455 }
456
457 template class DislocationCrossSlip<3>;
458}
459#endif
static std::shared_ptr< BaseCrossSlipModel< dim > > getModel(const PolycrystallineMaterialBase &material, const DefectiveCrystalTraitsIO &traitsIO)
void findCrossSlipSegments(const double dt=-1.0)
DislocationCrossSlip(DislocationNetwork< dim > &DN_in)
SplineNode< DislocationLoopNode< dim >, dim, 0, Hermite > SplineNodeType
std::vector< VectorDim > VectorOfNormalsType
Scalar readScalar(const std::string &key, const bool &verbose=false)
a
Definition int_R.m:14
b
Definition int_R.m:16
size_t InstanceCount< Derived >::count
static std::string magentaColor
Eigen::Matrix< double, dim, 1 > oneSidedMotion(const Eigen::Matrix< double, dim, 1 > &requested, const std::vector< Eigen::Matrix< double, dim, 1 > > &equalities, const std::vector< Eigen::Matrix< double, dim, 1 > > &outward)
static std::string defaultColor
LatticePlaneKey< dim > GlidePlaneKey
m edges
REAL t0
int parent
REAL u[4]
int k
vertex first
int current