Bembel
 
Loading...
Searching...
No Matches
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
14namespace Bembel {
15/*
16
17Every edge has an index between 0 and 3, where
180 = (0,0)->(1,0)
191 = (1,0)->(1,1)
202 = (1,1)->(0,1)
213 = (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
38namespace GlueRoutines {
43 std::vector<int> dofs;
44 int coef;
45};
49template <typename Derived, unsigned int DF>
51 static std::vector<dofIdentification> makeIdentification(
52 const std::vector<std::array<int, 4>> &edges_,
54 assert(false && "Needs to be specialized");
55 return {};
56 }
57};
58
59} // namespace GlueRoutines
60
75template <typename Derived>
76class Glue {
77 std::vector<std::array<int, 4>> edges_;
78 int dofs_after_glue;
79 Eigen::SparseMatrix<double> glue_mat_;
80
81 public:
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(
137 Derived,
138 LinearOperatorTraits<Derived>::Form>::makeIdentification(edges_,
140 proj);
141 }
153 Eigen::SparseMatrix<double> assembleGlueMatrix(
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 =
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;
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;
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
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
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],
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
231 return glue_matrix;
232 }
233};
234
235namespace GlueRoutines {
236
259std::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.
313inline bool edgeIsForwardParametrized(int edgeCase) {
314 return edgeCase == 0 || edgeCase == 1;
315}
316inline bool edgeIsBackwardsParametrized(int edgeCase) {
317 return !(edgeIsForwardParametrized(edgeCase));
318}
319inline bool normalComponentIsInwardDirected(int edgeCase) {
320 return edgeCase == 0 || edgeCase == 3;
321}
322inline bool normalComponentIsOutwardDirected(int edgeCase) {
323 return !(normalComponentIsInwardDirected(edgeCase));
324}
325
326inline 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
335inline 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}
343inline bool edgeToBeGluedInFirstComp(int edgeCase) {
344 return (edgeCase == 0 || edgeCase == 2) ? false : true;
345}
346
352template <typename Derived>
353struct glue_identificationmaker_<Derived, DifferentialForm::Discontinuous> {
354 static std::vector<dofIdentification> makeIdentification(
355 const std::vector<std::array<int, 4>> &edges_,
357 return {};
358 }
359};
360
370template <typename Derived>
371struct glue_identificationmaker_<Derived, DifferentialForm::Continuous> {
372 static std::vector<dofIdentification> makeIdentification(
373 const std::vector<std::array<int, 4>> &edges_,
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);
422 } else {
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],
431 const int large_store = std::max(already_stored_in[large_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.
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);
454 } else {
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],
463 const int large_store = std::max(already_stored_in[large_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.
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);
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
511template <typename Derived>
512struct glue_identificationmaker_<Derived, DifferentialForm::DivConforming> {
513 static std::vector<dofIdentification> makeIdentification(
514 const std::vector<std::array<int, 4>> &edges_,
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);
525 // sanity check
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.
541 (edgeToBeGluedInFirstComp(edge[2])
542 ? 0
544 superspace.get_number_of_patches()));
546 (edgeToBeGluedInFirstComp(edge[3])
547 ? 0
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_
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
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
Eigen::SparseMatrix< double > get_glue_matrix() const
Returns Glue matrix to assemble continuous B-splines.
Definition Glue.hpp:120
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
Routines for the evalutation of pointwise errors.
constexpr int getFunctionSpaceOutputDimension()
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...