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  for (auto dofset : dof_id) {
169  // This sorting is required, since by construction only dofs[0]<dofs[1] is
170  // given, but in the case of corners and H1 discretizations, it might
171  // happen that dofs[0]>dofs[2]. Moreover, we count how many dofs we "glue
172  // away".
173  std::sort(dofset.dofs.begin(), dofset.dofs.end());
174  dof_is_master[dofset.dofs[0]] = true;
175  for (int i = 1; i < dofset.dofs.size(); ++i) {
176  dof_is_slave[dofset.dofs[i]] = true;
177  ++number_of_slaves;
178  }
179  }
180 
181  // Now, we sort w.r.t. the masters.
182  std::sort(dof_id.begin(), dof_id.end(),
185  return a.dofs[0] < b.dofs[0];
186  });
187 
188  const int post_dofs = pre_dofs - number_of_slaves;
189 
190  // This block is the heart of the algorithms. Skip keeps track of how many
191  // slaves have been skipped already, and master_index keeps track on which
192  // master is about to be glued next.
193  int skip = 0;
194  int master_index = 0;
195  for (int i = 0; i < post_dofs; ++i) {
196  const int post_index = i;
197  while (dof_is_slave[i + skip] && ((i + skip) < pre_dofs)) ++skip;
198  const int pre_index = i + skip;
199  trips.push_back(Eigen::Triplet<double>(pre_index, post_index, 1));
200  // The dof cannot be declared slave and master at the same time
201  assert(!(dof_is_master[pre_index] && dof_is_slave[pre_index]));
202  if (dof_is_master[pre_index]) {
203  // The dofs in dof_id don't know they might be moved forward. So the
204  // smallest dof of those to be identified should coincide with the
205  // pre_index.
206  assert(pre_index == dof_id[master_index].dofs[0]);
207  const int number_of_partners = dof_id[master_index].dofs.size();
208  for (int j = 1; j < number_of_partners; ++j) {
209  trips.push_back(Eigen::Triplet<double>(dof_id[master_index].dofs[j],
210  post_index,
211  dof_id[master_index].coef));
212  }
213  ++master_index;
214  }
215  }
216 
217  Eigen::SparseMatrix<double> glue_matrix(pre_dofs, post_dofs);
218  glue_matrix.setFromTriplets(trips.begin(), trips.end());
219 
220  assert(trips.size() == pre_dofs);
221  return glue_matrix;
222  }
223 };
224 
225 namespace GlueRoutines {
226 
249 std::vector<int> getEdgeDofIndices(int edgeCase, int dimXdir, int dimYdir,
250  int shift) {
251  std::vector<int> out;
252  switch (edgeCase) {
253  case (0): {
254  out.reserve(dimXdir);
255  assert(
256  "This should be a given. If not something went wrong in Glue.hpp" &&
257  dimXdir <= dimYdir);
258  for (int i = 0; i < dimXdir; ++i) {
259  out.push_back(i + shift);
260  }
261  return out;
262  };
263  case (1): {
264  out.reserve(dimYdir);
265  assert(
266  "This should be a given. If not something went wrong in Glue.hpp" &&
267  dimYdir <= dimXdir);
268  for (int i = 0; i < dimYdir; ++i) {
269  out.push_back(dimXdir * (i + 1) - 1 + shift);
270  }
271  return out;
272  };
273  case (2): {
274  out.reserve(dimXdir);
275  assert(
276  "This should be a given. If not something went wrong in Glue.hpp" &&
277  dimXdir <= dimYdir);
278  for (int i = 0; i < dimXdir; ++i) {
279  out.push_back(dimXdir * (dimYdir - 1) + i + shift);
280  }
281  return out;
282  };
283  case (3): {
284  out.reserve(dimYdir);
285  assert(
286  "This should be a given. If not something went wrong in Glue.hpp" &&
287  dimYdir <= dimXdir);
288  for (int i = 0; i < dimYdir; ++i) {
289  out.push_back(dimXdir * i + shift);
290  }
291  return out;
292  };
293  default: {
294  };
295  // An edge might have a -1 index. This occurs only when no partner could
296  // be found, and the -1 is the placeholder of the missing partner.
297  }
298  return out;
299 }
300 
301 // The following 7 functions figure out how edges and the vector components
302 // are oriented w.r.t. each other.
303 inline bool edgeIsForwardParametrized(int edgeCase) {
304  return edgeCase == 0 || edgeCase == 1;
305 }
306 inline bool edgeIsBackwardsParametrized(int edgeCase) {
307  return !(edgeIsForwardParametrized(edgeCase));
308 }
309 inline bool normalComponentIsInwardDirected(int edgeCase) {
310  return edgeCase == 0 || edgeCase == 3;
311 }
312 inline bool normalComponentIsOutwardDirected(int edgeCase) {
313  return !(normalComponentIsInwardDirected(edgeCase));
314 }
315 
316 inline bool reverseParametrized(const std::array<int, 4> &edge) {
317  return ((edgeIsForwardParametrized(edge[2]) &&
318  edgeIsForwardParametrized(edge[3])) ||
319  (edgeIsBackwardsParametrized(edge[2]) &&
320  edgeIsBackwardsParametrized(edge[3])))
321  ? true
322  : false;
323 }
324 
325 inline int glueCoefficientDivergenceConforming(const std::array<int, 4> &edge) {
326  return ((normalComponentIsInwardDirected(edge[2]) &&
327  normalComponentIsInwardDirected(edge[3])) ||
328  (normalComponentIsOutwardDirected(edge[2]) &&
329  normalComponentIsOutwardDirected(edge[3])))
330  ? -1
331  : 1;
332 }
333 inline bool edgeToBeGluedInFirstComp(int edgeCase) {
334  return (edgeCase == 0 || edgeCase == 2) ? false : true;
335 }
336 
342 template <typename Derived>
343 struct glue_identificationmaker_<Derived, DifferentialForm::Discontinuous> {
344  static std::vector<dofIdentification> makeIdentification(
345  const std::vector<std::array<int, 4>> &edges_,
346  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
347  return {};
348  }
349 };
350 
360 template <typename Derived>
361 struct glue_identificationmaker_<Derived, DifferentialForm::Continuous> {
362  static std::vector<dofIdentification> makeIdentification(
363  const std::vector<std::array<int, 4>> &edges_,
364  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
365  // We check if the space can even be continuous globally.
366  assert(superspace.get_polynomial_degree() >= 1);
367  const int one_d_dim = superspace.get_polynomial_degree() + 1 +
368  proj.get_knot_repetition() *
369  ((1 << superspace.get_refinement_level()) - 1);
370  const int dofs_per_patch = one_d_dim * one_d_dim;
371 
372  std::vector<GlueRoutines::dofIdentification> out;
373  out.reserve(edges_.size() * one_d_dim);
374 
375  std::vector<int> already_stored_in(proj.get_dofs_after_projector(), -1);
376  int d_count = 0;
377  for (auto edge : edges_) {
378  // The shift is to account for different patches, since the
379  // getEdgeDof-routines only can enumerate w.r.t. patch 0.
380  const int shift_e1 = edge[0] * dofs_per_patch;
381  const int shift_e2 = edge[1] * dofs_per_patch;
382  const bool needReversion = reverseParametrized(edge);
383 
384  std::vector<int> dofs_e1 =
385  getEdgeDofIndices(edge[2], one_d_dim, one_d_dim, shift_e1);
386  std::vector<int> dofs_e2 =
387  getEdgeDofIndices(edge[3], one_d_dim, one_d_dim, shift_e2);
388 
389  // Check if the edge is hanging. For screens one would need an "else".
390  if (edge[1] > -1 && edge[0] > -1) {
391  for (int i = 0; i < one_d_dim; ++i) {
393  const int j = needReversion ? one_d_dim - 1 - i : i;
394  // const int j = i;
395  assert(dofs_e1[i] != dofs_e2[j] &&
396  "If this happens something went horribly wrong.");
397 
398  const int small_dof = std::min(dofs_e1[i], dofs_e2[j]);
399  const int large_dof = std::max(dofs_e1[i], dofs_e2[j]);
400 
401  // In the continuous case, more than two dofs might be glued together.
402  // Therefore, we must check if we know one of the dofs already. If
403  // yes, we just add the new one to the dof_id, if not, we make a new
404  // identification group.
405  if (already_stored_in[small_dof] > -1) {
406  // It could be that the large_dof is already matched with another
407  // dof, i.e., in the case of a 'circle'. Then there is nothing to
408  // do. If not, we match it with the master of the partner.
409  if (already_stored_in[large_dof] == -1) {
410  out[already_stored_in[small_dof]].dofs.push_back(large_dof);
411  already_stored_in[large_dof] = already_stored_in[small_dof];
412  } else {
413  if (!(already_stored_in[small_dof] ==
414  already_stored_in[large_dof])) {
415  // This case is tricky. Assume that we have to identify four
416  // DOFs with each other, but they have already been assigned in
417  // pairs of two. Then we need to reverse this process. First, we
418  // grab the two storage locations.
419  const int small_store = std::min(already_stored_in[large_dof],
420  already_stored_in[small_dof]);
421  const int large_store = std::max(already_stored_in[large_dof],
422  already_stored_in[small_dof]);
423  for (auto dfs : out[large_store].dofs) {
424  // now we put all of those in the larger location into the
425  // smaller one.
426  already_stored_in[dfs] = small_store;
427  for (auto other_dfs : out[small_store].dofs) {
428  assert(dfs != other_dfs);
429  }
430  out[small_store].dofs.push_back(dfs);
431  }
432  // Now we set the larger storage location to empty.
433  out[large_store].dofs = {};
434  out[large_store].coef = 0;
435  }
436  }
437  } else if (already_stored_in[large_dof] > -1) {
438  // It could be that the small_dof is already matched with another
439  // dof, i.e., in the case of a 'circle'. Then there is nothing to
440  // do. If not, we match it with the master of the partner.
441  if (already_stored_in[small_dof] == -1) {
442  out[already_stored_in[large_dof]].dofs.push_back(small_dof);
443  already_stored_in[small_dof] = already_stored_in[large_dof];
444  } else {
445  if (!(already_stored_in[small_dof] ==
446  already_stored_in[large_dof])) {
447  // This case is tricky. Assume that we have to identify four
448  // DOFs with each other, but they have already been assigned in
449  // pairs of two. Then we need to reverse this process. First, we
450  // grab the two storage locations.
451  const int small_store = std::min(already_stored_in[large_dof],
452  already_stored_in[small_dof]);
453  const int large_store = std::max(already_stored_in[large_dof],
454  already_stored_in[small_dof]);
455  for (auto dfs : out[large_store].dofs) {
456  // now we put all of those in the larger location into the
457  // smaller one.
458  already_stored_in[dfs] = small_store;
459  for (auto other_dfs : out[small_store].dofs) {
460  assert(dfs != other_dfs);
461  }
462  out[small_store].dofs.push_back(dfs);
463  }
464  // Now we set the larger storage location to empty.
465  out[large_store].dofs = {};
466  out[large_store].coef = 0;
467  }
468  }
469  } else {
470  // With the exception of corners, this will be the default case.
471  // We just add a pair of dofs to the bookkeeping.
472  d.dofs.push_back(small_dof);
473  d.dofs.push_back(large_dof);
474  already_stored_in[small_dof] = d_count;
475  already_stored_in[large_dof] = d_count;
476  d_count++;
477  d.coef = 1;
478  out.push_back(d);
479  }
480  }
481  }
482  }
483  // Now we need to clean up the empty dofsets, since subsequent routines
484  // assume there to be at least one element.
485  for (auto x = out.begin(); x != out.end(); ++x) {
486  if ((*x).dofs.size() == 0) {
487  out.erase(x);
488  }
489  }
490  return out;
491  }
492 };
493 
501 template <typename Derived>
502 struct glue_identificationmaker_<Derived, DifferentialForm::DivConforming> {
503  static std::vector<dofIdentification> makeIdentification(
504  const std::vector<std::array<int, 4>> &edges_,
505  const SuperSpace<Derived> &superspace, const Projector<Derived> &proj) {
506  // since we assume px == py and uniform refinement in both directions, there
507  // will be a small_dim and a large_dim in every vector component.
508  const int small_dim = superspace.get_polynomial_degree() +
509  proj.get_knot_repetition() *
510  ((1 << superspace.get_refinement_level()) - 1);
511  const int large_dim = superspace.get_polynomial_degree() + 1 +
512  proj.get_knot_repetition() *
513  ((1 << superspace.get_refinement_level()) - 1);
514  const int dofs_per_patch_per_component = small_dim * large_dim;
515  // sanity check
516  assert(dofs_per_patch_per_component ==
517  (proj.get_dofs_after_projector() /
518  (superspace.get_number_of_patches() * 2)) &&
519  "The assembly of the glue matrix is highly specific to the space. "
520  "Something went wrong; is the discrete space correct?");
521 
522  std::vector<GlueRoutines::dofIdentification> out;
523  out.reserve(edges_.size() * small_dim);
524 
525  for (auto edge : edges_) {
526  assert(edge[0] <= edge[1] || edge[1] == -1);
527  // The DOFs that need to be glued on edges 0 and 2 are in the second
528  // vector component. The remainder of the dof-index-shift is to account
529  // for dofs on other patches.
530  const int shift_e1 = edge[0] * dofs_per_patch_per_component +
531  (edgeToBeGluedInFirstComp(edge[2])
532  ? 0
533  : (dofs_per_patch_per_component *
534  superspace.get_number_of_patches()));
535  const int shift_e2 = edge[1] * dofs_per_patch_per_component +
536  (edgeToBeGluedInFirstComp(edge[3])
537  ? 0
538  : (dofs_per_patch_per_component *
539  superspace.get_number_of_patches()));
540 
541  const int xdir_dim_1 =
542  edgeToBeGluedInFirstComp(edge[2]) ? large_dim : small_dim;
543  const int ydir_dim_1 =
544  edgeToBeGluedInFirstComp(edge[2]) ? small_dim : large_dim;
545  const int xdir_dim_2 =
546  edgeToBeGluedInFirstComp(edge[3]) ? large_dim : small_dim;
547  const int ydir_dim_2 =
548  edgeToBeGluedInFirstComp(edge[3]) ? small_dim : large_dim;
549  std::vector<int> dofs_e1 =
550  getEdgeDofIndices(edge[2], xdir_dim_1, ydir_dim_1, shift_e1);
551  std::vector<int> dofs_e2 =
552  getEdgeDofIndices(edge[3], xdir_dim_2, ydir_dim_2, shift_e2);
553 
554  const int size_of_edge_dofs = dofs_e1.size();
555  assert(size_of_edge_dofs == dofs_e2.size() || edge[1] == -1);
556 
557  const bool needReversion = reverseParametrized(edge);
558  const int coef = glueCoefficientDivergenceConforming(edge);
559 
560  // Check if the edge is hanging. For screens one would need an "else".
561  if (edge[1] > -1 && edge[0] > -1) {
562  for (int i = 0; i < size_of_edge_dofs; ++i) {
564  const int j = needReversion ? size_of_edge_dofs - 1 - i : i;
565  assert(dofs_e1[i] != dofs_e2[j] &&
566  "If this happens something went horribly wrong.");
567  d.dofs.push_back(std::min(dofs_e1[i], dofs_e2[j]));
568  d.dofs.push_back(std::max(dofs_e1[i], dofs_e2[j]));
569  d.coef = coef;
570  out.push_back(d);
571  }
572  }
573  }
574  out.shrink_to_fit();
575  return out;
576  }
577 };
578 }; // namespace GlueRoutines
579 
580 } // namespace Bembel
581 #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