PolyFEM
Loading...
Searching...
No Matches
OutData.cpp
Go to the documentation of this file.
1#include "OutData.hpp"
2
3#include "Evaluator.hpp"
4#include "MatrixIO.hpp"
5
10
12
17
25
30
31#include <paraviewo/VTMWriter.hpp>
32#include <paraviewo/PVDWriter.hpp>
33
34#include <ipc/potentials/normal_adhesion_potential.hpp>
35#include <ipc/potentials/tangential_adhesion_potential.hpp>
36
37#include <SimpleBVH/BVH.hpp>
38
39#include <igl/write_triangle_mesh.h>
40#include <igl/edges.h>
41#include <igl/facet_adjacency_matrix.h>
42#include <igl/connected_components.h>
43
44#include <ipc/ipc.hpp>
45
46#include <algorithm>
47#include <filesystem>
48
49namespace polyfem::io
50{
51 bool OutputFieldOptions::export_field(const std::string &field) const
52 {
53 return fields.empty() || std::find(fields.begin(), fields.end(), field) != fields.end();
54 }
55
56 using CellType = paraviewo::CellType;
57 using CellElement = paraviewo::CellElement;
58
59 namespace
60 {
61 void add_output_fields(
62 paraviewo::ParaviewWriter &writer,
63 const OutputSample &sample,
64 const OutputFieldFunction &output_fields)
65 {
66 if (!output_fields)
67 return;
68
69 for (const OutputField &field : output_fields(sample))
70 {
71 if (field.values.rows() <= 0)
72 continue;
73
74 const int expected_rows =
75 field.association == OutputField::Association::Cell
76 ? sample.cell_count
77 : sample.points.rows();
78 if (field.values.rows() != expected_rows)
79 {
80 logger().warn(
81 "Skipping output field '{}' with {} rows; expected {} {} rows",
82 field.name, field.values.rows(), expected_rows,
83 field.association == OutputField::Association::Cell ? "cell" : "point");
84 continue;
85 }
86
87 if (field.association == OutputField::Association::Cell)
88 writer.add_cell_field(field.name, field.values);
89 else
90 writer.add_field(field.name, field.values);
91 }
92 }
93 } // namespace
94
96 const mesh::Mesh &mesh,
97 const int n_bases,
98 const std::vector<basis::ElementBases> &bases,
99 const std::vector<mesh::LocalBoundary> &total_local_boundary,
100 Eigen::MatrixXd &node_positions,
101 Eigen::MatrixXi &boundary_edges,
102 Eigen::MatrixXi &boundary_triangles,
103 std::vector<Eigen::Triplet<double>> &displacement_map_entries)
104 {
105 using namespace polyfem::mesh;
106
107 displacement_map_entries.clear();
108
109 if (mesh.is_volume())
110 {
111 if (mesh.has_poly())
112 {
113 logger().warn("Skipping as the mesh has polygons");
114 return;
115 }
116
117 const bool is_simplicial = mesh.is_simplicial();
118
119 node_positions.resize(n_bases + (is_simplicial ? 0 : mesh.n_faces()), 3);
120 node_positions.setZero();
121 const Mesh3D &mesh3d = dynamic_cast<const Mesh3D &>(mesh);
122
123 std::vector<std::tuple<int, int, int>> tris;
124
125 std::vector<bool> visited_node(n_bases, false);
126
127 std::stringstream print_warning;
128
129 for (const LocalBoundary &lb : total_local_boundary)
130 {
131 const basis::ElementBases &b = bases[lb.element_id()];
132
133 for (int j = 0; j < lb.size(); ++j)
134 {
135 const int eid = lb.global_primitive_id(j);
136 const int lid = lb[j];
137 const Eigen::VectorXi nodes = b.local_nodes_for_primitive(eid, mesh3d);
138
139 if (mesh.is_cube(lb.element_id()))
140 {
141 assert(!is_simplicial);
142 assert(!mesh.has_poly());
143 std::vector<int> loc_nodes;
144 RowVectorNd bary = RowVectorNd::Zero(3);
145
146 for (long n = 0; n < nodes.size(); ++n)
147 {
148 auto &bs = b.bases[nodes(n)];
149 const auto &glob = bs.global();
150 if (glob.size() != 1)
151 continue;
152
153 int gindex = glob.front().index;
154 node_positions.row(gindex) = glob.front().node;
155 bary += glob.front().node;
156 loc_nodes.push_back(gindex);
157 }
158
159 if (loc_nodes.size() != 4)
160 {
161 logger().trace("skipping element {} since it is not Q1", eid);
162 continue;
163 }
164
165 bary /= 4;
166
167 const int new_node = n_bases + eid;
168 node_positions.row(new_node) = bary;
169 tris.emplace_back(loc_nodes[1], loc_nodes[0], new_node);
170 tris.emplace_back(loc_nodes[2], loc_nodes[1], new_node);
171 tris.emplace_back(loc_nodes[3], loc_nodes[2], new_node);
172 tris.emplace_back(loc_nodes[0], loc_nodes[3], new_node);
173
174 for (int q = 0; q < 4; ++q)
175 {
176 if (!visited_node[loc_nodes[q]])
177 displacement_map_entries.emplace_back(loc_nodes[q], loc_nodes[q], 1);
178
179 visited_node[loc_nodes[q]] = true;
180 displacement_map_entries.emplace_back(new_node, loc_nodes[q], 0.25);
181 }
182
183 continue;
184 }
185 else if (mesh.is_prism(lb.element_id()))
186 {
187 assert(!is_simplicial);
188 assert(!mesh.has_poly());
189 std::vector<int> loc_nodes;
190 RowVectorNd bary = RowVectorNd::Zero(3);
191
192 for (long n = 0; n < nodes.size(); ++n)
193 {
194 auto &bs = b.bases[nodes(n)];
195 const auto &glob = bs.global();
196 if (glob.size() != 1)
197 continue;
198
199 int gindex = glob.front().index;
200 node_positions.row(gindex) = glob.front().node;
201 bary += glob.front().node;
202 loc_nodes.push_back(gindex);
203 }
204
205 auto update_mapping = [&displacement_map_entries, &visited_node](const std::vector<int> &loc_nodes) {
206 for (int k = 0; k < loc_nodes.size(); ++k)
207 {
208 if (!visited_node[loc_nodes[k]])
209 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
210
211 visited_node[loc_nodes[k]] = true;
212 }
213 };
214
215 if (loc_nodes.size() == 3)
216 {
217 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
218
219 update_mapping(loc_nodes);
220 }
221 else if (loc_nodes.size() == 6)
222 {
223 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[5]);
224 tris.emplace_back(loc_nodes[3], loc_nodes[1], loc_nodes[4]);
225 tris.emplace_back(loc_nodes[4], loc_nodes[2], loc_nodes[5]);
226 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[5]);
227
228 update_mapping(loc_nodes);
229 }
230 else if (loc_nodes.size() == 10)
231 {
232 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[8]);
233 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[9]);
234 tris.emplace_back(loc_nodes[4], loc_nodes[1], loc_nodes[5]);
235 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[9]);
236 tris.emplace_back(loc_nodes[6], loc_nodes[2], loc_nodes[7]);
237 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[9]);
238 tris.emplace_back(loc_nodes[8], loc_nodes[3], loc_nodes[9]);
239 tris.emplace_back(loc_nodes[9], loc_nodes[4], loc_nodes[5]);
240 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[9]);
241 update_mapping(loc_nodes);
242 }
243 else if (loc_nodes.size() == 15)
244 {
245 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[11]);
246 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[12]);
247 tris.emplace_back(loc_nodes[3], loc_nodes[12], loc_nodes[11]);
248 tris.emplace_back(loc_nodes[12], loc_nodes[10], loc_nodes[11]);
249 tris.emplace_back(loc_nodes[4], loc_nodes[5], loc_nodes[13]);
250 tris.emplace_back(loc_nodes[4], loc_nodes[13], loc_nodes[12]);
251 tris.emplace_back(loc_nodes[12], loc_nodes[13], loc_nodes[14]);
252 tris.emplace_back(loc_nodes[12], loc_nodes[14], loc_nodes[10]);
253 tris.emplace_back(loc_nodes[14], loc_nodes[9], loc_nodes[10]);
254 tris.emplace_back(loc_nodes[5], loc_nodes[1], loc_nodes[6]);
255 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[13]);
256 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[13]);
257 tris.emplace_back(loc_nodes[13], loc_nodes[7], loc_nodes[14]);
258 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[14]);
259 tris.emplace_back(loc_nodes[14], loc_nodes[8], loc_nodes[9]);
260 tris.emplace_back(loc_nodes[8], loc_nodes[2], loc_nodes[9]);
261 update_mapping(loc_nodes);
262 }
263 else if (loc_nodes.size() == 4)
264 {
265 bary /= 4;
266
267 const int new_node = n_bases + eid;
268 node_positions.row(new_node) = bary;
269 tris.emplace_back(loc_nodes[1], loc_nodes[0], new_node);
270 tris.emplace_back(loc_nodes[2], loc_nodes[1], new_node);
271 tris.emplace_back(loc_nodes[3], loc_nodes[2], new_node);
272 tris.emplace_back(loc_nodes[0], loc_nodes[3], new_node);
273
274 update_mapping(loc_nodes);
275 }
276 else
277 {
278 logger().trace("skipping element {} since it is not linear, it has {} nodes", eid, loc_nodes.size());
279 continue;
280 }
281
282 continue;
283 }
284
285 else if (mesh.is_pyramid(lb.element_id()))
286 {
287 assert(!is_simplicial);
288 assert(!mesh.has_poly());
289 std::vector<int> loc_nodes;
290
291 for (long n = 0; n < nodes.size(); ++n)
292 {
293 auto &bs = b.bases[nodes(n)];
294 const auto &glob = bs.global();
295 if (glob.size() != 1)
296 continue;
297
298 int gindex = glob.front().index;
299 node_positions.row(gindex) = glob.front().node;
300 loc_nodes.push_back(gindex);
301 }
302
303 auto update_mapping = [&displacement_map_entries, &visited_node](const std::vector<int> &loc_nodes) {
304 for (int k = 0; k < loc_nodes.size(); ++k)
305 {
306 if (!visited_node[loc_nodes[k]])
307 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
308
309 visited_node[loc_nodes[k]] = true;
310 }
311 };
312
313 if (loc_nodes.size() == 3)
314 {
315 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
316 update_mapping(loc_nodes);
317 }
318 else if (loc_nodes.size() == 4)
319 {
320 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
321 tris.emplace_back(loc_nodes[0], loc_nodes[2], loc_nodes[3]);
322 update_mapping(loc_nodes);
323 }
324 else
325 {
326 logger().trace("skipping element {} since it is not linear, it has {} nodes", eid, loc_nodes.size());
327 continue;
328 }
329
330 continue;
331 }
332
333 if (!mesh.is_simplex(lb.element_id()))
334 {
335 logger().trace("skipping element {} since it is not a simplex or hex", eid);
336 continue;
337 }
338
339 assert(mesh.is_simplex(lb.element_id()));
340
341 std::vector<int> loc_nodes;
342
343 bool is_follower = false;
344 if (!mesh3d.is_conforming())
345 {
346 for (long n = 0; n < nodes.size(); ++n)
347 {
348 auto &bs = b.bases[nodes(n)];
349 const auto &glob = bs.global();
350 if (glob.size() != 1)
351 {
352 is_follower = true;
353 break;
354 }
355 }
356 }
357
358 if (is_follower)
359 continue;
360
361 for (long n = 0; n < nodes.size(); ++n)
362 {
363 const basis::Basis &bs = b.bases[nodes(n)];
364 const std::vector<basis::Local2Global> &glob = bs.global();
365 if (glob.size() != 1)
366 continue;
367
368 int gindex = glob.front().index;
369 node_positions.row(gindex) = glob.front().node;
370 loc_nodes.push_back(gindex);
371 }
372
373 if (loc_nodes.size() == 3)
374 {
375 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
376 }
377 else if (loc_nodes.size() == 6)
378 {
379 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[5]);
380 tris.emplace_back(loc_nodes[3], loc_nodes[1], loc_nodes[4]);
381 tris.emplace_back(loc_nodes[4], loc_nodes[2], loc_nodes[5]);
382 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[5]);
383 }
384 else if (loc_nodes.size() == 10)
385 {
386 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[8]);
387 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[9]);
388 tris.emplace_back(loc_nodes[4], loc_nodes[1], loc_nodes[5]);
389 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[9]);
390 tris.emplace_back(loc_nodes[6], loc_nodes[2], loc_nodes[7]);
391 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[9]);
392 tris.emplace_back(loc_nodes[8], loc_nodes[3], loc_nodes[9]);
393 tris.emplace_back(loc_nodes[9], loc_nodes[4], loc_nodes[5]);
394 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[9]);
395 }
396 else if (loc_nodes.size() == 15)
397 {
398 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[11]);
399 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[12]);
400 tris.emplace_back(loc_nodes[3], loc_nodes[12], loc_nodes[11]);
401 tris.emplace_back(loc_nodes[12], loc_nodes[10], loc_nodes[11]);
402 tris.emplace_back(loc_nodes[4], loc_nodes[5], loc_nodes[13]);
403 tris.emplace_back(loc_nodes[4], loc_nodes[13], loc_nodes[12]);
404 tris.emplace_back(loc_nodes[12], loc_nodes[13], loc_nodes[14]);
405 tris.emplace_back(loc_nodes[12], loc_nodes[14], loc_nodes[10]);
406 tris.emplace_back(loc_nodes[14], loc_nodes[9], loc_nodes[10]);
407 tris.emplace_back(loc_nodes[5], loc_nodes[1], loc_nodes[6]);
408 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[13]);
409 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[13]);
410 tris.emplace_back(loc_nodes[13], loc_nodes[7], loc_nodes[14]);
411 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[14]);
412 tris.emplace_back(loc_nodes[14], loc_nodes[8], loc_nodes[9]);
413 tris.emplace_back(loc_nodes[8], loc_nodes[2], loc_nodes[9]);
414 }
415 else
416 {
417 print_warning << loc_nodes.size() << " ";
418 // assert(false);
419 }
420
421 if (!is_simplicial)
422 {
423 for (int k = 0; k < loc_nodes.size(); ++k)
424 {
425 if (!visited_node[loc_nodes[k]])
426 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
427
428 visited_node[loc_nodes[k]] = true;
429 }
430 }
431 }
432 }
433
434 if (print_warning.str().size() > 0)
435 logger().warn("Skipping faces as theys have {} nodes, boundary export supported up to p4", print_warning.str());
436
437 boundary_triangles.resize(tris.size(), 3);
438 for (int i = 0; i < tris.size(); ++i)
439 {
440 boundary_triangles.row(i) << std::get<0>(tris[i]), std::get<2>(tris[i]), std::get<1>(tris[i]);
441 }
442
443 if (boundary_triangles.rows() > 0)
444 {
445 igl::edges(boundary_triangles, boundary_edges);
446 }
447 }
448 else
449 {
450 node_positions.resize(n_bases, 2);
451 node_positions.setZero();
452 const Mesh2D &mesh2d = dynamic_cast<const Mesh2D &>(mesh);
453
454 std::vector<std::pair<int, int>> edges;
455
456 for (const LocalBoundary &lb : total_local_boundary)
457 {
458 const basis::ElementBases &b = bases[lb.element_id()];
459
460 for (int j = 0; j < lb.size(); ++j)
461 {
462 const int eid = lb.global_primitive_id(j);
463 const int lid = lb[j];
464 const Eigen::VectorXi nodes = b.local_nodes_for_primitive(eid, mesh2d);
465
466 int prev_node = -1;
467
468 for (long n = 0; n < nodes.size(); ++n)
469 {
470 const basis::Basis &bs = b.bases[nodes(n)];
471 const std::vector<basis::Local2Global> &glob = bs.global();
472 if (glob.size() != 1)
473 continue;
474
475 int gindex = glob.front().index;
476 node_positions.row(gindex) = glob.front().node.head<2>();
477
478 if (prev_node >= 0)
479 edges.emplace_back(prev_node, gindex);
480
481 prev_node = gindex;
482 }
483 }
484 }
485
486 boundary_triangles.resize(0, 0);
487 boundary_edges.resize(edges.size(), 2);
488 for (int i = 0; i < edges.size(); ++i)
489 {
490 boundary_edges.row(i) << edges[i].first, edges[i].second;
491 }
492 }
493 }
494
496 const mesh::Mesh &mesh,
497 const std::vector<basis::ElementBases> &gbases,
498 const std::vector<mesh::LocalBoundary> &total_local_boundary,
499 Eigen::MatrixXd &boundary_vis_vertices,
500 Eigen::MatrixXd &boundary_vis_local_vertices,
501 Eigen::MatrixXi &boundary_vis_elements,
502 Eigen::MatrixXi &boundary_vis_elements_ids,
503 Eigen::MatrixXi &boundary_vis_primitive_ids,
504 Eigen::MatrixXd &boundary_vis_normals) const
505 {
506 using namespace polyfem::mesh;
507
508 std::vector<Eigen::MatrixXd> lv, vertices, allnormals;
509 std::vector<int> el_ids, global_primitive_ids;
510 Eigen::MatrixXd uv, local_pts, tmp_n, normals;
512 const auto &sampler = ref_element_sampler;
513 const int n_samples = sampler.num_samples();
514 int size = 0;
515
516 std::vector<std::pair<int, int>> edges;
517 std::vector<std::tuple<int, int, int>> tris;
518
519 for (auto it = total_local_boundary.begin(); it != total_local_boundary.end(); ++it)
520 {
521 const auto &lb = *it;
522 const auto &gbs = gbases[lb.element_id()];
523
524 for (int k = 0; k < lb.size(); ++k)
525 {
526 switch (lb.type())
527 {
528 case BoundaryType::TRI_LINE:
530 utils::BoundarySampler::sample_parametric_tri_edge(lb[k], n_samples, uv, local_pts);
531 break;
532 case BoundaryType::QUAD_LINE:
534 utils::BoundarySampler::sample_parametric_quad_edge(lb[k], n_samples, uv, local_pts);
535 break;
536 case BoundaryType::QUAD:
538 utils::BoundarySampler::sample_parametric_quad_face(lb[k], n_samples, uv, local_pts);
539 break;
540 case BoundaryType::TRI:
542 utils::BoundarySampler::sample_parametric_tri_face(lb[k], n_samples, uv, local_pts);
543 break;
544 case BoundaryType::PRISM:
546 utils::BoundarySampler::sample_parametric_prism_face(lb[k], n_samples, uv, local_pts);
547 break;
548 case BoundaryType::PYRAMID:
550 utils::BoundarySampler::sample_parametric_pyramid_face(lb[k], n_samples, uv, local_pts);
551 break;
552 case BoundaryType::POLYGON:
553 utils::BoundarySampler::normal_for_polygon_edge(lb.element_id(), lb.global_primitive_id(k), mesh, tmp_n);
554 utils::BoundarySampler::sample_polygon_edge(lb.element_id(), lb.global_primitive_id(k), n_samples, mesh, uv, local_pts);
555 break;
556 case BoundaryType::POLYHEDRON:
557 assert(false);
558 break;
559 case BoundaryType::INVALID:
560 assert(false);
561 break;
562 default:
563 assert(false);
564 }
565
566 vertices.emplace_back();
567 lv.emplace_back(local_pts);
568 el_ids.push_back(lb.element_id());
569 global_primitive_ids.push_back(lb.global_primitive_id(k));
570 gbs.eval_geom_mapping(local_pts, vertices.back());
571 vals.compute(lb.element_id(), mesh.is_volume(), local_pts, gbs, gbs);
572 const int tris_start = tris.size();
573
574 if (mesh.is_volume())
575 {
576 const bool prism_quad = lb.type() == BoundaryType::PRISM && lb[k] >= 2;
577 const bool prism_tri = lb.type() == BoundaryType::PRISM && lb[k] < 2;
578
579 const bool pyramid_quad = lb.type() == BoundaryType::PYRAMID && lb[k] == 0;
580 const bool pyramid_tri = lb.type() == BoundaryType::PYRAMID && lb[k] > 0;
581
582 if (lb.type() == BoundaryType::QUAD || prism_quad || pyramid_quad)
583 {
584 const auto map = [n_samples, size](int i, int j) { return j * n_samples + i + size; };
585
586 for (int j = 0; j < n_samples - 1; ++j)
587 {
588 for (int i = 0; i < n_samples - 1; ++i)
589 {
590 tris.emplace_back(map(i, j), map(i + 1, j), map(i, j + 1));
591 tris.emplace_back(map(i + 1, j + 1), map(i, j + 1), map(i + 1, j));
592 }
593 }
594 }
595 else if (lb.type() == BoundaryType::TRI || prism_tri || pyramid_tri)
596 {
597 int index = 0;
598 std::vector<int> mapp(n_samples * n_samples, -1);
599 for (int j = 0; j < n_samples; ++j)
600 {
601 for (int i = 0; i < n_samples - j; ++i)
602 {
603 mapp[j * n_samples + i] = index;
604 ++index;
605 }
606 }
607 const auto map = [mapp, n_samples](int i, int j) {
608 if (j * n_samples + i >= mapp.size())
609 return -1;
610 return mapp[j * n_samples + i];
611 };
612
613 for (int j = 0; j < n_samples - 1; ++j)
614 {
615 for (int i = 0; i < n_samples - j; ++i)
616 {
617 if (map(i, j) >= 0 && map(i + 1, j) >= 0 && map(i, j + 1) >= 0)
618 tris.emplace_back(map(i, j) + size, map(i + 1, j) + size, map(i, j + 1) + size);
619
620 if (map(i + 1, j + 1) >= 0 && map(i, j + 1) >= 0 && map(i + 1, j) >= 0)
621 tris.emplace_back(map(i + 1, j + 1) + size, map(i, j + 1) + size, map(i + 1, j) + size);
622 }
623 }
624 }
625 else
626 {
627 assert(false);
628 }
629 }
630 else
631 {
632 for (int i = 0; i < vertices.back().rows() - 1; ++i)
633 edges.emplace_back(i + size, i + size + 1);
634 }
635
636 normals.resize(vals.jac_it.size(), tmp_n.cols());
637
638 for (int n = 0; n < vals.jac_it.size(); ++n)
639 {
640 normals.row(n) = tmp_n * vals.jac_it[n];
641 normals.row(n).normalize();
642 }
643
644 allnormals.push_back(normals);
645
646 tmp_n.setZero();
647 for (int n = 0; n < vals.jac_it.size(); ++n)
648 {
649 tmp_n += normals.row(n);
650 }
651
652 if (mesh.is_volume())
653 {
654 Eigen::Vector3d e1 = vertices.back().row(std::get<1>(tris.back()) - size) - vertices.back().row(std::get<0>(tris.back()) - size);
655 Eigen::Vector3d e2 = vertices.back().row(std::get<2>(tris.back()) - size) - vertices.back().row(std::get<0>(tris.back()) - size);
656
657 Eigen::Vector3d n = e1.cross(e2);
658 Eigen::Vector3d nn = tmp_n.transpose();
659
660 if (n.dot(nn) < 0)
661 {
662 for (int i = tris_start; i < tris.size(); ++i)
663 {
664 tris[i] = std::tuple<int, int, int>(std::get<0>(tris[i]), std::get<2>(tris[i]), std::get<1>(tris[i]));
665 }
666 }
667 }
668
669 size += vertices.back().rows();
670 }
671 }
672
673 boundary_vis_vertices.resize(size, vertices.front().cols());
674 boundary_vis_local_vertices.resize(size, vertices.front().cols());
675 boundary_vis_elements_ids.resize(size, 1);
676 boundary_vis_primitive_ids.resize(size, 1);
677 boundary_vis_normals.resize(size, vertices.front().cols());
678
679 if (mesh.is_volume())
680 boundary_vis_elements.resize(tris.size(), 3);
681 else
682 boundary_vis_elements.resize(edges.size(), 2);
683
684 int index = 0;
685 int ii = 0;
686 for (const auto &v : vertices)
687 {
688 boundary_vis_vertices.block(index, 0, v.rows(), v.cols()) = v;
689 boundary_vis_local_vertices.block(index, 0, v.rows(), v.cols()) = lv[ii];
690 boundary_vis_elements_ids.block(index, 0, v.rows(), 1).setConstant(el_ids[ii]);
691 boundary_vis_primitive_ids.block(index, 0, v.rows(), 1).setConstant(global_primitive_ids[ii++]);
692 index += v.rows();
693 }
694
695 index = 0;
696 for (const auto &n : allnormals)
697 {
698 boundary_vis_normals.block(index, 0, n.rows(), n.cols()) = n;
699 index += n.rows();
700 }
701
702 index = 0;
703 if (mesh.is_volume())
704 {
705 for (const auto &t : tris)
706 {
707 boundary_vis_elements.row(index) << std::get<0>(t), std::get<1>(t), std::get<2>(t);
708 ++index;
709 }
710 }
711 else
712 {
713 for (const auto &e : edges)
714 {
715 boundary_vis_elements.row(index) << e.first, e.second;
716 ++index;
717 }
718 }
719 }
720
722 const mesh::Mesh &mesh,
723 const Eigen::VectorXi &disc_orders,
724 const std::vector<basis::ElementBases> &gbases,
725 const std::map<int, Eigen::MatrixXd> &polys,
726 const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> &polys_3d,
727 const bool boundary_only,
728 Eigen::MatrixXd &points,
729 Eigen::MatrixXi &tets,
730 Eigen::MatrixXi &el_id,
731 Eigen::MatrixXd &discr,
732 Eigen::MatrixXd &local_points) const
733 {
734 const auto &sampler = ref_element_sampler;
735
736 const auto &current_bases = gbases;
737 int tet_total_size = 0;
738 int pts_total_size = 0;
739
740 Eigen::MatrixXd vis_pts_poly;
741 Eigen::MatrixXi vis_faces_poly, vis_edges_poly;
742
743 for (size_t i = 0; i < current_bases.size(); ++i)
744 {
745 const auto &bs = current_bases[i];
746
747 if (boundary_only && mesh.is_volume() && !mesh.is_boundary_element(i))
748 continue;
749
750 if (mesh.is_simplex(i))
751 {
752 tet_total_size += sampler.simplex_volume().rows();
753 pts_total_size += sampler.simplex_points().rows();
754 }
755 else if (mesh.is_cube(i))
756 {
757 tet_total_size += sampler.cube_volume().rows();
758 pts_total_size += sampler.cube_points().rows();
759 }
760 else if (mesh.is_prism(i))
761 {
762 tet_total_size += sampler.prism_volume().rows();
763 pts_total_size += sampler.prism_points().rows();
764 }
765 else if (mesh.is_pyramid(i))
766 {
767 tet_total_size += sampler.pyramid_volume().rows();
768 pts_total_size += sampler.pyramid_points().rows();
769 }
770 else
771 {
772 if (mesh.is_volume())
773 {
774 sampler.sample_polyhedron(polys_3d.at(i).first, polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
775
776 tet_total_size += vis_faces_poly.rows();
777 pts_total_size += vis_pts_poly.rows();
778 }
779 else
780 {
781 sampler.sample_polygon(polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
782
783 tet_total_size += vis_faces_poly.rows();
784 pts_total_size += vis_pts_poly.rows();
785 }
786 }
787 }
788
789 points.resize(pts_total_size, mesh.dimension());
790 local_points.resize(pts_total_size, mesh.dimension());
791 local_points.setZero();
792 tets.resize(tet_total_size, mesh.is_volume() ? 4 : 3);
793
794 el_id.resize(pts_total_size, 1);
795 discr.resize(pts_total_size, 1);
796
797 Eigen::MatrixXd mapped, tmp;
798 int tet_index = 0, pts_index = 0;
799
800 for (size_t i = 0; i < current_bases.size(); ++i)
801 {
802 const auto &bs = current_bases[i];
803
804 if (boundary_only && mesh.is_volume() && !mesh.is_boundary_element(i))
805 continue;
806
807 if (mesh.is_simplex(i))
808 {
809 bs.eval_geom_mapping(sampler.simplex_points(), mapped);
810
811 tets.block(tet_index, 0, sampler.simplex_volume().rows(), tets.cols()) = sampler.simplex_volume().array() + pts_index;
812 tet_index += sampler.simplex_volume().rows();
813
814 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
815 local_points.block(pts_index, 0, sampler.simplex_points().rows(), sampler.simplex_points().cols()) = sampler.simplex_points();
816 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
817 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
818 pts_index += mapped.rows();
819 }
820 else if (mesh.is_cube(i))
821 {
822 bs.eval_geom_mapping(sampler.cube_points(), mapped);
823
824 tets.block(tet_index, 0, sampler.cube_volume().rows(), tets.cols()) = sampler.cube_volume().array() + pts_index;
825 tet_index += sampler.cube_volume().rows();
826
827 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
828 local_points.block(pts_index, 0, sampler.cube_points().rows(), sampler.cube_points().cols()) = sampler.cube_points();
829 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
830 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
831 pts_index += mapped.rows();
832 }
833 else if (mesh.is_prism(i))
834 {
835 bs.eval_geom_mapping(sampler.prism_points(), mapped);
836
837 tets.block(tet_index, 0, sampler.prism_volume().rows(), tets.cols()) = sampler.prism_volume().array() + pts_index;
838 tet_index += sampler.prism_volume().rows();
839
840 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
841 local_points.block(pts_index, 0, sampler.prism_points().rows(), sampler.prism_points().cols()) = sampler.prism_points();
842 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
843 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
844 pts_index += mapped.rows();
845 }
846 else if (mesh.is_pyramid(i))
847 {
848 bs.eval_geom_mapping(sampler.pyramid_points(), mapped);
849
850 tets.block(tet_index, 0, sampler.pyramid_volume().rows(), tets.cols()) = sampler.pyramid_volume().array() + pts_index;
851 tet_index += sampler.pyramid_volume().rows();
852
853 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
854 local_points.block(pts_index, 0, sampler.pyramid_points().rows(), sampler.pyramid_points().cols()) = sampler.pyramid_points();
855 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
856 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
857 pts_index += mapped.rows();
858 }
859 else
860 {
861 if (mesh.is_volume())
862 {
863 sampler.sample_polyhedron(polys_3d.at(i).first, polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
864 bs.eval_geom_mapping(vis_pts_poly, mapped);
865
866 tets.block(tet_index, 0, vis_faces_poly.rows(), tets.cols()) = vis_faces_poly.array() + pts_index;
867 tet_index += vis_faces_poly.rows();
868
869 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
870 local_points.block(pts_index, 0, vis_pts_poly.rows(), vis_pts_poly.cols()) = vis_pts_poly;
871 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(-1);
872 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
873 pts_index += mapped.rows();
874 }
875 else
876 {
877 sampler.sample_polygon(polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
878 bs.eval_geom_mapping(vis_pts_poly, mapped);
879
880 tets.block(tet_index, 0, vis_faces_poly.rows(), tets.cols()) = vis_faces_poly.array() + pts_index;
881 tet_index += vis_faces_poly.rows();
882
883 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
884 local_points.block(pts_index, 0, vis_pts_poly.rows(), vis_pts_poly.cols()) = vis_pts_poly;
885 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(-1);
886 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
887 pts_index += mapped.rows();
888 }
889 }
890 }
891
892 assert(pts_index == points.rows());
893 assert(tet_index == tets.rows());
894 }
895
897 const mesh::Mesh &mesh,
898 const Eigen::VectorXi &output_orders,
899 const std::vector<basis::ElementBases> &bases,
900 Eigen::MatrixXd &points,
901 std::vector<CellElement> &elements,
902 Eigen::MatrixXi &el_id,
903 Eigen::MatrixXd &discr,
904 Eigen::MatrixXd &local_points) const
905 {
906 // if (!mesh)
907 // {
908 // logger().error("Load the mesh first!");
909 // return;
910 // }
911 // if (n_bases <= 0)
912 // {
913 // logger().error("Build the bases first!");
914 // return;
915 // }
916 // assert(mesh.is_linear());
917
918 std::vector<RowVectorNd> nodes;
919 int pts_total_size = 0;
920 elements.resize(bases.size());
921 Eigen::MatrixXd ref_pts;
922
923 for (size_t i = 0; i < bases.size(); ++i)
924 {
925 const auto &bs = bases[i];
926 if (mesh.is_volume())
927 {
928 if (mesh.is_simplex(i))
929 autogen::p_nodes_3d(output_orders(i), ref_pts);
930 else if (mesh.is_cube(i))
931 autogen::q_nodes_3d(output_orders(i), ref_pts);
932 else if (mesh.is_prism(i))
933 {
934 autogen::prism_nodes_3d(output_orders(i), output_orders(i), ref_pts);
935 }
936 else if (mesh.is_pyramid(i))
937 {
938 autogen::pyramid_nodes_3d(output_orders(i) == 2 ? -1 : output_orders(i), ref_pts);
939 }
940 else
941 continue;
942 }
943 else
944 {
945 if (mesh.is_simplex(i))
946 autogen::p_nodes_2d(output_orders(i), ref_pts);
947 else if (mesh.is_cube(i))
948 autogen::q_nodes_2d(output_orders(i), ref_pts);
949 else
950 {
951 const int n_v = static_cast<const mesh::Mesh2D &>(mesh).n_face_vertices(i);
952 ref_pts.resize(n_v, 2);
953 }
954 }
955
956 pts_total_size += ref_pts.rows();
957 }
958
959 points.resize(pts_total_size, mesh.dimension());
960 local_points.resize(pts_total_size, mesh.dimension());
961 local_points.setZero();
962
963 el_id.resize(pts_total_size, 1);
964 discr.resize(pts_total_size, 1);
965
966 Eigen::MatrixXd mapped;
967 int pts_index = 0;
968
969 std::string error_msg = "";
970
971 for (size_t i = 0; i < bases.size(); ++i)
972 {
973 const auto &bs = bases[i];
974 if (mesh.is_volume())
975 {
976 if (mesh.is_simplex(i))
977 autogen::p_nodes_3d(output_orders(i), ref_pts);
978 else if (mesh.is_cube(i))
979 autogen::q_nodes_3d(output_orders(i), ref_pts);
980 else if (mesh.is_prism(i))
981 {
982 autogen::prism_nodes_3d(output_orders(i), output_orders(i), ref_pts);
983 }
984 else if (mesh.is_pyramid(i))
985 {
986 autogen::pyramid_nodes_3d(output_orders(i) == 2 ? -1 : output_orders(i), ref_pts);
987 }
988 else
989 continue;
990 }
991 else
992 {
993 if (mesh.is_simplex(i))
994 autogen::p_nodes_2d(output_orders(i), ref_pts);
995 else if (mesh.is_cube(i))
996 autogen::q_nodes_2d(output_orders(i), ref_pts);
997 else
998 continue;
999 }
1000
1001 bs.eval_geom_mapping(ref_pts, mapped);
1002
1003 for (int j = 0; j < mapped.rows(); ++j)
1004 {
1005 points.row(pts_index) = mapped.row(j);
1006 local_points.row(pts_index).leftCols(ref_pts.cols()) = ref_pts.row(j);
1007 el_id(pts_index) = i;
1008 discr(pts_index) = output_orders(i);
1009 elements[i].vertices.push_back(pts_index);
1010
1011 pts_index++;
1012 }
1013
1014 if (mesh.is_simplex(i))
1015 {
1016 if (mesh.is_volume())
1017 {
1018 const int n_nodes = elements[i].vertices.size();
1019 if (output_orders(i) >= 3)
1020 {
1021 std::swap(elements[i].vertices[16], elements[i].vertices[17]);
1022 std::swap(elements[i].vertices[17], elements[i].vertices[18]);
1023 std::swap(elements[i].vertices[18], elements[i].vertices[19]);
1024 }
1025 if (output_orders(i) > 4)
1026 error_msg = "Saving high-order meshes not implemented for P5+ elements!";
1027 }
1028 else
1029 {
1030 if (output_orders(i) == 4)
1031 {
1032 const int n_nodes = elements[i].vertices.size();
1033 std::swap(elements[i].vertices[n_nodes - 1], elements[i].vertices[n_nodes - 2]);
1034 }
1035 if (output_orders(i) > 4)
1036 error_msg = "Saving high-order meshes not implemented for P5+ elements!";
1037 }
1038 }
1039 else if (mesh.is_cube(i) && mesh.is_volume())
1040 {
1041 const int n_nodes = elements[i].vertices.size();
1042 if (output_orders(i) == 2) // Lagrange hex, order=2
1043 {
1044 std::swap(elements[i].vertices[12], elements[i].vertices[16]);
1045 std::swap(elements[i].vertices[13], elements[i].vertices[17]);
1046 std::swap(elements[i].vertices[14], elements[i].vertices[18]);
1047 std::swap(elements[i].vertices[15], elements[i].vertices[19]);
1048 std::swap(elements[i].vertices[18], elements[i].vertices[19]); // a hack fix
1049 }
1050 // if (disc_orders(i) == 3) // Incomplete fix, need to fix order on the edge
1051 // {
1052 // std::swap(elements[i].vertices[24], elements[i].vertices[16]);
1053 // std::swap(elements[i].vertices[25], elements[i].vertices[17]);
1054 // std::swap(elements[i].vertices[26], elements[i].vertices[18]);
1055 // std::swap(elements[i].vertices[27], elements[i].vertices[19]);
1056 // std::swap(elements[i].vertices[28], elements[i].vertices[20]);
1057 // std::swap(elements[i].vertices[29], elements[i].vertices[21]);
1058 // std::swap(elements[i].vertices[30], elements[i].vertices[22]);
1059 // std::swap(elements[i].vertices[31], elements[i].vertices[23]);
1060 // std::swap(elements[i].vertices[28], elements[i].vertices[30]); // hack
1061 // std::swap(elements[i].vertices[29], elements[i].vertices[31]); // hack
1062 // }
1063 if (output_orders(i) > 2)
1064 error_msg = "Saving high-order meshes not implemented for P2+ elements!";
1065 }
1066 else if (output_orders(i) > 1)
1067 error_msg = "Saving high-order meshes not implemented for Q2+ elements!";
1068 }
1069
1070 if (!error_msg.empty())
1071 logger().warn(error_msg);
1072
1073 for (size_t i = 0; i < bases.size(); ++i)
1074 {
1075 if (mesh.is_volume() || !mesh.is_polytope(i))
1076 continue;
1077
1078 const auto &mesh2d = static_cast<const mesh::Mesh2D &>(mesh);
1079 const int n_v = mesh2d.n_face_vertices(i);
1080
1081 for (int j = 0; j < n_v; ++j)
1082 {
1083 points.row(pts_index) = mesh2d.point(mesh2d.face_vertex(i, j));
1084 local_points.row(pts_index) = mesh2d.point(mesh2d.face_vertex(i, j));
1085 el_id(pts_index) = i;
1086 discr(pts_index) = output_orders(i);
1087 elements[i].vertices.push_back(pts_index);
1088
1089 pts_index++;
1090 }
1091 }
1092
1093 for (size_t i = 0; i < bases.size(); ++i)
1094 {
1095 if (!mesh.is_volume())
1096 {
1097 if (elements[i].vertices.size() == 1)
1098 elements[i].ctype = CellType::Vertex;
1099 else if (elements[i].vertices.size() == 2)
1100 elements[i].ctype = CellType::Line;
1101 else if (mesh.is_simplex(i))
1102 elements[i].ctype = CellType::Triangle;
1103 else if (mesh.is_cube(i))
1104 elements[i].ctype = CellType::Quadrilateral;
1105 else
1106 elements[i].ctype = CellType::Polygon;
1107 }
1108 else
1109 {
1110 if (mesh.is_simplex(i))
1111 elements[i].ctype = CellType::Tetrahedron;
1112 else if (mesh.is_cube(i))
1113 elements[i].ctype = CellType::Hexahedron;
1114 else if (mesh.is_prism(i))
1115 elements[i].ctype = CellType::Wedge;
1116 else if (mesh.is_pyramid(i))
1117 elements[i].ctype = CellType::Pyramid;
1118 }
1119 }
1120
1121 assert(pts_index == points.rows());
1122 }
1123
1125 const OutputSpace &space,
1126 const OutputFieldFunction &output_fields,
1127 const bool is_time_dependent,
1128 const double tend_in,
1129 const double dt,
1130 const ExportOptions &opts,
1131 const std::string &vis_mesh_path) const
1132 {
1133 if (!space.mesh)
1134 {
1135 logger().error("Load the mesh first!");
1136 return;
1137 }
1138
1139 double tend = tend_in;
1140 if (tend <= 0)
1141 tend = 1;
1142
1143 if (!vis_mesh_path.empty() && !is_time_dependent)
1144 {
1145 save_vtu(
1146 vis_mesh_path, space, output_fields,
1147 tend, dt, opts);
1148 }
1149 }
1150
1151 bool OutGeometryData::ExportOptions::export_field(const std::string &field) const
1152 {
1153 return fields.empty() || std::find(fields.begin(), fields.end(), field) != fields.end();
1154 }
1155
1156 OutGeometryData::ExportOptions::ExportOptions(const json &args, const bool is_mesh_linear, const bool mesh_has_prisms, const bool is_problem_scalar)
1157 {
1158 fields = args["output"]["paraview"]["fields"];
1159
1160 volume = args["output"]["paraview"]["volume"];
1161 surface = args["output"]["paraview"]["surface"];
1162 wire = args["output"]["paraview"]["wireframe"];
1163 points = args["output"]["paraview"]["points"];
1164 contact_forces = args["output"]["paraview"]["options"]["contact_forces"] && !is_problem_scalar;
1165 friction_forces = args["output"]["paraview"]["options"]["friction_forces"] && !is_problem_scalar;
1166 normal_adhesion_forces = args["output"]["paraview"]["options"]["normal_adhesion_forces"] && !is_problem_scalar;
1167 tangential_adhesion_forces = args["output"]["paraview"]["options"]["tangential_adhesion_forces"] && !is_problem_scalar;
1168
1169 if (args["output"]["paraview"]["options"]["force_high_order"])
1170 use_sampler = false;
1171 else
1172 use_sampler = !(is_mesh_linear && args["output"]["paraview"]["high_order_mesh"]);
1173 boundary_only = use_sampler && args["output"]["advanced"]["vis_boundary_only"];
1174 sol_on_grid = args["output"]["advanced"]["sol_on_grid"] > 0;
1175
1176 discretization_order = args["output"]["paraview"]["options"]["discretization_order"];
1177
1178 reorder_output = args["output"]["data"]["advanced"]["reorder_nodes"];
1179
1180 use_hdf5 = args["output"]["paraview"]["options"]["use_hdf5"];
1181 }
1182
1184 const std::string &path,
1185 const OutputSpace &space,
1186 const OutputFieldFunction &output_fields,
1187 const double t,
1188 const double dt,
1189 const ExportOptions &opts) const
1190 {
1191 if (!space.mesh)
1192 {
1193 logger().error("Load the mesh first!");
1194 return;
1195 }
1196
1197 const bool save_contact =
1198 space.collision_mesh
1200 || (!opts.fields.empty() && opts.export_field("adaptive_dhat")));
1201
1202 logger().info("Saving vtu to {}; volume={}, surface={}, contact={}, points={}, wireframe={}",
1203 path, opts.volume, opts.surface, save_contact, opts.points, opts.wire);
1204
1205 const std::filesystem::path fs_path(path);
1206 const std::string path_stem = fs_path.stem().string();
1207 const std::string base_path = (fs_path.parent_path() / path_stem).string();
1208
1209 if (opts.volume)
1210 {
1211 save_volume(base_path + opts.file_extension(), space, output_fields, t, dt, opts);
1212 }
1213
1214 if (opts.surface)
1215 {
1216 save_surface(base_path + "_surf" + opts.file_extension(), space, output_fields, t, dt, opts);
1217 }
1218
1219 if (save_contact)
1220 {
1221 save_contact_surface(base_path + "_surf" + opts.file_extension(), space, output_fields, t, dt, opts);
1222 }
1223
1224 if (opts.wire)
1225 {
1226 save_wire(base_path + "_wire" + opts.file_extension(), space, output_fields, t, opts);
1227 }
1228
1229 if (opts.points)
1230 {
1231 save_points(base_path + "_points" + opts.file_extension(), space, output_fields, opts);
1232 }
1233
1234 paraviewo::VTMWriter vtm(t);
1235 if (opts.volume)
1236 vtm.add_dataset("Volume", "data", path_stem + opts.file_extension());
1237 if (opts.surface)
1238 vtm.add_dataset("Surface", "data", path_stem + "_surf" + opts.file_extension());
1239 if (save_contact)
1240 vtm.add_dataset("Contact", "data", path_stem + "_surf_contact" + opts.file_extension());
1241 if (opts.wire)
1242 vtm.add_dataset("Wireframe", "data", path_stem + "_wire" + opts.file_extension());
1243 if (opts.points)
1244 vtm.add_dataset("Points", "data", path_stem + "_points" + opts.file_extension());
1245 vtm.save(base_path + ".vtm");
1246 }
1247
1249 const std::string &path,
1250 const OutputSpace &space,
1251 const OutputFieldFunction &output_fields,
1252 const double t,
1253 const double dt,
1254 const ExportOptions &opts) const
1255 {
1256 if (!space.mesh || !space.geometry_bases)
1257 return;
1258
1259 static const std::map<int, Eigen::MatrixXd> empty_polys;
1260 static const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> empty_polys_3d;
1261
1262 const mesh::Mesh &mesh = *space.mesh;
1263 const std::vector<basis::ElementBases> &gbases = *space.geometry_bases;
1264 const std::map<int, Eigen::MatrixXd> &polys = space.polys ? *space.polys : empty_polys;
1265 const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> &polys_3d = space.polys_3d ? *space.polys_3d : empty_polys_3d;
1266 const Eigen::VectorXi output_orders =
1267 space.output_orders.size() == mesh.n_elements()
1268 ? space.output_orders
1269 : Eigen::VectorXi::Ones(mesh.n_elements());
1270 const mesh::Obstacle *obstacle = space.obstacle;
1271
1272 Eigen::MatrixXd points;
1273 Eigen::MatrixXi tets;
1274 Eigen::MatrixXi el_id;
1275 Eigen::MatrixXd discr;
1276 Eigen::MatrixXd local_points;
1277 std::vector<CellElement> elements;
1278
1279 if (opts.use_sampler)
1280 build_vis_mesh(mesh, output_orders, gbases,
1281 polys, polys_3d, opts.boundary_only,
1282 points, tets, el_id, discr, local_points);
1283 else
1284 {
1285 build_high_order_vis_mesh(mesh, output_orders, gbases,
1286 points, elements, el_id, discr, local_points);
1287 }
1288
1289 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1290 if (opts.use_hdf5)
1291 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1292 else
1293 tmpw = std::make_shared<paraviewo::VTUWriter>();
1294 paraviewo::ParaviewWriter &writer = *tmpw;
1295
1296 if (obstacle && obstacle->n_vertices() > 0)
1297 {
1298 discr.conservativeResize(discr.size() + obstacle->n_vertices(), 1);
1299 discr.bottomRows(obstacle->n_vertices()).setZero();
1300 }
1301
1302 if (opts.discretization_order && opts.export_field("discr"))
1303 writer.add_field("discr", discr);
1304
1305 if (obstacle && obstacle->n_vertices() > 0)
1306 {
1307 const int orig_p = points.rows();
1308 points.conservativeResize(points.rows() + obstacle->n_vertices(), points.cols());
1309 points.bottomRows(obstacle->n_vertices()) = obstacle->v();
1310
1311 if (elements.empty())
1312 {
1313 for (int i = 0; i < tets.rows(); ++i)
1314 {
1315 elements.emplace_back();
1316 elements.back().ctype = CellType::Tetrahedron;
1317 for (int j = 0; j < tets.cols(); ++j)
1318 elements.back().vertices.push_back(tets(i, j));
1319 }
1320 }
1321
1322 for (int i = 0; i < obstacle->get_face_connectivity().rows(); ++i)
1323 {
1324 elements.emplace_back();
1325 elements.back().ctype = CellType::Triangle;
1326 for (int j = 0; j < obstacle->get_face_connectivity().cols(); ++j)
1327 elements.back().vertices.push_back(obstacle->get_face_connectivity()(i, j) + orig_p);
1328 }
1329
1330 for (int i = 0; i < obstacle->get_edge_connectivity().rows(); ++i)
1331 {
1332 elements.emplace_back();
1333 elements.back().ctype = CellType::Line;
1334 for (int j = 0; j < obstacle->get_edge_connectivity().cols(); ++j)
1335 elements.back().vertices.push_back(obstacle->get_edge_connectivity()(i, j) + orig_p);
1336 }
1337
1338 for (int i = 0; i < obstacle->get_vertex_connectivity().size(); ++i)
1339 {
1340 elements.emplace_back();
1341 elements.back().ctype = CellType::Vertex;
1342 elements.back().vertices.push_back(obstacle->get_vertex_connectivity()(i) + orig_p);
1343 }
1344 }
1345
1346 // Write the solution alias last so it is the default for warp-by-vector.
1347 OutputSample sample;
1348 sample.points = points;
1349 sample.local_points = local_points;
1350 sample.element_ids = el_id.col(0);
1352 sample.cell_count = elements.empty() ? tets.rows() : static_cast<int>(elements.size());
1353 sample.time = t;
1354 sample.dt = dt;
1355 add_output_fields(writer, sample, output_fields);
1356
1357 if (opts.sol_on_grid && output_fields && grid_points.rows() > 0)
1358 {
1359 OutputSample grid_sample;
1360 grid_sample.points = grid_points;
1361 grid_sample.element_ids = grid_points_to_elements.col(0);
1362 grid_sample.domain = OutputSample::Domain::Grid;
1363 grid_sample.local_points.resize(grid_points.rows(), mesh.dimension());
1364 grid_sample.local_points.setZero();
1365 for (int i = 0; i < grid_points.rows(); ++i)
1366 {
1367 if (grid_sample.element_ids(i) >= 0)
1368 grid_sample.local_points.row(i) = grid_points_bc.row(i).rightCols(mesh.dimension());
1369 }
1370 grid_sample.time = t;
1371 grid_sample.dt = dt;
1372 grid_sample.requested_fields = {
1373 "solution",
1374 "solution_gradient",
1375 "pressure",
1376 "pressure_gradient",
1377 };
1378
1379 io::write_matrix(path + "_grid.txt", grid_points);
1380 for (const OutputField &field : output_fields(grid_sample))
1381 {
1382 if (field.association != OutputField::Association::Point || field.values.rows() != grid_points.rows())
1383 continue;
1384 if (field.name == "solution")
1385 io::write_matrix(path + "_sol.txt", field.values);
1386 else if (field.name == "solution_gradient")
1387 io::write_matrix(path + "_grad.txt", field.values);
1388 else if (field.name == "pressure")
1389 io::write_matrix(path + "_p_sol.txt", field.values);
1390 else if (field.name == "pressure_gradient")
1391 io::write_matrix(path + "_p_grad.txt", field.values);
1392 }
1393 }
1394
1395 if (elements.empty())
1396 writer.write_mesh(path, points, tets, mesh.is_volume() ? CellType::Tetrahedron : CellType::Triangle);
1397 else
1398 writer.write_mesh(path, points, elements);
1399 }
1400
1402 const std::string &export_surface,
1403 const OutputSpace &space,
1404 const OutputFieldFunction &output_fields,
1405 const double t,
1406 const double dt_in,
1407 const ExportOptions &opts) const
1408 {
1409 if (!space.mesh || !space.geometry_bases || !space.total_local_boundary)
1410 return;
1411
1412 const mesh::Mesh &mesh = *space.mesh;
1413 const std::vector<basis::ElementBases> &gbases = *space.geometry_bases;
1414
1415 Eigen::MatrixXd boundary_vis_vertices;
1416 Eigen::MatrixXd boundary_vis_local_vertices;
1417 Eigen::MatrixXi boundary_vis_elements;
1418 Eigen::MatrixXi boundary_vis_elements_ids;
1419 Eigen::MatrixXi boundary_vis_primitive_ids;
1420 Eigen::MatrixXd boundary_vis_normals;
1421
1422 build_vis_boundary_mesh(mesh, gbases, *space.total_local_boundary,
1423 boundary_vis_vertices, boundary_vis_local_vertices, boundary_vis_elements,
1424 boundary_vis_elements_ids, boundary_vis_primitive_ids, boundary_vis_normals);
1425
1426 Eigen::MatrixXd discr, b_sidesets;
1427 discr.resize(boundary_vis_vertices.rows(), 1);
1428 b_sidesets.resize(boundary_vis_vertices.rows(), 1);
1429 b_sidesets.setZero();
1430
1431 for (int i = 0; i < boundary_vis_vertices.rows(); ++i)
1432 {
1433 const auto s_id = mesh.get_boundary_id(boundary_vis_primitive_ids(i));
1434 if (s_id > 0)
1435 {
1436 b_sidesets(i) = s_id;
1437 }
1438
1439 const int el_index = boundary_vis_elements_ids(i);
1440 discr(i) = space.output_orders.size() == mesh.n_elements() ? space.output_orders(el_index) : 1;
1441 }
1442
1443 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1444 if (opts.use_hdf5)
1445 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1446 else
1447 tmpw = std::make_shared<paraviewo::VTUWriter>();
1448 paraviewo::ParaviewWriter &writer = *tmpw;
1449
1450 if (opts.export_field("normals"))
1451 writer.add_field("normals", boundary_vis_normals);
1452 if (opts.export_field("discr"))
1453 writer.add_field("discr", discr);
1454 if (opts.export_field("sidesets"))
1455 writer.add_field("sidesets", b_sidesets);
1456
1457 // Write the solution alias last so it is the default for warp-by-vector.
1458 OutputSample sample;
1459 sample.points = boundary_vis_vertices;
1460 sample.local_points = boundary_vis_local_vertices;
1461 sample.element_ids = boundary_vis_elements_ids.col(0);
1462 sample.primitive_ids = boundary_vis_primitive_ids;
1463 sample.normals = boundary_vis_normals;
1465 sample.cell_count = boundary_vis_elements.rows();
1466 sample.time = t;
1467 sample.dt = dt_in;
1468 add_output_fields(writer, sample, output_fields);
1469 writer.write_mesh(export_surface, boundary_vis_vertices, boundary_vis_elements, mesh.is_volume() ? CellType::Triangle : CellType::Line);
1470 }
1471
1473 const std::string &export_surface,
1474 const OutputSpace &space,
1475 const OutputFieldFunction &output_fields,
1476 const double t,
1477 const double dt_in,
1478 const ExportOptions &opts) const
1479 {
1480 if (!space.collision_mesh)
1481 return;
1482
1483 const ipc::CollisionMesh &collision_mesh = *space.collision_mesh;
1484
1485 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1486 if (opts.use_hdf5)
1487 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1488 else
1489 tmpw = std::make_shared<paraviewo::VTUWriter>();
1490 paraviewo::ParaviewWriter &writer = *tmpw;
1491
1492 // Write the solution alias last so it is the default for warp-by-vector.
1493 OutputSample sample;
1494 sample.points = collision_mesh.rest_positions();
1496 sample.cell_count = static_cast<int>(
1497 collision_mesh.dim() == 3 ? collision_mesh.num_faces() : collision_mesh.num_edges());
1498 sample.time = t;
1499 sample.dt = dt_in;
1500 add_output_fields(writer, sample, output_fields);
1501
1502 const std::filesystem::path surface_path(export_surface);
1503 const std::string contact_path =
1504 (surface_path.parent_path() / (surface_path.stem().string() + "_contact" + surface_path.extension().string())).string();
1505 writer.write_mesh(
1506 contact_path,
1507 collision_mesh.rest_positions(),
1508 collision_mesh.dim() == 3 ? collision_mesh.faces() : collision_mesh.edges(),
1509 collision_mesh.dim() == 3 ? CellType::Triangle : CellType::Line);
1510 }
1511
1513 const std::string &name,
1514 const OutputSpace &space,
1515 const OutputFieldFunction &output_fields,
1516 const double t,
1517 const ExportOptions &opts) const
1518 {
1519 if (!space.mesh || !space.geometry_bases)
1520 return;
1521
1522 static const std::map<int, Eigen::MatrixXd> empty_polys;
1523 static const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> empty_polys_3d;
1524
1525 const std::vector<basis::ElementBases> &gbases = *space.geometry_bases;
1526 const mesh::Mesh &mesh = *space.mesh;
1527 const std::map<int, Eigen::MatrixXd> &polys = space.polys ? *space.polys : empty_polys;
1528 const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> &polys_3d = space.polys_3d ? *space.polys_3d : empty_polys_3d;
1529 const Eigen::VectorXi output_orders =
1530 space.output_orders.size() == mesh.n_elements()
1531 ? space.output_orders
1532 : Eigen::VectorXi::Ones(mesh.n_elements());
1533
1534 Eigen::MatrixXd points, discr, local_points;
1535 Eigen::MatrixXi cells, element_ids, edges;
1536 build_vis_mesh(mesh, output_orders, gbases, polys, polys_3d, /*boundary_only=*/false, points, cells, element_ids, discr, local_points);
1537 if (cells.size() > 0)
1538 igl::edges(cells, edges);
1539 else
1540 edges.resize(0, 2);
1541
1542 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1543 if (opts.use_hdf5)
1544 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1545 else
1546 tmpw = std::make_shared<paraviewo::VTUWriter>();
1547 paraviewo::ParaviewWriter &writer = *tmpw;
1548
1549 // Write the solution alias last so it is the default for warp-by-vector.
1550 OutputSample sample;
1551 sample.points = points;
1552 sample.local_points = local_points;
1553 if (element_ids.cols() > 0)
1554 sample.element_ids = element_ids.col(0);
1556 sample.cell_count = edges.rows();
1557 sample.time = t;
1558 add_output_fields(writer, sample, output_fields);
1559
1560 writer.write_mesh(name, points, edges, CellType::Line);
1561 }
1562
1564 const std::string &path,
1565 const OutputSpace &space,
1566 const OutputFieldFunction &output_fields,
1567 const ExportOptions &opts) const
1568 {
1569 if (!space.mesh || !space.dirichlet_nodes || !space.dirichlet_nodes_position)
1570 return;
1571
1572 const auto &dirichlet_nodes = *space.dirichlet_nodes;
1573 const auto &dirichlet_nodes_position = *space.dirichlet_nodes_position;
1574 const mesh::Mesh &mesh = *space.mesh;
1575
1576 Eigen::MatrixXd b_sidesets(dirichlet_nodes_position.size(), 1);
1577 b_sidesets.setZero();
1578 Eigen::MatrixXd points(dirichlet_nodes_position.size(), mesh.dimension());
1579 std::vector<CellElement> cells(dirichlet_nodes_position.size());
1580
1581 for (int i = 0; i < dirichlet_nodes_position.size(); ++i)
1582 {
1583 const int n_id = dirichlet_nodes[i];
1584 const auto s_id = mesh.get_node_id(n_id);
1585 if (s_id > 0)
1586 {
1587 b_sidesets(i) = s_id;
1588 }
1589
1590 points.row(i) = dirichlet_nodes_position[i];
1591 cells[i].vertices.push_back(i);
1592 cells[i].ctype = CellType::Vertex;
1593 }
1594
1595 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1596 if (opts.use_hdf5)
1597 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1598 else
1599 tmpw = std::make_shared<paraviewo::VTUWriter>();
1600 paraviewo::ParaviewWriter &writer = *tmpw;
1601
1602 if (opts.export_field("sidesets"))
1603 writer.add_field("sidesets", b_sidesets);
1604
1605 // Write the solution alias last so it is the default for warp-by-vector.
1606 OutputSample sample;
1607 sample.points = points;
1608 sample.node_ids.resize(dirichlet_nodes.size());
1609 for (int i = 0; i < dirichlet_nodes.size(); ++i)
1610 sample.node_ids(i) = dirichlet_nodes[i];
1612 sample.cell_count = static_cast<int>(cells.size());
1613 add_output_fields(writer, sample, output_fields);
1614 writer.write_mesh(path, points, cells);
1615 }
1616
1618 const std::string &name,
1619 const std::function<std::string(int)> &vtu_names,
1620 int time_steps, double t0, double dt, int skip_frame) const
1621 {
1622 paraviewo::PVDWriter::save_pvd(name, vtu_names, time_steps, t0, dt, skip_frame);
1623 }
1624
1625 void OutGeometryData::init_sampler(const polyfem::mesh::Mesh &mesh, const double vismesh_rel_area)
1626 {
1627 ref_element_sampler.init(mesh.is_volume(), mesh.n_elements(), vismesh_rel_area);
1628 }
1629
1630 void OutGeometryData::build_grid(const polyfem::mesh::Mesh &mesh, const double spacing)
1631 {
1632 if (spacing <= 0)
1633 return;
1634
1635 RowVectorNd min, max;
1636 mesh.bounding_box(min, max);
1637 const RowVectorNd delta = max - min;
1638 const int nx = delta[0] / spacing + 1;
1639 const int ny = delta[1] / spacing + 1;
1640 const int nz = delta.cols() >= 3 ? (delta[2] / spacing + 1) : 1;
1641 const int n = nx * ny * nz;
1642
1643 grid_points.resize(n, delta.cols());
1644 int index = 0;
1645 for (int i = 0; i < nx; ++i)
1646 {
1647 const double x = (delta[0] / (nx - 1)) * i + min[0];
1648
1649 for (int j = 0; j < ny; ++j)
1650 {
1651 const double y = (delta[1] / (ny - 1)) * j + min[1];
1652
1653 if (delta.cols() <= 2)
1654 {
1655 grid_points.row(index++) << x, y;
1656 }
1657 else
1658 {
1659 for (int k = 0; k < nz; ++k)
1660 {
1661 const double z = (delta[2] / (nz - 1)) * k + min[2];
1662 grid_points.row(index++) << x, y, z;
1663 }
1664 }
1665 }
1666 }
1667
1668 assert(index == n);
1669
1670 std::vector<std::array<Eigen::Vector3d, 2>> boxes;
1671 mesh.elements_boxes(boxes);
1672
1673 SimpleBVH::BVH bvh;
1674 bvh.init(boxes);
1675
1676 const double eps = 1e-6;
1677
1678 grid_points_to_elements.resize(grid_points.rows(), 1);
1679 grid_points_to_elements.setConstant(-1);
1680
1681 grid_points_bc.resize(grid_points.rows(), mesh.is_volume() ? 4 : 3);
1682
1683 for (int i = 0; i < grid_points.rows(); ++i)
1684 {
1685 const Eigen::Vector3d min(
1686 grid_points(i, 0) - eps,
1687 grid_points(i, 1) - eps,
1688 (mesh.is_volume() ? grid_points(i, 2) : 0) - eps);
1689
1690 const Eigen::Vector3d max(
1691 grid_points(i, 0) + eps,
1692 grid_points(i, 1) + eps,
1693 (mesh.is_volume() ? grid_points(i, 2) : 0) + eps);
1694
1695 std::vector<unsigned int> candidates;
1696
1697 bvh.intersect_box(min, max, candidates);
1698
1699 for (const auto cand : candidates)
1700 {
1701 if (!mesh.is_simplex(cand))
1702 {
1703 logger().warn("Element {} is not simplex, skipping", cand);
1704 continue;
1705 }
1706
1707 Eigen::MatrixXd coords;
1708 mesh.barycentric_coords(grid_points.row(i), cand, coords);
1709
1710 for (int d = 0; d < coords.size(); ++d)
1711 {
1712 if (fabs(coords(d)) < 1e-8)
1713 coords(d) = 0;
1714 else if (fabs(coords(d) - 1) < 1e-8)
1715 coords(d) = 1;
1716 }
1717
1718 if (coords.array().minCoeff() >= 0 && coords.array().maxCoeff() <= 1)
1719 {
1720 grid_points_to_elements(i) = cand;
1721 grid_points_bc.row(i) = coords;
1722 break;
1723 }
1724 }
1725 }
1726 }
1727
1728 void OutStatsData::compute_mesh_size(const polyfem::mesh::Mesh &mesh_in, const std::vector<polyfem::basis::ElementBases> &bases_in, const int n_samples, const bool use_curved_mesh_size)
1729 {
1730 Eigen::MatrixXd samples_simplex, samples_cube, mapped, p0, p1, p;
1731
1732 mesh_size = 0;
1733 average_edge_length = 0;
1734 min_edge_length = std::numeric_limits<double>::max();
1735
1736 if (!use_curved_mesh_size)
1737 {
1738 mesh_in.get_edges(p0, p1);
1739 p = p0 - p1;
1740 min_edge_length = p.rowwise().norm().minCoeff();
1741 average_edge_length = p.rowwise().norm().mean();
1742 mesh_size = p.rowwise().norm().maxCoeff();
1743
1744 logger().info("hmin: {}", min_edge_length);
1745 logger().info("hmax: {}", mesh_size);
1746 logger().info("havg: {}", average_edge_length);
1747
1748 return;
1749 }
1750
1751 if (mesh_in.is_volume())
1752 {
1753 utils::EdgeSampler::sample_3d_simplex(n_samples, samples_simplex);
1754 utils::EdgeSampler::sample_3d_cube(n_samples, samples_cube);
1755 }
1756 else
1757 {
1758 utils::EdgeSampler::sample_2d_simplex(n_samples, samples_simplex);
1759 utils::EdgeSampler::sample_2d_cube(n_samples, samples_cube);
1760 }
1761
1762 int n = 0;
1763 for (size_t i = 0; i < bases_in.size(); ++i)
1764 {
1765 if (mesh_in.is_polytope(i))
1766 continue;
1767 int n_edges;
1768
1769 if (mesh_in.is_simplex(i))
1770 {
1771 n_edges = mesh_in.is_volume() ? 6 : 3;
1772 bases_in[i].eval_geom_mapping(samples_simplex, mapped);
1773 }
1774 else
1775 {
1776 n_edges = mesh_in.is_volume() ? 12 : 4;
1777 bases_in[i].eval_geom_mapping(samples_cube, mapped);
1778 }
1779
1780 for (int j = 0; j < n_edges; ++j)
1781 {
1782 double current_edge = 0;
1783 for (int k = 0; k < n_samples - 1; ++k)
1784 {
1785 p0 = mapped.row(j * n_samples + k);
1786 p1 = mapped.row(j * n_samples + k + 1);
1787 p = p0 - p1;
1788
1789 current_edge += p.norm();
1790 }
1791
1792 mesh_size = std::max(current_edge, mesh_size);
1793 min_edge_length = std::min(current_edge, min_edge_length);
1794 average_edge_length += current_edge;
1795 ++n;
1796 }
1797 }
1798
1799 average_edge_length /= n;
1800
1801 logger().info("hmin: {}", min_edge_length);
1802 logger().info("hmax: {}", mesh_size);
1803 logger().info("havg: {}", average_edge_length);
1804 }
1805
1807 {
1808 *this = OutStatsData();
1809 }
1810
1811 void OutStatsData::count_flipped_elements(const polyfem::mesh::Mesh &mesh, const std::vector<polyfem::basis::ElementBases> &gbases)
1812 {
1813 using namespace mesh;
1814
1815 logger().info("Counting flipped elements...");
1816 const auto &els_tag = mesh.elements_tag();
1817
1818 // flipped_elements.clear();
1819 for (size_t i = 0; i < gbases.size(); ++i)
1820 {
1821 if (mesh.is_polytope(i))
1822 continue;
1823
1825 if (!vals.is_geom_mapping_positive(mesh.is_volume(), gbases[i]))
1826 {
1827 ++n_flipped;
1828
1829 static const std::vector<std::string> element_type_names{{
1830 "Simplex",
1831 "RegularInteriorCube",
1832 "RegularBoundaryCube",
1833 "SimpleSingularInteriorCube",
1834 "MultiSingularInteriorCube",
1835 "SimpleSingularBoundaryCube",
1836 "InterfaceCube",
1837 "MultiSingularBoundaryCube",
1838 "BoundaryPolytope",
1839 "InteriorPolytope",
1840 "Undefined",
1841 }};
1842
1843 log_and_throw_error("element {} is flipped, type {}", i, element_type_names[static_cast<int>(els_tag[i])]);
1844 }
1845 }
1846
1847 logger().info(" done");
1848
1849 // dynamic_cast<Mesh3D *>(mesh.get())->save({56}, 1, "mesh.HYBRID");
1850
1851 // std::sort(flipped_elements.begin(), flipped_elements.end());
1852 // auto it = std::unique(flipped_elements.begin(), flipped_elements.end());
1853 // flipped_elements.resize(std::distance(flipped_elements.begin(), it));
1854 }
1855
1857 const int n_bases,
1858 const std::vector<polyfem::basis::ElementBases> &bases,
1859 const std::vector<polyfem::basis::ElementBases> &gbases,
1860 const polyfem::mesh::Mesh &mesh,
1861 const assembler::Problem &problem,
1862 const double tend,
1863 const Eigen::MatrixXd &sol)
1864 {
1865 if (n_bases <= 0)
1866 {
1867 logger().error("Build the bases first!");
1868 return;
1869 }
1870 if (sol.size() <= 0)
1871 {
1872 logger().error("Solve the problem first!");
1873 return;
1874 }
1875
1876 int actual_dim = 1;
1877 if (!problem.is_scalar())
1878 actual_dim = mesh.dimension();
1879
1880 igl::Timer timer;
1881 timer.start();
1882 logger().info("Computing errors...");
1883 using std::max;
1884
1885 const int n_el = int(bases.size());
1886
1887 Eigen::MatrixXd v_exact, v_approx;
1888 Eigen::MatrixXd v_exact_grad(0, 0), v_approx_grad;
1889
1890 l2_err = 0;
1891 h1_err = 0;
1892 grad_max_err = 0;
1893 h1_semi_err = 0;
1894 linf_err = 0;
1895 lp_err = 0;
1896 // double pred_norm = 0;
1897
1898 static const int p = 8;
1899
1900 // Eigen::MatrixXd err_per_el(n_el, 5);
1902
1903 for (int e = 0; e < n_el; ++e)
1904 {
1905 vals.compute(e, mesh.is_volume(), bases[e], gbases[e]);
1906
1907 if (problem.has_exact_sol())
1908 {
1909 problem.exact(vals.val, tend, v_exact);
1910 problem.exact_grad(vals.val, tend, v_exact_grad);
1911 }
1912
1913 v_approx.resize(vals.val.rows(), actual_dim);
1914 v_approx.setZero();
1915
1916 v_approx_grad.resize(vals.val.rows(), mesh.dimension() * actual_dim);
1917 v_approx_grad.setZero();
1918
1919 const int n_loc_bases = int(vals.basis_values.size());
1920
1921 for (int i = 0; i < n_loc_bases; ++i)
1922 {
1923 const auto &val = vals.basis_values[i];
1924
1925 for (size_t ii = 0; ii < val.global.size(); ++ii)
1926 {
1927 for (int d = 0; d < actual_dim; ++d)
1928 {
1929 v_approx.col(d) += val.global[ii].val * sol(val.global[ii].index * actual_dim + d) * val.val;
1930 v_approx_grad.block(0, d * val.grad_t_m.cols(), v_approx_grad.rows(), val.grad_t_m.cols()) += val.global[ii].val * sol(val.global[ii].index * actual_dim + d) * val.grad_t_m;
1931 }
1932 }
1933 }
1934
1935 const auto err = problem.has_exact_sol() ? (v_exact - v_approx).eval().rowwise().norm().eval() : (v_approx).eval().rowwise().norm().eval();
1936 const auto err_grad = problem.has_exact_sol() ? (v_exact_grad - v_approx_grad).eval().rowwise().norm().eval() : (v_approx_grad).eval().rowwise().norm().eval();
1937
1938 // for(long i = 0; i < err.size(); ++i)
1939 // errors.push_back(err(i));
1940
1941 linf_err = std::max(linf_err, err.maxCoeff());
1942 grad_max_err = std::max(linf_err, err_grad.maxCoeff());
1943
1944 // {
1945 // const auto &mesh3d = *dynamic_cast<Mesh3D *>(mesh.get());
1946 // const auto v0 = mesh3d.point(mesh3d.cell_vertex(e, 0));
1947 // const auto v1 = mesh3d.point(mesh3d.cell_vertex(e, 1));
1948 // const auto v2 = mesh3d.point(mesh3d.cell_vertex(e, 2));
1949 // const auto v3 = mesh3d.point(mesh3d.cell_vertex(e, 3));
1950
1951 // Eigen::Matrix<double, 6, 3> ee;
1952 // ee.row(0) = v0 - v1;
1953 // ee.row(1) = v1 - v2;
1954 // ee.row(2) = v2 - v0;
1955
1956 // ee.row(3) = v0 - v3;
1957 // ee.row(4) = v1 - v3;
1958 // ee.row(5) = v2 - v3;
1959
1960 // Eigen::Matrix<double, 6, 1> en = ee.rowwise().norm();
1961
1962 // // Eigen::Matrix<double, 3*4, 1> alpha;
1963 // // alpha(0) = angle3(e.row(0), -e.row(1)); alpha(1) = angle3(e.row(1), -e.row(2)); alpha(2) = angle3(e.row(2), -e.row(0));
1964 // // alpha(3) = angle3(e.row(0), -e.row(4)); alpha(4) = angle3(e.row(4), e.row(3)); alpha(5) = angle3(-e.row(3), -e.row(0));
1965 // // alpha(6) = angle3(-e.row(4), -e.row(1)); alpha(7) = angle3(e.row(1), -e.row(5)); alpha(8) = angle3(e.row(5), e.row(4));
1966 // // alpha(9) = angle3(-e.row(2), -e.row(5)); alpha(10) = angle3(e.row(5), e.row(3)); alpha(11) = angle3(-e.row(3), e.row(2));
1967
1968 // const double S = (ee.row(0).cross(ee.row(1)).norm() + ee.row(0).cross(ee.row(4)).norm() + ee.row(4).cross(ee.row(1)).norm() + ee.row(2).cross(ee.row(5)).norm()) / 2;
1969 // const double V = std::abs(ee.row(3).dot(ee.row(2).cross(-ee.row(0))))/6;
1970 // const double rho = 3 * V / S;
1971 // const double hp = en.maxCoeff();
1972 // const int pp = disc_orders(e);
1973 // const int p_ref = args["space"]["discr_order"];
1974
1975 // err_per_el(e, 0) = err.mean();
1976 // err_per_el(e, 1) = err.maxCoeff();
1977 // err_per_el(e, 2) = std::pow(hp, pp+1)/(rho/hp); // /std::pow(average_edge_length, p_ref+1) * (sqrt(6)/12);
1978 // err_per_el(e, 3) = rho/hp;
1979 // err_per_el(e, 4) = (vals.det.array() * vals.quadrature.weights.array()).sum();
1980
1981 // // pred_norm += (pow(std::pow(hp, pp+1)/(rho/hp),p) * vals.det.array() * vals.quadrature.weights.array()).sum();
1982 // }
1983
1984 l2_err += (err.array() * err.array() * vals.det.array() * vals.quadrature.weights.array()).sum();
1985 h1_err += (err_grad.array() * err_grad.array() * vals.det.array() * vals.quadrature.weights.array()).sum();
1986 lp_err += (err.array().pow(p) * vals.det.array() * vals.quadrature.weights.array()).sum();
1987 }
1988
1989 h1_semi_err = sqrt(fabs(h1_err));
1990 h1_err = sqrt(fabs(l2_err) + fabs(h1_err));
1991 l2_err = sqrt(fabs(l2_err));
1992
1993 lp_err = pow(fabs(lp_err), 1. / p);
1994
1995 // pred_norm = pow(fabs(pred_norm), 1./p);
1996
1997 timer.stop();
1998 const double computing_errors_time = timer.getElapsedTime();
1999 logger().info(" took {}s", computing_errors_time);
2000
2001 logger().info("-- L2 error: {}", l2_err);
2002 logger().info("-- Lp error: {}", lp_err);
2003 logger().info("-- H1 error: {}", h1_err);
2004 logger().info("-- H1 semi error: {}", h1_semi_err);
2005 // logger().info("-- Perd norm: {}", pred_norm);
2006
2007 logger().info("-- Linf error: {}", linf_err);
2008 logger().info("-- grad max error: {}", grad_max_err);
2009
2010 // {
2011 // std::ofstream out("errs.txt");
2012 // out<<err_per_el;
2013 // out.close();
2014 // }
2015 }
2016
2018 {
2019 using namespace polyfem::mesh;
2020
2021 simplex_count = 0;
2022 prism_count = 0;
2023 pyramid_count = 0;
2024 regular_count = 0;
2025 regular_boundary_count = 0;
2026 simple_singular_count = 0;
2027 multi_singular_count = 0;
2028 boundary_count = 0;
2029 non_regular_boundary_count = 0;
2030 non_regular_count = 0;
2031 undefined_count = 0;
2032 multi_singular_boundary_count = 0;
2033
2034 const auto &els_tag = mesh.elements_tag();
2035
2036 for (size_t i = 0; i < els_tag.size(); ++i)
2037 {
2038 const ElementType type = els_tag[i];
2039
2040 switch (type)
2041 {
2042 case ElementType::SIMPLEX:
2043 simplex_count++;
2044 break;
2045 case ElementType::PRISM:
2046 prism_count++;
2047 break;
2048 case ElementType::PYRAMID:
2049 pyramid_count++;
2050 break;
2051 case ElementType::REGULAR_INTERIOR_CUBE:
2052 regular_count++;
2053 break;
2054 case ElementType::REGULAR_BOUNDARY_CUBE:
2055 regular_boundary_count++;
2056 break;
2057 case ElementType::SIMPLE_SINGULAR_INTERIOR_CUBE:
2058 simple_singular_count++;
2059 break;
2060 case ElementType::MULTI_SINGULAR_INTERIOR_CUBE:
2061 multi_singular_count++;
2062 break;
2063 case ElementType::SIMPLE_SINGULAR_BOUNDARY_CUBE:
2064 boundary_count++;
2065 break;
2066 case ElementType::INTERFACE_CUBE:
2067 case ElementType::MULTI_SINGULAR_BOUNDARY_CUBE:
2068 multi_singular_boundary_count++;
2069 break;
2070 case ElementType::BOUNDARY_POLYTOPE:
2071 non_regular_boundary_count++;
2072 break;
2073 case ElementType::INTERIOR_POLYTOPE:
2074 non_regular_count++;
2075 break;
2076 case ElementType::UNDEFINED:
2077 undefined_count++;
2078 break;
2079 default:
2080 throw std::runtime_error("Unknown element type");
2081 }
2082 }
2083
2084 logger().info("simplex_count: \t{}", simplex_count);
2085 logger().info("prism_count: \t{}", prism_count);
2086 logger().info("pyramid_count: \t{}", pyramid_count);
2087 logger().info("regular_count: \t{}", regular_count);
2088 logger().info("regular_boundary_count: \t{}", regular_boundary_count);
2089 logger().info("simple_singular_count: \t{}", simple_singular_count);
2090 logger().info("multi_singular_count: \t{}", multi_singular_count);
2091 logger().info("boundary_count: \t{}", boundary_count);
2092 logger().info("multi_singular_boundary_count: \t{}", multi_singular_boundary_count);
2093 logger().info("non_regular_count: \t{}", non_regular_count);
2094 logger().info("non_regular_boundary_count: \t{}", non_regular_boundary_count);
2095 logger().info("undefined_count: \t{}", undefined_count);
2096 logger().info("total count:\t {}", mesh.n_elements());
2097 }
2098
2100 const nlohmann::json &args,
2101 const int n_bases, const int n_pressure_bases,
2102 const Eigen::MatrixXd &sol,
2103 const mesh::Mesh &mesh,
2104 const Eigen::VectorXi &disc_orders,
2105 const Eigen::VectorXi &disc_ordersq,
2106 const assembler::Problem &problem,
2107 const OutRuntimeData &runtime,
2108 const std::string &formulation,
2109 const bool isoparametric,
2110 const int sol_at_node_id,
2111 nlohmann::json &j) const
2112 {
2113
2114 j["args"] = args;
2115
2116 j["geom_order"] = mesh.orders().size() > 0 ? mesh.orders().maxCoeff() : 1;
2117 j["geom_order_min"] = mesh.orders().size() > 0 ? mesh.orders().minCoeff() : 1;
2118 j["discr_order_min"] = disc_orders.minCoeff();
2119 j["discr_order_max"] = disc_orders.maxCoeff();
2120 j["discr_orderq_min"] = disc_ordersq.minCoeff();
2121 j["discr_orderq_max"] = disc_ordersq.maxCoeff();
2122 j["iso_parametric"] = isoparametric;
2123 j["problem"] = problem.name();
2124 j["mat_size"] = mat_size;
2125 j["num_bases"] = n_bases;
2126 j["num_pressure_bases"] = n_pressure_bases;
2127 j["num_non_zero"] = nn_zero;
2128 j["num_flipped"] = n_flipped;
2129 j["num_dofs"] = num_dofs;
2130 j["num_vertices"] = mesh.n_vertices();
2131 j["num_elements"] = mesh.n_elements();
2132
2133 j["num_p1"] = (disc_orders.array() == 1).count();
2134 j["num_p2"] = (disc_orders.array() == 2).count();
2135 j["num_p3"] = (disc_orders.array() == 3).count();
2136 j["num_p4"] = (disc_orders.array() == 4).count();
2137 j["num_p5"] = (disc_orders.array() == 5).count();
2138
2139 j["mesh_size"] = mesh_size;
2140 j["max_angle"] = max_angle;
2141
2142 j["sigma_max"] = sigma_max;
2143 j["sigma_min"] = sigma_min;
2144 j["sigma_avg"] = sigma_avg;
2145
2146 j["min_edge_length"] = min_edge_length;
2147 j["average_edge_length"] = average_edge_length;
2148
2149 j["err_l2"] = l2_err;
2150 j["err_h1"] = h1_err;
2151 j["err_h1_semi"] = h1_semi_err;
2152 j["err_linf"] = linf_err;
2153 j["err_linf_grad"] = grad_max_err;
2154 j["err_lp"] = lp_err;
2155
2156 j["spectrum"] = {spectrum(0), spectrum(1), spectrum(2), spectrum(3)};
2157 j["spectrum_condest"] = std::abs(spectrum(3)) / std::abs(spectrum(0));
2158
2159 // j["errors"] = errors;
2160
2161 j["time_building_basis"] = runtime.building_basis_time;
2162 j["time_loading_mesh"] = runtime.loading_mesh_time;
2163 j["time_computing_poly_basis"] = runtime.computing_poly_basis_time;
2164 j["time_assembling_stiffness_mat"] = runtime.assembling_stiffness_mat_time;
2165 j["time_assembling_mass_mat"] = runtime.assembling_mass_mat_time;
2166 j["time_assigning_rhs"] = runtime.assigning_rhs_time;
2167 j["time_solving"] = runtime.solving_time;
2168 // j["time_computing_errors"] = runtime.computing_errors_time;
2169
2170 j["solver_info"] = solver_info;
2171
2172 j["count_simplex"] = simplex_count;
2173 j["count_prism"] = prism_count;
2174 j["count_pyramid"] = pyramid_count;
2175 j["count_regular"] = regular_count;
2176 j["count_regular_boundary"] = regular_boundary_count;
2177 j["count_simple_singular"] = simple_singular_count;
2178 j["count_multi_singular"] = multi_singular_count;
2179 j["count_boundary"] = boundary_count;
2180 j["count_non_regular_boundary"] = non_regular_boundary_count;
2181 j["count_non_regular"] = non_regular_count;
2182 j["count_undefined"] = undefined_count;
2183 j["count_multi_singular_boundary"] = multi_singular_boundary_count;
2184
2185 j["is_simplicial"] = mesh.n_elements() == simplex_count;
2186
2187 j["peak_memory"] = getPeakRSS() / (1024 * 1024);
2188
2189 const int actual_dim = problem.is_scalar() ? 1 : mesh.dimension();
2190
2191 std::vector<double> mmin(actual_dim);
2192 std::vector<double> mmax(actual_dim);
2193
2194 for (int d = 0; d < actual_dim; ++d)
2195 {
2196 mmin[d] = std::numeric_limits<double>::max();
2197 mmax[d] = -std::numeric_limits<double>::max();
2198 }
2199
2200 for (int i = 0; i < sol.size(); i += actual_dim)
2201 {
2202 for (int d = 0; d < actual_dim; ++d)
2203 {
2204 mmin[d] = std::min(mmin[d], sol(i + d));
2205 mmax[d] = std::max(mmax[d], sol(i + d));
2206 }
2207 }
2208
2209 std::vector<double> sol_at_node(actual_dim);
2210
2211 if (sol_at_node_id >= 0)
2212 {
2213 const int node_id = sol_at_node_id;
2214
2215 for (int d = 0; d < actual_dim; ++d)
2216 {
2217 sol_at_node[d] = sol(node_id * actual_dim + d);
2218 }
2219 }
2220
2221 j["sol_at_node"] = sol_at_node;
2222 j["sol_min"] = mmin;
2223 j["sol_max"] = mmax;
2224
2225#if defined(POLYFEM_WITH_CPP_THREADS)
2226 j["num_threads"] = utils::get_n_threads();
2227#elif defined(POLYFEM_WITH_TBB)
2228 j["num_threads"] = utils::get_n_threads();
2229#else
2230 j["num_threads"] = 1;
2231#endif
2232
2233 j["formulation"] = formulation;
2234
2235 logger().info("done");
2236 }
2237
2238} // namespace polyfem::io
double val
Definition Assembler.cpp:86
ElementAssemblyValues vals
Definition Assembler.cpp:22
int y
int z
int x
stores per element basis values at given quadrature points and geometric mapping
void compute(const int el_index, const bool is_volume, const Eigen::MatrixXd &pts, const basis::ElementBases &basis, const basis::ElementBases &gbasis)
computes the per element values at the local (ref el) points (pts) sets basis_values,...
const std::string & name() const
Definition Problem.hpp:29
virtual void exact_grad(const Eigen::MatrixXd &pts, const double t, Eigen::MatrixXd &val) const
Definition Problem.hpp:52
virtual bool is_scalar() const =0
virtual bool has_exact_sol() const =0
virtual void exact(const Eigen::MatrixXd &pts, const double t, Eigen::MatrixXd &val) const
Definition Problem.hpp:51
Represents one basis function and its gradient.
Definition Basis.hpp:44
const std::vector< Local2Global > & global() const
Definition Basis.hpp:104
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
void build_vis_boundary_mesh(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &gbases, const std::vector< mesh::LocalBoundary > &total_local_boundary, Eigen::MatrixXd &boundary_vis_vertices, Eigen::MatrixXd &boundary_vis_local_vertices, Eigen::MatrixXi &boundary_vis_elements, Eigen::MatrixXi &boundary_vis_elements_ids, Eigen::MatrixXi &boundary_vis_primitive_ids, Eigen::MatrixXd &boundary_vis_normals) const
builds the boundary mesh for visualization
Definition OutData.cpp:495
Eigen::MatrixXd grid_points_bc
grid mesh boundaries
Definition OutData.hpp:213
void build_high_order_vis_mesh(const mesh::Mesh &mesh, const Eigen::VectorXi &output_orders, const std::vector< basis::ElementBases > &bases, Eigen::MatrixXd &points, std::vector< paraviewo::CellElement > &elements, Eigen::MatrixXi &el_id, Eigen::MatrixXd &discr, Eigen::MatrixXd &local_points) const
builds high-der visualzation mesh per element all disconnected it also retuns the mapping to element ...
Eigen::MatrixXd grid_points
grid mesh points to export solution sampled on a grid
Definition OutData.hpp:209
void save_volume(const std::string &path, const OutputSpace &space, const OutputFieldFunction &output_fields, const double t, const double dt, const ExportOptions &opts) const
saves the volume vtu file
Definition OutData.cpp:1248
void build_vis_mesh(const mesh::Mesh &mesh, const Eigen::VectorXi &disc_orders, const std::vector< basis::ElementBases > &gbases, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const bool boundary_only, Eigen::MatrixXd &points, Eigen::MatrixXi &tets, Eigen::MatrixXi &el_id, Eigen::MatrixXd &discr, Eigen::MatrixXd &local_points) const
builds visualzation mesh, upsampled mesh used for visualization the visualization mesh is a dense mes...
Definition OutData.cpp:721
void build_grid(const polyfem::mesh::Mesh &mesh, const double spacing)
builds the grid to export the solution
Definition OutData.cpp:1630
void save_wire(const std::string &name, const OutputSpace &space, const OutputFieldFunction &output_fields, const double t, const ExportOptions &opts) const
saves the wireframe
Definition OutData.cpp:1512
static void extract_boundary_mesh(const mesh::Mesh &mesh, const int n_bases, const std::vector< basis::ElementBases > &bases, const std::vector< mesh::LocalBoundary > &total_local_boundary, Eigen::MatrixXd &node_positions, Eigen::MatrixXi &boundary_edges, Eigen::MatrixXi &boundary_triangles, std::vector< Eigen::Triplet< double > > &displacement_map_entries)
extracts the boundary mesh
Definition OutData.cpp:95
void save_pvd(const std::string &name, const std::function< std::string(int)> &vtu_names, int time_steps, double t0, double dt, int skip_frame=1) const
save a PVD of a time dependent simulation
Definition OutData.cpp:1617
void save_contact_surface(const std::string &export_surface, const OutputSpace &space, const OutputFieldFunction &output_fields, const double t, const double dt_in, const ExportOptions &opts) const
saves the surface vtu file for for constact quantites, eg contact or friction forces
Definition OutData.cpp:1472
void export_data(const OutputSpace &space, const OutputFieldFunction &output_fields, const bool is_time_dependent, const double tend_in, const double dt, const ExportOptions &opts, const std::string &vis_mesh_path) const
exports everytihng, txt, vtu, etc
Definition OutData.cpp:1124
void save_points(const std::string &path, const OutputSpace &space, const OutputFieldFunction &output_fields, const ExportOptions &opts) const
saves the nodal values
Definition OutData.cpp:1563
void save_vtu(const std::string &path, const OutputSpace &space, const OutputFieldFunction &output_fields, const double t, const double dt, const ExportOptions &opts) const
saves the vtu file for time t
Definition OutData.cpp:1183
void init_sampler(const polyfem::mesh::Mesh &mesh, const double vismesh_rel_area)
unitalize the ref element sampler
Definition OutData.cpp:1625
void save_surface(const std::string &export_surface, const OutputSpace &space, const OutputFieldFunction &output_fields, const double t, const double dt_in, const ExportOptions &opts) const
saves the surface vtu file for for surface quantites, eg traction forces
Definition OutData.cpp:1401
Eigen::MatrixXi grid_points_to_elements
grid mesh mapping to fe elements
Definition OutData.hpp:211
utils::RefElementSampler ref_element_sampler
used to sample the solution
Definition OutData.hpp:206
timers from polyfem.
double loading_mesh_time
time to load the mesh
double assembling_stiffness_mat_time
time to assembly
double assigning_rhs_time
time to computing the rhs
double assembling_mass_mat_time
time to assembly mass
double building_basis_time
time to construct the basis
double solving_time
time to solve
double computing_poly_basis_time
time to build the polygonal/polyhedral bases
all stats from polyfem
void count_flipped_elements(const polyfem::mesh::Mesh &mesh, const std::vector< polyfem::basis::ElementBases > &gbases)
counts the number of flipped elements
Definition OutData.cpp:1811
void compute_errors(const int n_bases, const std::vector< polyfem::basis::ElementBases > &bases, const std::vector< polyfem::basis::ElementBases > &gbases, const polyfem::mesh::Mesh &mesh, const assembler::Problem &problem, const double tend, const Eigen::MatrixXd &sol)
compute errors
Definition OutData.cpp:1856
void compute_mesh_size(const polyfem::mesh::Mesh &mesh_in, const std::vector< polyfem::basis::ElementBases > &bases_in, const int n_samples, const bool use_curved_mesh_size)
computes the mesh size, it samples every edges n_samples times uses curved_mesh_size (false by defaul...
Definition OutData.cpp:1728
void reset()
clears all stats
Definition OutData.cpp:1806
void save_json(const nlohmann::json &args, const int n_bases, const int n_pressure_bases, const Eigen::MatrixXd &sol, const mesh::Mesh &mesh, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const assembler::Problem &problem, const OutRuntimeData &runtime, const std::string &formulation, const bool isoparametric, const int sol_at_node_id, nlohmann::json &j) const
saves the output statistic to a json object
Definition OutData.cpp:2099
void compute_mesh_stats(const polyfem::mesh::Mesh &mesh)
compute stats (counts els type, mesh lenght, etc), step 1 of solve
Definition OutData.cpp:2017
Boundary primitive IDs for a single element.
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:41
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
Definition Mesh.hpp:163
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
virtual void get_edges(Eigen::MatrixXd &p0, Eigen::MatrixXd &p1) const =0
Get all the edges.
bool is_simplicial() const
checks if the mesh is simplicial
Definition Mesh.hpp:618
virtual bool is_conforming() const =0
if the mesh is conforming
virtual void bounding_box(RowVectorNd &min, RowVectorNd &max) const =0
computes the bbox of the mesh
virtual void barycentric_coords(const RowVectorNd &p, const int el_id, Eigen::MatrixXd &coord) const =0
constructs barycentric coodiantes for a point p.
bool is_cube(const int el_id) const
checks if element is cube compatible
Definition Mesh.cpp:352
const Eigen::MatrixXi & orders() const
order of each element
Definition Mesh.hpp:285
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:488
bool is_simplex(const int el_id) const
checks if element is simplex
Definition Mesh.cpp:422
bool is_prism(const int el_id) const
checks if element is a prism
Definition Mesh.cpp:427
virtual bool is_volume() const =0
checks if mesh is volume
bool has_poly() const
checks if the mesh has polytopes
Definition Mesh.hpp:576
int dimension() const
utily for dimension
Definition Mesh.hpp:153
virtual int n_faces() const =0
number of faces
const std::vector< ElementType > & elements_tag() const
Returns the elements types.
Definition Mesh.hpp:428
bool is_pyramid(const int el_id) const
checks if element is a pyramid
Definition Mesh.cpp:432
virtual int n_face_vertices(const int f_id) const =0
number of vertices of a face
virtual void elements_boxes(std::vector< std::array< Eigen::Vector3d, 2 > > &boxes) const =0
constructs a box around every element (3d cell, 2d face)
virtual bool is_boundary_element(const int element_global_id) const =0
is cell boundary
virtual int get_node_id(const int node_id) const
Get the boundary selection of a node.
Definition Mesh.hpp:497
const Eigen::MatrixXi & get_edge_connectivity() const
Definition Obstacle.hpp:48
const Eigen::MatrixXi & get_face_connectivity() const
Definition Obstacle.hpp:47
const Eigen::MatrixXd & v() const
Definition Obstacle.hpp:42
const Eigen::VectorXi & get_vertex_connectivity() const
Definition Obstacle.hpp:49
static void sample_parametric_prism_face(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void normal_for_quad_edge(int index, Eigen::MatrixXd &normal)
static void normal_for_tri_edge(int index, Eigen::MatrixXd &normal)
static void normal_for_quad_face(int index, Eigen::MatrixXd &normal)
static void sample_parametric_pyramid_face(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void sample_parametric_tri_face(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void normal_for_prism_face(int index, Eigen::MatrixXd &normal)
static void normal_for_tri_face(int index, Eigen::MatrixXd &normal)
static void sample_parametric_quad_face(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void normal_for_polygon_edge(int face_id, int edge_id, const mesh::Mesh &mesh, Eigen::MatrixXd &normal)
static void normal_for_pyramid_face(int index, Eigen::MatrixXd &normal)
static void sample_parametric_quad_edge(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void sample_polygon_edge(int face_id, int edge_id, int n_samples, const mesh::Mesh &mesh, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void sample_parametric_tri_edge(int index, int n_samples, Eigen::MatrixXd &uv, Eigen::MatrixXd &samples)
static void sample_3d_simplex(const int resolution, Eigen::MatrixXd &samples)
static void sample_3d_cube(const int resolution, Eigen::MatrixXd &samples)
static void sample_2d_cube(const int resolution, Eigen::MatrixXd &samples)
static void sample_2d_simplex(const int resolution, Eigen::MatrixXd &samples)
void init(const bool is_volume, const int n_elements, const double target_rel_area)
const Eigen::MatrixXi & simplex_volume() const
size_t getPeakRSS(void)
Returns the peak (maximum so far) resident set size (physical memory use) measured in bytes,...
Definition getRSS.c:37
list vertices
Definition p_bases.py:238
void q_nodes_2d(const int q, Eigen::MatrixXd &val)
void pyramid_nodes_3d(const int pyramid, Eigen::MatrixXd &val)
void prism_nodes_3d(const int p, const int q, Eigen::MatrixXd &val)
void p_nodes_2d(const int p, Eigen::MatrixXd &val)
void p_nodes_3d(const int p, Eigen::MatrixXd &val)
void q_nodes_3d(const int q, Eigen::MatrixXd &val)
std::function< std::vector< OutputField >(const OutputSample &)> OutputFieldFunction
paraviewo::CellElement CellElement
Definition OutData.cpp:57
paraviewo::CellType CellType
Definition OutData.cpp:56
bool write_matrix(const std::string &path, const Mat &mat)
Writes a matrix to a file. Determines the file format based on the path's extension.
Definition MatrixIO.cpp:42
ElementType
Type of Element, check [Poly-Spline Finite Element Method] for a complete description.
Definition Mesh.hpp:23
size_t get_n_threads()
Definition par_for.hpp:51
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
nlohmann::json json
Definition Common.hpp:9
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73
std::string file_extension() const
return the extension of the output paraview files depending on use_hdf5
Definition OutData.hpp:60
std::vector< std::string > fields
Definition OutData.hpp:29
ExportOptions(const json &args, const bool is_mesh_linear, const bool mesh_has_prisms, const bool is_problem_scalar)
initialize the flags based on the input args
Definition OutData.cpp:1156
bool export_field(const std::string &field) const
Definition OutData.cpp:1151
bool export_field(const std::string &field) const
Definition OutData.cpp:51
std::vector< std::string > fields
Eigen::VectorXi node_ids
Eigen::MatrixXd normals
Eigen::VectorXi primitive_ids
std::vector< std::string > requested_fields
Eigen::VectorXi element_ids
Eigen::MatrixXd local_points
const mesh::Mesh * mesh
Eigen::VectorXi output_orders
const std::vector< mesh::LocalBoundary > * total_local_boundary
const std::vector< basis::ElementBases > * geometry_bases
const std::vector< RowVectorNd > * dirichlet_nodes_position
const std::vector< int > * dirichlet_nodes
const std::map< int, Eigen::MatrixXd > * polys
const mesh::Obstacle * obstacle
const ipc::CollisionMesh * collision_mesh
const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > * polys_3d