mortaring_helpers.h
Go to the documentation of this file.
1 // This file is part of the MercuryDPM project (https://www.mercurydpm.org).
2 // Copyright (c), The MercuryDPM Developers Team. All rights reserved.
3 // License: BSD 3-Clause License; see the LICENSE file in the root directory.
4 
5 #ifndef MORTARING_HELPERS_HEADER
6 #define MORTARING_HELPERS_HEADER
7 
8 #include "mesh.h"
10 
11 using namespace oomph;
12 
13 
14 // From a list of nodes which are to be mortared create a list of spatially unique nodes so that we don't create singular constraints
15 // use point elements to perform setup multi domain interactions
16 // Do this twice, grabbing the element and local coordiante for each point element for each mesh
17 // Build constraint elements and return them
18 
19 // This doesn't use a mortaring status/dof - although this could be used for other implementations I suppose but it can be separate to this process
20 
21 // setup_mortaring_constraints_at_nodes attempt to match at nodes first before doing locate zetas.
22 // for any nodes which haven't been able to be identified as having a partner node try to locate zetas
23 
25 {
26 
27 // A helper class which merges two meshes and computes boundary elements on the new boundary numbering scheme
28 // Not used anywhere.
29 class MergedSolidMesh : public virtual SolidMesh
30 {
31 public:
32  MergedSolidMesh(Vector<SolidMesh*> solid_mesh_pt) : SolidMesh(solid_mesh_pt)
33  {
34  // Solid mesh constructor has merged meshes
35 
36  // We now need to update our representation of the boundary numbers
37 
38  // Loop over nodes adding them to the appropriate boundary lookup schemes
39  // in the mesh. Shift by the required number of boundaries
40  const unsigned n_mesh = solid_mesh_pt.size();
41  Vector<unsigned> Boundary_shift(n_mesh);
42 
43  unsigned n_boundary = 0;
44  for(unsigned m=0; m<n_mesh; m++)
45  {
46  n_boundary += solid_mesh_pt[m]->nboundary();
47  if(m==n_mesh-1) continue;
48  Boundary_shift[m+1] = solid_mesh_pt[m]->nboundary();
49  }
50  this->set_nboundary(n_boundary);
51  Boundary_element_pt.resize(n_boundary);
52  Face_index_at_boundary.resize(n_boundary);
53 
54  for(unsigned m=0; m<n_mesh; m++)
55  {
56  const unsigned n_node = solid_mesh_pt[m]->nnode();
57  for (unsigned nd = 0; nd < n_node; nd++)
58  {
59  Node* nd_pt = solid_mesh_pt[m]->node_pt(nd);
60  if (nd_pt->is_on_boundary())
61  {
62  // Get set of boundaries that the node is on
63  std::set<unsigned>* boundaries_pt;
64  nd_pt->get_boundaries_pt(boundaries_pt);
65 
66  // Add node pointer to the appropriate Boundary_node_pt vectors
67  std::set<unsigned>::const_iterator it;
68  for (it = boundaries_pt->begin(); it != boundaries_pt->end(); it++)
69  {
70  Boundary_node_pt[*it + Boundary_shift[m]].push_back(nd_pt);
71  }
72  }
73  }
74 
75  // Get the list of elements on the boundary of the constituent mesh and add them to the new shifted boundary
76  for(unsigned b=0; b<solid_mesh_pt[m]->nboundary(); b++)
77  {
78  Vector<FiniteElement*> boundary_elements(solid_mesh_pt[m]->nboundary_element(b));
79  Vector<int> boundary_faces(solid_mesh_pt[m]->nboundary_element(b));
80  for(unsigned e=0;e<solid_mesh_pt[m]->nboundary_element(b); e++)
81  {
82  boundary_elements[e] = solid_mesh_pt[m]->boundary_element_pt(b,e);
83  boundary_faces[e] = solid_mesh_pt[m]->face_index_at_boundary(b,e);
84  }
85  Boundary_element_pt[Boundary_shift[m]+b] = boundary_elements;
86  Face_index_at_boundary[Boundary_shift[m]+b] = boundary_faces;
87  }
88  }
89  }
90 };
91 
92 class PointIntegralScheme : public virtual Integral
93 {
94 public:
97 
99  PointIntegralScheme(const PointIntegralScheme& dummy) = delete;
100 
102  void operator=(const PointIntegralScheme&) = delete;
103 
105  unsigned nweight() const
106  {
107  return 1;
108  }
109 
111  double knot(const unsigned& i, const unsigned& j) const
112  {
113  return 0.0;
114  }
115 
117  double weight(const unsigned& i) const
118  {
119  return 1.0;
120  }
121 };
122 
123 
124 // A helper element for locating the point in a mesh at which a node in another mesh sits
126 {
127 private:
128  // Default integral scheme, defines the locations of the integral points
130 
131 public:
132  PointElementWithExternalElement(const Vector<double>& coord, const unsigned& n_interaction=1)
133  {
134  // Set the dimensions of the element and the nodes
135  this->set_dimension(coord.size());
136  // By default there is 1 interaction
137  this->set_ninteraction(n_interaction);
138  // Set the integration scheme
139  this->set_integration_scheme(&Interface_default_integration_scheme);
140 
141  // Position the node at the particle
142  Node* node_pt = this->construct_node(0, dynamic_cast<TimeStepper*>(&Mesh::Default_TimeStepper));
143  for(unsigned i=0; i<coord.size(); i++)
144  {
145  node_pt->x(i) = coord[i];
146  }
147  }
148 
150 };
151 PointIntegralScheme PointElementWithExternalElement::Interface_default_integration_scheme;
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
163 // Setup mortaring constraint interactions between two meshes at the locations of the nodes
164 // initially looks for co-located node pairs then with any remaining nodes uses locate zeta
165 // to find a suitable co-locating point in another element
166 // Provide two vectors, one for the nodes in each element
167 // For efficiency assumes that only 2 nodes will be co-located and will not check nodes against nodes of the same mesh
168 template<class NodeNodeConstraintClass, class NodeElementConstraintClass, class ELEMENTTYPE>
170  Vector<Vector<Node*>> node_pt,
171  Vector<SolidMesh*> mesh_pt,
172  const bool& accept_failure = false)
173 {
174  // Vector of constraint elements built
175  Vector<ConstraintElement*> constraint_element_pt;
176 
177  // Assume all the nodes/elements are the same dimension
178  const unsigned dim = node_pt[0][0]->ndim();
179 
180  // The number of nodes from each mesh
181  const Vector<long unsigned> n_node = {node_pt[0].size(), node_pt[1].size()};
182 
183  // Allocate for nodes which were not matched to other nodes
184  Vector<Vector<unsigned>> node_was_matched(2);
185  node_was_matched[0].resize(n_node[0], 0);
186  node_was_matched[1].resize(n_node[1], 0);
187 
188  // First attempt to match each node to another in the other mesh
189  unsigned n_matched_nodes = 0;
190  for(unsigned i=0; i<n_node[0]; i++)
191  {
192  SolidNode* node_0_pt = dynamic_cast<SolidNode*>(node_pt[0][i]);
193  for(unsigned j=0; j<n_node[1]; j++)
194  {
195  // If the node has already been matched to another node
196  if(node_was_matched[1][j]) continue;
197 
198  SolidNode* node_1_pt = dynamic_cast<SolidNode*>(node_pt[1][j]);
199 
200  // get the distance between the nodes TODO is it faster to just check absolute distance in each spatial direction?
201  double distance = 0.0;
202  for(unsigned d=0; d<dim; d++)
203  {
204  distance += (node_0_pt->xi(d) - node_1_pt->xi(d))*
205  (node_0_pt->xi(d) - node_1_pt->xi(d));
206  }
207  // if co-located build constraint element
209  {
210  // If the nodes are the same then skip this, we can't mortar a node to itself
211  // This check is useful because it will allow a mesh to be mortared to itself
212  if(node_0_pt == node_1_pt) continue;
213  // Record that these nodes were matched
214  node_was_matched[0][i] = 1;
215  node_was_matched[1][j] = 1;
216  n_matched_nodes++;
217  // Build the node-node constraint element
218  constraint_element_pt.push_back(new NodeNodeConstraintClass(node_0_pt, node_1_pt));
219 
220  // stop checking for this node in the first mesh
221  break;
222  }
223  }
224  }
225 
226  // If we have matched all nodes then return
227  if((n_node[0] - n_matched_nodes) == 0 && (n_node[1] - n_matched_nodes) == 0 ) return constraint_element_pt;
228 
229  // If we get to here then we know that some nodes have not been mortared.
230  // If the mesh pointers are the same then we know we have failed. We can't perform the following procedure
231  // if the meshes are the same.
232  if(mesh_pt[0] == mesh_pt[1])
233  {
234  throw OomphLibError("Mortaring failed - meshes are the same and node-node mortaring has not captured all nodes",
237  }
238 
239  const bool Previous_Accept_failed_locate_zeta_in_setup_multi_domain_interaction
242 
243 
244  // Figure out how many nodes are left to be matched from each mesh
245  Vector<long unsigned> n_unmatched_nodes{n_node[0] - n_matched_nodes,
246  n_node[1] - n_matched_nodes};
247 
248  for(unsigned m : {0,1})
249  {
250  if(n_unmatched_nodes[m] == 0) continue;
251 
252  // Get the nodes which were not matched
253  Vector<Node*> unmatched_node_pt(n_unmatched_nodes[m]);
254  unsigned count = 0;
255  for(unsigned i=0; i<n_node[m]; i++)
256  {
257  if(!node_was_matched[m][i])
258  {
259  unmatched_node_pt[count++] = node_pt[m][i];
260  }
261  }
262 
263  // Create a mesh for locating the zetas in the other mesh
264  Mesh* Locating_mesh_pt = new Mesh;
265 
266  Vector<PointElementWithExternalElement*> locating_elements_pt(n_unmatched_nodes[m]);
267  for(unsigned i=0; i<n_unmatched_nodes[m]; i++)
268  {
269  Vector<double> x(dim);
270  for(unsigned d=0; d<dim; d++)
271  {
272  x[d] = dynamic_cast<SolidNode*>(unmatched_node_pt[i])->xi(d);
273  }
274 
275  locating_elements_pt[i] = new PointElementWithExternalElement(x);
276  Locating_mesh_pt->add_element_pt(locating_elements_pt[i]);
277  }
278 
279  // Setup multi domain interactions with the second mesh
280  Multi_domain_functions::setup_multi_domain_interaction<ELEMENTTYPE>(problem_pt,
281  Locating_mesh_pt,
282  mesh_pt[unsigned(1-m)],
283  0);
284 
285  // Loop over the locating elements
286  for(unsigned i=0; i<n_unmatched_nodes[m]; i++)
287  {
288  // If the external element was not found skip this element - no need to crash out, that would have happened
289  // earlier if this was an issue (if accept_failure == false)
290  if(locating_elements_pt[i]->external_element_pt(0,0) == nullptr) continue;
291  constraint_element_pt.push_back(new NodeElementConstraintClass(dynamic_cast<SolidNode*>(unmatched_node_pt[i]),
292  locating_elements_pt[i]->external_element_pt(0,0),
293  locating_elements_pt[i]->external_element_local_coord(0,0)));
294  }
295  }
296  Multi_domain_functions::Accept_failed_locate_zeta_in_setup_multi_domain_interaction = Previous_Accept_failed_locate_zeta_in_setup_multi_domain_interaction;
297 
298  return constraint_element_pt;
299 }
300 
301 
302 
303 
304 
305 // ////////////////////////////////////////////////////////////////////
306 // Required to find and remove redundant mortaring elements from a problem
307 // Currently only works for nodes.
308 // To perform the same procedure on general Node-Element mortaring elements
309 // I think the Jacobian will have to be interrogated to remove linearly
310 // dependent rows.
311 // ////////////////////////////////////////////////////////////////////
312 
313 // Uses the depth first search (DFS)
314 
315 // Enum to represent the colours of the nodes in the DFS (WHITE=unvisited, GREY=being visited, BLACK=fully processed)
316 enum Colour { WHITE, GREY, BLACK };
317 
318 // Depth First Search (DFS) to detect cycles and identify redundant branches
319 template<class NodeNodeConstraintElementType>
320 void dfs(Node* node_pt,
321  Node* parent_node_pt,
322  std::map<Node*, Vector<std::pair<NodeNodeConstraintElementType*, Node*>>>& tree,
323  std::map<Node*, Colour>& colours,
325 {
326  // Mark the current node as being visited (GREY)
327  colours[node_pt] = GREY;
328 
329  // Get all branches (edges) from the current node
331  for (auto& branch : branches)
332  {
333  Node* next_node = branch.second;
334  NodeNodeConstraintElementType* element = branch.first;
335 
336  // If the branch has already been pruned, skip it
337  if (std::find(pruned_branches.begin(), pruned_branches.end(), element) != pruned_branches.end())
338  {
339  continue;
340  }
341 
342  // If we encounter a GREY node that is not the parent, a cycle is detected
343  if (colours[next_node] == GREY && next_node != parent_node_pt)
344  {
345  pruned_branches.push_back(element);
346  }
347  // If the next node is unvisited (WHITE), continue DFS
348  else if (colours[next_node] == WHITE)
349  {
350  dfs(next_node, node_pt, tree, colours, pruned_branches);
351  }
352  }
353 
354  // Mark the current node as fully processed (BLACK)
355  colours[node_pt] = BLACK;
356 }
357 
358 // Function to find redundant constraint elements using DFS
359 template<class NodeNodeConstraintElementType>
361 {
362  // Create a tree representation from the constraint elements
363  std::map<Node*, Vector<std::pair<NodeNodeConstraintElementType*, Node*>>> tree;
364  for (auto& element : element_pt)
365  {
366  Node* node1 = element->solid_node_pt(0);
367  Node* node2 = element->solid_node_pt(1);
368  tree[node1].push_back(std::make_pair(element, node2));
369  tree[node2].push_back(std::make_pair(element, node1));
370  }
371 
372  // Vector to store the pruned branches
374  // Map to store the colours of the nodes
375  std::map<Node*, Colour> colours;
376 
377  // Initialize all nodes as unvisited (WHITE)
378  for (auto& node_branches : tree)
379  {
380  Node* root_node = node_branches.first;
381  if (colours[root_node] == WHITE)
382  {
383  // // Perform DFS to detect cycles starting from the root node
384  dfs(root_node, nullptr, tree, colours, pruned_branches);
385  }
386  }
387 
388  return pruned_branches;
389 }
390 
391 
392 
393 }; // End namespace
394 
395 #endif
AnnoyingScalar sqrt(const AnnoyingScalar &x)
Definition: AnnoyingScalar.h:134
int i
Definition: BiCGSTAB_step_by_step.cpp:9
Array< double, 1, 3 > e(1./3., 0.5, 2.)
Scalar * b
Definition: benchVecAdd.cpp:17
Definition: mortaring_helpers.h:30
MergedSolidMesh(Vector< SolidMesh * > solid_mesh_pt)
Definition: mortaring_helpers.h:32
Definition: mortaring_helpers.h:126
PointElementWithExternalElement(const Vector< double > &coord, const unsigned &n_interaction=1)
Definition: mortaring_helpers.h:132
static PointIntegralScheme Interface_default_integration_scheme
Definition: mortaring_helpers.h:129
void fill_in_contribution_to_jacobian(Vector< double > &residuals, DenseMatrix< double > &jacobian)
Definition: mortaring_helpers.h:149
Definition: mortaring_helpers.h:93
PointIntegralScheme(const PointIntegralScheme &dummy)=delete
Broken copy constructor.
unsigned nweight() const
Number of integration points of the scheme.
Definition: mortaring_helpers.h:105
double knot(const unsigned &i, const unsigned &j) const
Return coordinate.
Definition: mortaring_helpers.h:111
void operator=(const PointIntegralScheme &)=delete
Broken assignment operator.
PointIntegralScheme()
Default constructor (empty)
Definition: mortaring_helpers.h:96
double weight(const unsigned &i) const
Return weight of integration point i.
Definition: mortaring_helpers.h:117
Definition: element_with_external_element.h:56
Definition: integral.h:49
Definition: mesh.h:67
static Steady< 0 > Default_TimeStepper
The Steady Timestepper.
Definition: mesh.h:75
void add_element_pt(GeneralisedElement *const &element_pt)
Add a (pointer to) an element to the mesh.
Definition: mesh.h:617
Definition: nodes.h:906
double & x(const unsigned &i)
Return the i-th nodal coordinate.
Definition: nodes.h:1060
virtual bool is_on_boundary() const
Definition: nodes.h:1373
virtual void get_boundaries_pt(std::set< unsigned > *&boundaries_pt)
Definition: nodes.h:1365
Definition: oomph_definitions.h:222
Definition: elements.h:3439
Definition: problem.h:151
Definition: mesh.h:2562
Definition: nodes.h:1686
double & xi(const unsigned &i)
Reference to i-th Lagrangian position.
Definition: nodes.h:1883
Definition: timesteppers.h:231
int * m
Definition: level2_cplx_impl.h:294
Definition: mortaring_helpers.h:25
Vector< NodeNodeConstraintElementType * > find_redundant_constraint_elements(Vector< NodeNodeConstraintElementType * > element_pt)
Definition: mortaring_helpers.h:360
static double Mortaring_Constraints_Distance_Tolerance
Definition: mortaring_helpers.h:162
Vector< ConstraintElement * > setup_constraint_elements_at_nodes(Problem *problem_pt, Vector< Vector< Node * >> node_pt, Vector< SolidMesh * > mesh_pt, const bool &accept_failure=false)
Definition: mortaring_helpers.h:169
void dfs(Node *node_pt, Node *parent_node_pt, std::map< Node *, Vector< std::pair< NodeNodeConstraintElementType *, Node * >>> &tree, std::map< Node *, Colour > &colours, Vector< NodeNodeConstraintElementType * > &pruned_branches)
Definition: mortaring_helpers.h:320
Colour
Definition: mortaring_helpers.h:316
@ WHITE
Definition: mortaring_helpers.h:316
@ BLACK
Definition: mortaring_helpers.h:316
@ GREY
Definition: mortaring_helpers.h:316
bool Accept_failed_locate_zeta_in_setup_multi_domain_interaction
Definition: multi_domain.cc:56
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
Definition: AnisotropicHookean.h:10
list x
Definition: plotDoE.py:28
#define OOMPH_EXCEPTION_LOCATION
Definition: oomph_definitions.h:61
#define OOMPH_CURRENT_FUNCTION
Definition: oomph_definitions.h:86
std::ptrdiff_t j
Definition: tut_arithmetic_redux_minmax.cpp:2