PolyFEM
Loading...
Searching...
No Matches
LagrangeBasis2d.cpp
Go to the documentation of this file.
1
2#include "LagrangeBasis2d.hpp"
3
8
10
12
13#include <cassert>
14#include <array>
16
17using namespace polyfem;
18using namespace polyfem::assembler;
19using namespace polyfem::basis;
20using namespace polyfem::mesh;
21using namespace polyfem::quadrature;
22using namespace polyfem::utils;
23
24/*
25
26Axes:
27 y
28 |
29 o──x
30
31Boundaries:
32X axis: left/right
33Y axis: bottom/top
34
35
36
37
38
39Corner nodes:
40 v2
41 │ \
42 │ \
43 │ \
44 x x
45 │ \
46 │ \
47 │ \
48v0──────x─────v1
49
50v0 = (0, 0)
51v1 = (1, 0)
52v2 = (0, 1)
53
54Edge nodes:
55 x
56 │ \
57 │ \
58 │ \
59e2 e1
60 │ \
61 │ \
62 │ \
63 x──────e0───x
64
65e0 = (0.5, 0)
66e1 = (0.5, 0.5)
67e2 = ( 0, 0.5)
68
69
70
71
72
73
74
75
76
77Corner nodes:
78v3──────x─────v2
79 │ ┆ │
80 │ ┆ │
81 │ ┆ │
82 x┄┄┄┄┄┄x┄┄┄┄┄┄x
83 │ ┆ │
84 │ ┆ │
85 │ ┆ │
86v0──────x─────v1
87
88v0 = (0, 0)
89v1 = (1, 0)
90v2 = (1, 1)
91v3 = (0, 1)
92
93Edge nodes:
94 x─────e2──────x
95 │ ┆ │
96 │ ┆ │
97 │ ┆ │
98e3┄┄┄┄┄┄x┄┄┄┄┄e1
99 │ ┆ │
100 │ ┆ │
101 │ ┆ │
102 x─────e0──────x
103
104e0 = (0.5, 0)
105e1 = ( 1, 0.5)
106e2 = (0.5, 1)
107e3 = ( 0, 0.5)
108
109*/
110
111namespace
112{
113
114 void sort(std::array<int, 2> &array)
115 {
116 if (array[0] > array[1])
117 {
118 std::swap(array[0], array[1]);
119 }
120
121 assert(array[0] <= array[1]);
122 }
123
124 template <class InputIterator, class T>
125 int find_index(InputIterator first, InputIterator last, const T &val)
126 {
127 return std::distance(first, std::find(first, last, val));
128 }
129
130 Navigation::Index find_edge(const Mesh2D &mesh, int f, int v1, int v2)
131 {
132 std::array<int, 2> v = {{v1, v2}};
133 std::array<int, 2> u;
134 // std::sort(v.begin(), v.end());
135 sort(v);
136 auto idx = mesh.get_index_from_face(f, 0);
137 for (int lv = 0; lv < mesh.n_face_vertices(idx.face); ++lv)
138 {
139 u[0] = idx.vertex;
140 u[1] = mesh.switch_vertex(idx).vertex;
141 // std::sort(u.begin(), u.end());
142 sort(u);
143 if (u == v)
144 {
145 if (idx.vertex != v1)
146 idx = mesh.switch_vertex(idx);
147 assert(idx.vertex == v1);
148 return idx;
149 }
150 idx = mesh.next_around_face(idx);
151 }
152 throw std::runtime_error("Edge not found");
153 }
154
155 std::array<int, 3> tri_vertices_local_to_global(const Mesh2D &mesh, int f)
156 {
157 assert(mesh.is_simplex(f));
158
159 // Vertex nodes
160 std::array<int, 3> l2g;
161 for (int lv = 0; lv < 3; ++lv)
162 {
163 l2g[lv] = mesh.face_vertex(f, lv);
164 }
165
166 return l2g;
167 }
168
169 std::array<int, 4> quad_vertices_local_to_global(const Mesh2D &mesh, int f)
170 {
171 assert(mesh.is_cube(f));
172
173 // Vertex nodes
174 std::array<int, 4> l2g;
175 for (int lv = 0; lv < 4; ++lv)
176 {
177 l2g[lv] = mesh.face_vertex(f, lv);
178 }
179
180 return l2g;
181 }
182
183 void tri_local_to_global(const bool is_geom_bases, const int p, const Mesh2D &mesh, int f, const Eigen::VectorXi &discr_order, const Eigen::VectorXi &edge_orders, std::vector<int> &res, MeshNodes &nodes, std::vector<std::vector<int>> &edge_virtual_nodes)
184 {
185 int edge_offset = mesh.n_vertices();
186 int face_offset = edge_offset + mesh.n_edges();
187
188 const int n_edge_nodes = p > 1 ? ((p - 1) * 3) : 0;
189 const int nn = p > 2 ? (p - 2) : 0;
190 const int n_face_nodes = nn * (nn + 1) / 2;
191
192 if (p == 0)
193 {
194 res.push_back(nodes.node_id_from_face(f));
195 return;
196 }
197
198 // std::vector<int> res;
199 res.reserve(3 + n_edge_nodes + n_face_nodes);
200
201 // Vertex nodes
202 auto v = tri_vertices_local_to_global(mesh, f);
203
204 // Edge nodes
205 Eigen::Matrix<Navigation::Index, 3, 1> e;
206 Eigen::Matrix<int, 3, 2> ev;
207 ev.row(0) << v[0], v[1];
208 ev.row(1) << v[1], v[2];
209 ev.row(2) << v[2], v[0];
210 for (int le = 0; le < e.rows(); ++le)
211 {
212 const auto index = find_edge(mesh, f, ev(le, 0), ev(le, 1));
213 e[le] = index;
214 }
215
216 for (size_t lv = 0; lv < v.size(); ++lv)
217 {
218 const auto index = e[lv];
219 const auto other_face = mesh.switch_face(index).face;
220
221 if (is_geom_bases)
222 res.push_back(nodes.node_id_from_primitive(v[lv]));
223 else
224 {
225 if (!mesh.is_conforming())
226 {
227 const auto &ncmesh = dynamic_cast<const NCMesh2D &>(mesh);
228 // hanging vertex
229 if (ncmesh.leader_edge_of_vertex(ncmesh.face_vertex(f, lv)) >= 0)
230 res.push_back(-lv - 1);
231 else
232 res.push_back(nodes.node_id_from_primitive(v[lv]));
233 }
234 else
235 {
236 if (discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face))
237 res.push_back(-lv - 1);
238 else
239 res.push_back(nodes.node_id_from_primitive(v[lv]));
240 }
241 }
242 }
243
244 for (int le = 0; le < e.rows(); ++le)
245 {
246 const auto index = e[le];
247 const auto other_face = mesh.switch_face(index).face;
248
249 if (is_geom_bases)
250 {
251 auto node_ids = nodes.node_ids_from_edge(index, p - 1);
252 res.insert(res.end(), node_ids.begin(), node_ids.end());
253 }
254 else
255 {
256 if (!mesh.is_conforming())
257 {
258 const auto &ncmesh = dynamic_cast<const NCMesh2D &>(mesh);
259 const int edge_id = ncmesh.face_edge(f, le);
260 // follower edge
261 if (ncmesh.leader_edge_of_edge(edge_id) >= 0)
262 {
263 for (int tmp = 0; tmp < p - 1; ++tmp)
264 res.push_back(-le - 1);
265 }
266 // leader or conforming edge with constrained order
267 else if (edge_orders[edge_id] < discr_order[f])
268 {
269 for (int tmp = 0; tmp < p - 1; ++tmp)
270 res.push_back(-le - 1);
271 // leader edge
272 if (ncmesh.n_follower_edges(edge_id) > 0)
273 edge_virtual_nodes[edge_id] = nodes.node_ids_from_edge(index, edge_orders[edge_id] - 1);
274 }
275 // leader or conforming edge with unconstrained order
276 else
277 {
278 auto node_ids = nodes.node_ids_from_edge(index, p - 1);
279 res.insert(res.end(), node_ids.begin(), node_ids.end());
280 }
281 }
282 else
283 {
284 bool skip_other = discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face);
285
286 if (discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face))
287 {
288 for (int tmp = 0; tmp < p - 1; ++tmp)
289 res.push_back(-le - 1);
290 }
291 else
292 {
293 auto node_ids = nodes.node_ids_from_edge(index, p - 1);
294 res.insert(res.end(), node_ids.begin(), node_ids.end());
295 }
296 }
297 }
298 }
299
300 if (n_face_nodes > 0)
301 {
302 const auto index = e[0];
303
304 auto node_ids = nodes.node_ids_from_face(index, p - 2);
305 res.insert(res.end(), node_ids.begin(), node_ids.end());
306 }
307
308 assert(res.size() == size_t(3 + n_edge_nodes + n_face_nodes));
309 // return res;
310 }
311
312 void quad_local_to_global(const bool serendipity, const int q, const Mesh2D &mesh, int f, const Eigen::VectorXi &discr_order, std::vector<int> &res, MeshNodes &nodes)
313 {
314 int edge_offset = mesh.n_vertices();
315 int face_offset = edge_offset + mesh.n_edges();
316
317 const int nn = q > 1 ? (q - 1) : 0;
318 const int n_edge_nodes = nn * 4;
319 const int n_face_nodes = serendipity ? 0 : nn * nn;
320
321 if (q == 0)
322 {
323 res.push_back(nodes.node_id_from_face(f));
324 return;
325 }
326
327 // std::vector<int> res;
328 res.reserve(4 + n_edge_nodes + n_face_nodes);
329
330 // Vertex nodes
331 auto v = quad_vertices_local_to_global(mesh, f);
332
333 // Edge nodes
334 Eigen::Matrix<Navigation::Index, 4, 1> e;
335 Eigen::Matrix<int, 4, 2> ev;
336 ev.row(0) << v[0], v[1];
337 ev.row(1) << v[1], v[2];
338 ev.row(2) << v[2], v[3];
339 ev.row(3) << v[3], v[0];
340 for (int le = 0; le < e.rows(); ++le)
341 {
342 const auto index = find_edge(mesh, f, ev(le, 0), ev(le, 1));
343 e[le] = index;
344 }
345
346 for (size_t lv = 0; lv < v.size(); ++lv)
347 {
348 const auto index = e[lv];
349 const auto other_face = mesh.switch_face(index).face;
350
351 if (discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face))
352 res.push_back(-lv - 1);
353 else
354 res.push_back(nodes.node_id_from_primitive(v[lv]));
355 }
356
357 for (int le = 0; le < e.rows(); ++le)
358 {
359 const auto index = e[le];
360 const auto other_face = mesh.switch_face(index).face;
361
362 bool skip_other = discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face);
363
364 if (discr_order.size() > 0 && other_face >= 0 && discr_order(f) > discr_order(other_face))
365 {
366 for (int tmp = 0; tmp < q - 1; ++tmp)
367 res.push_back(-le - 1);
368 }
369 else
370 {
371 auto node_ids = nodes.node_ids_from_edge(index, q - 1);
372 res.insert(res.end(), node_ids.begin(), node_ids.end());
373 }
374 }
375
376 if (n_face_nodes > 0)
377 {
378 const auto index = e[0];
379
380 auto node_ids = nodes.node_ids_from_face(index, q - 1);
381 res.insert(res.end(), node_ids.begin(), node_ids.end());
382 }
383
384 assert(res.size() == size_t(4 + n_edge_nodes + n_face_nodes));
385 // return res;
386 }
387
388 // -----------------------------------------------------------------------------
389
390 // -----------------------------------------------------------------------------
391
405 void compute_nodes(
406 const Mesh2D &mesh,
407 const Eigen::VectorXi &discr_orders,
408 const Eigen::VectorXi &edge_orders,
409 const bool serendipity,
410 const bool has_polys,
411 const bool is_geom_bases,
412 MeshNodes &nodes,
413 std::vector<std::vector<int>> &edge_virtual_nodes,
414 std::vector<std::vector<int>> &element_nodes_id,
415 std::vector<LocalBoundary> &local_boundary,
416 std::map<int, InterfaceData> &poly_edge_to_data)
417 {
418 // Step 1: Assign global node ids for each quads
419 local_boundary.clear();
420
421 element_nodes_id.resize(mesh.n_faces());
422
423 if (!mesh.is_conforming())
424 {
425 const auto &ncmesh = dynamic_cast<const NCMesh2D &>(mesh);
426 edge_virtual_nodes.resize(ncmesh.n_edges());
427 }
428
429 for (int f = 0; f < mesh.n_faces(); ++f)
430 {
431 const int discr_order = discr_orders(f);
432 if (mesh.is_cube(f))
433 {
434 quad_local_to_global(serendipity, discr_order, mesh, f, discr_orders, element_nodes_id[f], nodes);
435
436 LocalBoundary lb(f, BoundaryType::QUAD_LINE);
437
438 auto v = quad_vertices_local_to_global(mesh, f);
439 Eigen::Matrix<int, 4, 2> ev;
440 ev.row(0) << v[0], v[1];
441 ev.row(1) << v[1], v[2];
442 ev.row(2) << v[2], v[3];
443 ev.row(3) << v[3], v[0];
444
445 for (int i = 0; i < int(ev.rows()); ++i)
446 {
447 const auto index = find_edge(mesh, f, ev(i, 0), ev(i, 1));
448 const int edge = index.edge;
449
450 if (mesh.is_boundary_edge(edge) || mesh.get_boundary_id(edge) > 0)
451 {
452 lb.add_boundary_primitive(edge, i);
453 }
454 }
455
456 if (!lb.empty())
457 local_boundary.emplace_back(lb);
458 }
459 else if (mesh.is_simplex(f))
460 {
461 tri_local_to_global(is_geom_bases, discr_order, mesh, f, discr_orders, edge_orders, element_nodes_id[f], nodes, edge_virtual_nodes);
462
463 auto v = tri_vertices_local_to_global(mesh, f);
464
465 Eigen::Matrix<int, 3, 2> ev;
466 ev.row(0) << v[0], v[1];
467 ev.row(1) << v[1], v[2];
468 ev.row(2) << v[2], v[0];
469
470 LocalBoundary lb(f, BoundaryType::TRI_LINE);
471
472 for (int i = 0; i < int(ev.rows()); ++i)
473 {
474 const auto index = find_edge(mesh, f, ev(i, 0), ev(i, 1));
475 const int edge = index.edge;
476
477 if (mesh.is_boundary_edge(edge) || mesh.get_boundary_id(edge) > 0)
478 {
479 lb.add_boundary_primitive(edge, i);
480 }
481 }
482
483 if (!lb.empty())
484 local_boundary.emplace_back(lb);
485 }
486 }
487
488 if (!has_polys)
489 return;
490
491 // Step 2: Iterate over edges of polygons and compute interface weights
492 Eigen::VectorXi indices;
493 for (int f = 0; f < mesh.n_faces(); ++f)
494 {
495 // Skip non-polytopes
496 if (!mesh.is_polytope(f))
497 {
498 continue;
499 }
500
501 auto index = mesh.get_index_from_face(f, 0);
502 for (int lv = 0; lv < mesh.n_face_vertices(f); ++lv)
503 {
504 auto index2 = mesh.switch_face(index);
505 if (index2.face >= 0)
506 {
507 // Opposite face is a quad, we need to set interface data
508 int f2 = index2.face;
509 const int discr_order = discr_orders(f2);
510
511 if (mesh.is_cube(f2))
512 {
513 indices = LagrangeBasis2d::quad_edge_local_nodes(discr_order, mesh, index2);
514 }
515 else if (mesh.is_simplex(f2))
516 {
517 indices = LagrangeBasis2d::tri_edge_local_nodes(discr_order, mesh, index2);
518 }
519 else
520 {
521 // assert(false);
522 continue;
523 }
524 InterfaceData data;
525 assert(indices.size() == 2 + discr_order - 1);
526 data.local_indices.insert(data.local_indices.begin(), indices.data(), indices.data() + indices.size());
527 assert(indices.size() == data.local_indices.size());
528 poly_edge_to_data[index2.edge] = data;
529 }
530 index = mesh.next_around_face(index);
531 }
532 }
533 }
534
541 void local_to_global(const Eigen::MatrixXd &verts, const Eigen::MatrixXd &uv, Eigen::MatrixXd &pts)
542 {
543 const int dim = verts.cols();
544 const int N = uv.rows();
545 assert(dim == 2);
546 assert(uv.cols() == dim);
547 assert(verts.rows() == dim + 1);
548
549 pts.setZero(N, dim);
550 for (int i = 0; i < N; i++)
551 pts.row(i) = uv(i, 0) * verts.row(1) + uv(i, 1) * verts.row(2) + (1.0 - uv(i, 0) - uv(i, 1)) * verts.row(0);
552 }
553
560 void global_to_local(const Eigen::MatrixXd &verts, const Eigen::MatrixXd &pts, Eigen::MatrixXd &uv)
561 {
562 const int dim = verts.cols();
563 const int N = pts.rows();
564 assert(dim == 2);
565 assert(verts.rows() == dim + 1);
566 assert(pts.cols() == dim);
567
568 Eigen::Matrix2d J;
569 for (int i = 0; i < dim; i++)
570 J.col(i) = verts.row(i + 1) - verts.row(0);
571
572 double detJ = J(0, 0) * J(1, 1) - J(0, 1) * J(1, 0);
573 J /= detJ;
574
575 uv.setZero(N, dim);
576 maybe_parallel_for(N, [&](int start, int end, int thread_id) {
577 for (int i = start; i < end; i++)
578 {
579 auto point = pts.row(i) - verts.row(0);
580 uv(i, 0) = J(1, 1) * point(0) - J(0, 1) * point(1);
581 uv(i, 1) = J(0, 0) * point(1) - J(1, 0) * point(0);
582 }
583 });
584 }
585
592 void compute_edge_orders(const NCMesh2D &mesh, const Eigen::VectorXi &elem_orders, Eigen::VectorXi &edge_orders)
593 {
594 const int max_order = elem_orders.maxCoeff();
595 edge_orders.setConstant(mesh.n_edges(), max_order);
596
597 for (int i = 0; i < mesh.n_faces(); i++)
598 for (int j = 0; j < mesh.n_face_vertices(i); j++)
599 edge_orders[mesh.face_edge(i, j)] = std::min(edge_orders[mesh.face_edge(i, j)], elem_orders[i]);
600
601 for (int i = 0; i < mesh.n_edges(); i++)
602 if (mesh.leader_edge_of_edge(i) >= 0)
603 edge_orders[mesh.leader_edge_of_edge(i)] = std::min(edge_orders[mesh.leader_edge_of_edge(i)], edge_orders[i]);
604
605 for (int i = 0; i < mesh.n_edges(); i++)
606 if (mesh.leader_edge_of_edge(i) >= 0)
607 edge_orders[i] = std::min(edge_orders[mesh.leader_edge_of_edge(i)], edge_orders[i]);
608 }
609} // anonymous namespace
610
611Eigen::VectorXi LagrangeBasis2d::tri_edge_local_nodes(const int p, const Mesh2D &mesh, Navigation::Index index)
612{
613 int f = index.face;
614 assert(mesh.is_simplex(f));
615
616 // Local to global mapping of node indices
617 auto l2g = tri_vertices_local_to_global(mesh, f);
618
619 // Extract requested interface
620 Eigen::VectorXi result(2 + (p - 1));
621 result[0] = find_index(l2g.begin(), l2g.end(), index.vertex);
622 result[result.size() - 1] = find_index(l2g.begin(), l2g.end(), mesh.switch_vertex(index).vertex);
623
624 if ((result[0] == 0 && result[result.size() - 1] == 1) || (result[0] == 1 && result[result.size() - 1] == 2) || (result[0] == 2 && result[result.size() - 1] == 0))
625 {
626 for (int i = 0; i < p - 1; ++i)
627 {
628 result[i + 1] = 3 + result[0] * (p - 1) + i;
629 }
630 }
631 else
632 {
633 for (int i = 0; i < p - 1; ++i)
634 {
635 result[i + 1] = 3 + (result[0] + (result[0] == 0 ? 3 : 0)) * (p - 1) - i - 1;
636 }
637 }
638
639 return result;
640}
641
642Eigen::VectorXi LagrangeBasis2d::quad_edge_local_nodes(const int q, const Mesh2D &mesh, Navigation::Index index)
643{
644 int f = index.face;
645 assert(mesh.is_cube(f));
646
647 // Local to global mapping of node indices
648 auto l2g = quad_vertices_local_to_global(mesh, f);
649
650 // Extract requested interface
651 Eigen::VectorXi result(2 + (q - 1));
652 result[0] = find_index(l2g.begin(), l2g.end(), index.vertex);
653 result[result.size() - 1] = find_index(l2g.begin(), l2g.end(), mesh.switch_vertex(index).vertex);
654
655 if ((result[0] == 0 && result[result.size() - 1] == 1) || (result[0] == 1 && result[result.size() - 1] == 2) || (result[0] == 2 && result[result.size() - 1] == 3) || (result[0] == 3 && result[result.size() - 1] == 0))
656 {
657 for (int i = 0; i < q - 1; ++i)
658 {
659 result[i + 1] = 4 + result[0] * (q - 1) + i;
660 }
661 }
662 else
663 {
664 for (int i = 0; i < q - 1; ++i)
665 {
666 result[i + 1] = 4 + (result[0] + (result[0] == 0 ? 4 : 0)) * (q - 1) - i - 1;
667 }
668 }
669
670 return result;
671}
672
673// -----------------------------------------------------------------------------
674
676 const Mesh2D &mesh,
677 const std::string &assembler,
678 const int quadrature_order,
679 const int mass_quadrature_order,
680 const int discr_order,
681 const bool serendipity,
682 const bool has_polys,
683 const bool is_geom_bases,
684 std::vector<ElementBases> &bases,
685 std::vector<LocalBoundary> &local_boundary,
686 std::map<int, InterfaceData> &poly_edge_to_data,
687 std::shared_ptr<MeshNodes> &mesh_nodes)
688{
689
690 Eigen::VectorXi discr_orders(mesh.n_faces());
691 discr_orders.setConstant(discr_order);
692
693 return build_bases(mesh, assembler, quadrature_order, mass_quadrature_order, discr_orders, serendipity, has_polys, is_geom_bases, bases, local_boundary, poly_edge_to_data, mesh_nodes);
694}
695
697 const Mesh2D &mesh,
698 const std::string &assembler,
699 const int quadrature_order,
700 const int mass_quadrature_order,
701 const Eigen::VectorXi &discr_orders,
702 const bool serendipity,
703 const bool has_polys,
704 const bool is_geom_bases,
705 std::vector<ElementBases> &bases,
706 std::vector<LocalBoundary> &local_boundary,
707 std::map<int, InterfaceData> &poly_edge_to_data,
708 std::shared_ptr<MeshNodes> &mesh_nodes)
709{
710 assert(!mesh.is_volume());
711 assert(discr_orders.size() == mesh.n_faces());
712
713 const int max_p = discr_orders.maxCoeff();
714 const int nn = max_p > 1 ? (max_p - 1) : 0;
715 const int n_face_nodes = std::max(nn * nn, max_p == 1 ? 1 : 0);
716
717 Eigen::VectorXi edge_orders;
718 if (!mesh.is_conforming())
719 {
720 const auto &ncmesh = dynamic_cast<const NCMesh2D &>(mesh);
721 compute_edge_orders(ncmesh, discr_orders, edge_orders);
722 }
723
724 mesh_nodes = std::make_shared<MeshNodes>(mesh, has_polys, !is_geom_bases, (max_p > 1 ? (max_p - 1) : 0) * (is_geom_bases ? 2 : 1), max_p == 0 ? 1 : n_face_nodes);
725 MeshNodes &nodes = *mesh_nodes;
726 std::vector<std::vector<int>> element_nodes_id, edge_virtual_nodes;
727 compute_nodes(mesh, discr_orders, edge_orders, serendipity, has_polys, is_geom_bases, nodes, edge_virtual_nodes, element_nodes_id, local_boundary, poly_edge_to_data);
728 // boundary_nodes = nodes.boundary_nodes();
729
730 bases.resize(mesh.n_faces());
731 std::vector<int> interface_elements;
732 interface_elements.reserve(mesh.n_faces());
733
734 for (int e = 0; e < mesh.n_faces(); ++e)
735 {
736 ElementBases &b = bases[e];
737 const int discr_order = discr_orders(e);
738 const int n_el_bases = element_nodes_id[e].size();
739 b.bases.resize(n_el_bases);
740
741 bool skip_interface_element = false;
742
743 for (int j = 0; j < n_el_bases; ++j)
744 {
745 // mark interface between elements of different order
746 const int global_index = element_nodes_id[e][j];
747 if (global_index < 0)
748 {
749 skip_interface_element = true;
750 break;
751 }
752 }
753
754 if (skip_interface_element)
755 {
756 interface_elements.push_back(e);
757 }
758
759 if (mesh.is_cube(e))
760 {
761 const int real_order = quadrature_order > 0 ? quadrature_order : AssemblerUtils::quadrature_order(assembler, discr_order, AssemblerUtils::BasisType::CUBE_LAGRANGE, 2);
762 const int real_mass_order = mass_quadrature_order > 0 ? mass_quadrature_order : AssemblerUtils::quadrature_order("Mass", discr_order, AssemblerUtils::BasisType::CUBE_LAGRANGE, 2);
763 b.set_quadrature([real_order](Quadrature &quad) {
764 QuadQuadrature quad_quadrature;
765 quad_quadrature.get_quadrature(real_order, quad);
766 });
767 b.set_mass_quadrature([real_mass_order](Quadrature &quad) {
768 QuadQuadrature quad_quadrature;
769 quad_quadrature.get_quadrature(real_mass_order, quad);
770 });
771 // quad_quadrature.get_quadrature(real_order, b.quadrature);
772
773 b.set_local_node_from_primitive_func([discr_order, e](const int primitive_id, const Mesh &mesh) {
774 const auto &mesh2d = dynamic_cast<const Mesh2D &>(mesh);
775 auto index = mesh2d.get_index_from_face(e);
776
777 for (int le = 0; le < mesh2d.n_face_vertices(e); ++le)
778 {
779 if (index.edge == primitive_id)
780 break;
781 index = mesh2d.next_around_face(index);
782 }
783 assert(index.edge == primitive_id);
784 return quad_edge_local_nodes(discr_order, mesh2d, index);
785 });
786
787 for (int j = 0; j < n_el_bases; ++j)
788 {
789 const int global_index = element_nodes_id[e][j];
790
791 // if(!skip_interface_element)
792 b.bases[j].init(discr_order, global_index, j, nodes.node_position(global_index));
793
794 const int dtmp = serendipity ? -2 : discr_order;
795
796 b.bases[j].set_basis([dtmp, j](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) { autogen::q_basis_value_2d(dtmp, j, uv, val); });
797 b.bases[j].set_grad([dtmp, j](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) { autogen::q_grad_basis_value_2d(dtmp, j, uv, val); });
798 }
799 }
800 else if (mesh.is_simplex(e))
801 {
802 const int real_order = quadrature_order > 0 ? quadrature_order : AssemblerUtils::quadrature_order(assembler, discr_order, AssemblerUtils::BasisType::SIMPLEX_LAGRANGE, 2);
803 const int real_mass_order = mass_quadrature_order > 0 ? mass_quadrature_order : AssemblerUtils::quadrature_order("Mass", discr_order, AssemblerUtils::BasisType::SIMPLEX_LAGRANGE, 2);
804 b.set_quadrature([real_order](Quadrature &quad) {
805 TriQuadrature tri_quadrature;
806 tri_quadrature.get_quadrature(real_order, quad);
807 });
808 b.set_mass_quadrature([real_mass_order](Quadrature &quad) {
809 TriQuadrature tri_quadrature;
810 tri_quadrature.get_quadrature(real_mass_order, quad);
811 });
812
813 b.set_local_node_from_primitive_func([discr_order, e](const int primitive_id, const Mesh &mesh) {
814 const auto &mesh2d = dynamic_cast<const Mesh2D &>(mesh);
815 auto index = mesh2d.get_index_from_face(e);
816
817 for (int le = 0; le < mesh2d.n_face_vertices(e); ++le)
818 {
819 if (index.edge == primitive_id)
820 break;
821 index = mesh2d.next_around_face(index);
822 }
823 assert(index.edge == primitive_id);
824 return tri_edge_local_nodes(discr_order, mesh2d, index);
825 });
826
827 const bool rational = is_geom_bases && mesh.is_rational() && !mesh.cell_weights(e).empty();
828
829 for (int j = 0; j < n_el_bases; ++j)
830 {
831 const int global_index = element_nodes_id[e][j];
832
833 if (!skip_interface_element)
834 {
835 b.bases[j].init(discr_order, global_index, j, nodes.node_position(global_index));
836 }
837
838 if (rational)
839 {
840 const auto &w = mesh.cell_weights(e);
841 assert(discr_order == 2);
842 assert(w.size() == 6);
843
844 b.bases[j].set_basis([discr_order, j, w](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) {
845 autogen::p_basis_value_2d(discr_order, j, uv, val);
846 Eigen::MatrixXd denom = val;
847 denom.setZero();
848 Eigen::MatrixXd tmp;
849
850 for (int k = 0; k < 6; ++k)
851 {
852 autogen::p_basis_value_2d(discr_order, k, uv, tmp);
853 denom += w[k] * tmp;
854 }
855
856 val = (w[j] * val.array() / denom.array()).eval();
857 });
858
859 b.bases[j].set_grad([discr_order, j, w](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) {
860 Eigen::MatrixXd b;
861 autogen::p_basis_value_2d(discr_order, j, uv, b);
862 autogen::p_grad_basis_value_2d(discr_order, j, uv, val);
863 Eigen::MatrixXd denom = b;
864 denom.setZero();
865 Eigen::MatrixXd denom_prime = val;
866 denom_prime.setZero();
867 Eigen::MatrixXd tmp;
868
869 for (int k = 0; k < 6; ++k)
870 {
871 autogen::p_basis_value_2d(discr_order, k, uv, tmp);
872 denom += w[k] * tmp;
873
874 autogen::p_grad_basis_value_2d(discr_order, k, uv, tmp);
875 denom_prime += w[k] * tmp;
876 }
877
878 val.col(0) = ((w[j] * val.col(0).array() * denom.array() - w[j] * b.array() * denom_prime.col(0).array()) / (denom.array() * denom.array())).eval();
879 val.col(1) = ((w[j] * val.col(1).array() * denom.array() - w[j] * b.array() * denom_prime.col(1).array()) / (denom.array() * denom.array())).eval();
880 });
881 }
882 else
883 {
884 // pick out basis functions using autogenerated code
885 b.bases[j].set_basis([discr_order, j](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) { autogen::p_basis_value_2d(discr_order, j, uv, val); });
886 b.bases[j].set_grad([discr_order, j](const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) { autogen::p_grad_basis_value_2d(discr_order, j, uv, val); });
887 }
888 }
889 }
890 else
891 {
892 // Polygon bases are built later on
893 }
894
895#ifndef NDEBUG
896 if (mesh.is_conforming())
897 {
898 Eigen::MatrixXd uv(4, 2);
899 uv << 0.1, 0.1, 0.3, 0.3, 0.9, 0.01, 0.01, 0.9;
900 Eigen::MatrixXd dx(4, 1);
901 dx.setConstant(1e-6);
902 Eigen::MatrixXd uvdx = uv;
903 uvdx.col(0) += dx;
904 Eigen::MatrixXd uvdy = uv;
905 uvdy.col(1) += dx;
906 Eigen::MatrixXd grad, val, vdx, vdy;
907
908 for (int j = 0; j < n_el_bases; ++j)
909 {
910 b.bases[j].eval_grad(uv, grad);
911
912 b.bases[j].eval_basis(uv, val);
913 b.bases[j].eval_basis(uvdx, vdx);
914 b.bases[j].eval_basis(uvdy, vdy);
915
916 assert((grad.col(0) - (vdx - val) / 1e-6).norm() < 1e-4);
917 assert((grad.col(1) - (vdy - val) / 1e-6).norm() < 1e-4);
918 }
919 }
920#endif
921 }
922
923 if (!is_geom_bases)
924 {
925 if (!mesh.is_conforming())
926 {
927 const auto &ncmesh = dynamic_cast<const NCMesh2D &>(mesh);
928
929 std::vector<std::vector<int>> elementOrder;
930 {
931 const int max_order = discr_orders.maxCoeff(), min_order = discr_orders.minCoeff();
932 int max_level = 0;
933 for (int e = 0; e < ncmesh.n_faces(); e++)
934 if (max_level < ncmesh.face_ref_level(e))
935 max_level = ncmesh.face_ref_level(e);
936
937 elementOrder.resize((max_level + 1) * (max_order - min_order + 1));
938 int N = 0;
939 int cur_level = 0;
940 while (cur_level <= max_level)
941 {
942 int order = min_order;
943 while (order <= max_order)
944 {
945 int cur_bucket = (max_order - min_order + 1) * cur_level + (order - min_order);
946 for (int i = 0; i < ncmesh.n_faces(); i++)
947 {
948 if (ncmesh.face_ref_level(i) != cur_level || discr_orders[i] != order)
949 continue;
950
951 N++;
952 elementOrder[cur_bucket].push_back(i);
953 }
954 order++;
955 }
956 cur_level++;
957 }
958 }
959
960 for (const auto &bucket : elementOrder)
961 {
962 if (bucket.size() == 0)
963 continue;
964 for (const int e : bucket)
965 {
966 ElementBases &b = bases[e];
967 const int discr_order = discr_orders(e);
968 const int n_el_bases = element_nodes_id[e].size();
969
970 for (int j = 0; j < n_el_bases; ++j)
971 {
972 const int global_index = element_nodes_id[e][j];
973
974 if (global_index >= 0)
975 b.bases[j].init(discr_order, global_index, j, nodes.node_position(global_index));
976 else
977 {
978 int large_edge = -1, local_edge = -1;
979 int opposite_element = -1;
980 bool is_on_leader_edge = false;
981
982 if (j < 3 + 3 * (discr_order - 1) && j >= 3)
983 {
984 local_edge = (j - 3) / (discr_order - 1);
985 large_edge = ncmesh.face_edge(e, local_edge);
986 if (ncmesh.n_follower_edges(large_edge) > 0)
987 is_on_leader_edge = true;
988 }
989
990 // this node is on a leader edge, but its order is constrained
991 if (is_on_leader_edge)
992 {
993 const int edge_order = edge_orders[large_edge];
994
995 // indices of fake nodes on this edge in the opposite element
996 Eigen::VectorXi indices;
997 indices.resize(discr_order + 1);
998 for (int i = 0; i < discr_order - 1; i++)
999 {
1000 indices[i + 1] = 3 + local_edge * (discr_order - 1) + i;
1001 }
1002 int index = indices[(j - 3) % (discr_order - 1) + 1];
1003
1004 // the position of node j in the opposite element
1005 Eigen::MatrixXd lnodes;
1006 autogen::p_nodes_2d(discr_order, lnodes);
1007 Eigen::Vector2d node_position = lnodes.row(index);
1008
1009 std::function<double(const int, const int, const double)> basis_1d = [](const int order, const int id, const double x) -> double {
1010 assert(id <= order && id >= 0);
1011 double y = 1;
1012 for (int o = 0; o <= order; o++)
1013 {
1014 if (o != id)
1015 y *= (x * order - o) / (id - o);
1016 }
1017 return y;
1018 };
1019
1020 // apply basis projection
1021 double x = NCMesh2D::element_weight_to_edge_weight(local_edge, node_position);
1022 for (int i = 0; i < edge_virtual_nodes[large_edge].size(); i++)
1023 {
1024 const int global_index = edge_virtual_nodes[large_edge][i];
1025 const double weight = basis_1d(edge_order, i + 1, x);
1026 if (std::abs(weight) < 1e-12)
1027 continue;
1028 b.bases[j].global().emplace_back(global_index, nodes.node_position(global_index), weight);
1029 }
1030
1031 const auto &global_1 = b.bases[local_edge].global();
1032 const auto &global_2 = b.bases[(local_edge + 1) % 3].global();
1033 double weight = basis_1d(edge_order, 0, x);
1034 if (std::abs(weight) > 1e-12)
1035 for (size_t ii = 0; ii < global_1.size(); ++ii)
1036 b.bases[j].global().emplace_back(global_1[ii].index, global_1[ii].node, weight * global_1[ii].val);
1037 weight = basis_1d(edge_order, edge_order, x);
1038 if (std::abs(weight) > 1e-12)
1039 for (size_t ii = 0; ii < global_2.size(); ++ii)
1040 b.bases[j].global().emplace_back(global_2[ii].index, global_2[ii].node, weight * global_2[ii].val);
1041 }
1042 else
1043 {
1044 Eigen::MatrixXd global_position;
1045 // this node is a hanging vertex, and it's not on a follower edge
1046 if (j < 3)
1047 {
1048 const int v_id = ncmesh.face_vertex(e, j);
1049 large_edge = ncmesh.leader_edge_of_vertex(v_id);
1050 opposite_element = ncmesh.face_neighbor(large_edge);
1051 global_position = ncmesh.point(v_id);
1052 }
1053 // this node is on a follower edge
1054 else
1055 {
1056 // find the local id of small edge
1057 int small_edge = ncmesh.face_edge(e, local_edge);
1058 large_edge = ncmesh.leader_edge_of_edge(small_edge);
1059
1060 // this edge is non-conforming
1061 if (ncmesh.n_face_neighbors(small_edge) == 1)
1062 opposite_element = ncmesh.face_neighbor(large_edge);
1063 // this edge is conforming, but the order on two sides is different
1064 else
1065 {
1067 idx.edge = small_edge;
1068 idx.vertex = ncmesh.edge_vertex(small_edge, 0);
1069 idx.face_corner = -1;
1070 idx.face = e;
1071 opposite_element = ncmesh.switch_face(idx).face;
1072 }
1073
1074 // the position of node j in the opposite element
1075 Eigen::MatrixXd lnodes;
1076 autogen::p_nodes_2d(discr_order, lnodes);
1077 global_position = lnodes.row(j);
1078
1079 Eigen::MatrixXd verts(ncmesh.n_face_vertices(e), 2);
1080 for (int i = 0; i < verts.rows(); i++)
1081 verts.row(i) = ncmesh.point(ncmesh.face_vertex(e, i));
1082
1083 Eigen::MatrixXd local_position = lnodes.row(j);
1084 local_to_global(verts, local_position, global_position);
1085 }
1086
1087 Eigen::MatrixXd verts(ncmesh.n_face_vertices(e), 2);
1088 for (int i = 0; i < verts.rows(); i++)
1089 verts.row(i) = ncmesh.point(ncmesh.face_vertex(opposite_element, i));
1090
1091 Eigen::MatrixXd node_position;
1092 global_to_local(verts, global_position, node_position);
1093
1094 // evaluate the basis of the opposite element at this node
1095 const auto &other_bases = bases[opposite_element];
1096 std::vector<AssemblyValues> w;
1097 other_bases.evaluate_bases(node_position, w);
1098
1099 // apply basis projection
1100 for (long i = 0; i < w.size(); ++i)
1101 {
1102 assert(w[i].val.size() == 1);
1103 if (std::abs(w[i].val(0)) < 1e-12)
1104 continue;
1105
1106 for (size_t ii = 0; ii < other_bases.bases[i].global().size(); ++ii)
1107 {
1108 const auto &other_global = other_bases.bases[i].global()[ii];
1109 b.bases[j].global().emplace_back(other_global.index, other_global.node, w[i].val(0) * other_global.val);
1110 }
1111 }
1112 }
1113
1114 auto &global_ = b.bases[j].global();
1115 if (global_.size() <= 1)
1116 continue;
1117
1118 std::map<int, Local2Global> list;
1119 for (size_t ii = 0; ii < global_.size(); ii++)
1120 {
1121 auto pair = list.insert({global_[ii].index, global_[ii]});
1122 if (!pair.second && pair.first != list.end())
1123 {
1124 assert((pair.first->second.node - global_[ii].node).norm() < 1e-12);
1125 pair.first->second.val += global_[ii].val;
1126 }
1127 }
1128
1129 global_.clear();
1130 for (auto it = list.begin(); it != list.end(); ++it)
1131 {
1132 if (std::abs(it->second.val) > 1e-12)
1133 global_.push_back(it->second);
1134 }
1135 }
1136 }
1137 }
1138 }
1139 }
1140 else
1141 {
1142 // loop over all potential element orders (since multiple loops may be needed to constrain interfaces between more than two elements)
1143 for (int pp = 2; pp <= autogen::MAX_P_BASES; ++pp)
1144 {
1145 // loop again over interface elements to address mismatched polynomial orders by constraining the higher order nodes
1146 for (int e : interface_elements)
1147 {
1148 ElementBases &b = bases[e];
1149 const int discr_order = discr_orders(e);
1150 const int n_el_bases = element_nodes_id[e].size();
1151
1152 assert(discr_order > 1);
1153 if (discr_order != pp)
1154 continue;
1155
1156 if (mesh.is_cube(e))
1157 {
1158 // TODO
1159 assert(false);
1160 }
1161 else if (mesh.is_simplex(e))
1162 {
1163 for (int j = 0; j < n_el_bases; ++j)
1164 {
1165 const int global_index = element_nodes_id[e][j];
1166
1167 if (global_index >= 0)
1168 b.bases[j].init(discr_order, global_index, j, nodes.node_position(global_index));
1169 else
1170 {
1171 const auto le = -(global_index + 1);
1172
1173 auto v = tri_vertices_local_to_global(mesh, e);
1174 Eigen::Matrix<int, 3, 2> ev;
1175 ev.row(0) << v[0], v[1];
1176 ev.row(1) << v[1], v[2];
1177 ev.row(2) << v[2], v[0];
1178
1179 const auto index = mesh.switch_face(find_edge(mesh, e, ev(le, 0), ev(le, 1)));
1180 const auto other_face = index.face;
1181
1182 Eigen::RowVector2d node_position;
1183 assert(discr_order > 1);
1184
1185 auto indices = tri_edge_local_nodes(discr_order, mesh, index);
1186 Eigen::MatrixXd lnodes;
1187 autogen::p_nodes_2d(discr_order, lnodes);
1188
1189 if (j < 3)
1190 node_position = lnodes.row(indices(0));
1191 else if (j < 3 + 3 * (discr_order - 1))
1192 node_position = lnodes.row(indices(((j - 3) % (discr_order - 1)) + 1));
1193 else
1194 assert(false);
1195
1196 const auto &other_bases = bases[other_face];
1197 // Eigen::MatrixXd w;
1198 std::vector<AssemblyValues> w;
1199 other_bases.evaluate_bases(node_position, w);
1200
1201 for (long i = 0; i < w.size(); ++i)
1202 {
1203 assert(w[i].val.size() == 1);
1204 if (std::abs(w[i].val(0)) < 1e-8)
1205 continue;
1206
1207 for (size_t ii = 0; ii < other_bases.bases[i].global().size(); ++ii)
1208 {
1209 const auto &other_global = other_bases.bases[i].global()[ii];
1210 // std::cout<<"e "<<e<<" " <<j << " gid "<<other_global.index<<std::endl;
1211 b.bases[j].global().emplace_back(other_global.index, other_global.node, w[i].val(0) * other_global.val);
1212 }
1213 }
1214 }
1215 }
1216 }
1217 else
1218 {
1219 // Polygon bases are built later on
1220 }
1221 }
1222 }
1223 }
1224 }
1225
1226 return nodes.n_nodes();
1227}
double val
Definition Assembler.cpp:86
int y
int edge_id
int x
static int quadrature_order(const std::string &assembler, const int basis_degree, const BasisType &b_type, const int dim)
utility for retrieving the needed quadrature order to precisely integrate the given form on the given...
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
void set_quadrature(const QuadratureFunction &fun)
void set_local_node_from_primitive_func(LocalNodeFromPrimitiveFunc fun)
sets mapping from local nodes to global nodes
std::vector< Basis > bases
one basis function per node in the element
void set_mass_quadrature(const QuadratureFunction &fun)
static int build_bases(const mesh::Mesh2D &mesh, const std::string &assembler, const int quadrature_order, const int mass_quadrature_order, const int discr_order, const bool serendipity, const bool has_polys, const bool is_geom_bases, std::vector< ElementBases > &bases, std::vector< mesh::LocalBoundary > &local_boundary, std::map< int, InterfaceData > &poly_edge_to_data, std::shared_ptr< mesh::MeshNodes > &mesh_nodes)
Builds FE basis functions over the entire mesh (P1, P2 over triangles, Q1, Q2 over quads).
static Eigen::VectorXi tri_edge_local_nodes(const int p, const mesh::Mesh2D &mesh, mesh::Navigation::Index index)
static Eigen::VectorXi quad_edge_local_nodes(const int q, const mesh::Mesh2D &mesh, mesh::Navigation::Index index)
Boundary primitive IDs for a single element.
Navigation::Index next_around_face(Navigation::Index idx) const
Definition Mesh2D.hpp:61
bool is_volume() const override
checks if mesh is volume
Definition Mesh2D.hpp:25
virtual Navigation::Index switch_vertex(Navigation::Index idx) const =0
virtual Navigation::Index get_index_from_face(int f, int lv=0) const =0
virtual Navigation::Index switch_face(Navigation::Index idx) const =0
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:39
virtual int n_vertices() const =0
number of vertices
bool is_polytope(const int el_id) const
checks if element is polygon compatible
Definition Mesh.cpp:365
bool is_rational() const
check if curved mesh has rational polynomials elements
Definition Mesh.hpp:287
virtual bool is_conforming() const =0
if the mesh is conforming
bool is_cube(const int el_id) const
checks if element is cube compatible
Definition Mesh.cpp:352
virtual int get_boundary_id(const int primitive) const
Get the boundary selection of an element (face in 3d, edge in 2d)
Definition Mesh.hpp:475
bool is_simplex(const int el_id) const
checks if element is simples compatible
Definition Mesh.cpp:422
const std::vector< double > & cell_weights(const int cell_index) const
weights for rational polynomial meshes
Definition Mesh.hpp:550
virtual bool is_boundary_edge(const int edge_global_id) const =0
is edge boundary
virtual int n_faces() const =0
number of faces
virtual int n_edges() const =0
number of edges
virtual int n_face_vertices(const int f_id) const =0
number of vertices of a face
virtual int face_vertex(const int f_id, const int lv_id) const =0
id of the face vertex
int face_edge(const int f_id, const int le_id) const
Definition NCMesh2D.hpp:195
static double element_weight_to_edge_weight(const int l, const Eigen::Vector2d &pos)
Definition NCMesh2D.cpp:427
int n_faces() const override
number of faces
Definition NCMesh2D.hpp:168
int leader_edge_of_edge(const int e_id) const
Definition NCMesh2D.hpp:201
int face_ref_level(const int f_id) const
Definition NCMesh2D.hpp:189
int n_face_vertices(const int f_id) const override
number of vertices of a face
Definition NCMesh2D.hpp:187
int n_edges() const override
number of edges
Definition NCMesh2D.hpp:169
void get_quadrature(const int order, Quadrature &quad)
void get_quadrature(const int order, Quadrature &quad)
enable_if_t< t==0 > sort(Eigen::Matrix< T, 3, 3 > &U, Eigen::Matrix< T, 3, 1 > &sigma, Eigen::Matrix< T, 3, 3 > &V)
Helper function of 3X3 SVD for sorting singular values.
list tmp
Definition p_bases.py:339
str nodes
Definition p_bases.py:372
list indices
Definition p_bases.py:232
Used for test only.
void p_grad_basis_value_2d(const int p, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void q_grad_basis_value_2d(const int q, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void p_nodes_2d(const int p, Eigen::MatrixXd &val)
void q_basis_value_2d(const int q, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void p_basis_value_2d(const int p, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void maybe_parallel_for(int size, const std::function< void(int, int, int)> &partial_for)
std::vector< int > local_indices