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