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 std::map<dof_id_type, std::vector<dof_id_type>> & node_to_elem_map =
493  std::map<dof_id_type, std::set<dof_id_type>> crack_front_node_to_elem_map;
494 
495  for (const auto & node_id : nodes)
496  {
497  const auto & node_to_elem_pair = node_to_elem_map.find(node_id);
498  mooseAssert(node_to_elem_pair != node_to_elem_map.end(),
499  "Could not find crack front node " << node_id << " in the node to elem map");
500 
501  const std::vector<dof_id_type> & connected_elems = node_to_elem_pair->second;
502  for (std::size_t i = 0; i < connected_elems.size(); ++i)
503  crack_front_node_to_elem_map[node_id].insert(connected_elems[i]);
504  }
505 
506  // Determine which nodes are connected to each other via elements, and construct line elements
507  // to represent
508  // those connections
509  std::vector<std::vector<dof_id_type>> line_elems;
510  std::map<dof_id_type, std::vector<dof_id_type>> node_to_line_elem_map;
511 
512  for (auto cfnemit = crack_front_node_to_elem_map.begin();
513  cfnemit != crack_front_node_to_elem_map.end();
514  ++cfnemit)
515  {
516  auto cfnemit2 = cfnemit;
517  for (++cfnemit2; cfnemit2 != crack_front_node_to_elem_map.end(); ++cfnemit2)
518  {
519 
520  std::vector<dof_id_type> common_elements;
521  std::set<dof_id_type> & elements_connected_to_node1 = cfnemit->second;
522  std::set<dof_id_type> & elements_connected_to_node2 = cfnemit2->second;
523  std::set_intersection(elements_connected_to_node1.begin(),
524  elements_connected_to_node1.end(),
525  elements_connected_to_node2.begin(),
526  elements_connected_to_node2.end(),
527  std::inserter(common_elements, common_elements.end()));
528 
529  if (common_elements.size() > 0)
530  {
531  std::vector<dof_id_type> my_line_elem;
532  my_line_elem.push_back(cfnemit->first);
533  my_line_elem.push_back(cfnemit2->first);
534  node_to_line_elem_map[cfnemit->first].push_back(line_elems.size());
535  node_to_line_elem_map[cfnemit2->first].push_back(line_elems.size());
536  line_elems.push_back(my_line_elem);
537  }
538  }
539  }
540 
541  // Find nodes on ends of line (those connected to only one line element)
542  std::vector<dof_id_type> end_nodes;
543  for (auto nlemit = node_to_line_elem_map.begin(); nlemit != node_to_line_elem_map.end();
544  ++nlemit)
545  {
546  std::size_t num_connected_elems = nlemit->second.size();
547  if (num_connected_elems == 1)
548  end_nodes.push_back(nlemit->first);
549  else if (num_connected_elems != 2)
550  mooseError(
551  "Node ", nlemit->first, " is connected to >2 line segments in CrackFrontDefinition");
552  }
553 
554  // For embedded crack with closed loop of crack front nodes, must pick the end nodes
555  if (end_nodes.size() == 0) // Crack front is a loop. Pick nodes to be end nodes.
556  {
557  pickLoopCrackEndNodes(end_nodes, nodes, node_to_line_elem_map, line_elems);
558  _closed_loop = true;
561  paramError("end_direction_method",
562  "In CrackFrontDefinition, end_direction_method cannot be CrackDirectionVector "
563  "or CrackTangentVector for a closed-loop crack");
564  if (_intersecting_boundary_names.size() > 0)
565  paramError("intersecting_boundary",
566  "In CrackFrontDefinition, intersecting_boundary cannot be specified for a "
567  "closed-loop crack");
568  }
569  else if (end_nodes.size() == 2) // Rearrange the order of the end nodes if needed
570  orderEndNodes(end_nodes);
571  else
572  mooseError("In CrackFrontDefinition wrong number of end nodes. Number end nodes = ",
573  end_nodes.size());
574 
575  // Create an ordered list of the nodes going along the line of the crack front
576  _ordered_crack_front_nodes.push_back(end_nodes[0]);
577 
578  dof_id_type last_node = end_nodes[0];
579  dof_id_type second_last_node = last_node;
580  while (last_node != end_nodes[1])
581  {
582  std::vector<dof_id_type> & curr_node_line_elems = node_to_line_elem_map[last_node];
583  bool found_new_node = false;
584  for (std::size_t i = 0; i < curr_node_line_elems.size(); ++i)
585  {
586  std::vector<dof_id_type> curr_line_elem = line_elems[curr_node_line_elems[i]];
587  for (std::size_t j = 0; j < curr_line_elem.size(); ++j)
588  {
589  dof_id_type line_elem_node = curr_line_elem[j];
590  if (_closed_loop &&
591  (last_node == end_nodes[0] &&
592  line_elem_node == end_nodes[1])) // wrong direction around closed loop
593  continue;
594  if (line_elem_node != last_node && line_elem_node != second_last_node)
595  {
596  _ordered_crack_front_nodes.push_back(line_elem_node);
597  found_new_node = true;
598  break;
599  }
600  }
601  if (found_new_node)
602  break;
603  }
604  second_last_node = last_node;
605  last_node = _ordered_crack_front_nodes.back();
606  }
607  }
608 }
609 
610 void
611 CrackFrontDefinition::orderEndNodes(std::vector<dof_id_type> & end_nodes)
612 {
613  // Choose the node to be the first node. Do that based on undeformed coordinates for
614  // repeatability.
615  Node & node0 = _mesh.nodeRef(end_nodes[0]);
616  Node & node1 = _mesh.nodeRef(end_nodes[1]);
617 
618  std::size_t num_positive_coor0 = 0;
619  std::size_t num_positive_coor1 = 0;
620  Real dist_from_origin0 = 0.0;
621  Real dist_from_origin1 = 0.0;
622  for (std::size_t i = 0; i < 3; ++i)
623  {
624  dist_from_origin0 += node0(i) * node0(i);
625  dist_from_origin1 += node1(i) * node1(i);
626  if (MooseUtils::absoluteFuzzyGreaterThan(node0(i), 0.0, _tol))
627  ++num_positive_coor0;
628  if (MooseUtils::absoluteFuzzyGreaterThan(node1(i), 0.0, _tol))
629  ++num_positive_coor1;
630  }
631  dist_from_origin0 = std::sqrt(dist_from_origin0);
632  dist_from_origin1 = std::sqrt(dist_from_origin1);
633 
634  bool switch_ends = false;
635  if (num_positive_coor1 > num_positive_coor0)
636  {
637  switch_ends = true;
638  }
639  else
640  {
641  if (!MooseUtils::absoluteFuzzyEqual(dist_from_origin1, dist_from_origin0, _tol))
642  {
643  if (dist_from_origin1 < dist_from_origin0)
644  switch_ends = true;
645  }
646  else
647  {
648  if (end_nodes[1] < end_nodes[0])
649  switch_ends = true;
650  }
651  }
652  if (switch_ends)
653  {
654  std::size_t tmp_node = end_nodes[1];
655  end_nodes[1] = end_nodes[0];
656  end_nodes[0] = tmp_node;
657  }
658 }
659 
660 void
662  std::vector<dof_id_type> & end_nodes,
663  std::set<dof_id_type> & nodes,
664  std::map<dof_id_type, std::vector<dof_id_type>> & node_to_line_elem_map,
665  std::vector<std::vector<dof_id_type>> & line_elems)
666 {
667  dof_id_type max_dist_node = 0;
668  Real min_dist = std::numeric_limits<Real>::max();
669  Real max_dist = -std::numeric_limits<Real>::max();
670  // Pick the node farthest from the origin as the end node, or the one with
671  // the greatest x coordinate if the nodes are equidistant from the origin
672  for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
673  {
674  Node & node = _mesh.nodeRef(*nit);
675  Real dist = node.norm();
676  if (dist > max_dist)
677  {
678  max_dist = dist;
679  max_dist_node = *nit;
680  }
681  else if (dist < min_dist)
682  min_dist = dist;
683  }
684 
685  dof_id_type end_node;
686  if (MooseUtils::absoluteFuzzyGreaterThan(max_dist, min_dist, _tol))
687  end_node = max_dist_node;
688  else
689  {
690  std::vector<Node *> node_vec;
691  for (auto nit = nodes.begin(); nit != nodes.end(); ++nit)
692  node_vec.push_back(_mesh.nodePtr(*nit));
693  end_node = maxNodeCoor(node_vec);
694  }
695 
696  end_nodes.push_back(end_node);
697 
698  // Find the two nodes connected to the node identified as the end node, and pick one of those to
699  // be the other end node
700  auto end_node_line_elems = node_to_line_elem_map[end_node];
701  if (end_node_line_elems.size() != 2)
702  mooseError(
703  "Crack front nodes are in a loop, but crack end node is only connected to one other node");
704  std::vector<Node *> candidate_other_end_nodes;
705 
706  for (std::size_t i = 0; i < 2; ++i)
707  {
708  auto end_line_elem = line_elems[end_node_line_elems[i]];
709  for (std::size_t j = 0; j < end_line_elem.size(); ++j)
710  {
711  auto line_elem_node = end_line_elem[j];
712  if (line_elem_node != end_node)
713  candidate_other_end_nodes.push_back(_mesh.nodePtr(line_elem_node));
714  }
715  }
716  if (candidate_other_end_nodes.size() != 2)
717  mooseError(
718  "Crack front nodes are in a loop, but crack end node is not connected to two other nodes");
719  end_nodes.push_back(maxNodeCoor(candidate_other_end_nodes, 1));
720 }
721 
723 CrackFrontDefinition::maxNodeCoor(std::vector<Node *> & nodes, unsigned int dir0)
724 {
725  Real dirs[3];
726  if (dir0 == 0)
727  {
728  dirs[0] = 0;
729  dirs[1] = 1;
730  dirs[2] = 2;
731  }
732  else if (dir0 == 1)
733  {
734  dirs[0] = 1;
735  dirs[1] = 2;
736  dirs[2] = 0;
737  }
738  else if (dir0 == 2)
739  {
740  dirs[0] = 2;
741  dirs[1] = 0;
742  dirs[2] = 1;
743  }
744  else
745  mooseError("Invalid dir0 in CrackFrontDefinition::maxNodeCoor()");
746 
747  Real max_coor0 = -std::numeric_limits<Real>::max();
748  std::vector<Node *> max_coor0_nodes;
749  for (std::size_t i = 0; i < nodes.size(); ++i)
750  {
751  Real coor0 = (*nodes[i])(dirs[0]);
752  if (coor0 > max_coor0)
753  max_coor0 = coor0;
754  }
755  for (std::size_t i = 0; i < nodes.size(); ++i)
756  {
757  Real coor0 = (*nodes[i])(dirs[0]);
758  if (MooseUtils::absoluteFuzzyEqual(coor0, max_coor0, _tol))
759  max_coor0_nodes.push_back(nodes[i]);
760  }
761  if (max_coor0_nodes.size() > 1)
762  {
763  Real max_coor1 = -std::numeric_limits<Real>::max();
764  std::vector<Node *> max_coor1_nodes;
765  for (std::size_t i = 0; i < nodes.size(); ++i)
766  {
767  Real coor1 = (*nodes[i])(dirs[1]);
768  if (coor1 > max_coor1)
769  max_coor1 = coor1;
770  }
771  for (std::size_t i = 0; i < nodes.size(); ++i)
772  {
773  Real coor1 = (*nodes[i])(dirs[1]);
774  if (MooseUtils::absoluteFuzzyEqual(coor1, max_coor1, _tol))
775  max_coor1_nodes.push_back(nodes[i]);
776  }
777  if (max_coor1_nodes.size() > 1)
778  {
779  Real max_coor2 = -std::numeric_limits<Real>::max();
780  std::vector<Node *> max_coor2_nodes;
781  for (std::size_t i = 0; i < nodes.size(); ++i)
782  {
783  Real coor2 = (*nodes[i])(dirs[2]);
784  if (coor2 > max_coor2)
785  max_coor2 = coor2;
786  }
787  for (std::size_t i = 0; i < nodes.size(); ++i)
788  {
789  Real coor2 = (*nodes[i])(dirs[2]);
790  if (MooseUtils::absoluteFuzzyEqual(coor2, max_coor2, _tol))
791  max_coor2_nodes.push_back(nodes[i]);
792  }
793  if (max_coor2_nodes.size() > 1)
794  mooseError("Multiple nodes with same x,y,z coordinates within tolerance");
795  else
796  return max_coor2_nodes[0]->id();
797  }
798  else
799  return max_coor1_nodes[0]->id();
800  }
801  else
802  return max_coor0_nodes[0]->id();
803 }
804 
805 void
807 {
809  _segment_lengths.clear();
810  _tangent_directions.clear();
811  _crack_directions.clear();
812  _overall_length = 0.0;
813  _rot_matrix.clear();
814  _distances_along_front.clear();
815  _angles_along_front.clear();
816  _strain_along_front.clear();
817  _crack_plane_normals.clear();
818 
819  if (_treat_as_2d)
820  {
821  std::size_t num_crack_front_points = getNumCrackFrontPoints();
822  _segment_lengths.reserve(num_crack_front_points);
823  _tangent_directions.reserve(num_crack_front_points);
824  _crack_directions.reserve(num_crack_front_points);
825  if (_use_mesh_cutter)
827  _crack_front_points_provider->getCrackPlaneNormals(num_crack_front_points);
828 
829  for (std::size_t i = 0; i < num_crack_front_points; ++i)
830  {
831  RealVectorValue tangent_direction;
832  RealVectorValue crack_direction;
833  tangent_direction(_axis_2d) = 1.0;
834  _tangent_directions.push_back(tangent_direction);
835  const Point * crack_front_point = getCrackFrontPoint(i);
836 
837  crack_direction =
838  calculateCrackFrontDirection(*crack_front_point, tangent_direction, MIDDLE_NODE, i);
839  _crack_directions.push_back(crack_direction);
840 
841  RankTwoTensor rot_mat;
842  rot_mat.fillRow(0, crack_direction);
843  rot_mat(2, _axis_2d) = 1.0;
844 
845  if (!_use_mesh_cutter)
846  _crack_plane_normals.push_back(tangent_direction.cross(crack_direction));
847 
848  mooseAssert(i <= _crack_plane_normals.size(), "_crack_plane_normals is the wrong size.");
849  rot_mat.fillRow(1, _crack_plane_normals[i]);
850  _rot_matrix.push_back(rot_mat);
851 
852  _segment_lengths.push_back(std::make_pair(0.0, 0.0));
853  _distances_along_front.push_back(0.0);
854  _angles_along_front.push_back(0.0);
855  }
856  }
857  else
858  {
859  // filling crack_plane_normals from mesh_cutter
860  if (_use_mesh_cutter)
863  // else filling crack_plane_normals from curvedCrackFront
865 
866  std::size_t num_crack_front_points = getNumCrackFrontPoints();
867  _segment_lengths.reserve(num_crack_front_points);
868  _tangent_directions.reserve(num_crack_front_points);
869  _crack_directions.reserve(num_crack_front_points);
870 
871  RealVectorValue back_segment;
872  Real back_segment_len = 0.0;
873  if (_closed_loop)
874  {
875  back_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(num_crack_front_points - 1);
876  back_segment_len = back_segment.norm();
877  }
878 
879  for (std::size_t i = 0; i < num_crack_front_points; ++i)
880  {
881  CRACK_NODE_TYPE ntype;
882  if (_closed_loop)
883  ntype = MIDDLE_NODE;
884  else if (i == 0)
885  ntype = END_1_NODE;
886  else if (i == num_crack_front_points - 1)
887  ntype = END_2_NODE;
888  else
889  ntype = MIDDLE_NODE;
890 
891  RealVectorValue forward_segment;
892  Real forward_segment_len;
893  if (ntype == END_2_NODE)
894  forward_segment_len = 0.0;
895  else if (_closed_loop && i == num_crack_front_points - 1)
896  {
897  forward_segment = *getCrackFrontPoint(0) - *getCrackFrontPoint(i);
898  forward_segment_len = forward_segment.norm();
899  }
900  else
901  {
902  forward_segment = *getCrackFrontPoint(i + 1) - *getCrackFrontPoint(i);
903  forward_segment_len = forward_segment.norm();
904  _overall_length += forward_segment_len;
905  }
906 
907  _segment_lengths.push_back(std::make_pair(back_segment_len, forward_segment_len));
908  if (i == 0)
909  _distances_along_front.push_back(0.0);
910  else
911  _distances_along_front.push_back(back_segment_len + _distances_along_front[i - 1]);
912 
913  RealVectorValue tangent_direction = back_segment + forward_segment;
914  tangent_direction = tangent_direction / tangent_direction.norm();
915 
916  // If end tangent directions are given, correct the tangent at the end nodes
919  {
920  if (ntype == END_1_NODE)
921  tangent_direction = _crack_tangent_vector_end_1;
922  else if (ntype == END_2_NODE)
923  tangent_direction = _crack_tangent_vector_end_2;
924  }
925 
926  _tangent_directions.push_back(tangent_direction);
927  _crack_directions.push_back(
928  calculateCrackFrontDirection(*getCrackFrontPoint(i), tangent_direction, ntype, i));
929 
930  // correct tangent direction in the case of _use_mesh_cutter
931  if (_use_mesh_cutter)
933 
934  // If the end directions are given by the user, correct also the tangent at the end nodes
937  (ntype == END_1_NODE || ntype == END_2_NODE))
938  {
940  }
941 
942  back_segment = forward_segment;
943  back_segment_len = forward_segment_len;
944  }
945 
946  // For CURVED_CRACK_FRONT, _crack_plane_normals gets computed in
947  // computeCurvedCrackFrontCrackPlaneNormals
949  {
950  std::size_t mid_id = (num_crack_front_points - 1) / 2;
951  _crack_plane_normals.assign(num_crack_front_points,
952  _tangent_directions[mid_id].cross(_crack_directions[mid_id]));
953 
954  // Make sure the normal vector is non-zero
955  RealVectorValue zero_vec(0.0);
956  if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
957  mooseError("Crack plane normal vector evaluates to zero");
958  }
959 
960  // Calculate angles of each point along the crack front for an elliptical crack projected
961  // to a circle.
962  if (hasAngleAlongFront())
963  {
964  RealVectorValue origin_to_first_node = *getCrackFrontPoint(0) - _crack_mouth_coordinates;
965  Real hyp = origin_to_first_node.norm();
966  RealVectorValue norm_origin_to_first_node = origin_to_first_node / hyp;
967  RealVectorValue tangent_to_first_node =
968  -norm_origin_to_first_node.cross(_crack_plane_normals.front());
969  tangent_to_first_node /= tangent_to_first_node.norm();
970 
971  for (std::size_t i = 0; i < num_crack_front_points; ++i)
972  {
973  RealVectorValue origin_to_curr_node = *getCrackFrontPoint(i) - _crack_mouth_coordinates;
974 
975  Real adj = origin_to_curr_node * norm_origin_to_first_node;
976  Real opp = origin_to_curr_node * tangent_to_first_node;
977 
978  Real angle = acos(adj / hyp) * 180.0 / libMesh::pi;
979  if (opp < 0.0)
980  angle = 360.0 - angle;
981  _angles_along_front.push_back(angle);
982  }
983 
984  // Correct angle on end nodes if they are 0 or 360 to be consistent with neighboring node
985  if (num_crack_front_points > 1)
986  {
987  if (MooseUtils::absoluteFuzzyEqual(_angles_along_front[0], 0.0, _tol) &&
988  _angles_along_front[1] > 180.0)
989  _angles_along_front[0] = 360.0;
990  else if (MooseUtils::absoluteFuzzyEqual(_angles_along_front[0], 360.0, _tol) &&
991  _angles_along_front[1] < 180.0)
992  _angles_along_front[0] = 0.0;
993 
994  if (MooseUtils::absoluteFuzzyEqual(
995  _angles_along_front[num_crack_front_points - 1], 0.0, _tol) &&
996  _angles_along_front[num_crack_front_points - 2] > 180.0)
997  _angles_along_front[num_crack_front_points - 1] = 360.0;
998  else if (MooseUtils::absoluteFuzzyEqual(
999  _angles_along_front[num_crack_front_points - 1], 360.0, _tol) &&
1000  _angles_along_front[num_crack_front_points - 2] < 180.0)
1001  _angles_along_front[num_crack_front_points - 1] = 0.0;
1002  }
1003  }
1004  else
1005  _angles_along_front.resize(num_crack_front_points, 0.0);
1006 
1007  // Create rotation matrix
1008  for (std::size_t i = 0; i < num_crack_front_points; ++i)
1009  {
1010  RankTwoTensor rot_mat;
1011  rot_mat.fillRow(0, _crack_directions[i]);
1012  rot_mat.fillRow(1, _crack_plane_normals[i]);
1013  rot_mat.fillRow(2, _tangent_directions[i]);
1014  _rot_matrix.push_back(rot_mat);
1015  }
1016 
1017  _console << "Summary of crack front geometry (used for fracture integrals):" << std::endl;
1018  _console << "index node id x coord y coord z coord x dir y dir "
1019  " z dir angle position seg length"
1020  << std::endl;
1021  for (std::size_t i = 0; i < num_crack_front_points; ++i)
1022  {
1023  std::size_t point_id;
1025  point_id = _ordered_crack_front_nodes[i];
1026  else
1027  point_id = i;
1028  _console << std::left << std::setw(8) << i + 1 << std::setw(10) << point_id << std::setw(14)
1029  << (*getCrackFrontPoint(i))(0) << std::setw(14) << (*getCrackFrontPoint(i))(1)
1030  << std::setw(14) << (*getCrackFrontPoint(i))(2) << std::setw(14)
1031  << _crack_directions[i](0) << std::setw(14) << _crack_directions[i](1)
1032  << std::setw(14) << _crack_directions[i](2);
1033  if (hasAngleAlongFront())
1034  _console << std::left << std::setw(14) << _angles_along_front[i];
1035  else
1036  _console << std::left << std::setw(14) << "--";
1037  _console << std::left << std::setw(14) << _distances_along_front[i] << std::setw(14)
1038  << (_segment_lengths[i].first + _segment_lengths[i].second) / 2.0 << std::endl;
1039  }
1040  _console << "overall length: " << _overall_length << std::endl;
1041  }
1042 }
1043 
1044 void
1046 {
1047  if (_crack_mouth_boundary_ids.size() > 0)
1048  {
1050 
1051  std::set<Node *> crack_mouth_nodes;
1052  ConstBndNodeRange & bnd_nodes = *_mesh.getBoundaryNodeRange();
1053  for (auto nd = bnd_nodes.begin(); nd != bnd_nodes.end(); ++nd)
1054  {
1055  const BndNode * bnode = *nd;
1056  BoundaryID boundary_id = bnode->_bnd_id;
1057 
1058  for (std::size_t ibid = 0; ibid < _crack_mouth_boundary_ids.size(); ++ibid)
1059  {
1060  if (boundary_id == _crack_mouth_boundary_ids[ibid])
1061  {
1062  crack_mouth_nodes.insert(bnode->_node);
1063  break;
1064  }
1065  }
1066  }
1067 
1068  for (auto nit = crack_mouth_nodes.begin(); nit != crack_mouth_nodes.end(); ++nit)
1069  {
1070  _crack_mouth_coordinates += **nit;
1071  }
1072  _crack_mouth_coordinates /= static_cast<Real>(crack_mouth_nodes.size());
1073 
1074  if (_has_symmetry_plane)
1076  }
1077 }
1078 
1079 void
1081 {
1083  {
1084  _crack_plane_normals.clear();
1085 
1086  // Get 3 nodes on crack front
1087  std::size_t num_points = getNumCrackFrontPoints();
1088  if (num_points < 3)
1089  {
1090  mooseError("Crack front must contain at least 3 nodes to use CurvedCrackFront option");
1091  }
1092  std::size_t start_id;
1093  std::size_t mid_id;
1094  std::size_t end_id;
1095 
1096  if (_closed_loop)
1097  {
1098  start_id = 0;
1099  mid_id = (num_points - 1) / 3;
1100  end_id = 2 * mid_id;
1101  }
1102  else
1103  {
1104  start_id = 0;
1105  mid_id = (num_points - 1) / 2;
1106  end_id = num_points - 1;
1107  }
1108  const Point * start = getCrackFrontPoint(start_id);
1109  const Point * mid = getCrackFrontPoint(mid_id);
1110  const Point * end = getCrackFrontPoint(end_id);
1111 
1112  // Create two vectors connecting them
1113  RealVectorValue v1 = *mid - *start;
1114  RealVectorValue v2 = *end - *mid;
1115 
1116  // Take cross product to get normal
1117  Point crack_plane_normal = v1.cross(v2);
1118  crack_plane_normal = crack_plane_normal.unit();
1119  _crack_plane_normals.assign(num_points, crack_plane_normal);
1120 
1121  // Make sure they're not collinear
1122  RealVectorValue zero_vec(0.0);
1123  if (_crack_plane_normals.front().absolute_fuzzy_equals(zero_vec, _tol))
1124  {
1125  mooseError("Nodes on crack front are too close to being collinear");
1126  }
1127  }
1128 }
1129 
1132  const RealVectorValue & tangent_direction,
1133  const CRACK_NODE_TYPE ntype,
1134  const std::size_t crack_front_point_index) const
1135 {
1136  RealVectorValue crack_dir;
1137  RealVectorValue zero_vec(0.0);
1138 
1139  bool calc_dir = true;
1141  {
1142  if (ntype == END_1_NODE)
1143  {
1144  crack_dir = _crack_direction_vector_end_1;
1145  calc_dir = false;
1146  }
1147  else if (ntype == END_2_NODE)
1148  {
1149  crack_dir = _crack_direction_vector_end_2;
1150  calc_dir = false;
1151  }
1152  }
1153 
1154  if (calc_dir)
1155  {
1157  {
1158  crack_dir = _crack_direction_vector;
1159  }
1161  {
1162  if (_crack_mouth_coordinates.absolute_fuzzy_equals(crack_front_point, _tol))
1163  {
1164  mooseError("Crack mouth too close to crack front node");
1165  }
1166  RealVectorValue mouth_to_front = crack_front_point - _crack_mouth_coordinates;
1167 
1168  RealVectorValue crack_plane_normal = mouth_to_front.cross(tangent_direction);
1169  if (crack_plane_normal.absolute_fuzzy_equals(zero_vec, _tol))
1170  {
1171  mooseError(
1172  "Vector from crack mouth to crack front node is collinear with crack front segment");
1173  }
1174 
1175  crack_dir = tangent_direction.cross(crack_plane_normal);
1176  Real dotprod = crack_dir * mouth_to_front;
1177  if (dotprod < 0)
1178  {
1179  crack_dir = -crack_dir;
1180  }
1181  }
1183  {
1184  crack_dir = tangent_direction.cross(_crack_plane_normals[crack_front_point_index]);
1185  }
1186  }
1187  crack_dir = crack_dir.unit();
1188 
1189  return crack_dir;
1190 }
1191 
1192 void
1194 {
1195  _num_points_from_provider = num_points;
1196 }
1197 
1198 const Node *
1199 CrackFrontDefinition::getCrackFrontNodePtr(const std::size_t node_index) const
1200 {
1201  mooseAssert(node_index < _ordered_crack_front_nodes.size(), "node_index out of range");
1202  const Node * crack_front_node = _mesh.nodePtr(_ordered_crack_front_nodes[node_index]);
1203  mooseAssert(crack_front_node != nullptr, "invalid crack front node");
1204  return crack_front_node;
1205 }
1206 
1207 const Point *
1208 CrackFrontDefinition::getCrackFrontPoint(const std::size_t point_index) const
1209 {
1211  {
1212  return getCrackFrontNodePtr(point_index);
1213  }
1214  else
1215  {
1216  mooseAssert(point_index < _crack_front_points.size(), "point_index out of range");
1217  return &_crack_front_points[point_index];
1218  }
1219 }
1220 
1221 const RealVectorValue &
1222 CrackFrontDefinition::getCrackFrontTangent(const std::size_t point_index) const
1223 {
1224  mooseAssert(point_index < _tangent_directions.size(), "point_index out of range");
1225  return _tangent_directions[point_index];
1226 }
1227 
1228 const RealVectorValue &
1229 CrackFrontDefinition::getCrackFrontNormal(const std::size_t point_index) const
1230 {
1231  mooseAssert(point_index < _crack_plane_normals.size(), "point_index out of range");
1232  return _crack_plane_normals[point_index];
1233 }
1234 
1235 Real
1237 {
1238  return _segment_lengths[point_index].second;
1239 }
1240 
1241 Real
1243 {
1244  return _segment_lengths[point_index].first;
1245 }
1246 
1247 const RealVectorValue &
1248 CrackFrontDefinition::getCrackDirection(const std::size_t point_index) const
1249 {
1250  return _crack_directions[point_index];
1251 }
1252 
1253 Real
1254 CrackFrontDefinition::getDistanceAlongFront(const std::size_t point_index) const
1255 {
1256  return _distances_along_front[point_index];
1257 }
1258 
1259 bool
1261 {
1262  return (_crack_mouth_boundary_names.size() > 0);
1263 }
1264 
1265 Real
1266 CrackFrontDefinition::getAngleAlongFront(const std::size_t point_index) const
1267 {
1268  if (!hasAngleAlongFront())
1269  paramError(
1270  "crack_mouth_boundary",
1271  "In CrackFrontDefinition, Requested angle along crack front, but definition of crack mouth "
1272  "boundary using 'crack_mouth_boundary' parameter is necessary to do that.");
1273  return _angles_along_front[point_index];
1274 }
1275 
1276 std::size_t
1278 {
1280  return _ordered_crack_front_nodes.size();
1281  else
1282  return _crack_front_points.size();
1283 }
1284 
1287  const std::size_t point_index) const
1288 {
1289  return _rot_matrix[point_index] * vector;
1290 }
1291 
1294  const std::size_t point_index) const
1295 {
1296  RealVectorValue vec = _rot_matrix[point_index].transpose() * vector;
1297  return vec;
1298 }
1299 
1302  const std::size_t point_index) const
1303 {
1304  RankTwoTensor tmp_tensor(tensor);
1305  tmp_tensor.rotate(_rot_matrix[point_index]);
1306  return tmp_tensor;
1307 }
1308 
1309 void
1311  const std::size_t point_index,
1312  Real & r,
1313  Real & theta) const
1314 {
1315  std::size_t num_points = getNumCrackFrontPoints();
1316  Point closest_point(0.0);
1317  RealVectorValue closest_point_to_p;
1318 
1319  const Point * crack_front_point = getCrackFrontPoint(point_index);
1320  RealVectorValue crack_front_point_rot = rotateToCrackFrontCoords(*crack_front_point, point_index);
1321 
1322  RealVectorValue crack_front_edge =
1323  rotateToCrackFrontCoords(_tangent_directions[point_index], point_index);
1324 
1325  Point p_rot = rotateToCrackFrontCoords(qp, point_index);
1326  p_rot = p_rot - crack_front_point_rot;
1327 
1328  if (_treat_as_2d)
1329  {
1330  // In 2D, the closest node is the crack tip node and the position of the crack tip node is
1331  // (0,0,0) in the crack front coordinate system
1332  // In case this is a 3D mesh treated as 2D, project point onto same plane as crack front node.
1333  // Note: In the crack front coordinate system, z is always in the tangent direction to the crack
1334  // front
1335  p_rot(2) = closest_point(2);
1336  closest_point_to_p = p_rot;
1337 
1338  // Find r, the distance between the qp and the crack front
1339  RealVectorValue r_vec = p_rot;
1340  r = r_vec.norm();
1341  }
1342  else
1343  {
1344  // Loop over crack front points to find the one closest to the point qp
1345  Real min_dist = std::numeric_limits<Real>::max();
1346  for (std::size_t pit = 0; pit != num_points; ++pit)
1347  {
1348  const Point * crack_front_point = getCrackFrontPoint(pit);
1349  RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1350  Real dist = crack_point_to_current_point.norm();
1351 
1352  if (dist < min_dist)
1353  {
1354  min_dist = dist;
1355  closest_point = *crack_front_point;
1356  }
1357  }
1358 
1359  // Rotate coordinates to crack front coordinate system
1360  closest_point = rotateToCrackFrontCoords(closest_point, point_index);
1361  closest_point = closest_point - crack_front_point_rot;
1362 
1363  // Find r, the distance between the qp and the crack front
1364  Real edge_length_sq = crack_front_edge.norm_sq();
1365  closest_point_to_p = p_rot - closest_point;
1366  Real perp = crack_front_edge * closest_point_to_p;
1367  Real dist_along_edge = perp / edge_length_sq;
1368  RealVectorValue point_on_edge = closest_point + crack_front_edge * dist_along_edge;
1369  RealVectorValue r_vec = p_rot - point_on_edge;
1370  r = r_vec.norm();
1371  }
1372 
1373  // Find theta, the angle between r and the crack front plane
1374  RealVectorValue crack_plane_normal =
1375  rotateToCrackFrontCoords(_crack_plane_normals[point_index], point_index);
1376  Real p_to_plane_dist = std::abs(closest_point_to_p * crack_plane_normal);
1377 
1378  // Determine if qp is above or below the crack plane
1379  Real y_local = p_rot(1) - closest_point(1);
1380 
1381  // Determine if qp is in front of or behind the crack front
1382  RealVectorValue p2(p_rot);
1383  p2(1) = 0;
1384  RealVectorValue p2_vec = p2 - closest_point;
1385  Real ahead = crack_front_edge(2) * p2_vec(0) - crack_front_edge(0) * p2_vec(2);
1386 
1387  Real x_local(0);
1388  if (ahead >= 0)
1389  x_local = 1;
1390  else
1391  x_local = -1;
1392 
1393  // Calculate theta based on in which quadrant in the crack front coordinate
1394  // system the qp is located
1395  if (r > 0)
1396  {
1397  Real theta_quadrant1(0.0);
1398  if (MooseUtils::absoluteFuzzyEqual(r, p_to_plane_dist, _tol))
1399  theta_quadrant1 = 0.5 * libMesh::pi;
1400  else if (p_to_plane_dist > r)
1401  mooseError(
1402  "Invalid distance p_to_plane_dist in CrackFrontDefinition::calculateRThetaToCrackFront");
1403  else
1404  theta_quadrant1 = std::asin(p_to_plane_dist / r);
1405 
1406  if (x_local >= 0 && y_local >= 0)
1407  theta = theta_quadrant1;
1408 
1409  else if (x_local < 0 && y_local >= 0)
1410  theta = libMesh::pi - theta_quadrant1;
1411 
1412  else if (x_local < 0 && y_local < 0)
1413  theta = -(libMesh::pi - theta_quadrant1);
1414 
1415  else if (x_local >= 0 && y_local < 0)
1416  theta = -theta_quadrant1;
1417  }
1418  else if (r == 0)
1419  theta = 0;
1420  else
1421  mooseError("Invalid distance r in CrackFrontDefinition::calculateRThetaToCrackFront");
1422 }
1423 
1424 std::size_t
1425 CrackFrontDefinition::calculateRThetaToCrackFront(const Point qp, Real & r, Real & theta) const
1426 {
1427  std::size_t num_points = getNumCrackFrontPoints();
1428 
1429  // Loop over crack front points to find the one closest to the point qp
1430  Real min_dist = std::numeric_limits<Real>::max();
1431  std::size_t point_index = 0;
1432  for (std::size_t pit = 0; pit != num_points; ++pit)
1433  {
1434  const Point * crack_front_point = getCrackFrontPoint(pit);
1435  RealVectorValue crack_point_to_current_point = qp - *crack_front_point;
1436  Real dist = crack_point_to_current_point.norm();
1437 
1438  if (dist < min_dist)
1439  {
1440  min_dist = dist;
1441  point_index = pit;
1442  }
1443  }
1444 
1445  calculateRThetaToCrackFront(qp, point_index, r, theta);
1446 
1447  return point_index;
1448 }
1449 
1450 bool
1452 {
1453  bool is_on_boundary = false;
1454  mooseAssert(node, "Invalid node");
1455  dof_id_type node_id = node->id();
1456  for (std::size_t i = 0; i < _intersecting_boundary_ids.size(); ++i)
1457  {
1459  {
1460  is_on_boundary = true;
1461  break;
1462  }
1463  }
1464  return is_on_boundary;
1465 }
1466 
1467 bool
1469 {
1470  bool is_on_boundary = false;
1472  {
1473  const Node * crack_front_node = getCrackFrontNodePtr(point_index);
1474  is_on_boundary = isNodeOnIntersectingBoundary(crack_front_node);
1475  }
1476  else
1477  {
1478  // If the intersecting boundary option is used with crack front points, the
1479  // first and last points are assumed to be on the intersecting boundaries.
1480  std::size_t num_crack_front_points = getNumCrackFrontPoints();
1481  if (point_index == 0 || point_index == num_crack_front_points - 1)
1482  is_on_boundary = true;
1483  }
1484  return is_on_boundary;
1485 }
1486 
1487 void
1489 {
1490  RealVectorValue disp_current_node;
1491  RealVectorValue disp_previous_node;
1492  RealVectorValue disp_next_node;
1493 
1494  RealVectorValue forward_segment0;
1495  RealVectorValue forward_segment1;
1496  Real forward_segment0_len;
1497  Real forward_segment1_len;
1498  RealVectorValue back_segment0;
1499  RealVectorValue back_segment1;
1500  Real back_segment0_len;
1501  Real back_segment1_len;
1502 
1503  std::size_t num_crack_front_nodes = _ordered_crack_front_nodes.size();
1504  const Node * current_node;
1505  const Node * previous_node;
1506  const Node * next_node;
1507 
1508  _strain_along_front.reserve(num_crack_front_nodes);
1509 
1510  // In finalize(), gatherMax builds and distributes the complete strain vector on all processors
1511  // -> reset the vector every time
1512  for (std::size_t i = 0; i < num_crack_front_nodes; ++i)
1513  _strain_along_front[i] = -std::numeric_limits<Real>::max();
1514 
1518 
1519  current_node = getCrackFrontNodePtr(0);
1520  if (current_node->processor_id() == processor_id())
1521  {
1522  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1523  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1524  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1525 
1526  next_node = getCrackFrontNodePtr(1);
1527  disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1528  disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1529  disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1530 
1531  forward_segment0 = *next_node - *current_node;
1532  forward_segment0 = (forward_segment0 * _tangent_directions[0]) * _tangent_directions[0];
1533  forward_segment0_len = forward_segment0.norm();
1534 
1535  forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1536  forward_segment1 = (forward_segment1 * _tangent_directions[0]) * _tangent_directions[0];
1537  forward_segment1_len = forward_segment1.norm();
1538 
1539  _strain_along_front[0] = (forward_segment1_len - forward_segment0_len) / forward_segment0_len;
1540  }
1541 
1542  for (std::size_t i = 1; i < num_crack_front_nodes - 1; ++i)
1543  {
1544  current_node = getCrackFrontNodePtr(i);
1545  if (current_node->processor_id() == processor_id())
1546  {
1547  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1548  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1549  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1550 
1551  previous_node = getCrackFrontNodePtr(i - 1);
1552  disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1553  disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1554  disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1555 
1556  next_node = getCrackFrontNodePtr(i + 1);
1557  disp_next_node(0) = disp_x_var.getNodalValue(*next_node);
1558  disp_next_node(1) = disp_y_var.getNodalValue(*next_node);
1559  disp_next_node(2) = disp_z_var.getNodalValue(*next_node);
1560 
1561  back_segment0 = *current_node - *previous_node;
1562  back_segment0 = (back_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1563  back_segment0_len = back_segment0.norm();
1564 
1565  back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1566  back_segment1 = (back_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1567  back_segment1_len = back_segment1.norm();
1568 
1569  forward_segment0 = *next_node - *current_node;
1570  forward_segment0 = (forward_segment0 * _tangent_directions[i]) * _tangent_directions[i];
1571  forward_segment0_len = forward_segment0.norm();
1572 
1573  forward_segment1 = (*next_node + disp_next_node) - (*current_node + disp_current_node);
1574  forward_segment1 = (forward_segment1 * _tangent_directions[i]) * _tangent_directions[i];
1575  forward_segment1_len = forward_segment1.norm();
1576 
1577  _strain_along_front[i] =
1578  0.5 * ((back_segment1_len - back_segment0_len) / back_segment0_len +
1579  (forward_segment1_len - forward_segment0_len) / forward_segment0_len);
1580  }
1581  }
1582 
1583  current_node = getCrackFrontNodePtr(num_crack_front_nodes - 1);
1584  if (current_node->processor_id() == processor_id())
1585  {
1586  disp_current_node(0) = disp_x_var.getNodalValue(*current_node);
1587  disp_current_node(1) = disp_y_var.getNodalValue(*current_node);
1588  disp_current_node(2) = disp_z_var.getNodalValue(*current_node);
1589 
1590  previous_node = getCrackFrontNodePtr(num_crack_front_nodes - 2);
1591  disp_previous_node(0) = disp_x_var.getNodalValue(*previous_node);
1592  disp_previous_node(1) = disp_y_var.getNodalValue(*previous_node);
1593  disp_previous_node(2) = disp_z_var.getNodalValue(*previous_node);
1594 
1595  back_segment0 = *current_node - *previous_node;
1596  back_segment0 = (back_segment0 * _tangent_directions[num_crack_front_nodes - 1]) *
1597  _tangent_directions[num_crack_front_nodes - 1];
1598  back_segment0_len = back_segment0.norm();
1599 
1600  back_segment1 = (*current_node + disp_current_node) - (*previous_node + disp_previous_node);
1601  back_segment1 = (back_segment1 * _tangent_directions[num_crack_front_nodes - 1]) *
1602  _tangent_directions[num_crack_front_nodes - 1];
1603  back_segment1_len = back_segment1.norm();
1604 
1605  _strain_along_front[num_crack_front_nodes - 1] =
1606  (back_segment1_len - back_segment0_len) / back_segment0_len;
1607  }
1608 }
1609 
1610 Real
1611 CrackFrontDefinition::getCrackFrontTangentialStrain(const std::size_t node_index) const
1612 {
1613  Real strain;
1614  if (_t_stress)
1615  {
1616  strain = _strain_along_front[node_index];
1617  mooseAssert(strain > -std::numeric_limits<Real>::max(),
1618  "Failure in parallel communication of crack tangential strain");
1619  }
1620  else
1621  mooseError("In CrackFrontDefinition, tangential strain not available");
1622 
1623  return strain;
1624 }
1625 
1626 void
1628 {
1629  // In the variable names, "cfn" = crack front node
1630  if (_treat_as_2d && _use_mesh_cutter == false) // 2D: the q-function defines an integral domain
1631  // that is constant along the crack front
1632  {
1633  std::vector<std::vector<const Elem *>> nodes_to_elem_map;
1634  MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1635 
1636  std::set<dof_id_type> nodes_prev_ring;
1637  nodes_prev_ring.insert(_ordered_crack_front_nodes.begin(), _ordered_crack_front_nodes.end());
1638 
1639  std::set<dof_id_type> connected_nodes_this_cfn;
1640  connected_nodes_this_cfn.insert(_ordered_crack_front_nodes.begin(),
1642 
1643  std::set<dof_id_type> old_ring_nodes_this_cfn = connected_nodes_this_cfn;
1644 
1645  // The first ring contains only the crack front node(s)
1646  std::pair<dof_id_type, std::size_t> node_ring_index =
1647  std::make_pair(_ordered_crack_front_nodes[0], 1);
1648  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1649  connected_nodes_this_cfn.end());
1650 
1651  // Build rings of nodes around the crack front node
1652  for (std::size_t ring = 2; ring <= _last_ring; ++ring)
1653  {
1654 
1655  // Find nodes connected to the nodes of the previous ring
1656  std::set<dof_id_type> new_ring_nodes_this_cfn;
1657  for (auto nit = old_ring_nodes_this_cfn.begin(); nit != old_ring_nodes_this_cfn.end(); ++nit)
1658  {
1659  std::vector<const Node *> neighbors;
1660  MeshTools::find_nodal_neighbors(
1661  _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1662  for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1663  {
1664  auto thisit = connected_nodes_this_cfn.find(neighbors[inei]->id());
1665 
1666  // Add only nodes that are not already present in any of the rings
1667  if (thisit == connected_nodes_this_cfn.end())
1668  new_ring_nodes_this_cfn.insert(neighbors[inei]->id());
1669  }
1670  }
1671 
1672  // Add new nodes to rings
1673  connected_nodes_this_cfn.insert(new_ring_nodes_this_cfn.begin(),
1674  new_ring_nodes_this_cfn.end());
1675  old_ring_nodes_this_cfn = new_ring_nodes_this_cfn;
1676 
1677  std::pair<dof_id_type, std::size_t> node_ring_index =
1678  std::make_pair(_ordered_crack_front_nodes[0], ring);
1679  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1680  connected_nodes_this_cfn.end());
1681  }
1682  }
1683  else // The q-function defines one integral domain around each crack front node
1684  {
1685  std::size_t num_crack_front_points = _ordered_crack_front_nodes.size();
1686  std::vector<std::vector<const Elem *>> nodes_to_elem_map;
1687  MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), nodes_to_elem_map);
1688  for (std::size_t icfn = 0; icfn < num_crack_front_points; ++icfn)
1689  {
1690  std::set<dof_id_type> nodes_prev_ring;
1691  nodes_prev_ring.insert(_ordered_crack_front_nodes[icfn]);
1692 
1693  std::set<dof_id_type> connected_nodes_prev_cfn;
1694  std::set<dof_id_type> connected_nodes_this_cfn;
1695  std::set<dof_id_type> connected_nodes_next_cfn;
1696 
1697  connected_nodes_this_cfn.insert(_ordered_crack_front_nodes[icfn]);
1698 
1699  if (_closed_loop && icfn == 0)
1700  {
1701  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[num_crack_front_points - 1]);
1702  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1703  }
1704  else if (_closed_loop && icfn == num_crack_front_points - 1)
1705  {
1706  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1707  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[0]);
1708  }
1709  else if (icfn == 0)
1710  {
1711  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1712  }
1713  else if (icfn == num_crack_front_points - 1)
1714  {
1715  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1716  }
1717  else
1718  {
1719  connected_nodes_prev_cfn.insert(_ordered_crack_front_nodes[icfn - 1]);
1720  connected_nodes_next_cfn.insert(_ordered_crack_front_nodes[icfn + 1]);
1721  }
1722 
1723  std::set<dof_id_type> old_ring_nodes_prev_cfn = connected_nodes_prev_cfn;
1724  std::set<dof_id_type> old_ring_nodes_this_cfn = connected_nodes_this_cfn;
1725  std::set<dof_id_type> old_ring_nodes_next_cfn = connected_nodes_next_cfn;
1726 
1727  // The first ring contains only the crack front node
1728  std::pair<dof_id_type, std::size_t> node_ring_index =
1729  std::make_pair(_ordered_crack_front_nodes[icfn], 1);
1730  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1731  connected_nodes_this_cfn.end());
1732 
1733  // Build rings of nodes around the crack front node
1734  for (std::size_t ring = 2; ring <= _last_ring; ++ring)
1735  {
1736 
1737  // Find nodes connected to the nodes of the previous ring, but exclude nodes in rings of
1738  // neighboring crack front nodes
1739  std::set<dof_id_type> new_ring_nodes_this_cfn;
1740  addNodesToQFunctionRing(new_ring_nodes_this_cfn,
1741  old_ring_nodes_this_cfn,
1742  connected_nodes_this_cfn,
1743  connected_nodes_prev_cfn,
1744  connected_nodes_next_cfn,
1745  nodes_to_elem_map);
1746 
1747  std::set<dof_id_type> new_ring_nodes_prev_cfn;
1748  addNodesToQFunctionRing(new_ring_nodes_prev_cfn,
1749  old_ring_nodes_prev_cfn,
1750  connected_nodes_prev_cfn,
1751  connected_nodes_this_cfn,
1752  connected_nodes_next_cfn,
1753  nodes_to_elem_map);
1754 
1755  std::set<dof_id_type> new_ring_nodes_next_cfn;
1756  addNodesToQFunctionRing(new_ring_nodes_next_cfn,
1757  old_ring_nodes_next_cfn,
1758  connected_nodes_next_cfn,
1759  connected_nodes_prev_cfn,
1760  connected_nodes_this_cfn,
1761  nodes_to_elem_map);
1762 
1763  // Add new nodes to the three sets of nodes
1764  connected_nodes_prev_cfn.insert(new_ring_nodes_prev_cfn.begin(),
1765  new_ring_nodes_prev_cfn.end());
1766  connected_nodes_this_cfn.insert(new_ring_nodes_this_cfn.begin(),
1767  new_ring_nodes_this_cfn.end());
1768  connected_nodes_next_cfn.insert(new_ring_nodes_next_cfn.begin(),
1769  new_ring_nodes_next_cfn.end());
1770  old_ring_nodes_prev_cfn = new_ring_nodes_prev_cfn;
1771  old_ring_nodes_this_cfn = new_ring_nodes_this_cfn;
1772  old_ring_nodes_next_cfn = new_ring_nodes_next_cfn;
1773 
1774  std::pair<dof_id_type, std::size_t> node_ring_index =
1775  std::make_pair(_ordered_crack_front_nodes[icfn], ring);
1776  _crack_front_node_to_node_map[node_ring_index].insert(connected_nodes_this_cfn.begin(),
1777  connected_nodes_this_cfn.end());
1778  }
1779  }
1780  }
1781 }
1782 
1783 void
1785  std::set<dof_id_type> & nodes_new_ring,
1786  const std::set<dof_id_type> & nodes_old_ring,
1787  const std::set<dof_id_type> & nodes_all_rings,
1788  const std::set<dof_id_type> & nodes_neighbor1,
1789  const std::set<dof_id_type> & nodes_neighbor2,
1790  std::vector<std::vector<const Elem *>> & nodes_to_elem_map)
1791 {
1792  for (auto nit = nodes_old_ring.begin(); nit != nodes_old_ring.end(); ++nit)
1793  {
1794  std::vector<const Node *> neighbors;
1795  MeshTools::find_nodal_neighbors(
1796  _mesh.getMesh(), _mesh.nodeRef(*nit), nodes_to_elem_map, neighbors);
1797  for (std::size_t inei = 0; inei < neighbors.size(); ++inei)
1798  {
1799  auto previt = nodes_all_rings.find(neighbors[inei]->id());
1800  auto thisit = nodes_neighbor1.find(neighbors[inei]->id());
1801  auto nextit = nodes_neighbor2.find(neighbors[inei]->id());
1802 
1803  // Add only nodes that are not already present in any of the three sets of nodes
1804  if (previt == nodes_all_rings.end() && thisit == nodes_neighbor1.end() &&
1805  nextit == nodes_neighbor2.end())
1806  nodes_new_ring.insert(neighbors[inei]->id());
1807  }
1808  }
1809 }
1810 
1811 bool
1812 CrackFrontDefinition::isNodeInRing(const std::size_t ring_index,
1813  const dof_id_type connected_node_id,
1814  const std::size_t node_index) const
1815 {
1816  bool is_node_in_ring = false;
1817  std::pair<dof_id_type, std::size_t> node_ring_key =
1818  std::make_pair(_ordered_crack_front_nodes[node_index], ring_index);
1819  auto nnmit = _crack_front_node_to_node_map.find(node_ring_key);
1820 
1821  if (nnmit == _crack_front_node_to_node_map.end())
1822  mooseError("Could not find crack front node ",
1823  _ordered_crack_front_nodes[node_index],
1824  " in the crack front node to q-function ring-node map for ring ",
1825  ring_index);
1826 
1827  std::set<dof_id_type> q_func_nodes = nnmit->second;
1828  if (q_func_nodes.find(connected_node_id) != q_func_nodes.end())
1829  is_node_in_ring = true;
1830 
1831  return is_node_in_ring;
1832 }
1833 
1834 Real
1835 CrackFrontDefinition::DomainIntegralQFunction(std::size_t crack_front_point_index,
1836  std::size_t ring_index,
1837  const Node * const current_node) const
1838 {
1839  Real dist_to_crack_front;
1840  Real dist_along_tangent;
1842  dist_to_crack_front, dist_along_tangent, crack_front_point_index, current_node);
1843 
1844  Real q = 1.0;
1845  if (dist_to_crack_front > _j_integral_radius_inner[ring_index] &&
1846  dist_to_crack_front < _j_integral_radius_outer[ring_index])
1847  q = (_j_integral_radius_outer[ring_index] - dist_to_crack_front) /
1848  (_j_integral_radius_outer[ring_index] - _j_integral_radius_inner[ring_index]);
1849  else if (dist_to_crack_front >= _j_integral_radius_outer[ring_index])
1850  q = 0.0;
1851 
1852  if (q > 0.0)
1853  {
1854  Real tangent_multiplier = 1.0;
1855  if (!_treat_as_2d)
1856  {
1857  const Real forward_segment_length =
1858  getCrackFrontForwardSegmentLength(crack_front_point_index);
1859  const Real backward_segment_length =
1860  getCrackFrontBackwardSegmentLength(crack_front_point_index);
1861 
1862  if (dist_along_tangent >= 0.0)
1863  {
1864  if (forward_segment_length > 0.0)
1865  tangent_multiplier = 1.0 - dist_along_tangent / forward_segment_length;
1866  }
1867  else
1868  {
1869  if (backward_segment_length > 0.0)
1870  tangent_multiplier = 1.0 + dist_along_tangent / backward_segment_length;
1871  }
1872  }
1873 
1874  tangent_multiplier = std::max(tangent_multiplier, 0.0);
1875  tangent_multiplier = std::min(tangent_multiplier, 1.0);
1876 
1877  // Set to zero if a node is on a designated free surface and its crack front node is not.
1878  if (isNodeOnIntersectingBoundary(current_node) &&
1879  !_is_point_on_intersecting_boundary[crack_front_point_index])
1880  tangent_multiplier = 0.0;
1881 
1882  q *= tangent_multiplier;
1883  }
1884 
1885  return q;
1886 }
1887 
1888 Real
1890  std::size_t ring_index,
1891  const Node * const current_node) const
1892 {
1893  Real q = 0;
1894  bool is_node_in_ring = isNodeInRing(ring_index, current_node->id(), crack_front_point_index);
1895  if (is_node_in_ring)
1896  q = 1;
1897 
1898  return q;
1899 }
1900 
1901 void
1903  Real & dist_along_tangent,
1904  std::size_t crack_front_point_index,
1905  const Node * const current_node) const
1906 {
1907  const Point * crack_front_point = getCrackFrontPoint(crack_front_point_index);
1908 
1909  Point p = *current_node;
1910  const RealVectorValue & crack_front_tangent = getCrackFrontTangent(crack_front_point_index);
1911 
1912  RealVectorValue crack_node_to_current_node = p - *crack_front_point;
1913  dist_along_tangent = crack_node_to_current_node * crack_front_tangent;
1914  RealVectorValue projection_point = *crack_front_point + dist_along_tangent * crack_front_tangent;
1915  RealVectorValue axis_to_current_node = p - projection_point;
1916  dist_to_front = axis_to_current_node.norm();
1917 }
1918 
1919 void
1920 CrackFrontDefinition::isCutterModified(const bool is_cutter_modified)
1921 {
1922  _is_cutter_modified = is_cutter_modified;
1923 }
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.
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 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