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