https://mooseframework.inl.gov
MeshCut2DUserObjectBase.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 
12 #include "CrackFrontDefinition.h"
13 
14 #include "XFEMFuncs.h"
15 #include "MooseError.h"
16 #include "MooseMesh.h"
17 #include "libmesh/edge_edge2.h"
18 #include "libmesh/serial_mesh.h"
19 #include "libmesh/mesh_tools.h"
20 
23 {
25  params.addRequiredParam<MeshFileName>(
26  "mesh_file",
27  "Mesh file for the XFEM geometric cut; currently only the Exodus type is supported");
28  params.addParam<UserObjectName>("nucleate_uo", "The MeshCutNucleation UO for nucleating cracks.");
29  params.addParam<UserObjectName>("crack_front_definition",
30  "crackFrontDefinition",
31  "The CrackFrontDefinition user object name");
32  params.addClassDescription("Creates a UserObject base class for a mesh cutter in 2D problems");
33  return params;
34 }
35 
37  : GeometricCutUserObject(parameters, true),
38  _mesh(_subproblem.mesh()),
39  _nucleate_uo(isParamValid("nucleate_uo")
40  ? &getUserObject<MeshCut2DNucleationBase>("nucleate_uo")
41  : nullptr),
42  _is_mesh_modified(false)
43 {
44  _depend_uo.insert(getParam<UserObjectName>("crack_front_definition"));
45 
46  // only the Exodus type is currently supported
47  MeshFileName cutterMeshFileName = getParam<MeshFileName>("mesh_file");
48  _cutter_mesh = std::make_unique<ReplicatedMesh>(_communicator);
49  _cutter_mesh->read(cutterMeshFileName);
50  // test element type; only line elements are allowed
51  for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
52  {
53  if (cut_elem->n_nodes() != 2)
54  mooseError("The input cut mesh should include EDGE2 elements only!");
55  if (cut_elem->dim() != 1)
56  mooseError("The input cut mesh should have 1D elements (in a 2D space) only!");
57  }
58 
59  // find node fronts of the original cutmesh. This is used to order EVERYTHING.
61 }
62 
63 void
65 {
66  const auto uo_name = getParam<UserObjectName>("crack_front_definition");
68 }
69 
70 bool
72  std::vector<Xfem::CutEdge> & cut_edges,
73  std::vector<Xfem::CutNode> & cut_nodes) const
74 {
75  // With the crack defined by a line, this method cuts a 2D elements by a line
76  // Fixme lynn Copy and paste from InterfaceMeshCut2DUserObject::cutElementByGeometry
77  mooseAssert(elem->dim() == 2, "Dimension of element to be cut must be 2");
78 
79  bool elem_cut = false;
80 
81  for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
82  {
83  unsigned int n_sides = elem->n_sides();
84 
85  for (unsigned int i = 0; i < n_sides; ++i)
86  {
87  std::unique_ptr<const Elem> curr_side = elem->side_ptr(i);
88 
89  mooseAssert(curr_side->type() == EDGE2, "Element side type must be EDGE2.");
90 
91  const Node * node1 = curr_side->node_ptr(0);
92  const Node * node2 = curr_side->node_ptr(1);
93  Real seg_int_frac = 0.0;
94 
95  const std::pair<Point, Point> elem_endpoints(cut_elem->node_ref(0), cut_elem->node_ref(1));
96 
97  if (Xfem::intersectSegmentWithCutLine(*node1, *node2, elem_endpoints, 1.0, seg_int_frac))
98  {
99  if (seg_int_frac > Xfem::tol && seg_int_frac < 1.0 - Xfem::tol)
100  {
101  elem_cut = true;
102  Xfem::CutEdge mycut;
103  mycut._id1 = node1->id();
104  mycut._id2 = node2->id();
105  mycut._distance = seg_int_frac;
106  mycut._host_side_id = i;
107  cut_edges.push_back(mycut);
108  }
109  else if (seg_int_frac < Xfem::tol)
110  {
111  elem_cut = true;
112  Xfem::CutNode mycut;
113  mycut._id = node1->id();
114  mycut._host_id = i;
115  cut_nodes.push_back(mycut);
116  }
117  }
118  }
119  }
120  return elem_cut;
121 }
122 
123 bool
125  std::vector<Xfem::CutFace> & /*cut_faces*/) const
126 {
127  mooseError("Invalid method for 2D mesh cutting.");
128  return false;
129 }
130 
131 bool
132 MeshCut2DUserObjectBase::cutFragmentByGeometry(std::vector<std::vector<Point>> & frag_edges,
133  std::vector<Xfem::CutEdge> & cut_edges) const
134 {
135  bool cut_frag = false;
136 
137  for (const auto & cut_elem : _cutter_mesh->element_ptr_range())
138  {
139  const std::pair<Point, Point> elem_endpoints(cut_elem->node_ref(0), cut_elem->node_ref(1));
140  unsigned int n_sides = frag_edges.size();
141  for (unsigned int i = 0; i < n_sides; ++i)
142  {
143  Real seg_int_frac = 0.0;
145  frag_edges[i][0], frag_edges[i][1], elem_endpoints, 1, seg_int_frac))
146  {
147  cut_frag = true;
148  Xfem::CutEdge mycut;
149  mycut._id1 = i;
150  mycut._id2 = (i < (n_sides - 1) ? (i + 1) : 0);
151  mycut._distance = seg_int_frac;
152  mycut._host_side_id = i;
153  cut_edges.push_back(mycut);
154  }
155  }
156  }
157  return cut_frag;
158 }
159 
160 bool
161 MeshCut2DUserObjectBase::cutFragmentByGeometry(std::vector<std::vector<Point>> & /*frag_faces*/,
162  std::vector<Xfem::CutFace> & /*cut_faces*/) const
163 {
164  mooseError("Invalid method for 2D mesh fragment cutting.");
165  return false;
166 }
167 
168 MeshBase &
170 {
171  mooseAssert(_cutter_mesh, "MeshCut2DUserObjectBase::getCutterMesh _cutter_mesh is nullptr");
172  return *_cutter_mesh;
173 }
174 
175 const std::vector<Point>
176 MeshCut2DUserObjectBase::getCrackFrontPoints(unsigned int number_crack_front_points) const
177 {
178  std::vector<Point> crack_front_points(number_crack_front_points);
179  // number_crack_front_points is updated via
180  // _crack_front_definition->updateNumberOfCrackFrontPoints(_crack_front_points.size())
181  if (number_crack_front_points != _original_and_current_front_node_ids.size())
182  mooseError("MeshCut2DFractureUserObject::getCrackFrontPoints: number_crack_front_points=" +
183  Moose::stringify(number_crack_front_points) +
184  " does not match the number of nodes given in "
185  "_original_and_current_front_node_ids=" +
187 
188  for (unsigned int i = 0; i < number_crack_front_points; ++i)
189  {
191  Node * this_node = _cutter_mesh->node_ptr(id);
192  mooseAssert(this_node, "Node is NULL");
193  Point & this_point = *this_node;
194  crack_front_points[i] = this_point;
195  }
196  return crack_front_points;
197 }
198 
199 const std::vector<RealVectorValue>
200 MeshCut2DUserObjectBase::getCrackPlaneNormals(unsigned int number_crack_front_points) const
201 {
202  if (number_crack_front_points != _original_and_current_front_node_ids.size())
203  mooseError("MeshCut2DFractureUserObject::getCrackPlaneNormals: number_crack_front_points=" +
204  Moose::stringify(number_crack_front_points) +
205  " does not match the number of nodes given in "
206  "_original_and_current_front_node_ids=" +
208  ". This will happen if a crack front exits the boundary because the number of "
209  "points in the CrackFrontDefinition is never updated.");
210 
211  std::vector<std::pair<dof_id_type, RealVectorValue>> crack_plane_normals;
212  for (const auto & elem : _cutter_mesh->element_ptr_range())
213  {
214  dof_id_type id0 = elem->node_id(0);
215  dof_id_type id1 = elem->node_id(1);
216  dof_id_type id;
217 
218  auto it0 = std::find_if(_original_and_current_front_node_ids.begin(),
220  [&id0](const std::pair<dof_id_type, dof_id_type> & element)
221  { return element.second == id0; });
222  auto it1 = std::find_if(_original_and_current_front_node_ids.begin(),
224  [&id1](const std::pair<dof_id_type, dof_id_type> & element)
225  { return element.second == id1; });
226 
227  bool found_it0 = (it0 != _original_and_current_front_node_ids.end());
228  bool found_it1 = (it1 != _original_and_current_front_node_ids.end());
229 
230  // Newly nucleated crack elements can have one normal if they are on the edge OR
231  // two normals if they are in the bulk.
232  if (found_it0)
233  {
234  Point end_pt, connecting_pt;
235 
236  end_pt = elem->node_ref(0);
237  connecting_pt = elem->node_ref(1);
238  id = it0->first; // sort by original crack front node ids
239 
240  Point fracture_dir = end_pt - connecting_pt;
241  // The crack normal is orthogonal to the crack extension direction (fracture_dir),
242  // and is defined in this implementation as the cross product of the direction of crack
243  // extension with the tangent direction, which is always (0, 0, 1) in 2D.
244  RealVectorValue normal_dir{fracture_dir(1), -fracture_dir(0), 0};
245  normal_dir /= normal_dir.norm();
246  crack_plane_normals.push_back(std::make_pair(id, normal_dir));
247  }
248 
249  if (found_it1)
250  {
251  Point end_pt, connecting_pt;
252 
253  end_pt = elem->node_ref(1);
254  connecting_pt = elem->node_ref(0);
255  id = it1->first; // sort by original crack front node ids
256 
257  Point fracture_dir = end_pt - connecting_pt;
258  // The crack normal is orthogonal to the crack extension direction (fracture_dir),
259  // and is defined in this implementation as the cross product of the direction of crack
260  // extension with the tangent direction, which is always (0, 0, 1) in 2D.
261  RealVectorValue normal_dir{fracture_dir(1), -fracture_dir(0), 0};
262  normal_dir /= normal_dir.norm();
263  crack_plane_normals.push_back(std::make_pair(id, normal_dir));
264  }
265  }
266  mooseAssert(
267  _original_and_current_front_node_ids.size() == crack_plane_normals.size(),
268  "Boundary nodes are attached to more than one element. This should not happen for a 1D "
269  "cutter mesh."
270  "\n Number of _original_and_current_front_node_ids=" +
272  "\n Number of crack_plane_normals=" + Moose::stringify(crack_plane_normals.size()));
273 
274  // the crack_plane_normals are now sorted by the ORIGINAL crack front ids
275  std::sort(crack_plane_normals.begin(), crack_plane_normals.end());
276  std::vector<RealVectorValue> sorted_crack_plane_normals;
277  for (auto & crack : crack_plane_normals)
278  sorted_crack_plane_normals.push_back(crack.second);
279 
280  return sorted_crack_plane_normals;
281 }
282 
283 void
285 {
286  std::unique_ptr<PointLocatorBase> pl = _mesh.getPointLocator();
287  pl->enable_out_of_mesh_mode();
288  std::unordered_set boundary_nodes = MeshTools::find_boundary_nodes(*_cutter_mesh);
289  for (const auto & node : boundary_nodes)
290  {
291  auto node_id = node;
292  Node * this_node = _cutter_mesh->node_ptr(node_id);
293  mooseAssert(this_node, "Node is NULL");
294  Point & this_point = *this_node;
295 
296  const Elem * elem = (*pl)(this_point);
297  if (elem != NULL)
298  _original_and_current_front_node_ids.push_back(std::make_pair(node, node));
299  }
300  std::sort(_original_and_current_front_node_ids.begin(),
302 }
303 
304 void
306 {
307  dof_id_type current_front_node_id;
308  for (std::size_t i = 0; i < _original_and_current_front_node_ids.size(); ++i)
309  {
310  current_front_node_id = _original_and_current_front_node_ids[i].second;
311  // check if node front node id is active
312  auto direction_iter =
313  std::find_if(_active_front_node_growth_vectors.begin(),
315  [&current_front_node_id](const std::pair<dof_id_type, Point> & element)
316  { return element.first == current_front_node_id; });
317  // only add an element for active node front ids
318  if (direction_iter != _active_front_node_growth_vectors.end())
319  {
320  Node * this_node = _cutter_mesh->node_ptr(current_front_node_id);
321  mooseAssert(this_node, "Node is NULL");
322  Point & this_point = *this_node;
323 
324  Point new_node_offset = direction_iter->second;
325  Point x = this_point + new_node_offset;
326 
327  // TODO: Should check if cut line segment created between "this_point" and "x" crosses
328  // another line element in the cutter mesh or solid mesh boundary.
329  // Crossing another line element would be a special case that still needs to be handled,
330  // however, it doesnot cause an error, it will just ignore the other line segment and recut
331  // the solid mesh element.
332  // Crossing a solid mesh boundary would be for aesthetics reasons so
333  // that element was trimmed close to the boundary but would have not effect on the simulation.
334  // Crossing a solid mesh boundary should be handled by something like
335  // MeshCut2DRankTwoTensorNucleation::lineLineIntersect2D
336 
337  // add node to front
338  this_node = Node::build(x, _cutter_mesh->n_nodes()).release();
339  _cutter_mesh->add_node(this_node);
340  dof_id_type new_front_node_id = _cutter_mesh->n_nodes() - 1;
341 
342  // add element to front
343  std::vector<dof_id_type> elem;
344  elem.push_back(current_front_node_id);
345  elem.push_back(new_front_node_id);
346  Elem * new_elem = Elem::build(EDGE2).release();
347  for (unsigned int i = 0; i < new_elem->n_nodes(); ++i)
348  {
349  mooseAssert(_cutter_mesh->node_ptr(elem[i]) != nullptr, "Node is NULL");
350  new_elem->set_node(i, _cutter_mesh->node_ptr(elem[i]));
351  }
352  _cutter_mesh->add_elem(new_elem);
353  // now push to the end of _original_and_current_front_node_ids for tracking and fracture
354  // integrals
355  _original_and_current_front_node_ids[i].second = new_front_node_id;
356  _is_mesh_modified = true;
357  }
358  }
359  _cutter_mesh->prepare_for_use();
360 }
361 
362 void
364 {
365  if (_nucleate_uo)
366  {
367  std::map<unsigned int, std::pair<RealVectorValue, RealVectorValue>> nucleated_elems_map =
369  const Real nucleationRadius = _nucleate_uo->getNucleationRadius();
370 
371  removeNucleatedCracksTooCloseToEachOther(nucleated_elems_map, nucleationRadius);
372  removeNucleatedCracksTooCloseToExistingCracks(nucleated_elems_map, nucleationRadius);
373 
374  std::unique_ptr<PointLocatorBase> pl = _mesh.getPointLocator();
375  pl->enable_out_of_mesh_mode();
376  for (const auto & elem_nodes : nucleated_elems_map)
377  {
378  std::pair<RealVectorValue, RealVectorValue> nodes = elem_nodes.second;
379  // add nodes for the elements that define the nucleated cracks
380  Node * node_0 = Node::build(nodes.first, _cutter_mesh->n_nodes()).release();
381  _cutter_mesh->add_node(node_0);
382  dof_id_type node_id_0 = _cutter_mesh->n_nodes() - 1;
383  Node * node_1 = Node::build(nodes.second, _cutter_mesh->n_nodes()).release();
384  _cutter_mesh->add_node(node_1);
385  dof_id_type node_id_1 = _cutter_mesh->n_nodes() - 1;
386  // add elements that define nucleated cracks
387  std::vector<dof_id_type> elem;
388  elem.push_back(node_id_0);
389  elem.push_back(node_id_1);
390  Elem * new_elem = Elem::build(EDGE2).release();
391  for (unsigned int i = 0; i < new_elem->n_nodes(); ++i)
392  {
393  mooseAssert(_cutter_mesh->node_ptr(elem[i]) != nullptr, "Node is NULL");
394  new_elem->set_node(i, _cutter_mesh->node_ptr(elem[i]));
395  }
396  _cutter_mesh->add_elem(new_elem);
397  // now add the nucleated nodes to the crack id data struct
398  // edge nucleated cracks will add one node to _original_and_current_front_node_ids
399  // bulk nucleated cracks will add two nodes to _original_and_current_front_node_ids
400  Point & point_0 = *node_0;
401  const Elem * crack_front_elem_0 = (*pl)(point_0);
402  if (crack_front_elem_0 != NULL)
403  _original_and_current_front_node_ids.push_back(std::make_pair(node_id_0, node_id_0));
404 
405  Point & point_1 = *node_1;
406  const Elem * crack_front_elem_1 = (*pl)(point_1);
407  if (crack_front_elem_1 != NULL)
408  _original_and_current_front_node_ids.push_back(std::make_pair(node_id_1, node_id_1));
409 
410  _is_mesh_modified = true;
411  }
412  _cutter_mesh->prepare_for_use();
413  }
414 }
415 
416 void
418  std::map<unsigned int, std::pair<RealVectorValue, RealVectorValue>> & nucleated_elems_map,
419  const Real nucleationRadius)
420 {
421  // remove nucleated elements that are too close too each other. Lowest key wins
422  for (auto it1 = nucleated_elems_map.begin(); it1 != nucleated_elems_map.end(); ++it1)
423  {
424  std::pair<RealVectorValue, RealVectorValue> nodes = it1->second;
425  Point p2 = nodes.first;
426  Point p1 = nodes.second;
427  Point p = p1 + (p2 - p1) / 2;
428  for (auto it2 = nucleated_elems_map.begin(); it2 != nucleated_elems_map.end();)
429  {
430  if (it1 == it2)
431  {
432  ++it2;
433  continue;
434  }
435 
436  nodes = it2->second;
437  p2 = nodes.first;
438  p1 = nodes.second;
439  Point q = p1 + (p2 - p1) / 2;
440  Point pq = q - p;
441  if (pq.norm() <= nucleationRadius)
442  it2 = nucleated_elems_map.erase(it2);
443  else
444  ++it2;
445  }
446  }
447 }
448 
449 void
451  std::map<unsigned int, std::pair<RealVectorValue, RealVectorValue>> & nucleated_elems_map,
452  const Real nucleationRadius)
453 {
454  for (auto it = nucleated_elems_map.begin(); it != nucleated_elems_map.end();)
455  {
456  std::pair<RealVectorValue, RealVectorValue> nodes = it->second;
457  Point p2 = nodes.first;
458  Point p1 = nodes.second;
459  Point p = p1 + (p2 - p1) / 2;
460  bool removeNucleatedElem = false;
461  for (const auto & cutter_elem :
462  as_range(_cutter_mesh->active_elements_begin(), _cutter_mesh->active_elements_end()))
463  {
464  const Node * const * cutter_elem_nodes = cutter_elem->get_nodes();
465  Point m = *cutter_elem_nodes[1] - *cutter_elem_nodes[0];
466  Real t = m * (p - *cutter_elem_nodes[0]) / m.norm_sq();
467  Real d = std::numeric_limits<Real>::max();
468  if (t <= 0)
469  {
470  Point j = p - *cutter_elem_nodes[0];
471  d = j.norm();
472  }
473  else if (t >= 0)
474  {
475  Point j = p - *cutter_elem_nodes[1];
476  d = j.norm();
477  }
478  else
479  {
480  Point j = p - (*cutter_elem_nodes[0] + t * m);
481  d = j.norm();
482  }
483  if (d <= nucleationRadius)
484  {
485  removeNucleatedElem = true;
486  break;
487  }
488  }
489  if (removeNucleatedElem)
490  it = nucleated_elems_map.erase(it);
491  else
492  ++it;
493  }
494 }
Data structure defining a cut through a node.
static InputParameters validParams()
Factory constructor, takes parameters so that all derived classes can be built using the same constru...
auto norm() const -> decltype(std::norm(Real()))
virtual bool cutElementByGeometry(const Elem *elem, std::vector< Xfem::CutEdge > &cut_edges, std::vector< Xfem::CutNode > &cut_nodes) const override
T & getUserObject(const std::string &name, unsigned int tid=0) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
static InputParameters validParams()
void findOriginalCrackFrontNodes()
Find the original crack front nodes in the cutter mesh and use to populate _original_and_current_fron...
MeshCut2DUserObjectBase(const InputParameters &parameters)
unsigned int _host_id
Local ID of this node in the host element.
MeshBase & mesh
std::unique_ptr< MeshBase > _cutter_mesh
The xfem cutter mesh.
const Parallel::Communicator & _communicator
std::vector< std::pair< dof_id_type, dof_id_type > > _original_and_current_front_node_ids
This vector of pairs orders crack tips to make the order used in this class the same as those for the...
virtual bool cutFragmentByGeometry(std::vector< std::vector< Point >> &frag_edges, std::vector< Xfem::CutEdge > &cut_edges) const override
void addRequiredParam(const std::string &name, const std::string &doc_string)
Data structure defining a cut on an element edge.
Class used in fracture integrals to define geometric characteristics of the crack front...
Real getNucleationRadius() const
Provide getter to MeshCut2DUserObjectBase for member data set in input.
unsigned int _id1
ID of the first node on the edge.
const std::vector< double > x
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
std::string stringify(const T &t)
static const double tol
Definition: XFEMFuncs.h:21
bool intersectSegmentWithCutLine(const Point &segment_point1, const Point &segment_point2, const std::pair< Point, Point > &cutting_line_points, const Real &cutting_line_fraction, Real &segment_intersection_fraction)
Determine whether a line segment is intersected by a cutting line, and compute the fraction along tha...
Definition: XFEMFuncs.C:774
MooseMesh & _mesh
The FE solution mesh.
std::map< unsigned int, std::pair< RealVectorValue, RealVectorValue > > getNucleatedElemsMap() const
Provide getter to MeshCut2DUserObjectBase for a map of nucleated cracks.
void addNucleatedCracksToMesh()
Calls into MeshCutNucleation UO to add cracks.
const MeshCut2DNucleationBase * _nucleate_uo
2D UO for nucleating cracks
virtual void initialSetup() override final
std::set< std::string > _depend_uo
Real _distance
Fractional distance along the edge (from node 1 to 2) where the cut is located.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
EDGE2
unsigned int _id2
ID of the second node on the edge.
FEProblemBase & _fe_problem
bool _is_mesh_modified
Indicator that shows if the cutting mesh is modified or not in this calculation step.
void mooseError(Args &&... args) const
unsigned int _host_side_id
Local ID of this side in the host element.
virtual const std::vector< Point > getCrackFrontPoints(unsigned int num_crack_front_points) const override
get a set of points along a crack front from a XFEM GeometricCutUserObject
void addClassDescription(const std::string &doc_string)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
virtual std::unique_ptr< libMesh::PointLocatorBase > getPointLocator() const
void growFront()
grow the cutter mesh
void removeNucleatedCracksTooCloseToExistingCracks(std::map< unsigned int, std::pair< RealVectorValue, RealVectorValue >> &nucleated_elems_map, Real nucleationRadius)
Remove nucleated cracks that are too close to a pre-existing crack in the mesh.
void removeNucleatedCracksTooCloseToEachOther(std::map< unsigned int, std::pair< RealVectorValue, RealVectorValue >> &nucleated_elems_map, Real nucleationRadius)
Remove nucleated cracks that are too close too each other.
std::vector< std::pair< dof_id_type, Point > > _active_front_node_growth_vectors
contains the active node ids and their growth vectors
unsigned int _id
ID of the cut node.
virtual const std::vector< RealVectorValue > getCrackPlaneNormals(unsigned int num_crack_front_points) const override
get a set of normal vectors along a crack front from a XFEM GeometricCutUserObject CrackFrontDefiniti...
CrackFrontDefinition * _crack_front_definition
user object for communicating between solid_mechanics interaction integrals and xfem cutter mesh ...
uint8_t dof_id_type