LCOV - code coverage report
Current view: top level - src/userobjects - CrackMeshCut3DUserObject.C (source / functions) Hit Total Coverage
Test: idaholab/moose xfem: #31730 (e8b711) with base e0c998 Lines: 512 612 83.7 %
Date: 2025-10-29 16:56:32 Functions: 25 30 83.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "CrackMeshCut3DUserObject.h"
      11             : 
      12             : #include "XFEMFuncs.h"
      13             : #include "MooseError.h"
      14             : #include "libmesh/string_to_enum.h"
      15             : #include "MooseMesh.h"
      16             : #include "MooseEnum.h"
      17             : #include "libmesh/face_tri3.h"
      18             : #include "libmesh/edge_edge2.h"
      19             : #include "libmesh/serial_mesh.h"
      20             : #include "libmesh/plane.h"
      21             : #include "libmesh/mesh_tools.h"
      22             : #include "Function.h"
      23             : 
      24             : registerMooseObject("XFEMApp", CrackMeshCut3DUserObject);
      25             : 
      26             : InputParameters
      27          40 : CrackMeshCut3DUserObject::validParams()
      28             : {
      29          40 :   InputParameters params = MeshCutUserObjectBase::validParams();
      30          80 :   MooseEnum growthDirection("MAX_HOOP_STRESS FUNCTION", "FUNCTION");
      31          80 :   params.addParam<MooseEnum>(
      32             :       "growth_dir_method", growthDirection, "choose from FUNCTION, MAX_HOOP_STRESS");
      33          80 :   MooseEnum growthRate("REPORTER FUNCTION", "FUNCTION");
      34          80 :   params.addParam<MooseEnum>(
      35             :       "growth_increment_method", growthRate, "choose from FUNCTION, REPORTER");
      36          80 :   params.addParam<FunctionName>("growth_direction_x",
      37             :                                 "Function defining x-component of crack growth direction");
      38          80 :   params.addParam<FunctionName>("growth_direction_y",
      39             :                                 "Function defining y-component of crack growth direction");
      40          80 :   params.addParam<FunctionName>("growth_direction_z",
      41             :                                 "Function defining z-component of crack growth direction");
      42             : 
      43          80 :   params.addParam<VectorPostprocessorName>(
      44             :       "ki_vectorpostprocessor", "II_KI_1", "Name of the VectorPostprocessor that computes K_I");
      45          80 :   params.addParam<VectorPostprocessorName>("kii_vectorpostprocessor",
      46             :                                            "II_KII_1",
      47             :                                            "The name of the vectorpostprocessor that contains KII");
      48          80 :   params.addParam<ReporterName>("growth_reporter",
      49             :                                 "The name of the Reporter that computes the growth increment");
      50          80 :   params.addParam<FunctionName>("growth_rate", "Function defining crack growth rate");
      51          80 :   params.addParam<Real>(
      52          80 :       "size_control", 0, "Criterion for refining elements while growing the crack");
      53          80 :   params.addParam<unsigned int>("n_step_growth", 0, "Number of steps for crack growth");
      54          80 :   params.addParam<std::vector<dof_id_type>>("crack_front_nodes",
      55             :                                             "Set of nodes to define crack front");
      56          40 :   params.addClassDescription("Creates a UserObject for a mesh cutter in 3D problems");
      57          40 :   return params;
      58          40 : }
      59             : 
      60             : // This code does not allow predefined crack growth as a function of time
      61             : // all inital cracks are defined at t_start = t_end = 0
      62          20 : CrackMeshCut3DUserObject::CrackMeshCut3DUserObject(const InputParameters & parameters)
      63             :   : MeshCutUserObjectBase(parameters),
      64          20 :     _mesh(_subproblem.mesh()),
      65          40 :     _growth_dir_method(getParam<MooseEnum>("growth_dir_method").getEnum<GrowthDirectionEnum>()),
      66          20 :     _growth_increment_method(
      67          20 :         getParam<MooseEnum>("growth_increment_method").getEnum<GrowthRateEnum>()),
      68          20 :     _n_step_growth(getParam<unsigned int>("n_step_growth")),
      69          20 :     _is_mesh_modified(false),
      70          28 :     _func_x(parameters.isParamValid("growth_direction_x") ? &getFunction("growth_direction_x")
      71             :                                                           : nullptr),
      72          28 :     _func_y(parameters.isParamValid("growth_direction_y") ? &getFunction("growth_direction_y")
      73             :                                                           : nullptr),
      74          28 :     _func_z(parameters.isParamValid("growth_direction_z") ? &getFunction("growth_direction_z")
      75             :                                                           : nullptr),
      76          28 :     _func_v(parameters.isParamValid("growth_rate") ? &getFunction("growth_rate") : nullptr),
      77          40 :     _ki_vpp((_growth_dir_method == GrowthDirectionEnum::MAX_HOOP_STRESS)
      78          32 :                 ? &getVectorPostprocessorValue(
      79             :                       "ki_vectorpostprocessor",
      80          32 :                       getParam<VectorPostprocessorName>("ki_vectorpostprocessor"))
      81             :                 : nullptr),
      82          40 :     _kii_vpp((_growth_dir_method == GrowthDirectionEnum::MAX_HOOP_STRESS)
      83          32 :                  ? &getVectorPostprocessorValue(
      84             :                        "kii_vectorpostprocessor",
      85          32 :                        getParam<VectorPostprocessorName>("kii_vectorpostprocessor"))
      86             :                  : nullptr),
      87          40 :     _growth_inc_reporter((_growth_increment_method == GrowthRateEnum::REPORTER)
      88          44 :                              ? &getReporterValueByName<std::vector<Real>>(
      89             :                                    getParam<ReporterName>("growth_reporter"), REPORTER_MODE_ROOT)
      90          40 :                              : nullptr)
      91             : {
      92          20 :   _grow = (_n_step_growth == 0 ? 0 : 1);
      93             : 
      94          20 :   if (_grow)
      95             :   {
      96          40 :     if (!isParamValid("size_control"))
      97           0 :       paramError("size_control", "Crack growth needs size control.");
      98             : 
      99          40 :     _size_control = getParam<Real>("size_control");
     100             : 
     101          20 :     if (_growth_dir_method == GrowthDirectionEnum::FUNCTION &&
     102           8 :         (_func_x == nullptr || _func_y == nullptr || _func_z == nullptr))
     103           0 :       mooseError("function is not specified for the function method that defines growth direction");
     104             : 
     105          40 :     if (isParamValid("crack_front_nodes"))
     106             :     {
     107          48 :       _tracked_crack_front_points = getParam<std::vector<dof_id_type>>("crack_front_nodes");
     108          16 :       _num_crack_front_points = _tracked_crack_front_points.size();
     109          16 :       _crack_front_points = _tracked_crack_front_points;
     110          16 :       _cfd = true;
     111             :     }
     112             :     else
     113           4 :       _cfd = false;
     114             :   }
     115             : 
     116          20 :   if ((_growth_dir_method == GrowthDirectionEnum::MAX_HOOP_STRESS ||
     117           8 :        _growth_increment_method == GrowthRateEnum::REPORTER) &&
     118          16 :       !_cfd)
     119           0 :     paramError("crack_front_nodes",
     120             :                "Required for any crack growth rate or direction criterion that requires fracture "
     121             :                "integrals.");
     122             : 
     123             :   // test element type; only tri3 elements are allowed
     124         280 :   for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
     125             :   {
     126         120 :     if (cut_elem->n_nodes() != _cut_elem_nnode)
     127           0 :       mooseError("The input cut mesh should include tri elements only!");
     128         120 :     if (cut_elem->dim() != _cut_elem_dim)
     129           0 :       mooseError("The input cut mesh must be 2D elements only!");
     130          20 :   }
     131          20 : }
     132             : 
     133             : void
     134          20 : CrackMeshCut3DUserObject::initialSetup()
     135             : {
     136          20 :   if (_cfd)
     137          16 :     _crack_front_definition =
     138          32 :         &_fe_problem.getUserObject<CrackFrontDefinition>("crackFrontDefinition");
     139             : 
     140          20 :   if (_grow)
     141             :   {
     142          20 :     findBoundaryNodes();
     143          20 :     findBoundaryEdges();
     144          20 :     sortBoundaryNodes();
     145             :   }
     146          20 : }
     147             : 
     148             : void
     149         125 : CrackMeshCut3DUserObject::initialize()
     150             : {
     151         125 :   _is_mesh_modified = false;
     152             : 
     153         125 :   if (_grow)
     154             :   {
     155         125 :     if (_t_step == 1)
     156          21 :       _last_step_initialized = 1;
     157             : 
     158         125 :     _stop = 0;
     159             : 
     160         125 :     if (_t_step > 1 && _t_step != _last_step_initialized)
     161             :     {
     162          80 :       _last_step_initialized = _t_step;
     163             : 
     164         160 :       for (unsigned int i = 0; i < _n_step_growth; ++i)
     165             :       {
     166          80 :         if (_stop != 1)
     167             :         {
     168          80 :           findActiveBoundaryNodes();
     169          80 :           findActiveBoundaryDirection();
     170          80 :           _is_mesh_modified = true;
     171          80 :           growFront();
     172          80 :           sortFrontNodes();
     173          80 :           if (_inactive_boundary_pos.size() != 0)
     174          80 :             findFrontIntersection();
     175          80 :           refineFront();
     176          80 :           triangulation();
     177          80 :           joinBoundary();
     178             :         }
     179             :       }
     180             :     }
     181             :   }
     182         125 :   if (_cfd)
     183         104 :     _crack_front_definition->isCutterModified(_is_mesh_modified);
     184         125 : }
     185             : 
     186             : bool
     187           0 : CrackMeshCut3DUserObject::cutElementByGeometry(const Elem * /*elem*/,
     188             :                                                std::vector<Xfem::CutEdge> & /*cut_edges*/,
     189             :                                                std::vector<Xfem::CutNode> & /*cut_nodes*/) const
     190             : {
     191           0 :   mooseError("invalid method for 3D mesh cutting");
     192             :   return false;
     193             : }
     194             : 
     195             : bool
     196        5088 : CrackMeshCut3DUserObject::cutElementByGeometry(const Elem * elem,
     197             :                                                std::vector<Xfem::CutFace> & cut_faces) const
     198             : // With the crack defined by a planar mesh, this method cuts a solid element by all elements in the
     199             : // planar mesh
     200             : // TODO: Time evolving cuts not yet supported in 3D (hence the lack of use of the time variable)
     201             : {
     202             :   bool elem_cut = false;
     203             : 
     204        5088 :   if (elem->dim() != _elem_dim)
     205           0 :     mooseError("The structural mesh to be cut by a surface mesh must be 3D!");
     206             : 
     207       35616 :   for (unsigned int i = 0; i < elem->n_sides(); ++i)
     208             :   {
     209             :     // This returns the lowest-order type of side.
     210       30528 :     std::unique_ptr<const Elem> curr_side = elem->side_ptr(i);
     211       30528 :     if (curr_side->dim() != 2)
     212           0 :       mooseError("In cutElementByGeometry dimension of side must be 2, but it is ",
     213           0 :                  curr_side->dim());
     214       30528 :     unsigned int n_edges = curr_side->n_sides();
     215             : 
     216             :     std::vector<unsigned int> cut_edges;
     217             :     std::vector<Real> cut_pos;
     218             : 
     219      152640 :     for (unsigned int j = 0; j < n_edges; j++)
     220             :     {
     221             :       // This returns the lowest-order type of side.
     222      122112 :       std::unique_ptr<const Elem> curr_edge = curr_side->side_ptr(j);
     223      122112 :       if (curr_edge->type() != EDGE2)
     224           0 :         mooseError("In cutElementByGeometry face edge must be EDGE2, but type is: ",
     225           0 :                    libMesh::Utility::enum_to_string(curr_edge->type()),
     226             :                    " base element type is: ",
     227           0 :                    libMesh::Utility::enum_to_string(elem->type()));
     228             :       const Node * node1 = curr_edge->node_ptr(0);
     229             :       const Node * node2 = curr_edge->node_ptr(1);
     230             : 
     231     5302080 :       for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
     232             :       {
     233             :         std::vector<Point> vertices;
     234             : 
     235    10115712 :         for (auto & node : cut_elem->node_ref_range())
     236             :         {
     237     7586784 :           Point & this_point = node;
     238     7586784 :           vertices.push_back(this_point);
     239             :         }
     240             : 
     241             :         Point intersection;
     242     2528928 :         if (intersectWithEdge(*node1, *node2, vertices, intersection))
     243             :         {
     244        6888 :           cut_edges.push_back(j);
     245        6888 :           cut_pos.emplace_back(getRelativePosition(*node1, *node2, intersection));
     246             :         }
     247     2651040 :       }
     248      122112 :     }
     249             : 
     250             :     // if two edges of an element are cut, it is considered as an element being cut
     251       30528 :     if (cut_edges.size() == 2)
     252             :     {
     253             :       elem_cut = true;
     254             :       Xfem::CutFace mycut;
     255        3254 :       mycut._face_id = i;
     256        3254 :       mycut._face_edge.push_back(cut_edges[0]);
     257        3254 :       mycut._face_edge.push_back(cut_edges[1]);
     258        3254 :       mycut._position.push_back(cut_pos[0]);
     259        3254 :       mycut._position.push_back(cut_pos[1]);
     260        3254 :       cut_faces.push_back(mycut);
     261             :     }
     262       30528 :   }
     263        5088 :   return elem_cut;
     264             : }
     265             : 
     266             : bool
     267           0 : CrackMeshCut3DUserObject::cutFragmentByGeometry(std::vector<std::vector<Point>> & /*frag_edges*/,
     268             :                                                 std::vector<Xfem::CutEdge> & /*cut_edges*/) const
     269             : {
     270           0 :   mooseError("invalid method for 3D mesh cutting");
     271             :   return false;
     272             : }
     273             : 
     274             : bool
     275           0 : CrackMeshCut3DUserObject::cutFragmentByGeometry(std::vector<std::vector<Point>> & /*frag_faces*/,
     276             :                                                 std::vector<Xfem::CutFace> & /*cut_faces*/) const
     277             : {
     278             :   // TODO: Need this for branching in 3D
     279           0 :   mooseError("cutFragmentByGeometry not yet implemented for 3D mesh cutting");
     280             :   return false;
     281             : }
     282             : 
     283             : bool
     284     2528928 : CrackMeshCut3DUserObject::intersectWithEdge(const Point & p1,
     285             :                                             const Point & p2,
     286             :                                             const std::vector<Point> & vertices,
     287             :                                             Point & pint) const
     288             : {
     289             :   bool has_intersection = false;
     290             : 
     291     2528928 :   Plane elem_plane(vertices[0], vertices[1], vertices[2]);
     292     2528928 :   Point point = vertices[0];
     293     2528928 :   Point normal = elem_plane.unit_normal(point);
     294             : 
     295     2528928 :   std::array<Real, 3> plane_point = {{point(0), point(1), point(2)}};
     296     2528928 :   std::array<Real, 3> planenormal = {{normal(0), normal(1), normal(2)}};
     297     2528928 :   std::array<Real, 3> edge_point1 = {{p1(0), p1(1), p1(2)}};
     298     2528928 :   std::array<Real, 3> edge_point2 = {{p2(0), p2(1), p2(2)}};
     299     2528928 :   std::array<Real, 3> cut_point = {{0.0, 0.0, 0.0}};
     300             : 
     301     2528928 :   if (Xfem::plane_normal_line_exp_int_3d(
     302             :           &plane_point[0], &planenormal[0], &edge_point1[0], &edge_point2[0], &cut_point[0]) == 1)
     303             :   {
     304     1840032 :     Point temp_p(cut_point[0], cut_point[1], cut_point[2]);
     305     1840032 :     if (isInsideCutPlane(vertices, temp_p) && isInsideEdge(p1, p2, temp_p))
     306             :     {
     307        6888 :       pint = temp_p;
     308             :       has_intersection = true;
     309             :     }
     310             :   }
     311     2528928 :   return has_intersection;
     312     2528928 : }
     313             : 
     314             : bool
     315       15408 : CrackMeshCut3DUserObject::findIntersection(const Point & p1,
     316             :                                            const Point & p2,
     317             :                                            const std::vector<Point> & vertices,
     318             :                                            Point & pint) const
     319             : {
     320             :   bool has_intersection = false;
     321             : 
     322       15408 :   Plane elem_plane(vertices[0], vertices[1], vertices[2]);
     323       15408 :   Point point = vertices[0];
     324       15408 :   Point normal = elem_plane.unit_normal(point);
     325             : 
     326       15408 :   std::array<Real, 3> plane_point = {{point(0), point(1), point(2)}};
     327       15408 :   std::array<Real, 3> planenormal = {{normal(0), normal(1), normal(2)}};
     328       15408 :   std::array<Real, 3> p_begin = {{p1(0), p1(1), p1(2)}};
     329       15408 :   std::array<Real, 3> p_end = {{p2(0), p2(1), p2(2)}};
     330       15408 :   std::array<Real, 3> cut_point = {{0.0, 0.0, 0.0}};
     331             : 
     332       15408 :   if (Xfem::plane_normal_line_exp_int_3d(
     333             :           &plane_point[0], &planenormal[0], &p_begin[0], &p_end[0], &cut_point[0]) == 1)
     334             :   {
     335       14448 :     Point p(cut_point[0], cut_point[1], cut_point[2]);
     336       14448 :     Real dotp = ((p - p1) * (p2 - p1)) / ((p2 - p1) * (p2 - p1));
     337       14448 :     if (isInsideCutPlane(vertices, p) && dotp > 1)
     338             :     {
     339         160 :       pint = p;
     340             :       has_intersection = true;
     341             :     }
     342             :   }
     343       15408 :   return has_intersection;
     344       15408 : }
     345             : 
     346             : bool
     347       23624 : CrackMeshCut3DUserObject::isInsideEdge(const Point & p1, const Point & p2, const Point & p) const
     348             : {
     349             :   Real dotp1 = (p1 - p) * (p2 - p1);
     350             :   Real dotp2 = (p2 - p) * (p2 - p1);
     351       23624 :   return (dotp1 * dotp2 <= 0.0);
     352             : }
     353             : 
     354             : Real
     355        6888 : CrackMeshCut3DUserObject::getRelativePosition(const Point & p1,
     356             :                                               const Point & p2,
     357             :                                               const Point & p) const
     358             : {
     359        6888 :   Real full_len = (p2 - p1).norm();
     360        6888 :   Real len_p1_p = (p - p1).norm();
     361        6888 :   return len_p1_p / full_len;
     362             : }
     363             : 
     364             : bool
     365     1854480 : CrackMeshCut3DUserObject::isInsideCutPlane(const std::vector<Point> & vertices,
     366             :                                            const Point & p) const
     367             : {
     368     1854480 :   unsigned int n_node = vertices.size();
     369             : 
     370     1854480 :   Plane elem_plane(vertices[0], vertices[1], vertices[2]);
     371     1854480 :   Point normal = elem_plane.unit_normal(vertices[0]);
     372             : 
     373             :   bool inside = false;
     374             :   unsigned int counter = 0;
     375             : 
     376     7432368 :   for (unsigned int i = 0; i < n_node; ++i)
     377             :   {
     378     5577888 :     unsigned int iplus1 = (i < n_node - 1 ? i + 1 : 0);
     379     5577888 :     Point middle2p = p - 0.5 * (vertices[i] + vertices[iplus1]);
     380             :     const Point side_tang = vertices[iplus1] - vertices[i];
     381     5577888 :     Point side_norm = side_tang.cross(normal);
     382     5577888 :     Xfem::normalizePoint(middle2p);
     383     5577888 :     Xfem::normalizePoint(side_norm);
     384     5577888 :     if (middle2p * side_norm <= 0.0)
     385     3073368 :       counter += 1;
     386             :   }
     387     1854480 :   if (counter == n_node)
     388             :     inside = true;
     389     1854480 :   return inside;
     390     1854480 : }
     391             : 
     392             : void
     393          20 : CrackMeshCut3DUserObject::findBoundaryNodes()
     394             : {
     395          20 :   auto boundary_node_ids = MeshTools::find_boundary_nodes(*_cutter_mesh);
     396         180 :   for (auto it = boundary_node_ids.cbegin(); it != boundary_node_ids.cend(); it++)
     397             :   {
     398         160 :     dof_id_type id = *it;
     399             :     std::vector<dof_id_type> neighbors;
     400         160 :     _boundary_map[id] = neighbors;
     401         160 :   }
     402          20 : }
     403             : 
     404             : void
     405          20 : CrackMeshCut3DUserObject::findBoundaryEdges()
     406             : {
     407             :   _boundary_edges.clear();
     408             : 
     409             :   std::vector<dof_id_type> corner_elem_id;
     410             :   unsigned int counter = 0;
     411             : 
     412          20 :   std::vector<dof_id_type> node_id(_cut_elem_nnode);
     413          20 :   std::vector<bool> is_node_on_boundary(_cut_elem_nnode);
     414             : 
     415         160 :   for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
     416             :   {
     417         480 :     for (unsigned int i = 0; i < _cut_elem_nnode; ++i)
     418             :     {
     419         360 :       node_id[i] = cut_elem->node_ptr(i)->id();
     420         360 :       is_node_on_boundary[i] = (_boundary_map.find(node_id[i]) != _boundary_map.end());
     421             :     }
     422             : 
     423         120 :     if (is_node_on_boundary[0] && is_node_on_boundary[1] && is_node_on_boundary[2])
     424             :     {
     425             :       // this is an element at the corner; all nodes are on the boundary but not all edges are on
     426             :       // the boundary
     427         120 :       corner_elem_id.push_back(counter);
     428             :     }
     429             :     else
     430             :     {
     431             :       // for other elements, find and store boundary edges
     432           0 :       for (unsigned int i = 0; i < _cut_elem_nnode; ++i)
     433             :       {
     434             :         // if both nodes on an edge are on the boundary, it is a boundary edge.
     435           0 :         if (is_node_on_boundary[i] && is_node_on_boundary[(i + 1 <= 2) ? i + 1 : 0])
     436             :         {
     437           0 :           dof_id_type node1 = node_id[i];
     438           0 :           dof_id_type node2 = node_id[(i + 1 <= 2) ? i + 1 : 0];
     439           0 :           if (node1 > node2)
     440             :             std::swap(node1, node2);
     441             : 
     442             :           Xfem::CutEdge ce;
     443             : 
     444           0 :           if (node1 > node2)
     445             :             std::swap(node1, node2);
     446           0 :           ce._id1 = node1;
     447           0 :           ce._id2 = node2;
     448             : 
     449           0 :           _boundary_edges.insert(ce);
     450             :         }
     451             :       }
     452             :     }
     453         120 :     ++counter;
     454          20 :   }
     455             : 
     456             :   // loop over edges in corner elements
     457             :   // if an edge is shared by two elements, it is not an boundary edge (is_edge_inside = 1)
     458         140 :   for (unsigned int i = 0; i < corner_elem_id.size(); ++i)
     459             :   {
     460         120 :     auto elem_it = _cutter_mesh->elements_begin();
     461             : 
     462         420 :     for (dof_id_type j = 0; j < corner_elem_id[i]; ++j)
     463         300 :       ++elem_it;
     464         120 :     Elem * cut_elem = *elem_it;
     465             : 
     466         480 :     for (unsigned int j = 0; j < _cut_elem_nnode; ++j)
     467             :     {
     468             :       bool is_edge_inside = 0;
     469             : 
     470             :       dof_id_type node1 = cut_elem->node_ptr(j)->id();
     471         360 :       dof_id_type node2 = cut_elem->node_ptr((j + 1 <= 2) ? j + 1 : 0)->id();
     472         360 :       if (node1 > node2)
     473             :         std::swap(node1, node2);
     474             : 
     475             :       unsigned int counter = 0;
     476        2220 :       for (const auto & cut_elem2 : _cutter_mesh->element_ptr_range())
     477             :       {
     478        1700 :         if (counter != corner_elem_id[i])
     479             :         {
     480        5380 :           for (unsigned int k = 0; k < _cut_elem_nnode; ++k)
     481             :           {
     482        4140 :             dof_id_type node3 = cut_elem2->node_ptr(k)->id();
     483        4140 :             dof_id_type node4 = cut_elem2->node_ptr((k + 1 <= 2) ? k + 1 : 0)->id();
     484        4140 :             if (node3 > node4)
     485             :               std::swap(node3, node4);
     486             : 
     487        4140 :             if (node1 == node3 && node2 == node4)
     488             :             {
     489             :               is_edge_inside = 1;
     490         200 :               goto endloop;
     491             :             }
     492             :           }
     493             :         }
     494        1500 :         ++counter;
     495         360 :       }
     496             :     endloop:
     497             :       if (is_edge_inside == 0)
     498             :       {
     499             :         // store boundary edges
     500             :         Xfem::CutEdge ce;
     501             : 
     502         160 :         if (node1 > node2)
     503             :           std::swap(node1, node2);
     504         160 :         ce._id1 = node1;
     505         160 :         ce._id2 = node2;
     506             : 
     507         160 :         _boundary_edges.insert(ce);
     508             :       }
     509             :       else
     510             :       {
     511             :         // this is not a boundary edge; remove it from existing edge list
     512        1140 :         for (auto it = _boundary_edges.begin(); it != _boundary_edges.end();)
     513             :         {
     514         940 :           if ((*it)._id1 == node1 && (*it)._id2 == node2)
     515           0 :             it = _boundary_edges.erase(it);
     516             :           else
     517             :             ++it;
     518             :         }
     519             :       }
     520             :     }
     521             :   }
     522          20 : }
     523             : 
     524             : void
     525          20 : CrackMeshCut3DUserObject::sortBoundaryNodes()
     526             : {
     527          20 :   _boundary.clear();
     528             : 
     529         180 :   for (auto it = _boundary_edges.begin(); it != _boundary_edges.end(); ++it)
     530             :   {
     531         160 :     dof_id_type node1 = (*it)._id1;
     532         160 :     dof_id_type node2 = (*it)._id2;
     533             : 
     534             :     mooseAssert(_boundary_map.find(node1) != _boundary_map.end(),
     535             :                 "_boundary_map does not have this key");
     536             :     mooseAssert(_boundary_map.find(node2) != _boundary_map.end(),
     537             :                 "_boundary_map does not have this key");
     538             : 
     539         160 :     _boundary_map.find(node1)->second.push_back(node2);
     540         160 :     _boundary_map.find(node2)->second.push_back(node1);
     541             :   }
     542             : 
     543             :   auto it = _boundary_map.begin();
     544         180 :   while (it != _boundary_map.end())
     545             :   {
     546         160 :     if (it->second.size() != 2)
     547           0 :       mooseError(
     548             :           "Boundary nodes in the cutter mesh must have exactly two neighbors; this one has: ",
     549           0 :           it->second.size());
     550             :     ++it;
     551             :   }
     552             : 
     553             :   auto it2 = _boundary_edges.begin();
     554          20 :   dof_id_type node1 = (*it2)._id1;
     555          20 :   dof_id_type node2 = (*it2)._id2;
     556          20 :   _boundary.push_back(node1);
     557          20 :   _boundary.push_back(node2);
     558             : 
     559         160 :   for (unsigned int i = 0; i < _boundary_edges.size() - 1; ++i)
     560             :   {
     561             :     mooseAssert(_boundary_map.find(node2) != _boundary_map.end(),
     562             :                 "_boundary_map does not have this key");
     563             : 
     564         140 :     dof_id_type node3 = _boundary_map.find(node2)->second[0];
     565         140 :     dof_id_type node4 = _boundary_map.find(node2)->second[1];
     566             : 
     567         140 :     if (node3 == node1)
     568             :     {
     569          80 :       _boundary.push_back(node4);
     570          80 :       node1 = node2;
     571          80 :       node2 = node4;
     572             :     }
     573          60 :     else if (node4 == node1)
     574             :     {
     575          60 :       _boundary.push_back(node3);
     576          60 :       node1 = node2;
     577          60 :       node2 = node3;
     578             :     }
     579             :     else
     580           0 :       mooseError("Discontinuity in cutter boundary");
     581             :   }
     582          20 : }
     583             : 
     584             : Real
     585        1040 : CrackMeshCut3DUserObject::findDistance(dof_id_type node1, dof_id_type node2)
     586             : {
     587        1040 :   Node * n1 = _cutter_mesh->node_ptr(node1);
     588             :   mooseAssert(n1 != nullptr, "Node is NULL");
     589        1040 :   Node * n2 = _cutter_mesh->node_ptr(node2);
     590             :   mooseAssert(n2 != nullptr, "Node is NULL");
     591        1040 :   Real distance = (*n1 - *n2).norm();
     592        1040 :   return distance;
     593             : }
     594             : 
     595             : void
     596           0 : CrackMeshCut3DUserObject::refineBoundary()
     597             : {
     598           0 :   std::vector<dof_id_type> new_boundary_order(_boundary.begin(), _boundary.end());
     599             : 
     600             :   mooseAssert(_boundary.size() >= 2, "Boundary must be at least two nodes");
     601             : 
     602           0 :   for (unsigned int i = _boundary.size() - 1; i >= 1; --i)
     603             :   {
     604           0 :     dof_id_type node1 = _boundary[i - 1];
     605           0 :     dof_id_type node2 = _boundary[i];
     606             : 
     607           0 :     Real distance = findDistance(node1, node2);
     608             : 
     609           0 :     if (distance > _size_control)
     610             :     {
     611           0 :       unsigned int n = static_cast<unsigned int>(distance / _size_control);
     612             :       std::array<Real, 3> x1;
     613             :       std::array<Real, 3> x2;
     614             : 
     615           0 :       Node * n1 = _cutter_mesh->node_ptr(node1);
     616             :       mooseAssert(n1 != nullptr, "Node is NULL");
     617             :       Point & p1 = *n1;
     618           0 :       Node * n2 = _cutter_mesh->node_ptr(node2);
     619             :       mooseAssert(n2 != nullptr, "Node is NULL");
     620             :       Point & p2 = *n2;
     621             : 
     622           0 :       for (unsigned int j = 0; j < 3; ++j)
     623             :       {
     624           0 :         x1[j] = p1(j);
     625           0 :         x2[j] = p2(j);
     626             :       }
     627             : 
     628           0 :       for (unsigned int j = 0; j < n; ++j)
     629             :       {
     630             :         Point x;
     631           0 :         for (unsigned int k = 0; k < 3; ++k)
     632           0 :           x(k) = x2[k] - (x2[k] - x1[k]) * (j + 1) / (n + 1);
     633             : 
     634           0 :         Node * this_node = Node::build(x, _cutter_mesh->n_nodes()).release();
     635           0 :         _cutter_mesh->add_node(this_node);
     636             : 
     637           0 :         dof_id_type id = _cutter_mesh->n_nodes() - 1;
     638             :         auto it = new_boundary_order.begin();
     639           0 :         new_boundary_order.insert(it + i, id);
     640             :       }
     641             :     }
     642             :   }
     643             : 
     644           0 :   _boundary = new_boundary_order;
     645             :   mooseAssert(_boundary.size() > 0, "Boundary should not have zero size");
     646             :   _boundary.pop_back();
     647           0 : }
     648             : 
     649             : void
     650         140 : CrackMeshCut3DUserObject::findActiveBoundaryNodes()
     651             : {
     652         140 :   _active_boundary.clear();
     653         140 :   _inactive_boundary_pos.clear();
     654             : 
     655         140 :   std::unique_ptr<PointLocatorBase> pl = _mesh.getPointLocator();
     656         140 :   pl->enable_out_of_mesh_mode();
     657             : 
     658         140 :   unsigned int n_boundary = _boundary.size();
     659             : 
     660             :   // if the node is outside of the structural model, store its position in _boundary to
     661             :   // _inactive_boundary_pos
     662        1916 :   for (unsigned int j = 0; j < n_boundary; ++j)
     663             :   {
     664        1776 :     Node * this_node = _cutter_mesh->node_ptr(_boundary[j]);
     665             :     mooseAssert(this_node, "Node is NULL");
     666        1776 :     Point & this_point = *this_node;
     667             : 
     668        1776 :     const Elem * elem = (*pl)(this_point);
     669        1776 :     if (elem == nullptr)
     670        1496 :       _inactive_boundary_pos.push_back(j);
     671             :   }
     672             : 
     673         140 :   unsigned int n_inactive_boundary = _inactive_boundary_pos.size();
     674             : 
     675             :   // all nodes are inactive, stop
     676         140 :   if (n_inactive_boundary == n_boundary)
     677           0 :     _stop = 1;
     678             : 
     679             :   // find and store active boundary segments in "_active_boundary"
     680         140 :   if (n_inactive_boundary == 0)
     681           0 :     _active_boundary.push_back(_boundary);
     682             :   else
     683             :   {
     684        1496 :     for (unsigned int i = 0; i < n_inactive_boundary - 1; ++i)
     685             :     {
     686        1356 :       if (_inactive_boundary_pos[i + 1] - _inactive_boundary_pos[i] != 1)
     687             :       {
     688             :         std::vector<dof_id_type> temp;
     689         700 :         for (unsigned int j = _inactive_boundary_pos[i]; j <= _inactive_boundary_pos[i + 1]; ++j)
     690             :         {
     691         560 :           temp.push_back(_boundary[j]);
     692             :         }
     693         140 :         _active_boundary.push_back(temp);
     694         140 :       }
     695             :     }
     696         140 :     if (_inactive_boundary_pos[n_inactive_boundary - 1] - _inactive_boundary_pos[0] <
     697         140 :         n_boundary - 1)
     698             :     {
     699             :       std::vector<dof_id_type> temp;
     700           0 :       for (unsigned int j = _inactive_boundary_pos[n_inactive_boundary - 1]; j < n_boundary; ++j)
     701           0 :         temp.push_back(_boundary[j]);
     702           0 :       for (unsigned int j = 0; j <= _inactive_boundary_pos[0]; ++j)
     703           0 :         temp.push_back(_boundary[j]);
     704           0 :       _active_boundary.push_back(temp);
     705           0 :     }
     706             :   }
     707         140 : }
     708             : 
     709             : void
     710          80 : CrackMeshCut3DUserObject::findActiveBoundaryDirection()
     711             : {
     712             :   mooseAssert(!(_cfd && _active_boundary.size() != 1),
     713             :               "crack-front-definition using the cutter mesh only supports one active crack front "
     714             :               "segment for now");
     715             : 
     716          80 :   _active_direction.clear();
     717             : 
     718         160 :   for (unsigned int i = 0; i < _active_boundary.size(); ++i)
     719             :   {
     720             :     std::vector<Point> temp;
     721             :     Point dir;
     722             : 
     723          80 :     if (_inactive_boundary_pos.size() != 0)
     724             :     {
     725         320 :       for (unsigned int j = 0; j < 3; ++j)
     726         240 :         dir(j) = 0;
     727          80 :       temp.push_back(dir);
     728             :     }
     729             : 
     730             :     unsigned int i1 = 1;
     731          80 :     unsigned int i2 = _active_boundary[i].size() - 1;
     732          80 :     if (_inactive_boundary_pos.size() == 0)
     733             :     {
     734             :       i1 = 0;
     735             :       i2 = _active_boundary[i].size();
     736             :     }
     737             : 
     738          80 :     if (_growth_dir_method == GrowthDirectionEnum::FUNCTION)
     739             :       // loop over active front points
     740          72 :       for (unsigned int j = i1; j < i2; ++j)
     741             :       {
     742          48 :         Node * this_node = _cutter_mesh->node_ptr(_active_boundary[i][j]);
     743             :         mooseAssert(this_node, "Node is NULL");
     744          48 :         Point & this_point = *this_node;
     745          48 :         dir(0) = _func_x->value(0, this_point);
     746          48 :         dir(1) = _func_y->value(0, this_point);
     747          48 :         dir(2) = _func_z->value(0, this_point);
     748             : 
     749          48 :         temp.push_back(dir);
     750             :       }
     751             :     // determine growth direction based on KI and KII at the crack front
     752          56 :     else if (_growth_dir_method == GrowthDirectionEnum::MAX_HOOP_STRESS)
     753             :     {
     754             :       mooseAssert(_ki_vpp->size() == _kii_vpp->size(), "KI and KII VPPs must be the same size");
     755             :       mooseAssert(_ki_vpp->size() == _active_boundary[0].size(),
     756             :                   "the number of crack front nodes in the self-similar method should equal to the "
     757             :                   "size of VPP defined at the crack front");
     758             :       mooseAssert(_crack_front_points.size() == _active_boundary[0].size(),
     759             :                   "the number of crack front nodes should be the same in _crack_front_points and "
     760             :                   "_active_boundary[0]");
     761             : 
     762             :       // the node order in _active_boundary[0] and _crack_front_points may be the same or opposite,
     763             :       // their correspondence is needed
     764          56 :       std::vector<int> index = getFrontPointsIndex();
     765             : 
     766         168 :       for (unsigned int j = i1; j < i2; ++j)
     767             :       {
     768         112 :         int ind = index[j];
     769         112 :         Real ki = _ki_vpp->at(ind);
     770         112 :         Real kii = _kii_vpp->at(ind);
     771         112 :         Real theta = 2 * std::atan((ki - std::sqrt(ki * ki + kii * kii)) / (4 * kii));
     772             : 
     773             :         // growth direction in crack front coord (cfc) system based on the max hoop stress criterion
     774             :         RealVectorValue dir_cfc;
     775             : 
     776             :         // growth direction in global coord system based on the max hoop stress criterion
     777             :         RealVectorValue dir;
     778             : 
     779         112 :         dir_cfc(0) = std::cos(theta);
     780         112 :         dir_cfc(1) = std::sin(theta);
     781             :         dir_cfc(2) = 0;
     782         112 :         dir = _crack_front_definition->rotateFromCrackFrontCoordsToGlobal(dir_cfc, ind);
     783             : 
     784         112 :         temp.push_back(dir);
     785             :       }
     786          56 :     }
     787             :     else
     788           0 :       mooseError("This growth_dir_method is not pre-defined!");
     789             : 
     790          80 :     if (_inactive_boundary_pos.size() != 0)
     791             :     {
     792         320 :       for (unsigned int j = 0; j < 3; ++j)
     793         240 :         dir(j) = 0;
     794          80 :       temp.push_back(dir);
     795             :     }
     796             : 
     797          80 :     _active_direction.push_back(temp);
     798          80 :   }
     799             : 
     800             :   // normalize the directional vector
     801             :   Real maxl = 0;
     802             : 
     803         160 :   for (unsigned int i = 0; i < _active_direction.size(); ++i)
     804         400 :     for (unsigned int j = 0; j < _active_direction[i].size(); ++j)
     805             :     {
     806         320 :       Point pt = _active_direction[i][j];
     807         320 :       Real length = std::sqrt(pt * pt);
     808         320 :       if (length > maxl)
     809             :         maxl = length;
     810             :     }
     811             : 
     812         160 :   for (unsigned int i = 0; i < _active_direction.size(); ++i)
     813         400 :     for (unsigned int j = 0; j < _active_direction[i].size(); ++j)
     814             :       _active_direction[i][j] /= maxl;
     815          80 : }
     816             : 
     817             : void
     818          80 : CrackMeshCut3DUserObject::growFront()
     819             : {
     820          80 :   _front.clear();
     821             : 
     822         160 :   for (unsigned int i = 0; i < _active_boundary.size(); ++i)
     823             :   {
     824             :     std::vector<dof_id_type> temp;
     825             : 
     826             :     unsigned int i1 = 1;
     827          80 :     unsigned int i2 = _active_boundary[i].size() - 1;
     828          80 :     if (_inactive_boundary_pos.size() == 0)
     829             :     {
     830             :       i1 = 0;
     831             :       i2 = _active_boundary[i].size();
     832             :     }
     833             : 
     834          80 :     std::vector<int> index = getFrontPointsIndex();
     835         240 :     for (unsigned int j = i1; j < i2; ++j)
     836             :     {
     837         160 :       Node * this_node = _cutter_mesh->node_ptr(_active_boundary[i][j]);
     838             :       mooseAssert(this_node, "Node is NULL");
     839             :       Point & this_point = *this_node;
     840         160 :       Point dir = _active_direction[i][j];
     841             : 
     842             :       Point x;
     843             :       Real growth_increment = 0;
     844         160 :       switch (_growth_increment_method)
     845             :       {
     846          48 :         case GrowthRateEnum::FUNCTION:
     847             :         {
     848          48 :           growth_increment = _func_v->value(0, Point(0, 0, 0));
     849          48 :           break;
     850             :         }
     851         112 :         case GrowthRateEnum::REPORTER:
     852             :         {
     853         112 :           int ind = index[j];
     854         112 :           if (index[j] == -1)
     855             :             growth_increment = 0;
     856             :           else
     857         112 :             growth_increment = _growth_inc_reporter->at(ind);
     858             :           break;
     859             :         }
     860           0 :         default:
     861             :         {
     862           0 :           mooseError("This growth_increment_method is not pre-defined!");
     863             :           break;
     864             :         }
     865             :       }
     866         640 :       for (unsigned int k = 0; k < 3; ++k)
     867         480 :         x(k) = this_point(k) + dir(k) * growth_increment;
     868             : 
     869         320 :       this_node = Node::build(x, _cutter_mesh->n_nodes()).release();
     870         160 :       _cutter_mesh->add_node(this_node);
     871             : 
     872         160 :       dof_id_type id = _cutter_mesh->n_nodes() - 1;
     873         160 :       temp.push_back(id);
     874             : 
     875         160 :       if (_cfd)
     876             :       {
     877         136 :         auto it = std::find(_tracked_crack_front_points.begin(),
     878             :                             _tracked_crack_front_points.end(),
     879             :                             _active_boundary[0][j]);
     880         136 :         if (it != _tracked_crack_front_points.end())
     881             :         {
     882             :           unsigned int pos = std::distance(_tracked_crack_front_points.begin(), it);
     883         136 :           _tracked_crack_front_points[pos] = id;
     884             :         }
     885             :       }
     886             :     }
     887             : 
     888          80 :     _front.push_back(temp);
     889          80 :   }
     890          80 : }
     891             : 
     892             : void
     893          80 : CrackMeshCut3DUserObject::sortFrontNodes()
     894             : // TBD; it is not needed for current problems but will be useful for fracture growth
     895             : {
     896          80 : }
     897             : 
     898             : void
     899          80 : CrackMeshCut3DUserObject::findFrontIntersection()
     900             : {
     901          80 :   ConstBndElemRange & range = *_mesh.getBoundaryElementRange();
     902             : 
     903         160 :   for (unsigned int i = 0; i < _front.size(); ++i)
     904             :   {
     905          80 :     if (_front[i].size() >= 2)
     906             :     {
     907             :       std::vector<Point> pint1;
     908             :       std::vector<Point> pint2;
     909             :       std::vector<Real> length1;
     910             :       std::vector<Real> length2;
     911             : 
     912          80 :       Real node_id = _front[i][0];
     913          80 :       Node * this_node = _cutter_mesh->node_ptr(node_id);
     914             :       mooseAssert(this_node, "Node is NULL");
     915          80 :       Point & p2 = *this_node;
     916             : 
     917          80 :       if (_front[i].size() >= 4)
     918           0 :         node_id = _front[i][2];
     919             :       else
     920          80 :         node_id = _front[i][1];
     921             : 
     922          80 :       this_node = _cutter_mesh->node_ptr(node_id);
     923             :       mooseAssert(this_node, "Node is NULL");
     924          80 :       Point & p1 = *this_node;
     925             : 
     926          80 :       node_id = _front[i].back();
     927          80 :       this_node = _cutter_mesh->node_ptr(node_id);
     928             :       mooseAssert(this_node, "Node is NULL");
     929          80 :       Point & p4 = *this_node;
     930             : 
     931          80 :       if (_front[i].size() >= 4)
     932           0 :         node_id = _front[i][_front[i].size() - 3];
     933             :       else
     934          80 :         node_id = _front[i][_front[i].size() - 2];
     935             : 
     936          80 :       this_node = _cutter_mesh->node_ptr(node_id);
     937             :       mooseAssert(this_node, "Node is NULL");
     938          80 :       Point & p3 = *this_node;
     939             : 
     940             :       bool do_inter1 = 1;
     941             :       bool do_inter2 = 1;
     942             : 
     943          80 :       std::unique_ptr<PointLocatorBase> pl = _mesh.getPointLocator();
     944          80 :       pl->enable_out_of_mesh_mode();
     945          80 :       const Elem * elem = (*pl)(p1);
     946          80 :       if (elem == nullptr)
     947             :         do_inter1 = 0;
     948          80 :       elem = (*pl)(p4);
     949          80 :       if (elem == nullptr)
     950             :         do_inter2 = 0;
     951             : 
     952        7784 :       for (const auto & belem : range)
     953             :       {
     954             :         Point pt;
     955             :         std::vector<Point> vertices;
     956             : 
     957        7704 :         elem = belem->_elem;
     958        7704 :         std::unique_ptr<const Elem> curr_side = elem->side_ptr(belem->_side);
     959       38520 :         for (unsigned int j = 0; j < curr_side->n_nodes(); ++j)
     960             :         {
     961             :           const Node * node = curr_side->node_ptr(j);
     962       30816 :           const Point & this_point = *node;
     963       30816 :           vertices.push_back(this_point);
     964             :         }
     965             : 
     966        7704 :         if (findIntersection(p1, p2, vertices, pt))
     967             :         {
     968          80 :           pint1.push_back(pt);
     969          80 :           length1.push_back((pt - p1) * (pt - p1));
     970             :         }
     971        7704 :         if (findIntersection(p3, p4, vertices, pt))
     972             :         {
     973          80 :           pint2.push_back(pt);
     974          80 :           length2.push_back((pt - p3) * (pt - p3));
     975             :         }
     976        7704 :       }
     977             : 
     978          80 :       if (length1.size() != 0 && do_inter1)
     979             :       {
     980             :         auto it1 = std::min_element(length1.begin(), length1.end());
     981          80 :         Point inter1 = pint1[std::distance(length1.begin(), it1)];
     982             :         inter1 += (inter1 - p1) * _const_intersection;
     983             : 
     984         160 :         Node * this_node = Node::build(inter1, _cutter_mesh->n_nodes()).release();
     985          80 :         _cutter_mesh->add_node(this_node);
     986             : 
     987             :         mooseAssert(_cutter_mesh->n_nodes() - 1 > 0, "The cut mesh must be at least one element.");
     988          80 :         unsigned int n = _cutter_mesh->n_nodes() - 1;
     989             : 
     990             :         auto it = _front[i].begin();
     991          80 :         _front[i].insert(it, n);
     992             : 
     993          80 :         if (_cfd)
     994          68 :           _tracked_crack_front_points[_tracked_crack_front_points.size() - 1] = n;
     995             :       }
     996             : 
     997          80 :       if (length2.size() != 0 && do_inter2)
     998             :       {
     999             :         auto it2 = std::min_element(length2.begin(), length2.end());
    1000          80 :         Point inter2 = pint2[std::distance(length2.begin(), it2)];
    1001             :         inter2 += (inter2 - p2) * _const_intersection;
    1002             : 
    1003         160 :         Node * this_node = Node::build(inter2, _cutter_mesh->n_nodes()).release();
    1004          80 :         _cutter_mesh->add_node(this_node);
    1005             : 
    1006          80 :         dof_id_type n = _cutter_mesh->n_nodes() - 1;
    1007             : 
    1008             :         auto it = _front[i].begin();
    1009             :         unsigned int m = _front[i].size();
    1010          80 :         _front[i].insert(it + m, n);
    1011             : 
    1012          80 :         if (_cfd)
    1013          68 :           _tracked_crack_front_points[0] = n;
    1014             :       }
    1015          80 :     }
    1016             :   }
    1017          80 : }
    1018             : 
    1019             : void
    1020          80 : CrackMeshCut3DUserObject::refineFront()
    1021             : {
    1022          80 :   std::vector<std::vector<dof_id_type>> new_front(_front.begin(), _front.end());
    1023             : 
    1024         160 :   for (unsigned int ifront = 0; ifront < _front.size(); ++ifront)
    1025             :   {
    1026          80 :     unsigned int i1 = _front[ifront].size() - 1;
    1027          80 :     if (_inactive_boundary_pos.size() == 0)
    1028             :       i1 = _front[ifront].size();
    1029             : 
    1030         320 :     for (unsigned int i = i1; i >= 1; --i)
    1031             :     {
    1032             :       unsigned int i2 = i;
    1033         240 :       if (_inactive_boundary_pos.size() == 0)
    1034           0 :         i2 = (i <= _front[ifront].size() - 1 ? i : 0);
    1035             : 
    1036         240 :       dof_id_type node1 = _front[ifront][i - 1];
    1037         240 :       dof_id_type node2 = _front[ifront][i2];
    1038         240 :       Real distance = findDistance(node1, node2);
    1039             : 
    1040         240 :       if (distance > _size_control)
    1041             :       {
    1042           0 :         unsigned int n = static_cast<int>(distance / _size_control);
    1043             :         std::array<Real, 3> x1;
    1044             :         std::array<Real, 3> x2;
    1045             : 
    1046           0 :         Node * this_node = _cutter_mesh->node_ptr(node1);
    1047             :         mooseAssert(this_node, "Node is NULL");
    1048             :         Point & p1 = *this_node;
    1049           0 :         this_node = _cutter_mesh->node_ptr(node2);
    1050             :         mooseAssert(this_node, "Node is NULL");
    1051             :         Point & p2 = *this_node;
    1052             : 
    1053           0 :         for (unsigned int j = 0; j < 3; ++j)
    1054             :         {
    1055           0 :           x1[j] = p1(j);
    1056           0 :           x2[j] = p2(j);
    1057             :         }
    1058             : 
    1059           0 :         for (unsigned int j = 0; j < n; ++j)
    1060             :         {
    1061             :           Point x;
    1062           0 :           for (unsigned int k = 0; k < 3; ++k)
    1063           0 :             x(k) = x2[k] - (x2[k] - x1[k]) * (j + 1) / (n + 1);
    1064             : 
    1065           0 :           Node * this_node = Node::build(x, _cutter_mesh->n_nodes()).release();
    1066           0 :           _cutter_mesh->add_node(this_node);
    1067             : 
    1068           0 :           dof_id_type id = _cutter_mesh->n_nodes() - 1;
    1069             : 
    1070             :           auto it = new_front[ifront].begin();
    1071           0 :           new_front[ifront].insert(it + i, id);
    1072             :         }
    1073             :       }
    1074             :     }
    1075             :   }
    1076             : 
    1077          80 :   _front = new_front;
    1078             : 
    1079          80 :   if (_cfd)
    1080             :   {
    1081          68 :     if (_front[0][0] == _tracked_crack_front_points[0] &&
    1082           0 :         _front[0].back() == _tracked_crack_front_points.back())
    1083           0 :       _crack_front_points = _front[0];
    1084          68 :     else if (_front[0][0] == _tracked_crack_front_points.back() &&
    1085          68 :              _front[0].back() == _tracked_crack_front_points[0])
    1086             :     {
    1087          68 :       _crack_front_points = _front[0];
    1088          68 :       std::reverse(_crack_front_points.begin(), _crack_front_points.end());
    1089             :     }
    1090             :     else
    1091           0 :       mooseError("the crack front and the tracked crack front definition must match in terms of "
    1092           0 :                  "their end nodes\n _front[0][0]= " +
    1093           0 :                  Moose::stringify(_front[0][0]) + "\n _tracked_crack_front_points[0]= " +
    1094           0 :                  Moose::stringify(_tracked_crack_front_points[0]) +
    1095           0 :                  "\n _tracked_crack_front_points.back()=" +
    1096           0 :                  Moose::stringify(_tracked_crack_front_points.back()));
    1097             : 
    1098          68 :     _num_crack_front_points = _crack_front_points.size();
    1099          68 :     _crack_front_definition->updateNumberOfCrackFrontPoints(_num_crack_front_points);
    1100             :   }
    1101          80 : }
    1102             : 
    1103             : void
    1104          80 : CrackMeshCut3DUserObject::triangulation()
    1105             : {
    1106             : 
    1107             :   mooseAssert(_active_boundary.size() == _front.size(),
    1108             :               "_active_boundary and _front must be the same size!");
    1109             : 
    1110          80 :   if (_inactive_boundary_pos.size() == 0)
    1111             :   {
    1112           0 :     _active_boundary[0].push_back(_active_boundary[0][0]);
    1113           0 :     _front[0].push_back(_front[0][0]);
    1114             :   }
    1115             : 
    1116             :   // loop over active segments
    1117         160 :   for (unsigned int k = 0; k < _front.size(); ++k)
    1118             :   {
    1119          80 :     unsigned int n1 = _active_boundary[k].size();
    1120          80 :     unsigned int n2 = _front[k].size();
    1121             : 
    1122             :     unsigned int i1 = 0;
    1123             :     unsigned int i2 = 0;
    1124             : 
    1125             :     // stop when all nodes are associated with an element
    1126         560 :     while (!(i1 == n1 - 1 && i2 == n2 - 1))
    1127             :     {
    1128             :       std::vector<dof_id_type> elem;
    1129             : 
    1130         480 :       dof_id_type p1 = _active_boundary[k][i1]; // node in the old front
    1131         480 :       dof_id_type p2 = _front[k][i2];           // node in the new front
    1132             : 
    1133         480 :       if (i1 != n1 - 1 && i2 != n2 - 1)
    1134             :       {
    1135         400 :         dof_id_type p3 = _active_boundary[k][i1 + 1]; // next node in the old front
    1136         400 :         dof_id_type p4 = _front[k][i2 + 1];           // next node in the new front
    1137             : 
    1138         400 :         elem.push_back(p1);
    1139         400 :         elem.push_back(p2);
    1140             : 
    1141         400 :         Real d1 = findDistance(p1, p4);
    1142         400 :         Real d2 = findDistance(p3, p2);
    1143             : 
    1144         400 :         if (d1 < d2)
    1145             :         {
    1146         208 :           elem.push_back(p4);
    1147             :           i2++;
    1148             :         }
    1149             : 
    1150             :         else
    1151             :         {
    1152         192 :           elem.push_back(p3);
    1153             :           i1++;
    1154             :         }
    1155         400 :       }
    1156             : 
    1157          80 :       else if (i1 == n1 - 1)
    1158             :       {
    1159          32 :         dof_id_type p4 = _front[k][i2 + 1]; // next node in the new front
    1160             : 
    1161          32 :         elem.push_back(p1);
    1162          32 :         elem.push_back(p2);
    1163          32 :         elem.push_back(p4);
    1164             :         i2++;
    1165             :       }
    1166             : 
    1167          48 :       else if (i2 == n2 - 1)
    1168             :       {
    1169          48 :         dof_id_type p3 = _active_boundary[k][i1 + 1]; // next node in the old front
    1170             : 
    1171          48 :         elem.push_back(p1);
    1172          48 :         elem.push_back(p2);
    1173          48 :         elem.push_back(p3);
    1174             :         i1++;
    1175             :       }
    1176             : 
    1177         480 :       Elem * new_elem = Elem::build(TRI3).release();
    1178             : 
    1179        1920 :       for (unsigned int i = 0; i < _cut_elem_nnode; ++i)
    1180             :       {
    1181             :         mooseAssert(_cutter_mesh->node_ptr(elem[i]) != nullptr, "Node is NULL");
    1182        1440 :         new_elem->set_node(i, _cutter_mesh->node_ptr(elem[i]));
    1183             :       }
    1184             : 
    1185         480 :       _cutter_mesh->add_elem(new_elem);
    1186         480 :     }
    1187             :   }
    1188          80 : }
    1189             : 
    1190             : void
    1191          80 : CrackMeshCut3DUserObject::joinBoundary()
    1192             : {
    1193          80 :   if (_inactive_boundary_pos.size() == 0)
    1194             :   {
    1195           0 :     _boundary = _front[0];
    1196             :     _boundary.pop_back();
    1197           0 :     return;
    1198             :   }
    1199             : 
    1200             :   std::vector<dof_id_type> full_front;
    1201             : 
    1202          80 :   unsigned int size1 = _active_boundary.size();
    1203             : 
    1204         160 :   for (unsigned int i = 0; i < size1; ++i)
    1205             :   {
    1206          80 :     unsigned int size2 = _active_boundary[i].size();
    1207             : 
    1208          80 :     dof_id_type bd1 = _active_boundary[i][size2 - 1];
    1209          80 :     dof_id_type bd2 = _active_boundary[i + 1 < size1 ? i + 1 : 0][0];
    1210             : 
    1211          80 :     full_front.insert(full_front.end(), _front[i].begin(), _front[i].end());
    1212             : 
    1213          80 :     auto it1 = std::find(_boundary.begin(), _boundary.end(), bd1);
    1214          80 :     unsigned int pos1 = std::distance(_boundary.begin(), it1);
    1215          80 :     auto it2 = std::find(_boundary.begin(), _boundary.end(), bd2);
    1216          80 :     unsigned int pos2 = std::distance(_boundary.begin(), it2);
    1217             : 
    1218          80 :     if (pos1 <= pos2)
    1219           0 :       full_front.insert(full_front.end(), _boundary.begin() + pos1, _boundary.begin() + pos2 + 1);
    1220             :     else
    1221             :     {
    1222          80 :       full_front.insert(full_front.end(), _boundary.begin() + pos1, _boundary.end());
    1223          80 :       full_front.insert(full_front.end(), _boundary.begin(), _boundary.begin() + pos2 + 1);
    1224             :     }
    1225             :   }
    1226             : 
    1227          80 :   _boundary = full_front;
    1228          80 : }
    1229             : 
    1230             : const std::vector<Point>
    1231         136 : CrackMeshCut3DUserObject::getCrackFrontPoints(unsigned int number_crack_front_points) const
    1232             : {
    1233         136 :   std::vector<Point> crack_front_points(number_crack_front_points);
    1234             :   // number_crack_front_points is updated via
    1235             :   // _crack_front_definition->updateNumberOfCrackFrontPoints(_crack_front_points.size())
    1236         136 :   if (number_crack_front_points != _crack_front_points.size())
    1237           0 :     mooseError("Number of nodes in CrackFrontDefinition does not match the number of nodes in the "
    1238           0 :                "cutter_mesh.\nCrackFrontDefinition nodes = " +
    1239           0 :                Moose::stringify(number_crack_front_points) +
    1240           0 :                "\ncutter_mesh nodes = " + Moose::stringify(_crack_front_points.size()));
    1241             : 
    1242         680 :   for (unsigned int i = 0; i < number_crack_front_points; ++i)
    1243             :   {
    1244         544 :     dof_id_type id = _crack_front_points[i];
    1245         544 :     Node * this_node = _cutter_mesh->node_ptr(id);
    1246             :     mooseAssert(this_node, "Node is NULL");
    1247             :     Point & this_point = *this_node;
    1248         544 :     crack_front_points[i] = this_point;
    1249             :   }
    1250         136 :   return crack_front_points;
    1251           0 : }
    1252             : 
    1253             : const std::vector<RealVectorValue>
    1254         136 : CrackMeshCut3DUserObject::getCrackPlaneNormals(unsigned int number_crack_front_points) const
    1255             : {
    1256         136 :   std::vector<RealVectorValue> crack_plane_normals(number_crack_front_points);
    1257             : 
    1258             :   // build the node-to-elems map
    1259             :   std::unordered_map<dof_id_type, std::vector<dof_id_type>> node_to_elems_map;
    1260             :   node_to_elems_map.clear();
    1261        7616 :   for (const auto & elem : _cutter_mesh->element_ptr_range())
    1262       14688 :     for (auto & node : elem->node_ref_range())
    1263       11152 :       node_to_elems_map[node.id()].push_back(elem->id());
    1264             : 
    1265             :   // build the elem-to-normal map
    1266             :   std::unordered_map<dof_id_type, RealVectorValue> elem_to_normal_map;
    1267             :   elem_to_normal_map.clear();
    1268        7616 :   for (const auto & elem : _cutter_mesh->element_ptr_range())
    1269             :   {
    1270        3672 :     Point & p1 = *elem->node_ptr(0);
    1271        3672 :     Point & p2 = *elem->node_ptr(1);
    1272        3672 :     Point & p3 = *elem->node_ptr(2);
    1273        3672 :     Plane elem_plane(p3, p2, p1); // to match the current normal of 0,0,-1;
    1274        3672 :     RealVectorValue normal = elem_plane.unit_normal(p1);
    1275        3672 :     elem_to_normal_map[elem->id()] = normal;
    1276        3808 :   }
    1277             : 
    1278             :   // for any front node, the normal is averaged based on the normals of all elements sharing this
    1279             :   // node this code may fail when the front node has no element connected to it, e.g. refinement at
    1280             :   // step 1 has to be disabled
    1281         680 :   for (unsigned int i = 0; i < number_crack_front_points; ++i)
    1282             :   {
    1283         544 :     dof_id_type id = _crack_front_points[i];
    1284         544 :     std::vector<dof_id_type> elems = node_to_elems_map[id];
    1285         544 :     unsigned int n_elem = elems.size();
    1286             : 
    1287             :     RealVectorValue normal_avr = 0;
    1288        1736 :     for (unsigned int j = 0; j < n_elem; ++j)
    1289        1192 :       normal_avr += elem_to_normal_map[elems[j]];
    1290             :     normal_avr = normal_avr / n_elem;
    1291         544 :     crack_plane_normals[i] = normal_avr;
    1292         544 :   }
    1293         136 :   return crack_plane_normals;
    1294           0 : }
    1295             : 
    1296             : std::vector<int>
    1297         196 : CrackMeshCut3DUserObject::getFrontPointsIndex() const
    1298             : {
    1299             :   // Crack front definition using the cutter mesh currently only supports one active crack front
    1300             :   // segment
    1301             :   unsigned int ibnd = 0;
    1302         196 :   unsigned int size_this_segment = _active_boundary[ibnd].size();
    1303         196 :   unsigned int n_inactive_nodes = _inactive_boundary_pos.size();
    1304             : 
    1305         196 :   std::vector<int> index(size_this_segment, -1);
    1306             : 
    1307         196 :   unsigned int i1 = n_inactive_nodes == 0 ? 0 : 1;
    1308         196 :   unsigned int i2 = n_inactive_nodes == 0 ? size_this_segment : size_this_segment - 1;
    1309             : 
    1310             :   // loop over active front points
    1311         588 :   for (unsigned int j = i1; j < i2; ++j)
    1312             :   {
    1313         392 :     dof_id_type id = _active_boundary[ibnd][j];
    1314         392 :     auto it = std::find(_crack_front_points.begin(), _crack_front_points.end(), id);
    1315         392 :     index[j] = std::distance(_crack_front_points.begin(), it);
    1316             :   }
    1317             : 
    1318         196 :   return index;
    1319             : }
    1320             : 
    1321             : unsigned int
    1322           0 : CrackMeshCut3DUserObject::getNumberOfCrackFrontPoints() const
    1323             : {
    1324           0 :   return _num_crack_front_points;
    1325             : }

Generated by: LCOV version 1.14