Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "CrackFrontDefinition.h"
11 :
12 : // MOOSE includes
13 : #include "MooseError.h"
14 : #include "MooseMesh.h"
15 : #include "MooseVariable.h"
16 : #include "RankTwoTensor.h"
17 :
18 : #include "libmesh/mesh_tools.h"
19 : #include "libmesh/string_to_enum.h"
20 : #include "libmesh/quadrature.h"
21 :
22 : registerMooseObject("SolidMechanicsApp", CrackFrontDefinition);
23 :
24 : InputParameters
25 608 : CrackFrontDefinition::validParams()
26 : {
27 608 : InputParameters params = GeneralUserObject::validParams();
28 608 : params.addClassDescription("Used to describe geometric characteristics of the crack front for "
29 : "fracture integral calculations");
30 608 : params += BoundaryRestrictable::validParams();
31 608 : CrackFrontDefinition::includeCrackFrontDefinitionParams(params);
32 608 : params.set<bool>("use_displaced_mesh") = false;
33 :
34 1216 : params.addRelationshipManager("ElementSideNeighborLayers",
35 : Moose::RelationshipManagerType::ALGEBRAIC,
36 237 : [](const InputParameters &, InputParameters & rm_params)
37 237 : { rm_params.set<unsigned short>("layers") = 2; });
38 608 : return params;
39 0 : }
40 :
41 : void
42 1746 : CrackFrontDefinition::includeCrackFrontDefinitionParams(InputParameters & params)
43 : {
44 3492 : MooseEnum direction_method("CrackDirectionVector CrackMouth CurvedCrackFront");
45 : MooseEnum end_direction_method("NoSpecialTreatment CrackDirectionVector CrackTangentVector",
46 3492 : "NoSpecialTreatment");
47 3492 : params.addParam<std::vector<Point>>("crack_front_points", "Set of points to define crack front");
48 3492 : params.addParam<bool>("closed_loop", false, "Set of points forms forms a closed loop");
49 3492 : params.addRequiredParam<MooseEnum>(
50 : "crack_direction_method",
51 : direction_method,
52 1746 : "Method to determine direction of crack propagation. Choices are: " +
53 1746 : direction_method.getRawNames());
54 3492 : params.addParam<MooseEnum>(
55 : "crack_end_direction_method",
56 : end_direction_method,
57 1746 : "Method to determine direction of crack propagation at ends of crack. Choices are: " +
58 1746 : end_direction_method.getRawNames());
59 3492 : params.addParam<RealVectorValue>("crack_direction_vector", "Direction of crack propagation");
60 3492 : params.addParam<RealVectorValue>(
61 : "crack_direction_vector_end_1",
62 : "Direction of crack propagation for the node at end 1 of the crack");
63 3492 : params.addParam<RealVectorValue>(
64 : "crack_direction_vector_end_2",
65 : "Direction of crack propagation for the node at end 2 of the crack");
66 3492 : params.addParam<RealVectorValue>("crack_tangent_vector_end_1",
67 : "Direction of crack tangent for the node at end 1 of the crack");
68 3492 : params.addParam<RealVectorValue>("crack_tangent_vector_end_2",
69 : "Direction of crack tangent for the node at end 2 of the crack");
70 3492 : params.addParam<std::vector<BoundaryName>>(
71 : "crack_mouth_boundary", "Boundaries whose average coordinate defines the crack mouth");
72 3492 : params.addParam<std::vector<BoundaryName>>("intersecting_boundary",
73 : "Boundaries intersected by ends of crack");
74 3492 : params.addParam<bool>("2d", false, "Treat body as two-dimensional");
75 5238 : params.addRangeCheckedParam<unsigned int>(
76 : "axis_2d",
77 3492 : 2,
78 : "axis_2d>=0 & axis_2d<=2",
79 : "Out of plane axis for models treated as two-dimensional (0=x, 1=y, 2=z)");
80 3492 : params.addParam<unsigned int>("symmetry_plane",
81 : "Account for a symmetry plane passing through "
82 : "the plane of the crack, normal to the specified "
83 : "axis (0=x, 1=y, 2=z)");
84 3492 : params.addParam<bool>("t_stress", false, "Calculate T-stress");
85 3492 : params.addParam<bool>("q_function_rings", false, "Generate rings of nodes for q-function");
86 3492 : params.addParam<unsigned int>("last_ring", "The number of rings of nodes to generate");
87 3492 : params.addParam<unsigned int>("first_ring", "The number of rings of nodes to generate");
88 3492 : params.addParam<unsigned int>("nrings", "The number of rings of nodes to generate");
89 3492 : params.addParam<VariableName>("disp_x", "Variable containing the x displacement");
90 3492 : params.addParam<VariableName>("disp_y", "Variable containing the y displacement");
91 3492 : params.addParam<VariableName>("disp_z", "Variable containing the z displacement");
92 3492 : params.addParam<std::vector<Real>>(
93 : "j_integral_radius_inner", {}, "Radius for J-Integral calculation");
94 3492 : params.addParam<std::vector<Real>>(
95 : "j_integral_radius_outer", {}, "Radius for J-Integral calculation");
96 3492 : MooseEnum q_function_type("Geometry Topology", "Geometry");
97 3492 : params.addParam<MooseEnum>("q_function_type",
98 : q_function_type,
99 1746 : "The method used to define the integration domain. Options are: " +
100 1746 : q_function_type.getRawNames());
101 3492 : params.addParam<UserObjectName>(
102 : "crack_front_points_provider",
103 : "The UserObject provides the crack front points from XFEM GeometricCutObject");
104 :
105 3492 : params.addParam<unsigned int>("number_points_from_provider",
106 : "The number of crack front points, only needed for "
107 : "crack_front_points_provider that do not use a cut mesh.");
108 3492 : params.addParam<Real>(
109 : "crack_front_node_tolerance",
110 3492 : 1e-10,
111 : "General tolerance for locating nodes on the crack front based on xyz coordinates.");
112 1746 : }
113 :
114 196 : CrackFrontDefinition::CrackFrontDefinition(const InputParameters & parameters)
115 : : GeneralUserObject(parameters),
116 : BoundaryRestrictable(this, true), // false means nodesets
117 196 : _direction_method(getParam<MooseEnum>("crack_direction_method").getEnum<DIRECTION_METHOD>()),
118 196 : _end_direction_method(
119 196 : getParam<MooseEnum>("crack_end_direction_method").getEnum<END_DIRECTION_METHOD>()),
120 196 : _aux(_fe_problem.getAuxiliarySystem()),
121 196 : _mesh(_subproblem.mesh()),
122 392 : _treat_as_2d(getParam<bool>("2d")),
123 196 : _use_mesh_cutter(false),
124 196 : _is_cutter_modified(false),
125 392 : _closed_loop(getParam<bool>("closed_loop")),
126 392 : _axis_2d(getParam<unsigned int>("axis_2d")),
127 392 : _has_symmetry_plane(isParamValid("symmetry_plane")),
128 466 : _symmetry_plane(_has_symmetry_plane ? getParam<unsigned int>("symmetry_plane")
129 : : std::numeric_limits<unsigned int>::max()),
130 392 : _t_stress(getParam<bool>("t_stress")),
131 392 : _q_function_rings(getParam<bool>("q_function_rings")),
132 588 : _q_function_type(getParam<MooseEnum>("q_function_type")),
133 196 : _crack_front_points_provider(nullptr),
134 588 : _tol(getParam<Real>("crack_front_node_tolerance"))
135 : {
136 392 : auto boundary = isParamValid("boundary") ? getParam<std::vector<BoundaryName>>("boundary")
137 556 : : std::vector<BoundaryName>{};
138 392 : if (isParamValid("crack_front_points"))
139 : {
140 16 : if (boundary.size())
141 0 : paramError("crack_front_points",
142 : "CrackFrontDefinition error: since boundary is defined, crack_front_points should "
143 : "not be added.");
144 32 : if (isParamValid("crack_front_points_provider"))
145 0 : paramError("crack_front_points_provider",
146 : "As crack_front_points have been provided, the crack_front_points_provider will "
147 : "not be used and needs to be removed.");
148 32 : _crack_front_points = getParam<std::vector<Point>>("crack_front_points");
149 16 : _geom_definition_method = CRACK_GEOM_DEFINITION::CRACK_FRONT_POINTS;
150 16 : if (_t_stress)
151 0 : paramError("t_stress", "t_stress not yet supported with crack_front_points");
152 16 : if (_q_function_rings)
153 0 : paramError("q_function_rings", "q_function_rings not supported with crack_front_points");
154 : }
155 360 : else if (isParamValid("crack_front_points_provider"))
156 : {
157 0 : if (boundary.size())
158 0 : paramError("crack_front_points_provider",
159 : "CrackFrontDefinition error: since boundary is defined, "
160 : "crack_front_points_provider should not be added.");
161 :
162 0 : _geom_definition_method = CRACK_GEOM_DEFINITION::CRACK_FRONT_POINTS;
163 : }
164 360 : else if (isParamValid("number_points_from_provider"))
165 0 : paramError("number_points_from_provider",
166 : "CrackFrontDefinition error: number_points_from_provider is provided but "
167 : "crack_front_points_provider cannot be found.");
168 180 : else if (boundary.size())
169 : {
170 180 : _geom_definition_method = CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES;
171 360 : if (parameters.isParamSetByUser("closed_loop"))
172 0 : paramError("closed_loop",
173 : "In CrackFrontDefinition, if 'boundary' is defined, 'closed_loop' should not be "
174 : "set by user because closed loops are detected automatically");
175 : }
176 : else
177 0 : mooseError("In CrackFrontDefinition, must define one of 'boundary', 'crack_front_points' "
178 : "and 'crack_front_points_provider'");
179 :
180 392 : if (isParamValid("crack_mouth_boundary"))
181 54 : _crack_mouth_boundary_names = getParam<std::vector<BoundaryName>>("crack_mouth_boundary");
182 :
183 196 : if (_has_symmetry_plane)
184 135 : if (_symmetry_plane > 2)
185 0 : paramError("symmetry_plane",
186 : "symmetry_plane out of bounds: ",
187 : _symmetry_plane,
188 : " Must be >=0 and <=2.");
189 :
190 196 : switch (_direction_method)
191 : {
192 155 : case DIRECTION_METHOD::CRACK_DIRECTION_VECTOR:
193 310 : if (!isParamValid("crack_direction_vector"))
194 0 : paramError("crack_direction_vector",
195 : "crack_direction_vector must be specified if crack_direction_method = "
196 : "CrackDirectionVector");
197 310 : _crack_direction_vector = getParam<RealVectorValue>("crack_direction_vector");
198 155 : break;
199 18 : case DIRECTION_METHOD::CRACK_MOUTH:
200 36 : if (isParamValid("crack_direction_vector"))
201 0 : paramError("crack_direction_vector",
202 : "crack_direction_vector must not be specified if crack_direction_method = "
203 : "CrackMouthNodes");
204 18 : if (_crack_mouth_boundary_names.size() == 0)
205 0 : paramError(
206 : "crack_mouth_boundary",
207 : "crack_mouth_boundary must be specified if crack_direction_method = CrackMouthNodes");
208 : break;
209 23 : case DIRECTION_METHOD::CURVED_CRACK_FRONT:
210 46 : if (isParamValid("crack_direction_vector"))
211 0 : paramError("crack_direction_vector",
212 : "crack_direction_vector must not be specified if crack_direction_method = "
213 : "CurvedCrackFront");
214 : break;
215 0 : default:
216 0 : paramError("crack_direction_method", "Invalid direction_method");
217 : }
218 :
219 392 : if (isParamValid("intersecting_boundary"))
220 66 : _intersecting_boundary_names = getParam<std::vector<BoundaryName>>("intersecting_boundary");
221 :
222 196 : if (_end_direction_method == END_DIRECTION_METHOD::END_CRACK_DIRECTION_VECTOR)
223 : {
224 52 : if (!isParamValid("crack_direction_vector_end_1"))
225 0 : paramError("crack_direction_vector_end_1",
226 : "crack_direction_vector_end_1 must be specified if crack_end_direction_method = "
227 : "CrackDirectionVector");
228 52 : if (!isParamValid("crack_direction_vector_end_2"))
229 0 : paramError("crack_direction_vector_end_2",
230 : "crack_direction_vector_end_2 must be specified if crack_end_direction_method = "
231 : "CrackDirectionVector");
232 52 : _crack_direction_vector_end_1 = getParam<RealVectorValue>("crack_direction_vector_end_1");
233 52 : _crack_direction_vector_end_2 = getParam<RealVectorValue>("crack_direction_vector_end_2");
234 : }
235 :
236 196 : if (_end_direction_method == END_DIRECTION_METHOD::END_CRACK_TANGENT_VECTOR)
237 : {
238 0 : if (!isParamValid("crack_tangent_vector_end_1"))
239 0 : paramError("crack_tangent_vector_end_1",
240 : "crack_tangent_vector_end_1 must be specified if crack_end_tangent_method = "
241 : "CrackTangentVector");
242 0 : if (!isParamValid("crack_tangent_vector_end_2"))
243 0 : paramError("crack_tangent_vector_end_2",
244 : "crack_tangent_vector_end_2 must be specified if crack_end_tangent_method = "
245 : "CrackTangentVector");
246 0 : _crack_tangent_vector_end_1 = getParam<RealVectorValue>("crack_tangent_vector_end_1");
247 0 : _crack_tangent_vector_end_2 = getParam<RealVectorValue>("crack_tangent_vector_end_2");
248 : }
249 :
250 440 : if (isParamValid("disp_x") && isParamValid("disp_y") && isParamValid("disp_z"))
251 : {
252 16 : _disp_x_var_name = getParam<VariableName>("disp_x");
253 16 : _disp_y_var_name = getParam<VariableName>("disp_y");
254 24 : _disp_z_var_name = getParam<VariableName>("disp_z");
255 : }
256 188 : else if (_t_stress == true && _treat_as_2d == false)
257 0 : paramError("displacements", "Displacement variables must be provided for T-stress calculation");
258 :
259 196 : if (_q_function_rings)
260 : {
261 40 : if (!isParamValid("last_ring"))
262 0 : paramError("last_ring",
263 : "The max number of rings of nodes to generate must be provided if "
264 : "q_function_rings = true");
265 40 : _last_ring = getParam<unsigned int>("last_ring");
266 40 : _first_ring = getParam<unsigned int>("first_ring");
267 : }
268 : else
269 : {
270 352 : _j_integral_radius_inner = getParam<std::vector<Real>>("j_integral_radius_inner");
271 528 : _j_integral_radius_outer = getParam<std::vector<Real>>("j_integral_radius_outer");
272 : }
273 196 : }
274 :
275 : void
276 439 : CrackFrontDefinition::execute()
277 : {
278 : // Because J-Integral is based on original geometry, the crack front geometry
279 : // is never updated, so everything that needs to happen is done in initialSetup()
280 439 : if (_t_stress == true && _treat_as_2d == false)
281 18 : calculateTangentialStrainAlongFront();
282 439 : }
283 :
284 : void
285 195 : CrackFrontDefinition::initialSetup()
286 : {
287 390 : if (isParamValid("crack_front_points_provider"))
288 : {
289 0 : _crack_front_points_provider = &getUserObjectByName<CrackFrontPointsProvider>(
290 : getParam<UserObjectName>("crack_front_points_provider"));
291 0 : if (_crack_front_points_provider->usesMesh())
292 : {
293 0 : _use_mesh_cutter = true;
294 :
295 : // Automatically get number of crack front points from mesh-based provider
296 0 : _num_points_from_provider = _crack_front_points_provider->getNumberOfCrackFrontPoints();
297 0 : mooseInfo("CrackFrontDefinition: Automatically detected ",
298 0 : _num_points_from_provider,
299 : " crack front points from mesh-based provider");
300 :
301 0 : if (_direction_method != DIRECTION_METHOD::CURVED_CRACK_FRONT)
302 0 : paramError("crack_direction_method",
303 : "Using a `crack_front_points_provider` that uses an XFEM cutter mesh also "
304 : "requires setting 'crack_direction_method = CurvedCrackFront'");
305 0 : if (isParamValid("crack_mouth_boundary"))
306 0 : paramError("crack_mouth_boundary",
307 : "'crack_mouth_boundary' cannot be set when using a "
308 : "'crack_front_points_provider' that uses an XFEM cutter mesh");
309 : }
310 0 : else if (isParamValid("number_points_from_provider"))
311 : {
312 0 : _num_points_from_provider = getParam<unsigned int>("number_points_from_provider");
313 : }
314 0 : else if (_num_points_from_provider == 0)
315 : {
316 0 : paramError(
317 : "number_points_from_provider",
318 : "CrackFrontDefinition error: When using a non-mesh-based crack_front_points_provider, "
319 : "the number_points_from_provider parameter must be provided.");
320 : }
321 : }
322 195 : if (_crack_front_points_provider != nullptr)
323 : {
324 : _crack_front_points =
325 0 : _crack_front_points_provider->getCrackFrontPoints(_num_points_from_provider);
326 : }
327 :
328 195 : _crack_mouth_boundary_ids = _mesh.getBoundaryIDs(_crack_mouth_boundary_names, true);
329 195 : _intersecting_boundary_ids = _mesh.getBoundaryIDs(_intersecting_boundary_names, true);
330 :
331 195 : if (_geom_definition_method == CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES)
332 : {
333 : std::set<dof_id_type> nodes;
334 179 : getCrackFrontNodes(nodes);
335 179 : orderCrackFrontNodes(nodes);
336 : }
337 :
338 195 : if (_closed_loop && _intersecting_boundary_names.size() > 0)
339 0 : paramError("intersecting_boundary", "Cannot use intersecting_boundary with closed-loop cracks");
340 :
341 195 : updateCrackFrontGeometry();
342 :
343 195 : if (_q_function_rings)
344 20 : createQFunctionRings();
345 :
346 195 : if (_t_stress)
347 : {
348 : std::size_t num_crack_front_nodes = _ordered_crack_front_nodes.size();
349 72 : for (std::size_t i = 0; i < num_crack_front_nodes; ++i)
350 60 : _strain_along_front.push_back(-std::numeric_limits<Real>::max());
351 : }
352 :
353 195 : std::size_t num_crack_front_points = getNumCrackFrontPoints();
354 195 : if (_q_function_type == "GEOMETRY")
355 : {
356 175 : if (!_treat_as_2d)
357 67 : if (num_crack_front_points < 1)
358 0 : mooseError("num_crack_front_points is not > 0");
359 753 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
360 : {
361 578 : bool is_point_on_intersecting_boundary = isPointWithIndexOnIntersectingBoundary(i);
362 578 : _is_point_on_intersecting_boundary.push_back(is_point_on_intersecting_boundary);
363 : }
364 : }
365 195 : }
366 :
367 : void
368 439 : CrackFrontDefinition::initialize()
369 : {
370 : // Update the crack front for fracture integral calculations
371 : // This is only useful for growing cracks which are currently described by the mesh
372 : // cutter
373 439 : if (_use_mesh_cutter && _is_cutter_modified)
374 : {
375 : // Automatically update the number of crack front points as crack grows
376 0 : _num_points_from_provider = _crack_front_points_provider->getNumberOfCrackFrontPoints();
377 :
378 : _crack_front_points =
379 0 : _crack_front_points_provider->getCrackFrontPoints(_num_points_from_provider);
380 0 : updateCrackFrontGeometry();
381 0 : std::size_t num_crack_front_points = getNumCrackFrontPoints();
382 0 : if (_q_function_type == "GEOMETRY")
383 0 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
384 : {
385 0 : bool is_point_on_intersecting_boundary = isPointWithIndexOnIntersectingBoundary(i);
386 0 : _is_point_on_intersecting_boundary.push_back(is_point_on_intersecting_boundary);
387 : }
388 : }
389 439 : }
390 :
391 : void
392 439 : CrackFrontDefinition::finalize()
393 : {
394 439 : if (_t_stress)
395 27 : _communicator.max(_strain_along_front);
396 439 : }
397 :
398 : void
399 179 : CrackFrontDefinition::getCrackFrontNodes(std::set<dof_id_type> & nodes)
400 : {
401 179 : ConstBndNodeRange & bnd_nodes = *_mesh.getBoundaryNodeRange();
402 18301 : for (auto nd = bnd_nodes.begin(); nd != bnd_nodes.end(); ++nd)
403 : {
404 18122 : const BndNode * bnode = *nd;
405 18122 : BoundaryID boundary_id = bnode->_bnd_id;
406 :
407 18122 : if (hasBoundary(boundary_id))
408 562 : nodes.insert(bnode->_node->id());
409 : }
410 :
411 179 : if (_treat_as_2d && _use_mesh_cutter == false)
412 : {
413 116 : if (nodes.size() > 1)
414 : {
415 : // Check that the nodes are collinear in the axis normal to the 2d plane
416 : unsigned int axis0;
417 : unsigned int axis1;
418 :
419 12 : switch (_axis_2d)
420 : {
421 : case 0:
422 : axis0 = 1;
423 : axis1 = 2;
424 : break;
425 : case 1:
426 : axis0 = 0;
427 : axis1 = 2;
428 : break;
429 : case 2:
430 : axis0 = 0;
431 : axis1 = 1;
432 : break;
433 0 : default:
434 0 : mooseError("Invalid axis.");
435 : }
436 :
437 : Real node0coor0 = 0;
438 : Real node0coor1 = 0;
439 :
440 36 : for (auto sit = nodes.begin(); sit != nodes.end(); ++sit)
441 : {
442 24 : Node & curr_node = _mesh.nodeRef(*sit);
443 24 : if (sit == nodes.begin())
444 : {
445 12 : node0coor0 = curr_node(axis0);
446 12 : node0coor1 = curr_node(axis1);
447 : }
448 : else
449 : {
450 12 : if (!MooseUtils::absoluteFuzzyEqual(curr_node(axis0), node0coor0, _tol) ||
451 : !MooseUtils::absoluteFuzzyEqual(curr_node(axis1), node0coor1, _tol))
452 0 : mooseError("Boundary provided in CrackFrontDefinition contains ",
453 0 : nodes.size(),
454 : " nodes, which are not collinear in the ",
455 0 : _axis_2d,
456 : " axis. Must contain either 1 node or collinear nodes to treat as 2D.");
457 : }
458 : }
459 : }
460 : }
461 179 : }
462 :
463 : void
464 179 : CrackFrontDefinition::orderCrackFrontNodes(std::set<dof_id_type> & nodes)
465 : {
466 179 : _ordered_crack_front_nodes.clear();
467 179 : if (nodes.size() < 1)
468 0 : mooseError("No crack front nodes");
469 179 : else if (nodes.size() == 1)
470 : {
471 104 : _ordered_crack_front_nodes.push_back(*nodes.begin());
472 104 : if (!_treat_as_2d)
473 0 : mooseError("Boundary provided in CrackFrontDefinition contains 1 node, but model is not "
474 : "treated as 2d");
475 : }
476 75 : else if (_treat_as_2d && _use_mesh_cutter)
477 : {
478 : // This is for the 2D case that uses a mesh cutter object so every node is its own crack front
479 : // and is not connected to other crack front nodes. Copying the order here makes it the same
480 : // as that given in the MeshCut2DFractureUserObject
481 0 : std::copy(nodes.begin(), nodes.end(), _ordered_crack_front_nodes.begin());
482 : }
483 : else
484 : {
485 : // Loop through the set of crack front nodes, and create a node to element map for just the
486 : // crack front nodes
487 : // The main reason for creating a second map is that we need to do a sort prior to the
488 : // set_intersection.
489 : // The original map contains vectors, and we can't sort them, so we create sets in the local
490 : // map.
491 : const std::map<dof_id_type, std::vector<dof_id_type>> & node_to_elem_map =
492 75 : _mesh.nodeToElemMap();
493 : std::map<dof_id_type, std::set<dof_id_type>> crack_front_node_to_elem_map;
494 :
495 533 : for (const auto & node_id : nodes)
496 : {
497 : const auto & node_to_elem_pair = node_to_elem_map.find(node_id);
498 : mooseAssert(node_to_elem_pair != node_to_elem_map.end(),
499 : "Could not find crack front node " << node_id << " in the node to elem map");
500 :
501 : const std::vector<dof_id_type> & connected_elems = node_to_elem_pair->second;
502 2058 : for (std::size_t i = 0; i < connected_elems.size(); ++i)
503 1600 : crack_front_node_to_elem_map[node_id].insert(connected_elems[i]);
504 : }
505 :
506 : // Determine which nodes are connected to each other via elements, and construct line elements
507 : // to represent
508 : // those connections
509 : std::vector<std::vector<dof_id_type>> line_elems;
510 : std::map<dof_id_type, std::vector<dof_id_type>> node_to_line_elem_map;
511 :
512 75 : for (auto cfnemit = crack_front_node_to_elem_map.begin();
513 533 : cfnemit != crack_front_node_to_elem_map.end();
514 : ++cfnemit)
515 : {
516 : auto cfnemit2 = cfnemit;
517 3935 : for (++cfnemit2; cfnemit2 != crack_front_node_to_elem_map.end(); ++cfnemit2)
518 : {
519 :
520 : std::vector<dof_id_type> common_elements;
521 : std::set<dof_id_type> & elements_connected_to_node1 = cfnemit->second;
522 : std::set<dof_id_type> & elements_connected_to_node2 = cfnemit2->second;
523 3477 : std::set_intersection(elements_connected_to_node1.begin(),
524 : elements_connected_to_node1.end(),
525 : elements_connected_to_node2.begin(),
526 : elements_connected_to_node2.end(),
527 : std::inserter(common_elements, common_elements.end()));
528 :
529 3477 : if (common_elements.size() > 0)
530 : {
531 : std::vector<dof_id_type> my_line_elem;
532 390 : my_line_elem.push_back(cfnemit->first);
533 390 : my_line_elem.push_back(cfnemit2->first);
534 390 : node_to_line_elem_map[cfnemit->first].push_back(line_elems.size());
535 390 : node_to_line_elem_map[cfnemit2->first].push_back(line_elems.size());
536 390 : line_elems.push_back(my_line_elem);
537 390 : }
538 3477 : }
539 : }
540 :
541 : // Find nodes on ends of line (those connected to only one line element)
542 : std::vector<dof_id_type> end_nodes;
543 533 : for (auto nlemit = node_to_line_elem_map.begin(); nlemit != node_to_line_elem_map.end();
544 : ++nlemit)
545 : {
546 : std::size_t num_connected_elems = nlemit->second.size();
547 458 : if (num_connected_elems == 1)
548 136 : end_nodes.push_back(nlemit->first);
549 322 : else if (num_connected_elems != 2)
550 0 : mooseError(
551 0 : "Node ", nlemit->first, " is connected to >2 line segments in CrackFrontDefinition");
552 : }
553 :
554 : // For embedded crack with closed loop of crack front nodes, must pick the end nodes
555 75 : if (end_nodes.size() == 0) // Crack front is a loop. Pick nodes to be end nodes.
556 : {
557 7 : pickLoopCrackEndNodes(end_nodes, nodes, node_to_line_elem_map, line_elems);
558 7 : _closed_loop = true;
559 7 : if (_end_direction_method == END_DIRECTION_METHOD::END_CRACK_DIRECTION_VECTOR ||
560 : _end_direction_method == END_DIRECTION_METHOD::END_CRACK_TANGENT_VECTOR)
561 0 : paramError("end_direction_method",
562 : "In CrackFrontDefinition, end_direction_method cannot be CrackDirectionVector "
563 : "or CrackTangentVector for a closed-loop crack");
564 7 : if (_intersecting_boundary_names.size() > 0)
565 0 : paramError("intersecting_boundary",
566 : "In CrackFrontDefinition, intersecting_boundary cannot be specified for a "
567 : "closed-loop crack");
568 : }
569 68 : else if (end_nodes.size() == 2) // Rearrange the order of the end nodes if needed
570 68 : orderEndNodes(end_nodes);
571 : else
572 0 : mooseError("In CrackFrontDefinition wrong number of end nodes. Number end nodes = ",
573 0 : end_nodes.size());
574 :
575 : // Create an ordered list of the nodes going along the line of the crack front
576 75 : _ordered_crack_front_nodes.push_back(end_nodes[0]);
577 :
578 75 : dof_id_type last_node = end_nodes[0];
579 : dof_id_type second_last_node = last_node;
580 458 : while (last_node != end_nodes[1])
581 : {
582 383 : std::vector<dof_id_type> & curr_node_line_elems = node_to_line_elem_map[last_node];
583 : bool found_new_node = false;
584 411 : for (std::size_t i = 0; i < curr_node_line_elems.size(); ++i)
585 : {
586 411 : std::vector<dof_id_type> curr_line_elem = line_elems[curr_node_line_elems[i]];
587 488 : for (std::size_t j = 0; j < curr_line_elem.size(); ++j)
588 : {
589 460 : dof_id_type line_elem_node = curr_line_elem[j];
590 460 : if (_closed_loop &&
591 238 : (last_node == end_nodes[0] &&
592 28 : line_elem_node == end_nodes[1])) // wrong direction around closed loop
593 7 : continue;
594 453 : if (line_elem_node != last_node && line_elem_node != second_last_node)
595 : {
596 383 : _ordered_crack_front_nodes.push_back(line_elem_node);
597 : found_new_node = true;
598 383 : break;
599 : }
600 : }
601 : if (found_new_node)
602 : break;
603 411 : }
604 383 : second_last_node = last_node;
605 383 : last_node = _ordered_crack_front_nodes.back();
606 : }
607 150 : }
608 179 : }
609 :
610 : void
611 68 : CrackFrontDefinition::orderEndNodes(std::vector<dof_id_type> & end_nodes)
612 : {
613 : // Choose the node to be the first node. Do that based on undeformed coordinates for
614 : // repeatability.
615 68 : Node & node0 = _mesh.nodeRef(end_nodes[0]);
616 68 : Node & node1 = _mesh.nodeRef(end_nodes[1]);
617 :
618 : std::size_t num_positive_coor0 = 0;
619 : std::size_t num_positive_coor1 = 0;
620 : Real dist_from_origin0 = 0.0;
621 : Real dist_from_origin1 = 0.0;
622 272 : for (std::size_t i = 0; i < 3; ++i)
623 : {
624 204 : dist_from_origin0 += node0(i) * node0(i);
625 204 : dist_from_origin1 += node1(i) * node1(i);
626 204 : if (MooseUtils::absoluteFuzzyGreaterThan(node0(i), 0.0, _tol))
627 18 : ++num_positive_coor0;
628 204 : if (MooseUtils::absoluteFuzzyGreaterThan(node1(i), 0.0, _tol))
629 68 : ++num_positive_coor1;
630 : }
631 68 : dist_from_origin0 = std::sqrt(dist_from_origin0);
632 68 : dist_from_origin1 = std::sqrt(dist_from_origin1);
633 :
634 : bool switch_ends = false;
635 68 : if (num_positive_coor1 > num_positive_coor0)
636 : {
637 : switch_ends = true;
638 : }
639 : else
640 : {
641 18 : if (!MooseUtils::absoluteFuzzyEqual(dist_from_origin1, dist_from_origin0, _tol))
642 : {
643 18 : if (dist_from_origin1 < dist_from_origin0)
644 : switch_ends = true;
645 : }
646 : else
647 : {
648 0 : if (end_nodes[1] < end_nodes[0])
649 : switch_ends = true;
650 : }
651 : }
652 : if (switch_ends)
653 : {
654 68 : std::size_t tmp_node = end_nodes[1];
655 68 : end_nodes[1] = end_nodes[0];
656 68 : end_nodes[0] = tmp_node;
657 : }
658 68 : }
659 :
660 : void
661 7 : CrackFrontDefinition::pickLoopCrackEndNodes(
662 : std::vector<dof_id_type> & end_nodes,
663 : std::set<dof_id_type> & nodes,
664 : std::map<dof_id_type, std::vector<dof_id_type>> & node_to_line_elem_map,
665 : std::vector<std::vector<dof_id_type>> & line_elems)
666 : {
667 : dof_id_type max_dist_node = 0;
668 : Real min_dist = std::numeric_limits<Real>::max();
669 : Real max_dist = -std::numeric_limits<Real>::max();
670 : // Pick the node farthest from the origin as the end node, or the one with
671 : // the greatest x coordinate if the nodes are equidistant from the origin
672 217 : for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
673 : {
674 210 : Node & node = _mesh.nodeRef(*nit);
675 210 : Real dist = node.norm();
676 210 : if (dist > max_dist)
677 : {
678 : max_dist = dist;
679 14 : max_dist_node = *nit;
680 : }
681 196 : else if (dist < min_dist)
682 : min_dist = dist;
683 : }
684 :
685 : dof_id_type end_node;
686 7 : if (MooseUtils::absoluteFuzzyGreaterThan(max_dist, min_dist, _tol))
687 0 : end_node = max_dist_node;
688 : else
689 : {
690 : std::vector<Node *> node_vec;
691 217 : for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
692 210 : node_vec.push_back(_mesh.nodePtr(*nit));
693 7 : end_node = maxNodeCoor(node_vec);
694 7 : }
695 :
696 7 : end_nodes.push_back(end_node);
697 :
698 : // Find the two nodes connected to the node identified as the end node, and pick one of those to
699 : // be the other end node
700 7 : auto end_node_line_elems = node_to_line_elem_map[end_node];
701 7 : if (end_node_line_elems.size() != 2)
702 0 : mooseError(
703 : "Crack front nodes are in a loop, but crack end node is only connected to one other node");
704 : std::vector<Node *> candidate_other_end_nodes;
705 :
706 21 : for (std::size_t i = 0; i < 2; ++i)
707 : {
708 14 : auto end_line_elem = line_elems[end_node_line_elems[i]];
709 42 : for (std::size_t j = 0; j < end_line_elem.size(); ++j)
710 : {
711 28 : auto line_elem_node = end_line_elem[j];
712 28 : if (line_elem_node != end_node)
713 14 : candidate_other_end_nodes.push_back(_mesh.nodePtr(line_elem_node));
714 : }
715 14 : }
716 7 : if (candidate_other_end_nodes.size() != 2)
717 0 : mooseError(
718 : "Crack front nodes are in a loop, but crack end node is not connected to two other nodes");
719 7 : end_nodes.push_back(maxNodeCoor(candidate_other_end_nodes, 1));
720 7 : }
721 :
722 : dof_id_type
723 14 : CrackFrontDefinition::maxNodeCoor(std::vector<Node *> & nodes, unsigned int dir0)
724 : {
725 : Real dirs[3];
726 : if (dir0 == 0)
727 : {
728 : dirs[0] = 0;
729 : dirs[1] = 1;
730 : dirs[2] = 2;
731 : }
732 : else if (dir0 == 1)
733 : {
734 : dirs[0] = 1;
735 : dirs[1] = 2;
736 : dirs[2] = 0;
737 : }
738 : else if (dir0 == 2)
739 : {
740 : dirs[0] = 2;
741 : dirs[1] = 0;
742 : dirs[2] = 1;
743 : }
744 : else
745 0 : mooseError("Invalid dir0 in CrackFrontDefinition::maxNodeCoor()");
746 :
747 : Real max_coor0 = -std::numeric_limits<Real>::max();
748 : std::vector<Node *> max_coor0_nodes;
749 238 : for (std::size_t i = 0; i < nodes.size(); ++i)
750 : {
751 224 : Real coor0 = (*nodes[i])(dirs[0]);
752 224 : if (coor0 > max_coor0)
753 : max_coor0 = coor0;
754 : }
755 238 : for (std::size_t i = 0; i < nodes.size(); ++i)
756 : {
757 224 : Real coor0 = (*nodes[i])(dirs[0]);
758 224 : if (MooseUtils::absoluteFuzzyEqual(coor0, max_coor0, _tol))
759 217 : max_coor0_nodes.push_back(nodes[i]);
760 : }
761 14 : if (max_coor0_nodes.size() > 1)
762 : {
763 : Real max_coor1 = -std::numeric_limits<Real>::max();
764 : std::vector<Node *> max_coor1_nodes;
765 217 : for (std::size_t i = 0; i < nodes.size(); ++i)
766 : {
767 210 : Real coor1 = (*nodes[i])(dirs[1]);
768 210 : if (coor1 > max_coor1)
769 : max_coor1 = coor1;
770 : }
771 217 : for (std::size_t i = 0; i < nodes.size(); ++i)
772 : {
773 210 : Real coor1 = (*nodes[i])(dirs[1]);
774 210 : if (MooseUtils::absoluteFuzzyEqual(coor1, max_coor1, _tol))
775 14 : max_coor1_nodes.push_back(nodes[i]);
776 : }
777 7 : if (max_coor1_nodes.size() > 1)
778 : {
779 : Real max_coor2 = -std::numeric_limits<Real>::max();
780 : std::vector<Node *> max_coor2_nodes;
781 217 : for (std::size_t i = 0; i < nodes.size(); ++i)
782 : {
783 210 : Real coor2 = (*nodes[i])(dirs[2]);
784 210 : if (coor2 > max_coor2)
785 : max_coor2 = coor2;
786 : }
787 217 : for (std::size_t i = 0; i < nodes.size(); ++i)
788 : {
789 210 : Real coor2 = (*nodes[i])(dirs[2]);
790 210 : if (MooseUtils::absoluteFuzzyEqual(coor2, max_coor2, _tol))
791 7 : max_coor2_nodes.push_back(nodes[i]);
792 : }
793 7 : if (max_coor2_nodes.size() > 1)
794 0 : mooseError("Multiple nodes with same x,y,z coordinates within tolerance");
795 : else
796 7 : return max_coor2_nodes[0]->id();
797 7 : }
798 : else
799 0 : return max_coor1_nodes[0]->id();
800 7 : }
801 : else
802 7 : return max_coor0_nodes[0]->id();
803 14 : }
804 :
805 : void
806 195 : CrackFrontDefinition::updateCrackFrontGeometry()
807 : {
808 195 : computeCrackMouthNodes();
809 195 : _segment_lengths.clear();
810 195 : _tangent_directions.clear();
811 195 : _crack_directions.clear();
812 195 : _overall_length = 0.0;
813 195 : _rot_matrix.clear();
814 195 : _distances_along_front.clear();
815 195 : _angles_along_front.clear();
816 195 : _strain_along_front.clear();
817 195 : _crack_plane_normals.clear();
818 :
819 195 : if (_treat_as_2d)
820 : {
821 120 : std::size_t num_crack_front_points = getNumCrackFrontPoints();
822 120 : _segment_lengths.reserve(num_crack_front_points);
823 120 : _tangent_directions.reserve(num_crack_front_points);
824 120 : _crack_directions.reserve(num_crack_front_points);
825 120 : if (_use_mesh_cutter)
826 : _crack_plane_normals =
827 0 : _crack_front_points_provider->getCrackPlaneNormals(num_crack_front_points);
828 :
829 252 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
830 : {
831 : RealVectorValue tangent_direction;
832 : RealVectorValue crack_direction;
833 132 : tangent_direction(_axis_2d) = 1.0;
834 132 : _tangent_directions.push_back(tangent_direction);
835 132 : const Point * crack_front_point = getCrackFrontPoint(i);
836 :
837 : crack_direction =
838 132 : calculateCrackFrontDirection(*crack_front_point, tangent_direction, MIDDLE_NODE, i);
839 132 : _crack_directions.push_back(crack_direction);
840 :
841 132 : RankTwoTensor rot_mat;
842 132 : rot_mat.fillRow(0, crack_direction);
843 132 : rot_mat(2, _axis_2d) = 1.0;
844 :
845 132 : if (!_use_mesh_cutter)
846 132 : _crack_plane_normals.push_back(tangent_direction.cross(crack_direction));
847 :
848 : mooseAssert(i <= _crack_plane_normals.size(), "_crack_plane_normals is the wrong size.");
849 132 : rot_mat.fillRow(1, _crack_plane_normals[i]);
850 132 : _rot_matrix.push_back(rot_mat);
851 :
852 132 : _segment_lengths.push_back(std::make_pair(0.0, 0.0));
853 132 : _distances_along_front.push_back(0.0);
854 132 : _angles_along_front.push_back(0.0);
855 : }
856 : }
857 : else
858 : {
859 : // filling crack_plane_normals from mesh_cutter
860 75 : if (_use_mesh_cutter)
861 : _crack_plane_normals =
862 0 : _crack_front_points_provider->getCrackPlaneNormals(_num_points_from_provider);
863 : // else filling crack_plane_normals from curvedCrackFront
864 75 : computeCurvedCrackFrontCrackPlaneNormals();
865 :
866 75 : std::size_t num_crack_front_points = getNumCrackFrontPoints();
867 75 : _segment_lengths.reserve(num_crack_front_points);
868 75 : _tangent_directions.reserve(num_crack_front_points);
869 75 : _crack_directions.reserve(num_crack_front_points);
870 :
871 : RealVectorValue back_segment;
872 : Real back_segment_len = 0.0;
873 75 : if (_closed_loop)
874 : {
875 7 : back_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(num_crack_front_points - 1);
876 7 : back_segment_len = back_segment.norm();
877 : }
878 :
879 561 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
880 : {
881 : CRACK_NODE_TYPE ntype;
882 486 : if (_closed_loop)
883 : ntype = MIDDLE_NODE;
884 276 : else if (i == 0)
885 : ntype = END_1_NODE;
886 208 : else if (i == num_crack_front_points - 1)
887 : ntype = END_2_NODE;
888 : else
889 : ntype = MIDDLE_NODE;
890 :
891 : RealVectorValue forward_segment;
892 : Real forward_segment_len;
893 486 : if (ntype == END_2_NODE)
894 : forward_segment_len = 0.0;
895 418 : else if (_closed_loop && i == num_crack_front_points - 1)
896 : {
897 7 : forward_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(i);
898 7 : forward_segment_len = forward_segment.norm();
899 : }
900 : else
901 : {
902 411 : forward_segment = *getCrackFrontPoint(i + 1) - *getCrackFrontPoint(i);
903 411 : forward_segment_len = forward_segment.norm();
904 411 : _overall_length += forward_segment_len;
905 : }
906 :
907 486 : _segment_lengths.push_back(std::make_pair(back_segment_len, forward_segment_len));
908 486 : if (i == 0)
909 75 : _distances_along_front.push_back(0.0);
910 : else
911 411 : _distances_along_front.push_back(back_segment_len + _distances_along_front[i - 1]);
912 :
913 : RealVectorValue tangent_direction = back_segment + forward_segment;
914 486 : tangent_direction = tangent_direction / tangent_direction.norm();
915 :
916 : // If end tangent directions are given, correct the tangent at the end nodes
917 486 : if (_direction_method == DIRECTION_METHOD::CURVED_CRACK_FRONT &&
918 : _end_direction_method == END_DIRECTION_METHOD::END_CRACK_TANGENT_VECTOR)
919 : {
920 0 : if (ntype == END_1_NODE)
921 0 : tangent_direction = _crack_tangent_vector_end_1;
922 0 : else if (ntype == END_2_NODE)
923 0 : tangent_direction = _crack_tangent_vector_end_2;
924 : }
925 :
926 486 : _tangent_directions.push_back(tangent_direction);
927 : _crack_directions.push_back(
928 486 : calculateCrackFrontDirection(*getCrackFrontPoint(i), tangent_direction, ntype, i));
929 :
930 : // correct tangent direction in the case of _use_mesh_cutter
931 486 : if (_use_mesh_cutter)
932 0 : _tangent_directions[i] = _crack_plane_normals[i].cross(_crack_directions[i]);
933 :
934 : // If the end directions are given by the user, correct also the tangent at the end nodes
935 486 : if (_direction_method == DIRECTION_METHOD::CURVED_CRACK_FRONT &&
936 96 : _end_direction_method == END_DIRECTION_METHOD::END_CRACK_DIRECTION_VECTOR &&
937 96 : (ntype == END_1_NODE || ntype == END_2_NODE))
938 : {
939 32 : _tangent_directions[i] = _crack_plane_normals[i].cross(_crack_directions[i]);
940 : }
941 :
942 486 : back_segment = forward_segment;
943 : back_segment_len = forward_segment_len;
944 : }
945 :
946 : // For CURVED_CRACK_FRONT, _crack_plane_normals gets computed in
947 : // computeCurvedCrackFrontCrackPlaneNormals
948 75 : if (_direction_method != DIRECTION_METHOD::CURVED_CRACK_FRONT && !_use_mesh_cutter)
949 : {
950 52 : std::size_t mid_id = (num_crack_front_points - 1) / 2;
951 52 : _crack_plane_normals.assign(num_crack_front_points,
952 52 : _tangent_directions[mid_id].cross(_crack_directions[mid_id]));
953 :
954 : // Make sure the normal vector is non-zero
955 : RealVectorValue zero_vec(0.0);
956 52 : if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
957 0 : mooseError("Crack plane normal vector evaluates to zero");
958 : }
959 :
960 : // Calculate angles of each point along the crack front for an elliptical crack projected
961 : // to a circle.
962 75 : if (hasAngleAlongFront())
963 : {
964 14 : RealVectorValue origin_to_first_node = *getCrackFrontPoint(0) - _crack_mouth_coordinates;
965 14 : Real hyp = origin_to_first_node.norm();
966 : RealVectorValue norm_origin_to_first_node = origin_to_first_node / hyp;
967 : RealVectorValue tangent_to_first_node =
968 14 : -norm_origin_to_first_node.cross(_crack_plane_normals.front());
969 14 : tangent_to_first_node /= tangent_to_first_node.norm();
970 :
971 80 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
972 : {
973 66 : RealVectorValue origin_to_curr_node = *getCrackFrontPoint(i) - _crack_mouth_coordinates;
974 :
975 : Real adj = origin_to_curr_node * norm_origin_to_first_node;
976 : Real opp = origin_to_curr_node * tangent_to_first_node;
977 :
978 66 : Real angle = acos(adj / hyp) * 180.0 / libMesh::pi;
979 66 : if (opp < 0.0)
980 56 : angle = 360.0 - angle;
981 66 : _angles_along_front.push_back(angle);
982 : }
983 :
984 : // Correct angle on end nodes if they are 0 or 360 to be consistent with neighboring node
985 14 : if (num_crack_front_points > 1)
986 : {
987 14 : if (MooseUtils::absoluteFuzzyEqual(_angles_along_front[0], 0.0, _tol) &&
988 10 : _angles_along_front[1] > 180.0)
989 10 : _angles_along_front[0] = 360.0;
990 4 : else if (MooseUtils::absoluteFuzzyEqual(_angles_along_front[0], 360.0, _tol) &&
991 0 : _angles_along_front[1] < 180.0)
992 0 : _angles_along_front[0] = 0.0;
993 :
994 : if (MooseUtils::absoluteFuzzyEqual(
995 14 : _angles_along_front[num_crack_front_points - 1], 0.0, _tol) &&
996 0 : _angles_along_front[num_crack_front_points - 2] > 180.0)
997 0 : _angles_along_front[num_crack_front_points - 1] = 360.0;
998 : else if (MooseUtils::absoluteFuzzyEqual(
999 14 : _angles_along_front[num_crack_front_points - 1], 360.0, _tol) &&
1000 0 : _angles_along_front[num_crack_front_points - 2] < 180.0)
1001 0 : _angles_along_front[num_crack_front_points - 1] = 0.0;
1002 : }
1003 : }
1004 : else
1005 61 : _angles_along_front.resize(num_crack_front_points, 0.0);
1006 :
1007 : // Create rotation matrix
1008 561 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
1009 : {
1010 486 : RankTwoTensor rot_mat;
1011 486 : rot_mat.fillRow(0, _crack_directions[i]);
1012 486 : rot_mat.fillRow(1, _crack_plane_normals[i]);
1013 486 : rot_mat.fillRow(2, _tangent_directions[i]);
1014 486 : _rot_matrix.push_back(rot_mat);
1015 : }
1016 :
1017 75 : _console << "Summary of crack front geometry (used for fracture integrals):" << std::endl;
1018 : _console << "index node id x coord y coord z coord x dir y dir "
1019 75 : " z dir angle position seg length"
1020 75 : << std::endl;
1021 561 : for (std::size_t i = 0; i < num_crack_front_points; ++i)
1022 : {
1023 : std::size_t point_id;
1024 486 : if (_geom_definition_method == CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES)
1025 434 : point_id = _ordered_crack_front_nodes[i];
1026 : else
1027 52 : point_id = i;
1028 486 : _console << std::left << std::setw(8) << i + 1 << std::setw(10) << point_id << std::setw(14)
1029 486 : << (*getCrackFrontPoint(i))(0) << std::setw(14) << (*getCrackFrontPoint(i))(1)
1030 486 : << std::setw(14) << (*getCrackFrontPoint(i))(2) << std::setw(14)
1031 486 : << _crack_directions[i](0) << std::setw(14) << _crack_directions[i](1)
1032 486 : << std::setw(14) << _crack_directions[i](2);
1033 486 : if (hasAngleAlongFront())
1034 66 : _console << std::left << std::setw(14) << _angles_along_front[i];
1035 : else
1036 420 : _console << std::left << std::setw(14) << "--";
1037 486 : _console << std::left << std::setw(14) << _distances_along_front[i] << std::setw(14)
1038 486 : << (_segment_lengths[i].first + _segment_lengths[i].second) / 2.0 << std::endl;
1039 : }
1040 75 : _console << "overall length: " << _overall_length << std::endl;
1041 : }
1042 195 : }
1043 :
1044 : void
1045 195 : CrackFrontDefinition::computeCrackMouthNodes()
1046 : {
1047 195 : if (_crack_mouth_boundary_ids.size() > 0)
1048 : {
1049 : _crack_mouth_coordinates.zero();
1050 :
1051 : std::set<Node *> crack_mouth_nodes;
1052 18 : ConstBndNodeRange & bnd_nodes = *_mesh.getBoundaryNodeRange();
1053 2968 : for (auto nd = bnd_nodes.begin(); nd != bnd_nodes.end(); ++nd)
1054 : {
1055 2950 : const BndNode * bnode = *nd;
1056 2950 : BoundaryID boundary_id = bnode->_bnd_id;
1057 :
1058 5866 : for (std::size_t ibid = 0; ibid < _crack_mouth_boundary_ids.size(); ++ibid)
1059 : {
1060 2950 : if (boundary_id == _crack_mouth_boundary_ids[ibid])
1061 : {
1062 34 : crack_mouth_nodes.insert(bnode->_node);
1063 : break;
1064 : }
1065 : }
1066 : }
1067 :
1068 52 : for (auto nit = crack_mouth_nodes.begin(); nit != crack_mouth_nodes.end(); ++nit)
1069 : {
1070 34 : _crack_mouth_coordinates += **nit;
1071 : }
1072 18 : _crack_mouth_coordinates /= static_cast<Real>(crack_mouth_nodes.size());
1073 :
1074 18 : if (_has_symmetry_plane)
1075 10 : _crack_mouth_coordinates(_symmetry_plane) = 0.0;
1076 : }
1077 195 : }
1078 :
1079 : void
1080 75 : CrackFrontDefinition::computeCurvedCrackFrontCrackPlaneNormals()
1081 : {
1082 75 : if (_direction_method == DIRECTION_METHOD::CURVED_CRACK_FRONT && !_use_mesh_cutter)
1083 : {
1084 23 : _crack_plane_normals.clear();
1085 :
1086 : // Get 3 nodes on crack front
1087 23 : std::size_t num_points = getNumCrackFrontPoints();
1088 23 : if (num_points < 3)
1089 : {
1090 0 : mooseError("Crack front must contain at least 3 nodes to use CurvedCrackFront option");
1091 : }
1092 : std::size_t start_id;
1093 : std::size_t mid_id;
1094 : std::size_t end_id;
1095 :
1096 23 : if (_closed_loop)
1097 : {
1098 : start_id = 0;
1099 7 : mid_id = (num_points - 1) / 3;
1100 7 : end_id = 2 * mid_id;
1101 : }
1102 : else
1103 : {
1104 : start_id = 0;
1105 16 : mid_id = (num_points - 1) / 2;
1106 : end_id = num_points - 1;
1107 : }
1108 23 : const Point * start = getCrackFrontPoint(start_id);
1109 23 : const Point * mid = getCrackFrontPoint(mid_id);
1110 23 : const Point * end = getCrackFrontPoint(end_id);
1111 :
1112 : // Create two vectors connecting them
1113 : RealVectorValue v1 = *mid - *start;
1114 : RealVectorValue v2 = *end - *mid;
1115 :
1116 : // Take cross product to get normal
1117 23 : Point crack_plane_normal = v1.cross(v2);
1118 23 : crack_plane_normal = crack_plane_normal.unit();
1119 23 : _crack_plane_normals.assign(num_points, crack_plane_normal);
1120 :
1121 : // Make sure they're not collinear
1122 : RealVectorValue zero_vec(0.0);
1123 23 : if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
1124 : {
1125 0 : mooseError("Nodes on crack front are too close to being collinear");
1126 : }
1127 : }
1128 75 : }
1129 :
1130 : RealVectorValue
1131 618 : CrackFrontDefinition::calculateCrackFrontDirection(const Point & crack_front_point,
1132 : const RealVectorValue & tangent_direction,
1133 : const CRACK_NODE_TYPE ntype,
1134 : const std::size_t crack_front_point_index) const
1135 : {
1136 : RealVectorValue crack_dir;
1137 : RealVectorValue zero_vec(0.0);
1138 :
1139 : bool calc_dir = true;
1140 618 : if (_end_direction_method == END_DIRECTION_METHOD::END_CRACK_DIRECTION_VECTOR)
1141 : {
1142 150 : if (ntype == END_1_NODE)
1143 : {
1144 26 : crack_dir = _crack_direction_vector_end_1;
1145 : calc_dir = false;
1146 : }
1147 124 : else if (ntype == END_2_NODE)
1148 : {
1149 26 : crack_dir = _crack_direction_vector_end_2;
1150 : calc_dir = false;
1151 : }
1152 : }
1153 :
1154 : if (calc_dir)
1155 : {
1156 566 : if (_direction_method == DIRECTION_METHOD::CRACK_DIRECTION_VECTOR)
1157 : {
1158 242 : crack_dir = _crack_direction_vector;
1159 : }
1160 324 : else if (_direction_method == DIRECTION_METHOD::CRACK_MOUTH)
1161 : {
1162 50 : if (_crack_mouth_coordinates.absolute_fuzzy_equals(crack_front_point, _tol))
1163 : {
1164 0 : mooseError("Crack mouth too close to crack front node");
1165 : }
1166 : RealVectorValue mouth_to_front = crack_front_point - _crack_mouth_coordinates;
1167 :
1168 50 : RealVectorValue crack_plane_normal = mouth_to_front.cross(tangent_direction);
1169 50 : if (crack_plane_normal.absolute_fuzzy_equals(zero_vec, _tol))
1170 : {
1171 0 : mooseError(
1172 : "Vector from crack mouth to crack front node is collinear with crack front segment");
1173 : }
1174 :
1175 50 : crack_dir = tangent_direction.cross(crack_plane_normal);
1176 : Real dotprod = crack_dir * mouth_to_front;
1177 50 : if (dotprod < 0)
1178 : {
1179 0 : crack_dir = -crack_dir;
1180 : }
1181 : }
1182 274 : else if (_direction_method == DIRECTION_METHOD::CURVED_CRACK_FRONT)
1183 : {
1184 274 : crack_dir = tangent_direction.cross(_crack_plane_normals[crack_front_point_index]);
1185 : }
1186 : }
1187 618 : crack_dir = crack_dir.unit();
1188 :
1189 618 : return crack_dir;
1190 : }
1191 :
1192 : void
1193 0 : CrackFrontDefinition::updateNumberOfCrackFrontPoints(std::size_t num_points)
1194 : {
1195 0 : _num_points_from_provider = num_points;
1196 0 : }
1197 :
1198 : const Node *
1199 3540944 : CrackFrontDefinition::getCrackFrontNodePtr(const std::size_t node_index) const
1200 : {
1201 : mooseAssert(node_index < _ordered_crack_front_nodes.size(), "node_index out of range");
1202 3540944 : const Node * crack_front_node = _mesh.nodePtr(_ordered_crack_front_nodes[node_index]);
1203 : mooseAssert(crack_front_node != nullptr, "invalid crack front node");
1204 3540944 : return crack_front_node;
1205 : }
1206 :
1207 : const Point *
1208 3998543 : CrackFrontDefinition::getCrackFrontPoint(const std::size_t point_index) const
1209 : {
1210 3998543 : if (_geom_definition_method == CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES)
1211 : {
1212 3539882 : return getCrackFrontNodePtr(point_index);
1213 : }
1214 : else
1215 : {
1216 : mooseAssert(point_index < _crack_front_points.size(), "point_index out of range");
1217 458661 : return &_crack_front_points[point_index];
1218 : }
1219 : }
1220 :
1221 : const RealVectorValue &
1222 3657323 : CrackFrontDefinition::getCrackFrontTangent(const std::size_t point_index) const
1223 : {
1224 : mooseAssert(point_index < _tangent_directions.size(), "point_index out of range");
1225 3657323 : return _tangent_directions[point_index];
1226 : }
1227 :
1228 : const RealVectorValue &
1229 49320 : CrackFrontDefinition::getCrackFrontNormal(const std::size_t point_index) const
1230 : {
1231 : mooseAssert(point_index < _crack_plane_normals.size(), "point_index out of range");
1232 49320 : return _crack_plane_normals[point_index];
1233 : }
1234 :
1235 : Real
1236 1662929 : CrackFrontDefinition::getCrackFrontForwardSegmentLength(const std::size_t point_index) const
1237 : {
1238 1662929 : return _segment_lengths[point_index].second;
1239 : }
1240 :
1241 : Real
1242 1662929 : CrackFrontDefinition::getCrackFrontBackwardSegmentLength(const std::size_t point_index) const
1243 : {
1244 1662929 : return _segment_lengths[point_index].first;
1245 : }
1246 :
1247 : const RealVectorValue &
1248 2025360 : CrackFrontDefinition::getCrackDirection(const std::size_t point_index) const
1249 : {
1250 2025360 : return _crack_directions[point_index];
1251 : }
1252 :
1253 : Real
1254 2684 : CrackFrontDefinition::getDistanceAlongFront(const std::size_t point_index) const
1255 : {
1256 2684 : return _distances_along_front[point_index];
1257 : }
1258 :
1259 : bool
1260 750 : CrackFrontDefinition::hasAngleAlongFront() const
1261 : {
1262 750 : return (_crack_mouth_boundary_names.size() > 0);
1263 : }
1264 :
1265 : Real
1266 189 : CrackFrontDefinition::getAngleAlongFront(const std::size_t point_index) const
1267 : {
1268 189 : if (!hasAngleAlongFront())
1269 0 : paramError(
1270 : "crack_mouth_boundary",
1271 : "In CrackFrontDefinition, Requested angle along crack front, but definition of crack mouth "
1272 : "boundary using 'crack_mouth_boundary' parameter is necessary to do that.");
1273 189 : return _angles_along_front[point_index];
1274 : }
1275 :
1276 : std::size_t
1277 82698 : CrackFrontDefinition::getNumCrackFrontPoints() const
1278 : {
1279 82698 : if (_geom_definition_method == CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES)
1280 75620 : return _ordered_crack_front_nodes.size();
1281 : else
1282 7078 : return _crack_front_points.size();
1283 : }
1284 :
1285 : RealVectorValue
1286 577992 : CrackFrontDefinition::rotateToCrackFrontCoords(const RealVectorValue vector,
1287 : const std::size_t point_index) const
1288 : {
1289 577992 : return _rot_matrix[point_index] * vector;
1290 : }
1291 :
1292 : RealVectorValue
1293 0 : CrackFrontDefinition::rotateFromCrackFrontCoordsToGlobal(const RealVectorValue vector,
1294 : const std::size_t point_index) const
1295 : {
1296 0 : RealVectorValue vec = _rot_matrix[point_index].transpose() * vector;
1297 0 : return vec;
1298 : }
1299 :
1300 : RankTwoTensor
1301 242160 : CrackFrontDefinition::rotateToCrackFrontCoords(const RankTwoTensor tensor,
1302 : const std::size_t point_index) const
1303 : {
1304 242160 : RankTwoTensor tmp_tensor(tensor);
1305 242160 : tmp_tensor.rotate(_rot_matrix[point_index]);
1306 242160 : return tmp_tensor;
1307 : }
1308 :
1309 : void
1310 80720 : CrackFrontDefinition::calculateRThetaToCrackFront(const Point qp,
1311 : const std::size_t point_index,
1312 : Real & r,
1313 : Real & theta) const
1314 : {
1315 80720 : std::size_t num_points = getNumCrackFrontPoints();
1316 : Point closest_point(0.0);
1317 : RealVectorValue closest_point_to_p;
1318 :
1319 80720 : const Point * crack_front_point = getCrackFrontPoint(point_index);
1320 80720 : RealVectorValue crack_front_point_rot = rotateToCrackFrontCoords(*crack_front_point, point_index);
1321 :
1322 : RealVectorValue crack_front_edge =
1323 80720 : rotateToCrackFrontCoords(_tangent_directions[point_index], point_index);
1324 :
1325 80720 : Point p_rot = rotateToCrackFrontCoords(qp, point_index);
1326 : p_rot = p_rot - crack_front_point_rot;
1327 :
1328 80720 : if (_treat_as_2d)
1329 : {
1330 : // In 2D, the closest node is the crack tip node and the position of the crack tip node is
1331 : // (0,0,0) in the crack front coordinate system
1332 : // In case this is a 3D mesh treated as 2D, project point onto same plane as crack front node.
1333 : // Note: In the crack front coordinate system, z is always in the tangent direction to the crack
1334 : // front
1335 : p_rot(2) = closest_point(2);
1336 : closest_point_to_p = p_rot;
1337 :
1338 : // Find r, the distance between the qp and the crack front
1339 : RealVectorValue r_vec = p_rot;
1340 36368 : r = r_vec.norm();
1341 : }
1342 : else
1343 : {
1344 : // Loop over crack front points to find the one closest to the point qp
1345 : Real min_dist = std::numeric_limits<Real>::max();
1346 248576 : for (std::size_t pit = 0; pit != num_points; ++pit)
1347 : {
1348 204224 : const Point * crack_front_point = getCrackFrontPoint(pit);
1349 : RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1350 204224 : Real dist = crack_point_to_current_point.norm();
1351 :
1352 204224 : if (dist < min_dist)
1353 : {
1354 : min_dist = dist;
1355 : closest_point = *crack_front_point;
1356 : }
1357 : }
1358 :
1359 : // Rotate coordinates to crack front coordinate system
1360 44352 : closest_point = rotateToCrackFrontCoords(closest_point, point_index);
1361 : closest_point = closest_point - crack_front_point_rot;
1362 :
1363 : // Find r, the distance between the qp and the crack front
1364 : Real edge_length_sq = crack_front_edge.norm_sq();
1365 : closest_point_to_p = p_rot - closest_point;
1366 : Real perp = crack_front_edge * closest_point_to_p;
1367 44352 : Real dist_along_edge = perp / edge_length_sq;
1368 : RealVectorValue point_on_edge = closest_point + crack_front_edge * dist_along_edge;
1369 : RealVectorValue r_vec = p_rot - point_on_edge;
1370 44352 : r = r_vec.norm();
1371 : }
1372 :
1373 : // Find theta, the angle between r and the crack front plane
1374 : RealVectorValue crack_plane_normal =
1375 80720 : rotateToCrackFrontCoords(_crack_plane_normals[point_index], point_index);
1376 : Real p_to_plane_dist = std::abs(closest_point_to_p * crack_plane_normal);
1377 :
1378 : // Determine if qp is above or below the crack plane
1379 80720 : Real y_local = p_rot(1) - closest_point(1);
1380 :
1381 : // Determine if qp is in front of or behind the crack front
1382 : RealVectorValue p2(p_rot);
1383 : p2(1) = 0;
1384 : RealVectorValue p2_vec = p2 - closest_point;
1385 80720 : Real ahead = crack_front_edge(2) * p2_vec(0) - crack_front_edge(0) * p2_vec(2);
1386 :
1387 : Real x_local(0);
1388 80720 : if (ahead >= 0)
1389 : x_local = 1;
1390 : else
1391 : x_local = -1;
1392 :
1393 : // Calculate theta based on in which quadrant in the crack front coordinate
1394 : // system the qp is located
1395 80720 : if (r > 0)
1396 : {
1397 : Real theta_quadrant1(0.0);
1398 80720 : if (MooseUtils::absoluteFuzzyEqual(r, p_to_plane_dist, _tol))
1399 : theta_quadrant1 = 0.5 * libMesh::pi;
1400 80720 : else if (p_to_plane_dist > r)
1401 0 : mooseError(
1402 : "Invalid distance p_to_plane_dist in CrackFrontDefinition::calculateRThetaToCrackFront");
1403 : else
1404 80720 : theta_quadrant1 = std::asin(p_to_plane_dist / r);
1405 :
1406 80720 : if (x_local >= 0 && y_local >= 0)
1407 18536 : theta = theta_quadrant1;
1408 :
1409 62184 : else if (x_local < 0 && y_local >= 0)
1410 17832 : theta = libMesh::pi - theta_quadrant1;
1411 :
1412 17168 : else if (x_local < 0 && y_local < 0)
1413 17168 : theta = -(libMesh::pi - theta_quadrant1);
1414 :
1415 27184 : else if (x_local >= 0 && y_local < 0)
1416 27184 : theta = -theta_quadrant1;
1417 : }
1418 0 : else if (r == 0)
1419 0 : theta = 0;
1420 : else
1421 0 : mooseError("Invalid distance r in CrackFrontDefinition::calculateRThetaToCrackFront");
1422 80720 : }
1423 :
1424 : std::size_t
1425 0 : CrackFrontDefinition::calculateRThetaToCrackFront(const Point qp, Real & r, Real & theta) const
1426 : {
1427 0 : std::size_t num_points = getNumCrackFrontPoints();
1428 :
1429 : // Loop over crack front points to find the one closest to the point qp
1430 : Real min_dist = std::numeric_limits<Real>::max();
1431 : std::size_t point_index = 0;
1432 0 : for (std::size_t pit = 0; pit != num_points; ++pit)
1433 : {
1434 0 : const Point * crack_front_point = getCrackFrontPoint(pit);
1435 : RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1436 0 : Real dist = crack_point_to_current_point.norm();
1437 :
1438 0 : if (dist < min_dist)
1439 : {
1440 : min_dist = dist;
1441 : point_index = pit;
1442 : }
1443 : }
1444 :
1445 0 : calculateRThetaToCrackFront(qp, point_index, r, theta);
1446 :
1447 0 : return point_index;
1448 : }
1449 :
1450 : bool
1451 94315 : CrackFrontDefinition::isNodeOnIntersectingBoundary(const Node * const node) const
1452 : {
1453 : bool is_on_boundary = false;
1454 : mooseAssert(node, "Invalid node");
1455 : dof_id_type node_id = node->id();
1456 129440 : for (std::size_t i = 0; i < _intersecting_boundary_ids.size(); ++i)
1457 : {
1458 38204 : if (_mesh.isBoundaryNode(node_id, _intersecting_boundary_ids[i]))
1459 : {
1460 : is_on_boundary = true;
1461 : break;
1462 : }
1463 : }
1464 94315 : return is_on_boundary;
1465 : }
1466 :
1467 : bool
1468 736 : CrackFrontDefinition::isPointWithIndexOnIntersectingBoundary(const std::size_t point_index) const
1469 : {
1470 : bool is_on_boundary = false;
1471 736 : if (_geom_definition_method == CRACK_GEOM_DEFINITION::CRACK_FRONT_NODES)
1472 : {
1473 668 : const Node * crack_front_node = getCrackFrontNodePtr(point_index);
1474 668 : is_on_boundary = isNodeOnIntersectingBoundary(crack_front_node);
1475 : }
1476 : else
1477 : {
1478 : // If the intersecting boundary option is used with crack front points, the
1479 : // first and last points are assumed to be on the intersecting boundaries.
1480 68 : std::size_t num_crack_front_points = getNumCrackFrontPoints();
1481 68 : if (point_index == 0 || point_index == num_crack_front_points - 1)
1482 : is_on_boundary = true;
1483 : }
1484 736 : return is_on_boundary;
1485 : }
1486 :
1487 : void
1488 18 : CrackFrontDefinition::calculateTangentialStrainAlongFront()
1489 : {
1490 : RealVectorValue disp_current_node;
1491 : RealVectorValue disp_previous_node;
1492 : RealVectorValue disp_next_node;
1493 :
1494 : RealVectorValue forward_segment0;
1495 : RealVectorValue forward_segment1;
1496 : Real forward_segment0_len;
1497 : Real forward_segment1_len;
1498 : RealVectorValue back_segment0;
1499 : RealVectorValue back_segment1;
1500 : Real back_segment0_len;
1501 : Real back_segment1_len;
1502 :
1503 : std::size_t num_crack_front_nodes = _ordered_crack_front_nodes.size();
1504 : const Node * current_node;
1505 : const Node * previous_node;
1506 : const Node * next_node;
1507 :
1508 18 : _strain_along_front.reserve(num_crack_front_nodes);
1509 :
1510 : // In finalize(), gatherMax builds and distributes the complete strain vector on all processors
1511 : // -> reset the vector every time
1512 144 : for (std::size_t i = 0; i < num_crack_front_nodes; ++i)
1513 126 : _strain_along_front[i] = -std::numeric_limits<Real>::max();
1514 :
1515 18 : MooseVariable & disp_x_var = _subproblem.getStandardVariable(_tid, _disp_x_var_name);
1516 18 : MooseVariable & disp_y_var = _subproblem.getStandardVariable(_tid, _disp_y_var_name);
1517 18 : MooseVariable & disp_z_var = _subproblem.getStandardVariable(_tid, _disp_z_var_name);
1518 :
1519 18 : current_node = getCrackFrontNodePtr(0);
1520 18 : if (current_node->processor_id() == processor_id())
1521 : {
1522 14 : disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1523 14 : disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1524 14 : disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1525 :
1526 14 : next_node = getCrackFrontNodePtr(1);
1527 14 : disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1528 14 : disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1529 14 : disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1530 :
1531 : forward_segment0 = *next_node - *current_node;
1532 14 : forward_segment0 = (forward_segment0 * _tangent_directions[0]) * _tangent_directions[0];
1533 14 : forward_segment0_len = forward_segment0.norm();
1534 :
1535 : forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1536 14 : forward_segment1 = (forward_segment1 * _tangent_directions[0]) * _tangent_directions[0];
1537 14 : forward_segment1_len = forward_segment1.norm();
1538 :
1539 14 : _strain_along_front[0] = (forward_segment1_len - forward_segment0_len) / forward_segment0_len;
1540 : }
1541 :
1542 108 : for (std::size_t i = 1; i < num_crack_front_nodes - 1; ++i)
1543 : {
1544 90 : current_node = getCrackFrontNodePtr(i);
1545 90 : if (current_node->processor_id() == processor_id())
1546 : {
1547 70 : disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1548 70 : disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1549 70 : disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1550 :
1551 70 : previous_node = getCrackFrontNodePtr(i - 1);
1552 70 : disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1553 70 : disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1554 70 : disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1555 :
1556 70 : next_node = getCrackFrontNodePtr(i + 1);
1557 70 : disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1558 70 : disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1559 70 : disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1560 :
1561 : back_segment0 = *current_node - *previous_node;
1562 70 : back_segment0 = (back_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1563 70 : back_segment0_len = back_segment0.norm();
1564 :
1565 : back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1566 70 : back_segment1 = (back_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1567 70 : back_segment1_len = back_segment1.norm();
1568 :
1569 : forward_segment0 = *next_node - *current_node;
1570 70 : forward_segment0 = (forward_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1571 70 : forward_segment0_len = forward_segment0.norm();
1572 :
1573 : forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1574 70 : forward_segment1 = (forward_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1575 70 : forward_segment1_len = forward_segment1.norm();
1576 :
1577 70 : _strain_along_front[i] =
1578 70 : 0.5 * ((back_segment1_len - back_segment0_len) / back_segment0_len +
1579 70 : (forward_segment1_len - forward_segment0_len) / forward_segment0_len);
1580 : }
1581 : }
1582 :
1583 18 : current_node = getCrackFrontNodePtr(num_crack_front_nodes - 1);
1584 18 : if (current_node->processor_id() == processor_id())
1585 : {
1586 14 : disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1587 14 : disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1588 14 : disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1589 :
1590 14 : previous_node = getCrackFrontNodePtr(num_crack_front_nodes - 2);
1591 14 : disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1592 14 : disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1593 14 : disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1594 :
1595 : back_segment0 = *current_node - *previous_node;
1596 14 : back_segment0 = (back_segment0 * _tangent_directions[num_crack_front_nodes - 1]) *
1597 : _tangent_directions[num_crack_front_nodes - 1];
1598 14 : back_segment0_len = back_segment0.norm();
1599 :
1600 : back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1601 14 : back_segment1 = (back_segment1 * _tangent_directions[num_crack_front_nodes - 1]) *
1602 : _tangent_directions[num_crack_front_nodes - 1];
1603 14 : back_segment1_len = back_segment1.norm();
1604 :
1605 14 : _strain_along_front[num_crack_front_nodes - 1] =
1606 14 : (back_segment1_len - back_segment0_len) / back_segment0_len;
1607 : }
1608 18 : }
1609 :
1610 : Real
1611 210 : CrackFrontDefinition::getCrackFrontTangentialStrain(const std::size_t node_index) const
1612 : {
1613 : Real strain;
1614 210 : if (_t_stress)
1615 : {
1616 210 : strain = _strain_along_front[node_index];
1617 : mooseAssert(strain > -std::numeric_limits<Real>::max(),
1618 : "Failure in parallel communication of crack tangential strain");
1619 : }
1620 : else
1621 0 : mooseError("In CrackFrontDefinition, tangential strain not available");
1622 :
1623 210 : return strain;
1624 : }
1625 :
1626 : void
1627 20 : CrackFrontDefinition::createQFunctionRings()
1628 : {
1629 : // In the variable names, "cfn" = crack front node
1630 20 : if (_treat_as_2d && _use_mesh_cutter == false) // 2D: the q-function defines an integral domain
1631 : // that is constant along the crack front
1632 : {
1633 : std::vector<std::vector<const Elem *>> nodes_to_elem_map;
1634 12 : MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1635 :
1636 : std::set<dof_id_type> nodes_prev_ring;
1637 12 : nodes_prev_ring.insert(_ordered_crack_front_nodes.begin(), _ordered_crack_front_nodes.end());
1638 :
1639 : std::set<dof_id_type> connected_nodes_this_cfn;
1640 12 : connected_nodes_this_cfn.insert(_ordered_crack_front_nodes.begin(),
1641 : _ordered_crack_front_nodes.end());
1642 :
1643 : std::set<dof_id_type> old_ring_nodes_this_cfn = connected_nodes_this_cfn;
1644 :
1645 : // The first ring contains only the crack front node(s)
1646 : std::pair<dof_id_type, std::size_t> node_ring_index =
1647 : std::make_pair(_ordered_crack_front_nodes[0], 1);
1648 12 : _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1649 : connected_nodes_this_cfn.end());
1650 :
1651 : // Build rings of nodes around the crack front node
1652 44 : for (std::size_t ring = 2; ring <= _last_ring; ++ring)
1653 : {
1654 :
1655 : // Find nodes connected to the nodes of the previous ring
1656 : std::set<dof_id_type> new_ring_nodes_this_cfn;
1657 136 : for (auto nit = old_ring_nodes_this_cfn.begin(); nit != old_ring_nodes_this_cfn.end(); ++nit)
1658 : {
1659 : std::vector<const Node *> neighbors;
1660 104 : MeshTools::find_nodal_neighbors(
1661 104 : _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1662 488 : for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1663 : {
1664 384 : auto thisit = connected_nodes_this_cfn.find(neighbors[inei]->id());
1665 :
1666 : // Add only nodes that are not already present in any of the rings
1667 384 : if (thisit == connected_nodes_this_cfn.end())
1668 248 : new_ring_nodes_this_cfn.insert(neighbors[inei]->id());
1669 : }
1670 104 : }
1671 :
1672 : // Add new nodes to rings
1673 32 : connected_nodes_this_cfn.insert(new_ring_nodes_this_cfn.begin(),
1674 : new_ring_nodes_this_cfn.end());
1675 : old_ring_nodes_this_cfn = new_ring_nodes_this_cfn;
1676 :
1677 : std::pair<dof_id_type, std::size_t> node_ring_index =
1678 32 : std::make_pair(_ordered_crack_front_nodes[0], ring);
1679 64 : _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1680 : connected_nodes_this_cfn.end());
1681 : }
1682 12 : }
1683 : else // The q-function defines one integral domain around each crack front node
1684 : {
1685 : std::size_t num_crack_front_points = _ordered_crack_front_nodes.size();
1686 : std::vector<std::vector<const Elem *>> nodes_to_elem_map;
1687 8 : MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1688 32 : for (std::size_t icfn = 0; icfn < num_crack_front_points; ++icfn)
1689 : {
1690 : std::set<dof_id_type> nodes_prev_ring;
1691 24 : nodes_prev_ring.insert(_ordered_crack_front_nodes[icfn]);
1692 :
1693 : std::set<dof_id_type> connected_nodes_prev_cfn;
1694 : std::set<dof_id_type> connected_nodes_this_cfn;
1695 : std::set<dof_id_type> connected_nodes_next_cfn;
1696 :
1697 24 : connected_nodes_this_cfn.insert(_ordered_crack_front_nodes[icfn]);
1698 :
1699 24 : if (_closed_loop && icfn == 0)
1700 : {
1701 0 : connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[num_crack_front_points - 1]);
1702 0 : connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1703 : }
1704 24 : else if (_closed_loop && icfn == num_crack_front_points - 1)
1705 : {
1706 0 : connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1707 0 : connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[0]);
1708 : }
1709 24 : else if (icfn == 0)
1710 : {
1711 8 : connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1712 : }
1713 16 : else if (icfn == num_crack_front_points - 1)
1714 : {
1715 8 : connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1716 : }
1717 : else
1718 : {
1719 8 : connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1720 8 : connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1721 : }
1722 :
1723 : std::set<dof_id_type> old_ring_nodes_prev_cfn = connected_nodes_prev_cfn;
1724 : std::set<dof_id_type> old_ring_nodes_this_cfn = connected_nodes_this_cfn;
1725 : std::set<dof_id_type> old_ring_nodes_next_cfn = connected_nodes_next_cfn;
1726 :
1727 : // The first ring contains only the crack front node
1728 : std::pair<dof_id_type, std::size_t> node_ring_index =
1729 : std::make_pair(_ordered_crack_front_nodes[icfn], 1);
1730 24 : _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1731 : connected_nodes_this_cfn.end());
1732 :
1733 : // Build rings of nodes around the crack front node
1734 72 : for (std::size_t ring = 2; ring <= _last_ring; ++ring)
1735 : {
1736 :
1737 : // Find nodes connected to the nodes of the previous ring, but exclude nodes in rings of
1738 : // neighboring crack front nodes
1739 : std::set<dof_id_type> new_ring_nodes_this_cfn;
1740 48 : addNodesToQFunctionRing(new_ring_nodes_this_cfn,
1741 : old_ring_nodes_this_cfn,
1742 : connected_nodes_this_cfn,
1743 : connected_nodes_prev_cfn,
1744 : connected_nodes_next_cfn,
1745 : nodes_to_elem_map);
1746 :
1747 : std::set<dof_id_type> new_ring_nodes_prev_cfn;
1748 48 : addNodesToQFunctionRing(new_ring_nodes_prev_cfn,
1749 : old_ring_nodes_prev_cfn,
1750 : connected_nodes_prev_cfn,
1751 : connected_nodes_this_cfn,
1752 : connected_nodes_next_cfn,
1753 : nodes_to_elem_map);
1754 :
1755 : std::set<dof_id_type> new_ring_nodes_next_cfn;
1756 48 : addNodesToQFunctionRing(new_ring_nodes_next_cfn,
1757 : old_ring_nodes_next_cfn,
1758 : connected_nodes_next_cfn,
1759 : connected_nodes_prev_cfn,
1760 : connected_nodes_this_cfn,
1761 : nodes_to_elem_map);
1762 :
1763 : // Add new nodes to the three sets of nodes
1764 48 : connected_nodes_prev_cfn.insert(new_ring_nodes_prev_cfn.begin(),
1765 : new_ring_nodes_prev_cfn.end());
1766 48 : connected_nodes_this_cfn.insert(new_ring_nodes_this_cfn.begin(),
1767 : new_ring_nodes_this_cfn.end());
1768 48 : connected_nodes_next_cfn.insert(new_ring_nodes_next_cfn.begin(),
1769 : new_ring_nodes_next_cfn.end());
1770 : old_ring_nodes_prev_cfn = new_ring_nodes_prev_cfn;
1771 : old_ring_nodes_this_cfn = new_ring_nodes_this_cfn;
1772 : old_ring_nodes_next_cfn = new_ring_nodes_next_cfn;
1773 :
1774 : std::pair<dof_id_type, std::size_t> node_ring_index =
1775 48 : std::make_pair(_ordered_crack_front_nodes[icfn], ring);
1776 96 : _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1777 : connected_nodes_this_cfn.end());
1778 : }
1779 : }
1780 8 : }
1781 20 : }
1782 :
1783 : void
1784 144 : CrackFrontDefinition::addNodesToQFunctionRing(
1785 : std::set<dof_id_type> & nodes_new_ring,
1786 : const std::set<dof_id_type> & nodes_old_ring,
1787 : const std::set<dof_id_type> & nodes_all_rings,
1788 : const std::set<dof_id_type> & nodes_neighbor1,
1789 : const std::set<dof_id_type> & nodes_neighbor2,
1790 : std::vector<std::vector<const Elem *>> & nodes_to_elem_map)
1791 : {
1792 384 : for (auto nit = nodes_old_ring.begin(); nit != nodes_old_ring.end(); ++nit)
1793 : {
1794 : std::vector<const Node *> neighbors;
1795 240 : MeshTools::find_nodal_neighbors(
1796 240 : _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1797 1352 : for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1798 : {
1799 1112 : auto previt = nodes_all_rings.find(neighbors[inei]->id());
1800 1112 : auto thisit = nodes_neighbor1.find(neighbors[inei]->id());
1801 1112 : auto nextit = nodes_neighbor2.find(neighbors[inei]->id());
1802 :
1803 : // Add only nodes that are not already present in any of the three sets of nodes
1804 1112 : if (previt == nodes_all_rings.end() && thisit == nodes_neighbor1.end() &&
1805 : nextit == nodes_neighbor2.end())
1806 672 : nodes_new_ring.insert(neighbors[inei]->id());
1807 : }
1808 240 : }
1809 144 : }
1810 :
1811 : bool
1812 57377 : CrackFrontDefinition::isNodeInRing(const std::size_t ring_index,
1813 : const dof_id_type connected_node_id,
1814 : const std::size_t node_index) const
1815 : {
1816 : bool is_node_in_ring = false;
1817 : std::pair<dof_id_type, std::size_t> node_ring_key =
1818 57377 : std::make_pair(_ordered_crack_front_nodes[node_index], ring_index);
1819 : auto nnmit = _crack_front_node_to_node_map.find(node_ring_key);
1820 :
1821 57377 : if (nnmit == _crack_front_node_to_node_map.end())
1822 0 : mooseError("Could not find crack front node ",
1823 : _ordered_crack_front_nodes[node_index],
1824 : " in the crack front node to q-function ring-node map for ring ",
1825 : ring_index);
1826 :
1827 : std::set<dof_id_type> q_func_nodes = nnmit->second;
1828 57377 : if (q_func_nodes.find(connected_node_id) != q_func_nodes.end())
1829 : is_node_in_ring = true;
1830 :
1831 57377 : return is_node_in_ring;
1832 : }
1833 :
1834 : Real
1835 3606784 : CrackFrontDefinition::DomainIntegralQFunction(std::size_t crack_front_point_index,
1836 : std::size_t ring_index,
1837 : const Node * const current_node) const
1838 : {
1839 : Real dist_to_crack_front;
1840 : Real dist_along_tangent;
1841 3606784 : projectToFrontAtPoint(
1842 : dist_to_crack_front, dist_along_tangent, crack_front_point_index, current_node);
1843 :
1844 : Real q = 1.0;
1845 3606784 : if (dist_to_crack_front > _j_integral_radius_inner[ring_index] &&
1846 3539282 : dist_to_crack_front < _j_integral_radius_outer[ring_index])
1847 21339 : q = (_j_integral_radius_outer[ring_index] - dist_to_crack_front) /
1848 21339 : (_j_integral_radius_outer[ring_index] - _j_integral_radius_inner[ring_index]);
1849 3585445 : else if (dist_to_crack_front >= _j_integral_radius_outer[ring_index])
1850 : q = 0.0;
1851 :
1852 21339 : if (q > 0.0)
1853 : {
1854 88809 : Real tangent_multiplier = 1.0;
1855 88809 : if (!_treat_as_2d)
1856 : {
1857 : const Real forward_segment_length =
1858 40577 : getCrackFrontForwardSegmentLength(crack_front_point_index);
1859 : const Real backward_segment_length =
1860 40577 : getCrackFrontBackwardSegmentLength(crack_front_point_index);
1861 :
1862 40577 : if (dist_along_tangent >= 0.0)
1863 : {
1864 26395 : if (forward_segment_length > 0.0)
1865 24280 : tangent_multiplier = 1.0 - dist_along_tangent / forward_segment_length;
1866 : }
1867 : else
1868 : {
1869 14182 : if (backward_segment_length > 0.0)
1870 14024 : tangent_multiplier = 1.0 + dist_along_tangent / backward_segment_length;
1871 : }
1872 : }
1873 :
1874 88809 : tangent_multiplier = std::max(tangent_multiplier, 0.0);
1875 88809 : tangent_multiplier = std::min(tangent_multiplier, 1.0);
1876 :
1877 : // Set to zero if a node is on a designated free surface and its crack front node is not.
1878 88809 : if (isNodeOnIntersectingBoundary(current_node) &&
1879 : !_is_point_on_intersecting_boundary[crack_front_point_index])
1880 2180 : tangent_multiplier = 0.0;
1881 :
1882 88809 : q *= tangent_multiplier;
1883 : }
1884 :
1885 3606784 : return q;
1886 : }
1887 :
1888 : Real
1889 46464 : CrackFrontDefinition::DomainIntegralTopologicalQFunction(std::size_t crack_front_point_index,
1890 : std::size_t ring_index,
1891 : const Node * const current_node) const
1892 : {
1893 : Real q = 0;
1894 46464 : bool is_node_in_ring = isNodeInRing(ring_index, current_node->id(), crack_front_point_index);
1895 46464 : if (is_node_in_ring)
1896 : q = 1;
1897 :
1898 46464 : return q;
1899 : }
1900 :
1901 : void
1902 3606784 : CrackFrontDefinition::projectToFrontAtPoint(Real & dist_to_front,
1903 : Real & dist_along_tangent,
1904 : std::size_t crack_front_point_index,
1905 : const Node * const current_node) const
1906 : {
1907 3606784 : const Point * crack_front_point = getCrackFrontPoint(crack_front_point_index);
1908 :
1909 3606784 : Point p = *current_node;
1910 3606784 : const RealVectorValue & crack_front_tangent = getCrackFrontTangent(crack_front_point_index);
1911 :
1912 : RealVectorValue crack_node_to_current_node = p - *crack_front_point;
1913 3606784 : dist_along_tangent = crack_node_to_current_node * crack_front_tangent;
1914 : RealVectorValue projection_point = *crack_front_point + dist_along_tangent * crack_front_tangent;
1915 : RealVectorValue axis_to_current_node = p - projection_point;
1916 3606784 : dist_to_front = axis_to_current_node.norm();
1917 3606784 : }
1918 :
1919 : void
1920 0 : CrackFrontDefinition::isCutterModified(const bool is_cutter_modified)
1921 : {
1922 0 : _is_cutter_modified = is_cutter_modified;
1923 0 : }
|