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