Bembel
Glue.hpp
1 // This file is part of Bembel, the higher order C++ boundary element library.
2 //
3 // Copyright (C) 2022 see <http://www.bembel.eu>
4 //
5 // It was written as part of a cooperation of J. Doelz, H. Harbrecht, S. Kurz,
6 // M. Multerer, S. Schoeps, and F. Wolf at Technische Universitaet Darmstadt,
7 // Universitaet Basel, and Universita della Svizzera italiana, Lugano. This
8 // source code is subject to the GNU General Public License version 3 and
9 // provided WITHOUT ANY WARRANTY, see <http://www.bembel.eu> for further
10 // information.
11 #ifndef BEMBEL_SRC_ANSATZSPACE_GLUE_HPP_
12 #define BEMBEL_SRC_ANSATZSPACE_GLUE_HPP_
13 
14 namespace Bembel {
15 /*
16 
17 Every edge has an index between 0 and 3, where
18 0 = (0,0)->(1,0)
19 1 = (1,0)->(1,1)
20 2 = (1,1)->(0,1)
21 3 = (0,1)->(0,0)
22 */
23 
24 /*
25  Example of Dof enumeration on one patch:
26  dimXdir = 4;
27  dimYdir = 3;
28  Edge 2 (1,1)
29 
30  y |8 9 10 11|
31  A Edge 3 |4 5 6 7 | Edge 1
32  | |0 1 2 3 |
33 
34  Edge 0
35  (0,0) -> x
36 */
37 
38 namespace GlueRoutines {
43  std::vector<int> dofs;
44  int coef;
45 };
49 template <typename Derived, unsigned int DF>
51  static std::vector<dofIdentification> makeIdentification(
52  const std::vector<std::array<int, 4>> &edges_,
53  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
54  assert(false && "Needs to be specialized");
55  return {};
56  }
57 };
58 
59 } // namespace GlueRoutines
60 
75 template <typename Derived>
76 class Glue {
77  std::vector<std::array<int, 4>> edges_;
78  int dofs_after_glue;
79  Eigen::SparseMatrix<double> glue_mat_;
80 
81  public:
92  Glue(const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
93  init_Glue(superspace, proj);
94  }
104  void init_Glue(const SuperSpace<Derived> &superspace,
105  const Projector<Derived> &proj) {
106  edges_ = superspace.get_mesh().get_element_tree().patchTopologyInfo();
107  glue_mat_ = assembleGlueMatrix(superspace, proj);
108  return;
109  }
120  Eigen::SparseMatrix<double> get_glue_matrix() const { return glue_mat_; }
134  inline std::vector<GlueRoutines::dofIdentification> makeDofIdentificationList(
135  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
137  Derived,
138  LinearOperatorTraits<Derived>::Form>::makeIdentification(edges_,
139  superspace,
140  proj);
141  }
153  Eigen::SparseMatrix<double> assembleGlueMatrix(
154  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
155  const int pre_dofs = proj.get_dofs_after_projector();
156  // The dofs that need to be identified with each other are divided into
157  // master and slaves, where the master is the firs w.r.t. the tp-ordering
158  std::vector<Eigen::Triplet<double>> trips;
159  std::vector<GlueRoutines::dofIdentification> dof_id =
160  makeDofIdentificationList(superspace, proj);
161 
162  // We keep track of certain information
163  std::vector<bool> dof_is_slave(pre_dofs, false);
164  std::vector<bool> dof_is_master(pre_dofs, false);
165 
166  // Here, we fill the two vectors above
167  int number_of_slaves = 0;
168  int number_of_boundary_dofs = 0;
169  for (auto dofset : dof_id) {
170  // This sorting is required, since by construction only dofs[0]<dofs[1] is
171  // given, but in the case of corners and H1 discretizations, it might
172  // happen that dofs[0]>dofs[2]. Moreover, we count how many dofs we "glue
173  // away" or skipped on the boundary.
174  std::sort(dofset.dofs.begin(), dofset.dofs.end());
175  dof_is_master[dofset.dofs[0]] = true;
176  if (dofset.dofs.size() == 1) ++number_of_boundary_dofs;
177  for (int i = 1; i < dofset.dofs.size(); ++i) {
178  dof_is_slave[dofset.dofs[i]] = true;
179  ++number_of_slaves;
180  }
181  }
182 
183  // Now, we sort w.r.t. the masters.
184  std::sort(dof_id.begin(), dof_id.end(),
187  return a.dofs[0] < b.dofs[0];
188  });
189 
190  const int post_dofs = pre_dofs - number_of_slaves - number_of_boundary_dofs;
191  const int glued_dofs = pre_dofs - number_of_slaves;
192  assert(post_dofs != 0 && "All degrees of freedom are on the boundary!");
193  // This block is the heart of the algorithms. Skip keeps track of how many
194  // slaves have been skipped already, and master_index keeps track on which
195  // master is about to be glued next. post_index keeps track on how many
196  // dofs are on the boundary of the patches and therefore skipped.
197  int skip = 0;
198  int master_index = 0;
199  int post_index = 0;
200  for (int i = 0; i < glued_dofs; ++i) {
201  while (dof_is_slave[i + skip] && ((i + skip) < pre_dofs)) ++skip;
202  const int pre_index = i + skip;
203  // skip dofs on patch boundary without a slave patch
204  if (dof_is_master[pre_index] && dof_id[master_index].dofs.size() == 1) {
205  ++master_index;
206  continue;
207  }
208  trips.push_back(Eigen::Triplet<double>(pre_index, post_index, 1));
209  // The dof cannot be declared slave and master at the same time
210  assert(!(dof_is_master[pre_index] && dof_is_slave[pre_index]));
211  if (dof_is_master[pre_index]) {
212  // The dofs in dof_id don't know they might be moved forward. So the
213  // smallest dof of those to be identified should coincide with the
214  // pre_index.
215  assert(pre_index == dof_id[master_index].dofs[0]);
216  const int number_of_partners = dof_id[master_index].dofs.size();
217  for (int j = 1; j < number_of_partners; ++j) {
218  trips.push_back(Eigen::Triplet<double>(dof_id[master_index].dofs[j],
219  post_index,
220  dof_id[master_index].coef));
221  }
222  ++master_index;
223  }
224  ++post_index;
225  }
226 
227  Eigen::SparseMatrix<double> glue_matrix(pre_dofs, post_dofs);
228  glue_matrix.setFromTriplets(trips.begin(), trips.end());
229 
230  assert(trips.size() == (pre_dofs - number_of_boundary_dofs));
231  return glue_matrix;
232  }
233 };
234 
235 namespace GlueRoutines {
236 
259 std::vector<int> getEdgeDofIndices(int edgeCase, int dimXdir, int dimYdir,
260  int shift) {
261  std::vector<int> out;
262  switch (edgeCase) {
263  case (0): {
264  out.reserve(dimXdir);
265  assert(
266  "This should be a given. If not something went wrong in Glue.hpp" &&
267  dimXdir <= dimYdir);
268  for (int i = 0; i < dimXdir; ++i) {
269  out.push_back(i + shift);
270  }
271  return out;
272  };
273  case (1): {
274  out.reserve(dimYdir);
275  assert(
276  "This should be a given. If not something went wrong in Glue.hpp" &&
277  dimYdir <= dimXdir);
278  for (int i = 0; i < dimYdir; ++i) {
279  out.push_back(dimXdir * (i + 1) - 1 + shift);
280  }
281  return out;
282  };
283  case (2): {
284  out.reserve(dimXdir);
285  assert(
286  "This should be a given. If not something went wrong in Glue.hpp" &&
287  dimXdir <= dimYdir);
288  for (int i = 0; i < dimXdir; ++i) {
289  out.push_back(dimXdir * (dimYdir - 1) + i + shift);
290  }
291  return out;
292  };
293  case (3): {
294  out.reserve(dimYdir);
295  assert(
296  "This should be a given. If not something went wrong in Glue.hpp" &&
297  dimYdir <= dimXdir);
298  for (int i = 0; i < dimYdir; ++i) {
299  out.push_back(dimXdir * i + shift);
300  }
301  return out;
302  };
303  default: {
304  };
305  // An edge might have a -1 index. This occurs only when no partner could
306  // be found, and the -1 is the placeholder of the missing partner.
307  }
308  return out;
309 }
310 
311 // The following 7 functions figure out how edges and the vector components
312 // are oriented w.r.t. each other.
313 inline bool edgeIsForwardParametrized(int edgeCase) {
314  return edgeCase == 0 || edgeCase == 1;
315 }
316 inline bool edgeIsBackwardsParametrized(int edgeCase) {
317  return !(edgeIsForwardParametrized(edgeCase));
318 }
319 inline bool normalComponentIsInwardDirected(int edgeCase) {
320  return edgeCase == 0 || edgeCase == 3;
321 }
322 inline bool normalComponentIsOutwardDirected(int edgeCase) {
323  return !(normalComponentIsInwardDirected(edgeCase));
324 }
325 
326 inline bool reverseParametrized(const std::array<int, 4> &edge) {
327  return ((edgeIsForwardParametrized(edge[2]) &&
328  edgeIsForwardParametrized(edge[3])) ||
329  (edgeIsBackwardsParametrized(edge[2]) &&
330  edgeIsBackwardsParametrized(edge[3])))
331  ? true
332  : false;
333 }
334 
335 inline int glueCoefficientDivergenceConforming(const std::array<int, 4> &edge) {
336  return ((normalComponentIsInwardDirected(edge[2]) &&
337  normalComponentIsInwardDirected(edge[3])) ||
338  (normalComponentIsOutwardDirected(edge[2]) &&
339  normalComponentIsOutwardDirected(edge[3])))
340  ? -1
341  : 1;
342 }
343 inline bool edgeToBeGluedInFirstComp(int edgeCase) {
344  return (edgeCase == 0 || edgeCase == 2) ? false : true;
345 }
346 
352 template <typename Derived>
353 struct glue_identificationmaker_<Derived, DifferentialForm::Discontinuous> {
354  static std::vector<dofIdentification> makeIdentification(
355  const std::vector<std::array<int, 4>> &edges_,
356  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
357  return {};
358  }
359 };
360 
370 template <typename Derived>
371 struct glue_identificationmaker_<Derived, DifferentialForm::Continuous> {
372  static std::vector<dofIdentification> makeIdentification(
373  const std::vector<std::array<int, 4>> &edges_,
374  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
375  // We check if the space can even be continuous globally.
376  assert(superspace.get_polynomial_degree() >= 1);
377  const int one_d_dim = superspace.get_polynomial_degree() + 1 +
378  proj.get_knot_repetition() *
379  ((1 << superspace.get_refinement_level()) - 1);
380  const int dofs_per_patch = one_d_dim * one_d_dim;
381 
382  std::vector<GlueRoutines::dofIdentification> out;
383  out.reserve(edges_.size() * one_d_dim);
384 
385  std::vector<int> already_stored_in(proj.get_dofs_after_projector(), -1);
386  int d_count = 0;
387  for (auto edge : edges_) {
388  // The shift is to account for different patches, since the
389  // getEdgeDof-routines only can enumerate w.r.t. patch 0.
390  const int shift_e1 = edge[0] * dofs_per_patch;
391  const int shift_e2 = edge[1] * dofs_per_patch;
392  const bool needReversion = reverseParametrized(edge);
393 
394  std::vector<int> dofs_e1 =
395  getEdgeDofIndices(edge[2], one_d_dim, one_d_dim, shift_e1);
396  std::vector<int> dofs_e2 =
397  getEdgeDofIndices(edge[3], one_d_dim, one_d_dim, shift_e2);
398 
399  // Check if the edge is hanging. For screens one would need an "else".
400  if (edge[1] > -1 && edge[0] > -1) {
401  for (int i = 0; i < one_d_dim; ++i) {
403  const int j = needReversion ? one_d_dim - 1 - i : i;
404  // const int j = i;
405  assert(dofs_e1[i] != dofs_e2[j] &&
406  "If this happens something went horribly wrong.");
407 
408  const int small_dof = std::min(dofs_e1[i], dofs_e2[j]);
409  const int large_dof = std::max(dofs_e1[i], dofs_e2[j]);
410 
411  // In the continuous case, more than two dofs might be glued together.
412  // Therefore, we must check if we know one of the dofs already. If
413  // yes, we just add the new one to the dof_id, if not, we make a new
414  // identification group.
415  if (already_stored_in[small_dof] > -1) {
416  // It could be that the large_dof is already matched with another
417  // dof, i.e., in the case of a 'circle'. Then there is nothing to
418  // do. If not, we match it with the master of the partner.
419  if (already_stored_in[large_dof] == -1) {
420  out[already_stored_in[small_dof]].dofs.push_back(large_dof);
421  already_stored_in[large_dof] = already_stored_in[small_dof];
422  } else {
423  if (!(already_stored_in[small_dof] ==
424  already_stored_in[large_dof])) {
425  // This case is tricky. Assume that we have to identify four
426  // DOFs with each other, but they have already been assigned in
427  // pairs of two. Then we need to reverse this process. First, we
428  // grab the two storage locations.
429  const int small_store = std::min(already_stored_in[large_dof],
430  already_stored_in[small_dof]);
431  const int large_store = std::max(already_stored_in[large_dof],
432  already_stored_in[small_dof]);
433  for (auto dfs : out[large_store].dofs) {
434  // now we put all of those in the larger location into the
435  // smaller one.
436  already_stored_in[dfs] = small_store;
437  for (auto other_dfs : out[small_store].dofs) {
438  assert(dfs != other_dfs);
439  }
440  out[small_store].dofs.push_back(dfs);
441  }
442  // Now we set the larger storage location to empty.
443  out[large_store].dofs = {};
444  out[large_store].coef = 0;
445  }
446  }
447  } else if (already_stored_in[large_dof] > -1) {
448  // It could be that the small_dof is already matched with another
449  // dof, i.e., in the case of a 'circle'. Then there is nothing to
450  // do. If not, we match it with the master of the partner.
451  if (already_stored_in[small_dof] == -1) {
452  out[already_stored_in[large_dof]].dofs.push_back(small_dof);
453  already_stored_in[small_dof] = already_stored_in[large_dof];
454  } else {
455  if (!(already_stored_in[small_dof] ==
456  already_stored_in[large_dof])) {
457  // This case is tricky. Assume that we have to identify four
458  // DOFs with each other, but they have already been assigned in
459  // pairs of two. Then we need to reverse this process. First, we
460  // grab the two storage locations.
461  const int small_store = std::min(already_stored_in[large_dof],
462  already_stored_in[small_dof]);
463  const int large_store = std::max(already_stored_in[large_dof],
464  already_stored_in[small_dof]);
465  for (auto dfs : out[large_store].dofs) {
466  // now we put all of those in the larger location into the
467  // smaller one.
468  already_stored_in[dfs] = small_store;
469  for (auto other_dfs : out[small_store].dofs) {
470  assert(dfs != other_dfs);
471  }
472  out[small_store].dofs.push_back(dfs);
473  }
474  // Now we set the larger storage location to empty.
475  out[large_store].dofs = {};
476  out[large_store].coef = 0;
477  }
478  }
479  } else {
480  // With the exception of corners, this will be the default case.
481  // We just add a pair of dofs to the bookkeeping.
482  d.dofs.push_back(small_dof);
483  d.dofs.push_back(large_dof);
484  already_stored_in[small_dof] = d_count;
485  already_stored_in[large_dof] = d_count;
486  d_count++;
487  d.coef = 1;
488  out.push_back(d);
489  }
490  }
491  }
492  }
493  // Now we need to clean up the empty dofsets, since subsequent routines
494  // assume there to be at least one element.
495  for (auto x = out.begin(); x != out.end(); ++x) {
496  if ((*x).dofs.size() == 0) {
497  out.erase(x);
498  }
499  }
500  return out;
501  }
502 };
503 
511 template <typename Derived>
512 struct glue_identificationmaker_<Derived, DifferentialForm::DivConforming> {
513  static std::vector<dofIdentification> makeIdentification(
514  const std::vector<std::array<int, 4>> &edges_,
515  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
516  // since we assume px == py and uniform refinement in both directions, there
517  // will be a small_dim and a large_dim in every vector component.
518  const int small_dim = superspace.get_polynomial_degree() +
519  proj.get_knot_repetition() *
520  ((1 << superspace.get_refinement_level()) - 1);
521  const int large_dim = superspace.get_polynomial_degree() + 1 +
522  proj.get_knot_repetition() *
523  ((1 << superspace.get_refinement_level()) - 1);
524  const int dofs_per_patch_per_component = small_dim * large_dim;
525  // sanity check
526  assert(dofs_per_patch_per_component ==
527  (proj.get_dofs_after_projector() /
528  (superspace.get_number_of_patches() * 2)) &&
529  "The assembly of the glue matrix is highly specific to the space. "
530  "Something went wrong; is the discrete space correct?");
531 
532  std::vector<GlueRoutines::dofIdentification> out;
533  out.reserve(edges_.size() * small_dim);
534 
535  for (auto edge : edges_) {
536  assert(edge[0] <= edge[1] || edge[1] == -1);
537  // The DOFs that need to be glued on edges 0 and 2 are in the second
538  // vector component. The remainder of the dof-index-shift is to account
539  // for dofs on other patches.
540  const int shift_e1 = edge[0] * dofs_per_patch_per_component +
541  (edgeToBeGluedInFirstComp(edge[2])
542  ? 0
543  : (dofs_per_patch_per_component *
544  superspace.get_number_of_patches()));
545  const int shift_e2 = edge[1] * dofs_per_patch_per_component +
546  (edgeToBeGluedInFirstComp(edge[3])
547  ? 0
548  : (dofs_per_patch_per_component *
549  superspace.get_number_of_patches()));
550 
551  const int xdir_dim_1 =
552  edgeToBeGluedInFirstComp(edge[2]) ? large_dim : small_dim;
553  const int ydir_dim_1 =
554  edgeToBeGluedInFirstComp(edge[2]) ? small_dim : large_dim;
555  const int xdir_dim_2 =
556  edgeToBeGluedInFirstComp(edge[3]) ? large_dim : small_dim;
557  const int ydir_dim_2 =
558  edgeToBeGluedInFirstComp(edge[3]) ? small_dim : large_dim;
559  std::vector<int> dofs_e1 =
560  getEdgeDofIndices(edge[2], xdir_dim_1, ydir_dim_1, shift_e1);
561  std::vector<int> dofs_e2 =
562  getEdgeDofIndices(edge[3], xdir_dim_2, ydir_dim_2, shift_e2);
563 
564  const int size_of_edge_dofs = dofs_e1.size();
565  assert(size_of_edge_dofs == dofs_e2.size() || edge[1] == -1);
566 
567  const bool needReversion = reverseParametrized(edge);
568  const int coef = glueCoefficientDivergenceConforming(edge);
569 
570  // Check if the edge is hanging.
571  if (edge[1] > -1 && edge[0] > -1) {
572  for (int i = 0; i < size_of_edge_dofs; ++i) {
574  const int j = needReversion ? size_of_edge_dofs - 1 - i : i;
575  assert(dofs_e1[i] != dofs_e2[j] &&
576  "If this happens something went horribly wrong.");
577  d.dofs.push_back(std::min(dofs_e1[i], dofs_e2[j]));
578  d.dofs.push_back(std::max(dofs_e1[i], dofs_e2[j]));
579  d.coef = coef;
580  out.push_back(d);
581  }
582  } else {
583  for (int i = 0; i < size_of_edge_dofs; ++i) {
584  // Add only the master dof which is skipped in subsequent routines
586  d.dofs.push_back(dofs_e1[i]);
587  out.push_back(d);
588  }
589  }
590  }
591  out.shrink_to_fit();
592  return out;
593  }
594 };
595 }; // namespace GlueRoutines
596 
597 } // namespace Bembel
598 #endif // BEMBEL_SRC_ANSATZSPACE_GLUE_HPP_
ElementTree & get_element_tree()
getter
Definition: ClusterTree.hpp:95
std::vector< std::array< int, 4 > > patchTopologyInfo() const
Resolves neighborhood relations of the patches.
This class takes care of identifying DOFs on different edges, which must be identified with one anoth...
Definition: Glue.hpp:76
void init_Glue(const SuperSpace< Derived > &superspace, const Projector< Derived > &proj)
Initializes the AnsatzSpace object.
Definition: Glue.hpp:104
Eigen::SparseMatrix< double > get_glue_matrix() const
Returns Glue matrix to assemble continuous B-splines.
Definition: Glue.hpp:120
std::vector< GlueRoutines::dofIdentification > makeDofIdentificationList(const SuperSpace< Derived > &superspace, const Projector< Derived > &proj)
Generates a list of degrees of freedom (DOFs) identifications.
Definition: Glue.hpp:134
Glue(const SuperSpace< Derived > &superspace, const Projector< Derived > &proj)
Constructor for the Glue class.
Definition: Glue.hpp:92
Eigen::SparseMatrix< double > assembleGlueMatrix(const SuperSpace< Derived > &superspace, const Projector< Derived > &proj)
Assembles the Glue matrix according to the DOF identification list.
Definition: Glue.hpp:153
The Projector provides routines to assemble smooth B-Splines on each patch.
Definition: Projector.hpp:40
int get_knot_repetition() const
getter
Definition: Projector.hpp:88
int get_dofs_after_projector() const
Return number of smooth B-splines.
Definition: Projector.hpp:107
Routines for the evalutation of pointwise errors.
Definition: AnsatzSpace.hpp:14
Provides information about the discrete space required for the discretisation of a specific operator.
Helper struct for assembling the Glue for the different cases.
Definition: Glue.hpp:50
struct containing specifications on the linear operator has to be specialized or derived for any part...
The superspace manages local polynomial bases on each element of the mesh and provides an interface t...
Definition: SuperSpace.hpp:22