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>(
106  "number_points_from_provider",
107  "The number of crack front points, only needed if crack_front_points_provider is used.");
108 }
109 
110 const Real CrackFrontDefinition::_tol = 1e-10;
111 
113  : GeneralUserObject(parameters),
114  BoundaryRestrictable(this, true), // false means nodesets
115  _direction_method(getParam<MooseEnum>("crack_direction_method").getEnum<DIRECTION_METHOD>()),
116  _end_direction_method(
117  getParam<MooseEnum>("crack_end_direction_method").getEnum<END_DIRECTION_METHOD>()),
118  _aux(_fe_problem.getAuxiliarySystem()),
119  _mesh(_subproblem.mesh()),
120  _treat_as_2d(getParam<bool>("2d")),
121  _use_mesh_cutter(false),
122  _is_cutter_modified(false),
123  _closed_loop(getParam<bool>("closed_loop")),
124  _axis_2d(getParam<unsigned int>("axis_2d")),
125  _has_symmetry_plane(isParamValid("symmetry_plane")),
126  _symmetry_plane(_has_symmetry_plane ? getParam<unsigned int>("symmetry_plane")
127  : std::numeric_limits<unsigned int>::max()),
128  _t_stress(getParam<bool>("t_stress")),
129  _q_function_rings(getParam<bool>("q_function_rings")),
130  _q_function_type(getParam<MooseEnum>("q_function_type")),
131  _crack_front_points_provider(nullptr)
132 {
133  auto boundary = isParamValid("boundary") ? getParam<std::vector<BoundaryName>>("boundary")
134  : std::vector<BoundaryName>{};
135  if (isParamValid("crack_front_points"))
136  {
137  if (boundary.size())
138  paramError("crack_front_points",
139  "CrackFrontDefinition error: since boundary is defined, crack_front_points should "
140  "not be added.");
141  if (isParamValid("crack_front_points_provider"))
142  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  _crack_front_points = getParam<std::vector<Point>>("crack_front_points");
147  if (_t_stress)
148  paramError("t_stress", "t_stress not yet supported with crack_front_points");
149  if (_q_function_rings)
150  paramError("q_function_rings", "q_function_rings not supported with crack_front_points");
151  }
152  else if (isParamValid("crack_front_points_provider"))
153  {
154  if (boundary.size())
155  paramError("crack_front_points_provider",
156  "CrackFrontDefinition error: since boundary is defined, "
157  "crack_front_points_provider should not be added.");
158  if (!isParamValid("number_points_from_provider"))
159  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  _num_points_from_provider = getParam<unsigned int>("number_points_from_provider");
165  }
166  else if (isParamValid("number_points_from_provider"))
167  paramError("number_points_from_provider",
168  "CrackFrontDefinition error: number_points_from_provider is provided but "
169  "crack_front_points_provider cannot be found.");
170  else if (boundary.size())
171  {
173  if (parameters.isParamSetByUser("closed_loop"))
174  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  mooseError("In CrackFrontDefinition, must define one of 'boundary', 'crack_front_points' "
180  "and 'crack_front_points_provider'");
181 
182  if (isParamValid("crack_mouth_boundary"))
183  _crack_mouth_boundary_names = getParam<std::vector<BoundaryName>>("crack_mouth_boundary");
184 
186  if (_symmetry_plane > 2)
187  paramError("symmetry_plane",
188  "symmetry_plane out of bounds: ",
190  " Must be >=0 and <=2.");
191 
192  switch (_direction_method)
193  {
195  if (!isParamValid("crack_direction_vector"))
196  paramError("crack_direction_vector",
197  "crack_direction_vector must be specified if crack_direction_method = "
198  "CrackDirectionVector");
199  _crack_direction_vector = getParam<RealVectorValue>("crack_direction_vector");
200  break;
202  if (isParamValid("crack_direction_vector"))
203  paramError("crack_direction_vector",
204  "crack_direction_vector must not be specified if crack_direction_method = "
205  "CrackMouthNodes");
206  if (_crack_mouth_boundary_names.size() == 0)
207  paramError(
208  "crack_mouth_boundary",
209  "crack_mouth_boundary must be specified if crack_direction_method = CrackMouthNodes");
210  break;
212  if (isParamValid("crack_direction_vector"))
213  paramError("crack_direction_vector",
214  "crack_direction_vector must not be specified if crack_direction_method = "
215  "CurvedCrackFront");
216  break;
217  default:
218  paramError("crack_direction_method", "Invalid direction_method");
219  }
220 
221  if (isParamValid("intersecting_boundary"))
222  _intersecting_boundary_names = getParam<std::vector<BoundaryName>>("intersecting_boundary");
223 
225  {
226  if (!isParamValid("crack_direction_vector_end_1"))
227  paramError("crack_direction_vector_end_1",
228  "crack_direction_vector_end_1 must be specified if crack_end_direction_method = "
229  "CrackDirectionVector");
230  if (!isParamValid("crack_direction_vector_end_2"))
231  paramError("crack_direction_vector_end_2",
232  "crack_direction_vector_end_2 must be specified if crack_end_direction_method = "
233  "CrackDirectionVector");
234  _crack_direction_vector_end_1 = getParam<RealVectorValue>("crack_direction_vector_end_1");
235  _crack_direction_vector_end_2 = getParam<RealVectorValue>("crack_direction_vector_end_2");
236  }
237 
239  {
240  if (!isParamValid("crack_tangent_vector_end_1"))
241  paramError("crack_tangent_vector_end_1",
242  "crack_tangent_vector_end_1 must be specified if crack_end_tangent_method = "
243  "CrackTangentVector");
244  if (!isParamValid("crack_tangent_vector_end_2"))
245  paramError("crack_tangent_vector_end_2",
246  "crack_tangent_vector_end_2 must be specified if crack_end_tangent_method = "
247  "CrackTangentVector");
248  _crack_tangent_vector_end_1 = getParam<RealVectorValue>("crack_tangent_vector_end_1");
249  _crack_tangent_vector_end_2 = getParam<RealVectorValue>("crack_tangent_vector_end_2");
250  }
251 
252  if (isParamValid("disp_x") && isParamValid("disp_y") && isParamValid("disp_z"))
253  {
254  _disp_x_var_name = getParam<VariableName>("disp_x");
255  _disp_y_var_name = getParam<VariableName>("disp_y");
256  _disp_z_var_name = getParam<VariableName>("disp_z");
257  }
258  else if (_t_stress == true && _treat_as_2d == false)
259  paramError("displacements", "Displacement variables must be provided for T-stress calculation");
260 
261  if (_q_function_rings)
262  {
263  if (!isParamValid("last_ring"))
264  paramError("last_ring",
265  "The max number of rings of nodes to generate must be provided if "
266  "q_function_rings = true");
267  _last_ring = getParam<unsigned int>("last_ring");
268  _first_ring = getParam<unsigned int>("first_ring");
269  }
270  else
271  {
272  _j_integral_radius_inner = getParam<std::vector<Real>>("j_integral_radius_inner");
273  _j_integral_radius_outer = getParam<std::vector<Real>>("j_integral_radius_outer");
274  }
275 }
276 
278 
279 void
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  if (_t_stress == true && _treat_as_2d == false)
286 }
287 
288 void
290 {
291  if (isParamValid("crack_front_points_provider"))
292  {
293  _crack_front_points_provider = &getUserObjectByName<CrackFrontPointsProvider>(
294  getParam<UserObjectName>("crack_front_points_provider"));
296  {
297  _use_mesh_cutter = true;
299  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  if (isParamValid("crack_mouth_boundary"))
303  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  if (_crack_front_points_provider != nullptr)
309  {
312  }
313 
316 
318  {
319  std::set<dof_id_type> nodes;
320  getCrackFrontNodes(nodes);
321  orderCrackFrontNodes(nodes);
322  }
323 
324  if (_closed_loop && _intersecting_boundary_names.size() > 0)
325  paramError("intersecting_boundary", "Cannot use intersecting_boundary with closed-loop cracks");
326 
328 
329  if (_q_function_rings)
331 
332  if (_t_stress)
333  {
334  std::size_t num_crack_front_nodes = _ordered_crack_front_nodes.size();
335  for (std::size_t i = 0; i < num_crack_front_nodes; ++i)
336  _strain_along_front.push_back(-std::numeric_limits<Real>::max());
337  }
338 
339  std::size_t num_crack_front_points = getNumCrackFrontPoints();
340  if (_q_function_type == "GEOMETRY")
341  {
342  if (!_treat_as_2d)
343  if (num_crack_front_points < 1)
344  mooseError("num_crack_front_points is not > 0");
345  for (std::size_t i = 0; i < num_crack_front_points; ++i)
346  {
347  bool is_point_on_intersecting_boundary = isPointWithIndexOnIntersectingBoundary(i);
348  _is_point_on_intersecting_boundary.push_back(is_point_on_intersecting_boundary);
349  }
350  }
351 }
352 
353 void
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
360  {
364  std::size_t num_crack_front_points = getNumCrackFrontPoints();
365  if (_q_function_type == "GEOMETRY")
366  for (std::size_t i = 0; i < num_crack_front_points; ++i)
367  {
368  bool is_point_on_intersecting_boundary = isPointWithIndexOnIntersectingBoundary(i);
369  _is_point_on_intersecting_boundary.push_back(is_point_on_intersecting_boundary);
370  }
371  }
372 }
373 
374 void
376 {
377  if (_t_stress)
379 }
380 
381 void
382 CrackFrontDefinition::getCrackFrontNodes(std::set<dof_id_type> & nodes)
383 {
385  for (auto nd = bnd_nodes.begin(); nd != bnd_nodes.end(); ++nd)
386  {
387  const BndNode * bnode = *nd;
388  BoundaryID boundary_id = bnode->_bnd_id;
389 
390  if (hasBoundary(boundary_id))
391  nodes.insert(bnode->_node->id());
392  }
393 
394  if (_treat_as_2d && _use_mesh_cutter == false)
395  {
396  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  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  default:
417  mooseError("Invalid axis.");
418  }
419 
420  Real node0coor0 = 0;
421  Real node0coor1 = 0;
422 
423  for (auto sit = nodes.begin(); sit != nodes.end(); ++sit)
424  {
425  Node & curr_node = _mesh.nodeRef(*sit);
426  if (sit == nodes.begin())
427  {
428  node0coor0 = curr_node(axis0);
429  node0coor1 = curr_node(axis1);
430  }
431  else
432  {
433  if (!MooseUtils::absoluteFuzzyEqual(curr_node(axis0), node0coor0, _tol) ||
434  !MooseUtils::absoluteFuzzyEqual(curr_node(axis1), node0coor1, _tol))
435  mooseError("Boundary provided in CrackFrontDefinition contains ",
436  nodes.size(),
437  " nodes, which are not collinear in the ",
438  _axis_2d,
439  " axis. Must contain either 1 node or collinear nodes to treat as 2D.");
440  }
441  }
442  }
443  }
444 }
445 
446 void
447 CrackFrontDefinition::orderCrackFrontNodes(std::set<dof_id_type> & nodes)
448 {
450  if (nodes.size() < 1)
451  mooseError("No crack front nodes");
452  else if (nodes.size() == 1)
453  {
454  _ordered_crack_front_nodes.push_back(*nodes.begin());
455  if (!_treat_as_2d)
456  mooseError("Boundary provided in CrackFrontDefinition contains 1 node, but model is not "
457  "treated as 2d");
458  }
459  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  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 =
476  std::map<dof_id_type, std::set<dof_id_type>> crack_front_node_to_elem_map;
477 
478  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  for (std::size_t i = 0; i < connected_elems.size(); ++i)
486  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  for (auto cfnemit = crack_front_node_to_elem_map.begin();
496  cfnemit != crack_front_node_to_elem_map.end();
497  ++cfnemit)
498  {
499  auto cfnemit2 = cfnemit;
500  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  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  if (common_elements.size() > 0)
513  {
514  std::vector<dof_id_type> my_line_elem;
515  my_line_elem.push_back(cfnemit->first);
516  my_line_elem.push_back(cfnemit2->first);
517  node_to_line_elem_map[cfnemit->first].push_back(line_elems.size());
518  node_to_line_elem_map[cfnemit2->first].push_back(line_elems.size());
519  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  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  if (num_connected_elems == 1)
531  end_nodes.push_back(nlemit->first);
532  else if (num_connected_elems != 2)
533  mooseError(
534  "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  if (end_nodes.size() == 0) // Crack front is a loop. Pick nodes to be end nodes.
539  {
540  pickLoopCrackEndNodes(end_nodes, nodes, node_to_line_elem_map, line_elems);
541  _closed_loop = true;
544  paramError("end_direction_method",
545  "In CrackFrontDefinition, end_direction_method cannot be CrackDirectionVector "
546  "or CrackTangentVector for a closed-loop crack");
547  if (_intersecting_boundary_names.size() > 0)
548  paramError("intersecting_boundary",
549  "In CrackFrontDefinition, intersecting_boundary cannot be specified for a "
550  "closed-loop crack");
551  }
552  else if (end_nodes.size() == 2) // Rearrange the order of the end nodes if needed
553  orderEndNodes(end_nodes);
554  else
555  mooseError("In CrackFrontDefinition wrong number of end nodes. Number end nodes = ",
556  end_nodes.size());
557 
558  // Create an ordered list of the nodes going along the line of the crack front
559  _ordered_crack_front_nodes.push_back(end_nodes[0]);
560 
561  dof_id_type last_node = end_nodes[0];
562  dof_id_type second_last_node = last_node;
563  while (last_node != end_nodes[1])
564  {
565  std::vector<dof_id_type> & curr_node_line_elems = node_to_line_elem_map[last_node];
566  bool found_new_node = false;
567  for (std::size_t i = 0; i < curr_node_line_elems.size(); ++i)
568  {
569  std::vector<dof_id_type> curr_line_elem = line_elems[curr_node_line_elems[i]];
570  for (std::size_t j = 0; j < curr_line_elem.size(); ++j)
571  {
572  dof_id_type line_elem_node = curr_line_elem[j];
573  if (_closed_loop &&
574  (last_node == end_nodes[0] &&
575  line_elem_node == end_nodes[1])) // wrong direction around closed loop
576  continue;
577  if (line_elem_node != last_node && line_elem_node != second_last_node)
578  {
579  _ordered_crack_front_nodes.push_back(line_elem_node);
580  found_new_node = true;
581  break;
582  }
583  }
584  if (found_new_node)
585  break;
586  }
587  second_last_node = last_node;
588  last_node = _ordered_crack_front_nodes.back();
589  }
590  }
591 }
592 
593 void
594 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  Node & node0 = _mesh.nodeRef(end_nodes[0]);
599  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  for (std::size_t i = 0; i < 3; ++i)
606  {
607  dist_from_origin0 += node0(i) * node0(i);
608  dist_from_origin1 += node1(i) * node1(i);
609  if (MooseUtils::absoluteFuzzyGreaterThan(node0(i), 0.0, _tol))
610  ++num_positive_coor0;
611  if (MooseUtils::absoluteFuzzyGreaterThan(node1(i), 0.0, _tol))
612  ++num_positive_coor1;
613  }
614  dist_from_origin0 = std::sqrt(dist_from_origin0);
615  dist_from_origin1 = std::sqrt(dist_from_origin1);
616 
617  bool switch_ends = false;
618  if (num_positive_coor1 > num_positive_coor0)
619  {
620  switch_ends = true;
621  }
622  else
623  {
624  if (!MooseUtils::absoluteFuzzyEqual(dist_from_origin1, dist_from_origin0, _tol))
625  {
626  if (dist_from_origin1 < dist_from_origin0)
627  switch_ends = true;
628  }
629  else
630  {
631  if (end_nodes[1] < end_nodes[0])
632  switch_ends = true;
633  }
634  }
635  if (switch_ends)
636  {
637  std::size_t tmp_node = end_nodes[1];
638  end_nodes[1] = end_nodes[0];
639  end_nodes[0] = tmp_node;
640  }
641 }
642 
643 void
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  for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
656  {
657  Node & node = _mesh.nodeRef(*nit);
658  Real dist = node.norm();
659  if (dist > max_dist)
660  {
661  max_dist = dist;
662  max_dist_node = *nit;
663  }
664  else if (dist < min_dist)
665  min_dist = dist;
666  }
667 
668  dof_id_type end_node;
669  if (MooseUtils::absoluteFuzzyGreaterThan(max_dist, min_dist, _tol))
670  end_node = max_dist_node;
671  else
672  {
673  std::vector<Node *> node_vec;
674  for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
675  node_vec.push_back(_mesh.nodePtr(*nit));
676  end_node = maxNodeCoor(node_vec);
677  }
678 
679  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  auto end_node_line_elems = node_to_line_elem_map[end_node];
684  if (end_node_line_elems.size() != 2)
685  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  for (std::size_t i = 0; i < 2; ++i)
690  {
691  auto end_line_elem = line_elems[end_node_line_elems[i]];
692  for (std::size_t j = 0; j < end_line_elem.size(); ++j)
693  {
694  auto line_elem_node = end_line_elem[j];
695  if (line_elem_node != end_node)
696  candidate_other_end_nodes.push_back(_mesh.nodePtr(line_elem_node));
697  }
698  }
699  if (candidate_other_end_nodes.size() != 2)
700  mooseError(
701  "Crack front nodes are in a loop, but crack end node is not connected to two other nodes");
702  end_nodes.push_back(maxNodeCoor(candidate_other_end_nodes, 1));
703 }
704 
706 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  mooseError("Invalid dir0 in CrackFrontDefinition::maxNodeCoor()");
729 
730  Real max_coor0 = -std::numeric_limits<Real>::max();
731  std::vector<Node *> max_coor0_nodes;
732  for (std::size_t i = 0; i < nodes.size(); ++i)
733  {
734  Real coor0 = (*nodes[i])(dirs[0]);
735  if (coor0 > max_coor0)
736  max_coor0 = coor0;
737  }
738  for (std::size_t i = 0; i < nodes.size(); ++i)
739  {
740  Real coor0 = (*nodes[i])(dirs[0]);
741  if (MooseUtils::absoluteFuzzyEqual(coor0, max_coor0, _tol))
742  max_coor0_nodes.push_back(nodes[i]);
743  }
744  if (max_coor0_nodes.size() > 1)
745  {
746  Real max_coor1 = -std::numeric_limits<Real>::max();
747  std::vector<Node *> max_coor1_nodes;
748  for (std::size_t i = 0; i < nodes.size(); ++i)
749  {
750  Real coor1 = (*nodes[i])(dirs[1]);
751  if (coor1 > max_coor1)
752  max_coor1 = coor1;
753  }
754  for (std::size_t i = 0; i < nodes.size(); ++i)
755  {
756  Real coor1 = (*nodes[i])(dirs[1]);
757  if (MooseUtils::absoluteFuzzyEqual(coor1, max_coor1, _tol))
758  max_coor1_nodes.push_back(nodes[i]);
759  }
760  if (max_coor1_nodes.size() > 1)
761  {
762  Real max_coor2 = -std::numeric_limits<Real>::max();
763  std::vector<Node *> max_coor2_nodes;
764  for (std::size_t i = 0; i < nodes.size(); ++i)
765  {
766  Real coor2 = (*nodes[i])(dirs[2]);
767  if (coor2 > max_coor2)
768  max_coor2 = coor2;
769  }
770  for (std::size_t i = 0; i < nodes.size(); ++i)
771  {
772  Real coor2 = (*nodes[i])(dirs[2]);
773  if (MooseUtils::absoluteFuzzyEqual(coor2, max_coor2, _tol))
774  max_coor2_nodes.push_back(nodes[i]);
775  }
776  if (max_coor2_nodes.size() > 1)
777  mooseError("Multiple nodes with same x,y,z coordinates within tolerance");
778  else
779  return max_coor2_nodes[0]->id();
780  }
781  else
782  return max_coor1_nodes[0]->id();
783  }
784  else
785  return max_coor0_nodes[0]->id();
786 }
787 
788 void
790 {
792  _segment_lengths.clear();
793  _tangent_directions.clear();
794  _crack_directions.clear();
795  _overall_length = 0.0;
796  _rot_matrix.clear();
797  _distances_along_front.clear();
798  _angles_along_front.clear();
799  _strain_along_front.clear();
800  _crack_plane_normals.clear();
801 
802  if (_treat_as_2d)
803  {
804  std::size_t num_crack_front_points = getNumCrackFrontPoints();
805  _segment_lengths.reserve(num_crack_front_points);
806  _tangent_directions.reserve(num_crack_front_points);
807  _crack_directions.reserve(num_crack_front_points);
808  if (_use_mesh_cutter)
810  _crack_front_points_provider->getCrackPlaneNormals(num_crack_front_points);
811 
812  for (std::size_t i = 0; i < num_crack_front_points; ++i)
813  {
814  RealVectorValue tangent_direction;
815  RealVectorValue crack_direction;
816  tangent_direction(_axis_2d) = 1.0;
817  _tangent_directions.push_back(tangent_direction);
818  const Point * crack_front_point = getCrackFrontPoint(i);
819 
820  crack_direction =
821  calculateCrackFrontDirection(*crack_front_point, tangent_direction, MIDDLE_NODE, i);
822  _crack_directions.push_back(crack_direction);
823 
824  RankTwoTensor rot_mat;
825  rot_mat.fillRow(0, crack_direction);
826  rot_mat(2, _axis_2d) = 1.0;
827 
828  if (!_use_mesh_cutter)
829  _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  rot_mat.fillRow(1, _crack_plane_normals[i]);
833  _rot_matrix.push_back(rot_mat);
834 
835  _segment_lengths.push_back(std::make_pair(0.0, 0.0));
836  _distances_along_front.push_back(0.0);
837  _angles_along_front.push_back(0.0);
838  }
839  }
840  else
841  {
842  // filling crack_plane_normals from mesh_cutter
843  if (_use_mesh_cutter)
846  // else filling crack_plane_normals from curvedCrackFront
848 
849  std::size_t num_crack_front_points = getNumCrackFrontPoints();
850  _segment_lengths.reserve(num_crack_front_points);
851  _tangent_directions.reserve(num_crack_front_points);
852  _crack_directions.reserve(num_crack_front_points);
853 
854  RealVectorValue back_segment;
855  Real back_segment_len = 0.0;
856  if (_closed_loop)
857  {
858  back_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(num_crack_front_points - 1);
859  back_segment_len = back_segment.norm();
860  }
861 
862  for (std::size_t i = 0; i < num_crack_front_points; ++i)
863  {
864  CRACK_NODE_TYPE ntype;
865  if (_closed_loop)
866  ntype = MIDDLE_NODE;
867  else if (i == 0)
868  ntype = END_1_NODE;
869  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  if (ntype == END_2_NODE)
877  forward_segment_len = 0.0;
878  else if (_closed_loop && i == num_crack_front_points - 1)
879  {
880  forward_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(i);
881  forward_segment_len = forward_segment.norm();
882  }
883  else
884  {
885  forward_segment = *getCrackFrontPoint(i + 1) - *getCrackFrontPoint(i);
886  forward_segment_len = forward_segment.norm();
887  _overall_length += forward_segment_len;
888  }
889 
890  _segment_lengths.push_back(std::make_pair(back_segment_len, forward_segment_len));
891  if (i == 0)
892  _distances_along_front.push_back(0.0);
893  else
894  _distances_along_front.push_back(back_segment_len + _distances_along_front[i - 1]);
895 
896  RealVectorValue tangent_direction = back_segment + forward_segment;
897  tangent_direction = tangent_direction / tangent_direction.norm();
898 
899  // If end tangent directions are given, correct the tangent at the end nodes
902  {
903  if (ntype == END_1_NODE)
904  tangent_direction = _crack_tangent_vector_end_1;
905  else if (ntype == END_2_NODE)
906  tangent_direction = _crack_tangent_vector_end_2;
907  }
908 
909  _tangent_directions.push_back(tangent_direction);
910  _crack_directions.push_back(
911  calculateCrackFrontDirection(*getCrackFrontPoint(i), tangent_direction, ntype, i));
912 
913  // correct tangent direction in the case of _use_mesh_cutter
914  if (_use_mesh_cutter)
916 
917  // If the end directions are given by the user, correct also the tangent at the end nodes
920  (ntype == END_1_NODE || ntype == END_2_NODE))
921  {
923  }
924 
925  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
932  {
933  std::size_t mid_id = (num_crack_front_points - 1) / 2;
934  _crack_plane_normals.assign(num_crack_front_points,
935  _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  if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
940  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  if (hasAngleAlongFront())
946  {
947  RealVectorValue origin_to_first_node = *getCrackFrontPoint(0) - _crack_mouth_coordinates;
948  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  -norm_origin_to_first_node.cross(_crack_plane_normals.front());
952  tangent_to_first_node /= tangent_to_first_node.norm();
953 
954  for (std::size_t i = 0; i < num_crack_front_points; ++i)
955  {
956  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  Real angle = acos(adj / hyp) * 180.0 / libMesh::pi;
962  if (opp < 0.0)
963  angle = 360.0 - angle;
964  _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  if (num_crack_front_points > 1)
969  {
971  _angles_along_front[1] > 180.0)
972  _angles_along_front[0] = 360.0;
974  _angles_along_front[1] < 180.0)
975  _angles_along_front[0] = 0.0;
976 
978  _angles_along_front[num_crack_front_points - 1], 0.0, _tol) &&
979  _angles_along_front[num_crack_front_points - 2] > 180.0)
980  _angles_along_front[num_crack_front_points - 1] = 360.0;
982  _angles_along_front[num_crack_front_points - 1], 360.0, _tol) &&
983  _angles_along_front[num_crack_front_points - 2] < 180.0)
984  _angles_along_front[num_crack_front_points - 1] = 0.0;
985  }
986  }
987  else
988  _angles_along_front.resize(num_crack_front_points, 0.0);
989 
990  // Create rotation matrix
991  for (std::size_t i = 0; i < num_crack_front_points; ++i)
992  {
993  RankTwoTensor rot_mat;
994  rot_mat.fillRow(0, _crack_directions[i]);
995  rot_mat.fillRow(1, _crack_plane_normals[i]);
996  rot_mat.fillRow(2, _tangent_directions[i]);
997  _rot_matrix.push_back(rot_mat);
998  }
999 
1000  _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  " z dir angle position seg length"
1003  << std::endl;
1004  for (std::size_t i = 0; i < num_crack_front_points; ++i)
1005  {
1006  std::size_t point_id;
1008  point_id = _ordered_crack_front_nodes[i];
1009  else
1010  point_id = i;
1011  _console << std::left << std::setw(8) << i + 1 << std::setw(10) << point_id << std::setw(14)
1012  << (*getCrackFrontPoint(i))(0) << std::setw(14) << (*getCrackFrontPoint(i))(1)
1013  << std::setw(14) << (*getCrackFrontPoint(i))(2) << std::setw(14)
1014  << _crack_directions[i](0) << std::setw(14) << _crack_directions[i](1)
1015  << std::setw(14) << _crack_directions[i](2);
1016  if (hasAngleAlongFront())
1017  _console << std::left << std::setw(14) << _angles_along_front[i];
1018  else
1019  _console << std::left << std::setw(14) << "--";
1020  _console << std::left << std::setw(14) << _distances_along_front[i] << std::setw(14)
1021  << (_segment_lengths[i].first + _segment_lengths[i].second) / 2.0 << std::endl;
1022  }
1023  _console << "overall length: " << _overall_length << std::endl;
1024  }
1025 }
1026 
1027 void
1029 {
1030  if (_crack_mouth_boundary_ids.size() > 0)
1031  {
1033 
1034  std::set<Node *> crack_mouth_nodes;
1035  ConstBndNodeRange & bnd_nodes = *_mesh.getBoundaryNodeRange();
1036  for (auto nd = bnd_nodes.begin(); nd != bnd_nodes.end(); ++nd)
1037  {
1038  const BndNode * bnode = *nd;
1039  BoundaryID boundary_id = bnode->_bnd_id;
1040 
1041  for (std::size_t ibid = 0; ibid < _crack_mouth_boundary_ids.size(); ++ibid)
1042  {
1043  if (boundary_id == _crack_mouth_boundary_ids[ibid])
1044  {
1045  crack_mouth_nodes.insert(bnode->_node);
1046  break;
1047  }
1048  }
1049  }
1050 
1051  for (auto nit = crack_mouth_nodes.begin(); nit != crack_mouth_nodes.end(); ++nit)
1052  {
1053  _crack_mouth_coordinates += **nit;
1054  }
1055  _crack_mouth_coordinates /= static_cast<Real>(crack_mouth_nodes.size());
1056 
1057  if (_has_symmetry_plane)
1059  }
1060 }
1061 
1062 void
1064 {
1066  {
1067  _crack_plane_normals.clear();
1068 
1069  // Get 3 nodes on crack front
1070  std::size_t num_points = getNumCrackFrontPoints();
1071  if (num_points < 3)
1072  {
1073  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  if (_closed_loop)
1080  {
1081  start_id = 0;
1082  mid_id = (num_points - 1) / 3;
1083  end_id = 2 * mid_id;
1084  }
1085  else
1086  {
1087  start_id = 0;
1088  mid_id = (num_points - 1) / 2;
1089  end_id = num_points - 1;
1090  }
1091  const Point * start = getCrackFrontPoint(start_id);
1092  const Point * mid = getCrackFrontPoint(mid_id);
1093  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  Point crack_plane_normal = v1.cross(v2);
1101  crack_plane_normal = crack_plane_normal.unit();
1102  _crack_plane_normals.assign(num_points, crack_plane_normal);
1103 
1104  // Make sure they're not collinear
1105  RealVectorValue zero_vec(0.0);
1106  if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
1107  {
1108  mooseError("Nodes on crack front are too close to being collinear");
1109  }
1110  }
1111 }
1112 
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;
1124  {
1125  if (ntype == END_1_NODE)
1126  {
1127  crack_dir = _crack_direction_vector_end_1;
1128  calc_dir = false;
1129  }
1130  else if (ntype == END_2_NODE)
1131  {
1132  crack_dir = _crack_direction_vector_end_2;
1133  calc_dir = false;
1134  }
1135  }
1136 
1137  if (calc_dir)
1138  {
1140  {
1141  crack_dir = _crack_direction_vector;
1142  }
1144  {
1145  if (_crack_mouth_coordinates.absolute_fuzzy_equals(crack_front_point, _tol))
1146  {
1147  mooseError("Crack mouth too close to crack front node");
1148  }
1149  RealVectorValue mouth_to_front = crack_front_point - _crack_mouth_coordinates;
1150 
1151  RealVectorValue crack_plane_normal = mouth_to_front.cross(tangent_direction);
1152  if (crack_plane_normal.absolute_fuzzy_equals(zero_vec, _tol))
1153  {
1154  mooseError(
1155  "Vector from crack mouth to crack front node is collinear with crack front segment");
1156  }
1157 
1158  crack_dir = tangent_direction.cross(crack_plane_normal);
1159  Real dotprod = crack_dir * mouth_to_front;
1160  if (dotprod < 0)
1161  {
1162  crack_dir = -crack_dir;
1163  }
1164  }
1166  {
1167  crack_dir = tangent_direction.cross(_crack_plane_normals[crack_front_point_index]);
1168  }
1169  }
1170  crack_dir = crack_dir.unit();
1171 
1172  return crack_dir;
1173 }
1174 
1175 void
1177 {
1178  _num_points_from_provider = num_points;
1179 }
1180 
1181 const Node *
1182 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  const Node * crack_front_node = _mesh.nodePtr(_ordered_crack_front_nodes[node_index]);
1186  mooseAssert(crack_front_node != nullptr, "invalid crack front node");
1187  return crack_front_node;
1188 }
1189 
1190 const Point *
1191 CrackFrontDefinition::getCrackFrontPoint(const std::size_t point_index) const
1192 {
1194  {
1195  return getCrackFrontNodePtr(point_index);
1196  }
1197  else
1198  {
1199  mooseAssert(point_index < _crack_front_points.size(), "point_index out of range");
1200  return &_crack_front_points[point_index];
1201  }
1202 }
1203 
1204 const RealVectorValue &
1205 CrackFrontDefinition::getCrackFrontTangent(const std::size_t point_index) const
1206 {
1207  mooseAssert(point_index < _tangent_directions.size(), "point_index out of range");
1208  return _tangent_directions[point_index];
1209 }
1210 
1211 const RealVectorValue &
1212 CrackFrontDefinition::getCrackFrontNormal(const std::size_t point_index) const
1213 {
1214  mooseAssert(point_index < _crack_plane_normals.size(), "point_index out of range");
1215  return _crack_plane_normals[point_index];
1216 }
1217 
1218 Real
1220 {
1221  return _segment_lengths[point_index].second;
1222 }
1223 
1224 Real
1226 {
1227  return _segment_lengths[point_index].first;
1228 }
1229 
1230 const RealVectorValue &
1231 CrackFrontDefinition::getCrackDirection(const std::size_t point_index) const
1232 {
1233  return _crack_directions[point_index];
1234 }
1235 
1236 Real
1237 CrackFrontDefinition::getDistanceAlongFront(const std::size_t point_index) const
1238 {
1239  return _distances_along_front[point_index];
1240 }
1241 
1242 bool
1244 {
1245  return (_crack_mouth_boundary_names.size() > 0);
1246 }
1247 
1248 Real
1249 CrackFrontDefinition::getAngleAlongFront(const std::size_t point_index) const
1250 {
1251  if (!hasAngleAlongFront())
1252  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  return _angles_along_front[point_index];
1257 }
1258 
1259 std::size_t
1261 {
1263  return _ordered_crack_front_nodes.size();
1264  else
1265  return _crack_front_points.size();
1266 }
1267 
1270  const std::size_t point_index) const
1271 {
1272  return _rot_matrix[point_index] * vector;
1273 }
1274 
1277  const std::size_t point_index) const
1278 {
1279  RealVectorValue vec = _rot_matrix[point_index].transpose() * vector;
1280  return vec;
1281 }
1282 
1285  const std::size_t point_index) const
1286 {
1287  RankTwoTensor tmp_tensor(tensor);
1288  tmp_tensor.rotate(_rot_matrix[point_index]);
1289  return tmp_tensor;
1290 }
1291 
1292 void
1294  const std::size_t point_index,
1295  Real & r,
1296  Real & theta) const
1297 {
1298  std::size_t num_points = getNumCrackFrontPoints();
1299  Point closest_point(0.0);
1300  RealVectorValue closest_point_to_p;
1301 
1302  const Point * crack_front_point = getCrackFrontPoint(point_index);
1303  RealVectorValue crack_front_point_rot = rotateToCrackFrontCoords(*crack_front_point, point_index);
1304 
1305  RealVectorValue crack_front_edge =
1306  rotateToCrackFrontCoords(_tangent_directions[point_index], point_index);
1307 
1308  Point p_rot = rotateToCrackFrontCoords(qp, point_index);
1309  p_rot = p_rot - crack_front_point_rot;
1310 
1311  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  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  for (std::size_t pit = 0; pit != num_points; ++pit)
1330  {
1331  const Point * crack_front_point = getCrackFrontPoint(pit);
1332  RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1333  Real dist = crack_point_to_current_point.norm();
1334 
1335  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  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  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  r = r_vec.norm();
1354  }
1355 
1356  // Find theta, the angle between r and the crack front plane
1357  RealVectorValue crack_plane_normal =
1358  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  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  Real ahead = crack_front_edge(2) * p2_vec(0) - crack_front_edge(0) * p2_vec(2);
1369 
1370  Real x_local(0);
1371  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  if (r > 0)
1379  {
1380  Real theta_quadrant1(0.0);
1381  if (MooseUtils::absoluteFuzzyEqual(r, p_to_plane_dist, _tol))
1382  theta_quadrant1 = 0.5 * libMesh::pi;
1383  else if (p_to_plane_dist > r)
1384  mooseError(
1385  "Invalid distance p_to_plane_dist in CrackFrontDefinition::calculateRThetaToCrackFront");
1386  else
1387  theta_quadrant1 = std::asin(p_to_plane_dist / r);
1388 
1389  if (x_local >= 0 && y_local >= 0)
1390  theta = theta_quadrant1;
1391 
1392  else if (x_local < 0 && y_local >= 0)
1393  theta = libMesh::pi - theta_quadrant1;
1394 
1395  else if (x_local < 0 && y_local < 0)
1396  theta = -(libMesh::pi - theta_quadrant1);
1397 
1398  else if (x_local >= 0 && y_local < 0)
1399  theta = -theta_quadrant1;
1400  }
1401  else if (r == 0)
1402  theta = 0;
1403  else
1404  mooseError("Invalid distance r in CrackFrontDefinition::calculateRThetaToCrackFront");
1405 }
1406 
1407 std::size_t
1408 CrackFrontDefinition::calculateRThetaToCrackFront(const Point qp, Real & r, Real & theta) const
1409 {
1410  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  for (std::size_t pit = 0; pit != num_points; ++pit)
1416  {
1417  const Point * crack_front_point = getCrackFrontPoint(pit);
1418  RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1419  Real dist = crack_point_to_current_point.norm();
1420 
1421  if (dist < min_dist)
1422  {
1423  min_dist = dist;
1424  point_index = pit;
1425  }
1426  }
1427 
1428  calculateRThetaToCrackFront(qp, point_index, r, theta);
1429 
1430  return point_index;
1431 }
1432 
1433 bool
1435 {
1436  bool is_on_boundary = false;
1437  mooseAssert(node, "Invalid node");
1438  dof_id_type node_id = node->id();
1439  for (std::size_t i = 0; i < _intersecting_boundary_ids.size(); ++i)
1440  {
1442  {
1443  is_on_boundary = true;
1444  break;
1445  }
1446  }
1447  return is_on_boundary;
1448 }
1449 
1450 bool
1452 {
1453  bool is_on_boundary = false;
1455  {
1456  const Node * crack_front_node = getCrackFrontNodePtr(point_index);
1457  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  std::size_t num_crack_front_points = getNumCrackFrontPoints();
1464  if (point_index == 0 || point_index == num_crack_front_points - 1)
1465  is_on_boundary = true;
1466  }
1467  return is_on_boundary;
1468 }
1469 
1470 void
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  _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  for (std::size_t i = 0; i < num_crack_front_nodes; ++i)
1496  _strain_along_front[i] = -std::numeric_limits<Real>::max();
1497 
1501 
1502  current_node = getCrackFrontNodePtr(0);
1503  if (current_node->processor_id() == processor_id())
1504  {
1505  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1506  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1507  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1508 
1509  next_node = getCrackFrontNodePtr(1);
1510  disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1511  disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1512  disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1513 
1514  forward_segment0 = *next_node - *current_node;
1515  forward_segment0 = (forward_segment0 * _tangent_directions[0]) * _tangent_directions[0];
1516  forward_segment0_len = forward_segment0.norm();
1517 
1518  forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1519  forward_segment1 = (forward_segment1 * _tangent_directions[0]) * _tangent_directions[0];
1520  forward_segment1_len = forward_segment1.norm();
1521 
1522  _strain_along_front[0] = (forward_segment1_len - forward_segment0_len) / forward_segment0_len;
1523  }
1524 
1525  for (std::size_t i = 1; i < num_crack_front_nodes - 1; ++i)
1526  {
1527  current_node = getCrackFrontNodePtr(i);
1528  if (current_node->processor_id() == processor_id())
1529  {
1530  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1531  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1532  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1533 
1534  previous_node = getCrackFrontNodePtr(i - 1);
1535  disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1536  disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1537  disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1538 
1539  next_node = getCrackFrontNodePtr(i + 1);
1540  disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1541  disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1542  disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1543 
1544  back_segment0 = *current_node - *previous_node;
1545  back_segment0 = (back_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1546  back_segment0_len = back_segment0.norm();
1547 
1548  back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1549  back_segment1 = (back_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1550  back_segment1_len = back_segment1.norm();
1551 
1552  forward_segment0 = *next_node - *current_node;
1553  forward_segment0 = (forward_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1554  forward_segment0_len = forward_segment0.norm();
1555 
1556  forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1557  forward_segment1 = (forward_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1558  forward_segment1_len = forward_segment1.norm();
1559 
1560  _strain_along_front[i] =
1561  0.5 * ((back_segment1_len - back_segment0_len) / back_segment0_len +
1562  (forward_segment1_len - forward_segment0_len) / forward_segment0_len);
1563  }
1564  }
1565 
1566  current_node = getCrackFrontNodePtr(num_crack_front_nodes - 1);
1567  if (current_node->processor_id() == processor_id())
1568  {
1569  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1570  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1571  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1572 
1573  previous_node = getCrackFrontNodePtr(num_crack_front_nodes - 2);
1574  disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1575  disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1576  disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1577 
1578  back_segment0 = *current_node - *previous_node;
1579  back_segment0 = (back_segment0 * _tangent_directions[num_crack_front_nodes - 1]) *
1580  _tangent_directions[num_crack_front_nodes - 1];
1581  back_segment0_len = back_segment0.norm();
1582 
1583  back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1584  back_segment1 = (back_segment1 * _tangent_directions[num_crack_front_nodes - 1]) *
1585  _tangent_directions[num_crack_front_nodes - 1];
1586  back_segment1_len = back_segment1.norm();
1587 
1588  _strain_along_front[num_crack_front_nodes - 1] =
1589  (back_segment1_len - back_segment0_len) / back_segment0_len;
1590  }
1591 }
1592 
1593 Real
1594 CrackFrontDefinition::getCrackFrontTangentialStrain(const std::size_t node_index) const
1595 {
1596  Real strain;
1597  if (_t_stress)
1598  {
1599  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  mooseError("In CrackFrontDefinition, tangential strain not available");
1605 
1606  return strain;
1607 }
1608 
1609 void
1611 {
1612  // In the variable names, "cfn" = crack front node
1613  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  MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1618 
1619  std::set<dof_id_type> nodes_prev_ring;
1620  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  connected_nodes_this_cfn.insert(_ordered_crack_front_nodes.begin(),
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  _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  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  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  MeshTools::find_nodal_neighbors(
1644  _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1645  for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1646  {
1647  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  if (thisit == connected_nodes_this_cfn.end())
1651  new_ring_nodes_this_cfn.insert(neighbors[inei]->id());
1652  }
1653  }
1654 
1655  // Add new nodes to rings
1656  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  std::make_pair(_ordered_crack_front_nodes[0], ring);
1662  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1663  connected_nodes_this_cfn.end());
1664  }
1665  }
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  MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1671  for (std::size_t icfn = 0; icfn < num_crack_front_points; ++icfn)
1672  {
1673  std::set<dof_id_type> nodes_prev_ring;
1674  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  connected_nodes_this_cfn.insert(_ordered_crack_front_nodes[icfn]);
1681 
1682  if (_closed_loop && icfn == 0)
1683  {
1684  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[num_crack_front_points - 1]);
1685  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1686  }
1687  else if (_closed_loop && icfn == num_crack_front_points - 1)
1688  {
1689  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1690  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[0]);
1691  }
1692  else if (icfn == 0)
1693  {
1694  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1695  }
1696  else if (icfn == num_crack_front_points - 1)
1697  {
1698  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1699  }
1700  else
1701  {
1702  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1703  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  _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  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  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  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  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  connected_nodes_prev_cfn.insert(new_ring_nodes_prev_cfn.begin(),
1748  new_ring_nodes_prev_cfn.end());
1749  connected_nodes_this_cfn.insert(new_ring_nodes_this_cfn.begin(),
1750  new_ring_nodes_this_cfn.end());
1751  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  std::make_pair(_ordered_crack_front_nodes[icfn], ring);
1759  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1760  connected_nodes_this_cfn.end());
1761  }
1762  }
1763  }
1764 }
1765 
1766 void
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  for (auto nit = nodes_old_ring.begin(); nit != nodes_old_ring.end(); ++nit)
1776  {
1777  std::vector<const Node *> neighbors;
1778  MeshTools::find_nodal_neighbors(
1779  _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1780  for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1781  {
1782  auto previt = nodes_all_rings.find(neighbors[inei]->id());
1783  auto thisit = nodes_neighbor1.find(neighbors[inei]->id());
1784  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  if (previt == nodes_all_rings.end() && thisit == nodes_neighbor1.end() &&
1788  nextit == nodes_neighbor2.end())
1789  nodes_new_ring.insert(neighbors[inei]->id());
1790  }
1791  }
1792 }
1793 
1794 bool
1795 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  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  if (nnmit == _crack_front_node_to_node_map.end())
1805  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  if (q_func_nodes.find(connected_node_id) != q_func_nodes.end())
1812  is_node_in_ring = true;
1813 
1814  return is_node_in_ring;
1815 }
1816 
1817 Real
1818 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;
1825  dist_to_crack_front, dist_along_tangent, crack_front_point_index, current_node);
1826 
1827  Real q = 1.0;
1828  if (dist_to_crack_front > _j_integral_radius_inner[ring_index] &&
1829  dist_to_crack_front < _j_integral_radius_outer[ring_index])
1830  q = (_j_integral_radius_outer[ring_index] - dist_to_crack_front) /
1831  (_j_integral_radius_outer[ring_index] - _j_integral_radius_inner[ring_index]);
1832  else if (dist_to_crack_front >= _j_integral_radius_outer[ring_index])
1833  q = 0.0;
1834 
1835  if (q > 0.0)
1836  {
1837  Real tangent_multiplier = 1.0;
1838  if (!_treat_as_2d)
1839  {
1840  const Real forward_segment_length =
1841  getCrackFrontForwardSegmentLength(crack_front_point_index);
1842  const Real backward_segment_length =
1843  getCrackFrontBackwardSegmentLength(crack_front_point_index);
1844 
1845  if (dist_along_tangent >= 0.0)
1846  {
1847  if (forward_segment_length > 0.0)
1848  tangent_multiplier = 1.0 - dist_along_tangent / forward_segment_length;
1849  }
1850  else
1851  {
1852  if (backward_segment_length > 0.0)
1853  tangent_multiplier = 1.0 + dist_along_tangent / backward_segment_length;
1854  }
1855  }
1856 
1857  tangent_multiplier = std::max(tangent_multiplier, 0.0);
1858  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  if (isNodeOnIntersectingBoundary(current_node) &&
1862  !_is_point_on_intersecting_boundary[crack_front_point_index])
1863  tangent_multiplier = 0.0;
1864 
1865  q *= tangent_multiplier;
1866  }
1867 
1868  return q;
1869 }
1870 
1871 Real
1873  std::size_t ring_index,
1874  const Node * const current_node) const
1875 {
1876  Real q = 0;
1877  bool is_node_in_ring = isNodeInRing(ring_index, current_node->id(), crack_front_point_index);
1878  if (is_node_in_ring)
1879  q = 1;
1880 
1881  return q;
1882 }
1883 
1884 void
1886  Real & dist_along_tangent,
1887  std::size_t crack_front_point_index,
1888  const Node * const current_node) const
1889 {
1890  const Point * crack_front_point = getCrackFrontPoint(crack_front_point_index);
1891 
1892  Point p = *current_node;
1893  const RealVectorValue & crack_front_tangent = getCrackFrontTangent(crack_front_point_index);
1894 
1895  RealVectorValue crack_node_to_current_node = p - *crack_front_point;
1896  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  dist_to_front = axis_to_current_node.norm();
1900 }
1901 
1902 void
1903 CrackFrontDefinition::isCutterModified(const bool is_cutter_modified)
1904 {
1905  _is_cutter_modified = is_cutter_modified;
1906 }
void calculateTangentialStrainAlongFront()
Compute the strain in the direction tangent to the crack at all points on the crack front...
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.
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.
auto norm() const -> decltype(std::norm(Real()))
bool _has_symmetry_plane
Whether the crack plane is also a symmetry plane in the model.
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
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.
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.
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)
std::string getRawNames() const
BoundaryID _bnd_id
std::vector< BoundaryID > _crack_mouth_boundary_ids
IDs of boundaries used to define location of crack mouth.
void addCrackFrontDefinitionParams(InputParameters &params)
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...
SubProblem & _subproblem
static const Real _tol
Tolerance used in geometric calculations.
bool isParamValid(const std::string &name) const
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 -> decltype(std::norm(Real()))
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 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
void paramError(const std::string &param, Args... args) const
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.
OutputData getNodalValue(const Node &node) const
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.
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
static InputParameters validParams()
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
const THREAD_ID _tid
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)
const InputParameters & parameters() const
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 _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
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
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 std::map< dof_id_type, std::vector< dof_id_type > > & nodeToElemMap()
const Real pi
RealVectorValue _crack_mouth_coordinates
Coordinates of crack mouth.
enum CrackFrontDefinition::DIRECTION_METHOD _direction_method