PolyFEM
Loading...
Searching...
No Matches
OutData.cpp
Go to the documentation of this file.
1#include "OutData.hpp"
2
3#include "Evaluator.hpp"
4
5#include <polyfem/State.hpp>
6
11
13
17
19
32
40
45
46#include <paraviewo/VTMWriter.hpp>
47#include <paraviewo/PVDWriter.hpp>
48
49#include <SimpleBVH/BVH.hpp>
50
51#include <igl/write_triangle_mesh.h>
52#include <igl/edges.h>
53#include <igl/facet_adjacency_matrix.h>
54#include <igl/connected_components.h>
55
56#include <ipc/ipc.hpp>
57
58#include <filesystem>
59
60namespace polyfem::io
61{
62 using CellType = paraviewo::CellType;
63 using CellElement = paraviewo::CellElement;
64
65 namespace
66 {
67 void compute_traction_forces(const State &state, const Eigen::MatrixXd &solution, const double t, Eigen::MatrixXd &traction_forces, bool skip_dirichlet = true)
68 {
69 int actual_dim = 1;
70 if (!state.problem->is_scalar())
71 actual_dim = state.mesh->dimension();
72 else
73 return;
74
75 const std::vector<basis::ElementBases> &bases = state.bases;
76 const std::vector<basis::ElementBases> &gbases = state.geom_bases();
77
78 Eigen::MatrixXd uv, samples, gtmp, rhs_fun, deform_mat, trafo;
79 Eigen::VectorXi global_primitive_ids;
80 Eigen::MatrixXd points, normals;
81 Eigen::VectorXd weights;
83
84 traction_forces.setZero(state.n_bases * actual_dim, 1);
85
86 for (const auto &lb : state.total_local_boundary)
87 {
88 const int e = lb.element_id();
89 bool has_samples = utils::BoundarySampler::boundary_quadrature(lb, state.n_boundary_samples(), *state.mesh, false, uv, points, normals, weights, global_primitive_ids);
90
91 if (!has_samples)
92 continue;
93
94 const basis::ElementBases &gbs = gbases[e];
95 const basis::ElementBases &bs = bases[e];
96
97 vals.compute(e, state.mesh->is_volume(), points, bs, gbs);
98
99 for (int n = 0; n < normals.rows(); ++n)
100 {
101 trafo = vals.jac_it[n].inverse();
102
103 if (solution.size() > 0)
104 {
105 assert(actual_dim == 2 || actual_dim == 3);
106 deform_mat.resize(actual_dim, actual_dim);
107 deform_mat.setZero();
108 for (const auto &b : vals.basis_values)
109 {
110 for (const auto &g : b.global)
111 {
112 for (int d = 0; d < actual_dim; ++d)
113 {
114 deform_mat.row(d) += solution(g.index * actual_dim + d) * b.grad.row(n);
115 }
116 }
117 }
118
119 trafo += deform_mat;
120 }
121
122 normals.row(n) = normals.row(n) * trafo.inverse();
123 normals.row(n).normalize();
124 }
125
126 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
127 state.assembler->compute_tensor_value(assembler::OutputData(t, e, bs, gbs, points, solution), tensor_flat);
128
129 for (long n = 0; n < vals.basis_values.size(); ++n)
130 {
132
133 const int g_index = v.global[0].index * actual_dim;
134
135 for (int q = 0; q < points.rows(); ++q)
136 {
137 // TF computed only from cauchy stress
138 assert(tensor_flat[0].first == "cauchy_stess");
139 assert(tensor_flat[0].second.row(q).size() == actual_dim * actual_dim);
140
141 Eigen::MatrixXd stress_tensor = utils::unflatten(tensor_flat[0].second.row(q), actual_dim);
142
143 traction_forces.block(g_index, 0, actual_dim, 1) += stress_tensor * normals.row(q).transpose() * v.val(q) * weights(q);
144 }
145 }
146 }
147 }
148 } // namespace
149
151 const mesh::Mesh &mesh,
152 const int n_bases,
153 const std::vector<basis::ElementBases> &bases,
154 const std::vector<mesh::LocalBoundary> &total_local_boundary,
155 Eigen::MatrixXd &node_positions,
156 Eigen::MatrixXi &boundary_edges,
157 Eigen::MatrixXi &boundary_triangles,
158 std::vector<Eigen::Triplet<double>> &displacement_map_entries)
159 {
160 using namespace polyfem::mesh;
161
162 displacement_map_entries.clear();
163
164 if (mesh.is_volume())
165 {
166 if (mesh.has_poly())
167 {
168 logger().warn("Skipping as the mesh has polygons");
169 return;
170 }
171
172 const bool is_simplicial = mesh.is_simplicial();
173
174 node_positions.resize(n_bases + (is_simplicial ? 0 : mesh.n_faces()), 3);
175 node_positions.setZero();
176 const Mesh3D &mesh3d = dynamic_cast<const Mesh3D &>(mesh);
177
178 std::vector<std::tuple<int, int, int>> tris;
179
180 std::vector<bool> visited_node(n_bases, false);
181
182 std::stringstream print_warning;
183
184 for (const LocalBoundary &lb : total_local_boundary)
185 {
186 const basis::ElementBases &b = bases[lb.element_id()];
187
188 for (int j = 0; j < lb.size(); ++j)
189 {
190 const int eid = lb.global_primitive_id(j);
191 const int lid = lb[j];
192 const Eigen::VectorXi nodes = b.local_nodes_for_primitive(eid, mesh3d);
193
194 if (mesh.is_cube(lb.element_id()))
195 {
196 assert(!is_simplicial);
197 assert(!mesh.has_poly());
198 std::vector<int> loc_nodes;
199 RowVectorNd bary = RowVectorNd::Zero(3);
200
201 for (long n = 0; n < nodes.size(); ++n)
202 {
203 auto &bs = b.bases[nodes(n)];
204 const auto &glob = bs.global();
205 if (glob.size() != 1)
206 continue;
207
208 int gindex = glob.front().index;
209 node_positions.row(gindex) = glob.front().node;
210 bary += glob.front().node;
211 loc_nodes.push_back(gindex);
212 }
213
214 if (loc_nodes.size() != 4)
215 {
216 logger().trace("skipping element {} since it is not Q1", eid);
217 continue;
218 }
219
220 bary /= 4;
221
222 const int new_node = n_bases + eid;
223 node_positions.row(new_node) = bary;
224 tris.emplace_back(loc_nodes[1], loc_nodes[0], new_node);
225 tris.emplace_back(loc_nodes[2], loc_nodes[1], new_node);
226 tris.emplace_back(loc_nodes[3], loc_nodes[2], new_node);
227 tris.emplace_back(loc_nodes[0], loc_nodes[3], new_node);
228
229 for (int q = 0; q < 4; ++q)
230 {
231 if (!visited_node[loc_nodes[q]])
232 displacement_map_entries.emplace_back(loc_nodes[q], loc_nodes[q], 1);
233
234 visited_node[loc_nodes[q]] = true;
235 displacement_map_entries.emplace_back(new_node, loc_nodes[q], 0.25);
236 }
237
238 continue;
239 }
240 else if (mesh.is_prism(lb.element_id()))
241 {
242 assert(!is_simplicial);
243 assert(!mesh.has_poly());
244 std::vector<int> loc_nodes;
245 RowVectorNd bary = RowVectorNd::Zero(3);
246
247 for (long n = 0; n < nodes.size(); ++n)
248 {
249 auto &bs = b.bases[nodes(n)];
250 const auto &glob = bs.global();
251 if (glob.size() != 1)
252 continue;
253
254 int gindex = glob.front().index;
255 node_positions.row(gindex) = glob.front().node;
256 bary += glob.front().node;
257 loc_nodes.push_back(gindex);
258 }
259
260 auto update_mapping = [&displacement_map_entries, &visited_node](const std::vector<int> &loc_nodes) {
261 for (int k = 0; k < loc_nodes.size(); ++k)
262 {
263 if (!visited_node[loc_nodes[k]])
264 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
265
266 visited_node[loc_nodes[k]] = true;
267 }
268 };
269
270 if (loc_nodes.size() == 3)
271 {
272 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
273
274 update_mapping(loc_nodes);
275 }
276 else if (loc_nodes.size() == 6)
277 {
278 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[5]);
279 tris.emplace_back(loc_nodes[3], loc_nodes[1], loc_nodes[4]);
280 tris.emplace_back(loc_nodes[4], loc_nodes[2], loc_nodes[5]);
281 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[5]);
282
283 update_mapping(loc_nodes);
284 }
285 else if (loc_nodes.size() == 10)
286 {
287 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[8]);
288 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[9]);
289 tris.emplace_back(loc_nodes[4], loc_nodes[1], loc_nodes[5]);
290 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[9]);
291 tris.emplace_back(loc_nodes[6], loc_nodes[2], loc_nodes[7]);
292 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[9]);
293 tris.emplace_back(loc_nodes[8], loc_nodes[3], loc_nodes[9]);
294 tris.emplace_back(loc_nodes[9], loc_nodes[4], loc_nodes[5]);
295 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[9]);
296 update_mapping(loc_nodes);
297 }
298 else if (loc_nodes.size() == 15)
299 {
300 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[11]);
301 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[12]);
302 tris.emplace_back(loc_nodes[3], loc_nodes[12], loc_nodes[11]);
303 tris.emplace_back(loc_nodes[12], loc_nodes[10], loc_nodes[11]);
304 tris.emplace_back(loc_nodes[4], loc_nodes[5], loc_nodes[13]);
305 tris.emplace_back(loc_nodes[4], loc_nodes[13], loc_nodes[12]);
306 tris.emplace_back(loc_nodes[12], loc_nodes[13], loc_nodes[14]);
307 tris.emplace_back(loc_nodes[12], loc_nodes[14], loc_nodes[10]);
308 tris.emplace_back(loc_nodes[14], loc_nodes[9], loc_nodes[10]);
309 tris.emplace_back(loc_nodes[5], loc_nodes[1], loc_nodes[6]);
310 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[13]);
311 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[13]);
312 tris.emplace_back(loc_nodes[13], loc_nodes[7], loc_nodes[14]);
313 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[14]);
314 tris.emplace_back(loc_nodes[14], loc_nodes[8], loc_nodes[9]);
315 tris.emplace_back(loc_nodes[8], loc_nodes[2], loc_nodes[9]);
316 update_mapping(loc_nodes);
317 }
318 else if (loc_nodes.size() == 4)
319 {
320 bary /= 4;
321
322 const int new_node = n_bases + eid;
323 node_positions.row(new_node) = bary;
324 tris.emplace_back(loc_nodes[1], loc_nodes[0], new_node);
325 tris.emplace_back(loc_nodes[2], loc_nodes[1], new_node);
326 tris.emplace_back(loc_nodes[3], loc_nodes[2], new_node);
327 tris.emplace_back(loc_nodes[0], loc_nodes[3], new_node);
328
329 update_mapping(loc_nodes);
330 }
331 else
332 {
333 logger().trace("skipping element {} since it is not linear, it has {} nodes", eid, loc_nodes.size());
334 continue;
335 }
336
337 continue;
338 }
339
340 else if (mesh.is_pyramid(lb.element_id()))
341 {
342 assert(!is_simplicial);
343 assert(!mesh.has_poly());
344 std::vector<int> loc_nodes;
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 continue;
352
353 int gindex = glob.front().index;
354 node_positions.row(gindex) = glob.front().node;
355 loc_nodes.push_back(gindex);
356 }
357
358 auto update_mapping = [&displacement_map_entries, &visited_node](const std::vector<int> &loc_nodes) {
359 for (int k = 0; k < loc_nodes.size(); ++k)
360 {
361 if (!visited_node[loc_nodes[k]])
362 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
363
364 visited_node[loc_nodes[k]] = true;
365 }
366 };
367
368 if (loc_nodes.size() == 3)
369 {
370 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
371 update_mapping(loc_nodes);
372 }
373 else if (loc_nodes.size() == 4)
374 {
375 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
376 tris.emplace_back(loc_nodes[0], loc_nodes[2], loc_nodes[3]);
377 update_mapping(loc_nodes);
378 }
379 else
380 {
381 logger().trace("skipping element {} since it is not linear, it has {} nodes", eid, loc_nodes.size());
382 continue;
383 }
384
385 continue;
386 }
387
388 if (!mesh.is_simplex(lb.element_id()))
389 {
390 logger().trace("skipping element {} since it is not a simplex or hex", eid);
391 continue;
392 }
393
394 assert(mesh.is_simplex(lb.element_id()));
395
396 std::vector<int> loc_nodes;
397
398 bool is_follower = false;
399 if (!mesh3d.is_conforming())
400 {
401 for (long n = 0; n < nodes.size(); ++n)
402 {
403 auto &bs = b.bases[nodes(n)];
404 const auto &glob = bs.global();
405 if (glob.size() != 1)
406 {
407 is_follower = true;
408 break;
409 }
410 }
411 }
412
413 if (is_follower)
414 continue;
415
416 for (long n = 0; n < nodes.size(); ++n)
417 {
418 const basis::Basis &bs = b.bases[nodes(n)];
419 const std::vector<basis::Local2Global> &glob = bs.global();
420 if (glob.size() != 1)
421 continue;
422
423 int gindex = glob.front().index;
424 node_positions.row(gindex) = glob.front().node;
425 loc_nodes.push_back(gindex);
426 }
427
428 if (loc_nodes.size() == 3)
429 {
430 tris.emplace_back(loc_nodes[0], loc_nodes[1], loc_nodes[2]);
431 }
432 else if (loc_nodes.size() == 6)
433 {
434 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[5]);
435 tris.emplace_back(loc_nodes[3], loc_nodes[1], loc_nodes[4]);
436 tris.emplace_back(loc_nodes[4], loc_nodes[2], loc_nodes[5]);
437 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[5]);
438 }
439 else if (loc_nodes.size() == 10)
440 {
441 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[8]);
442 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[9]);
443 tris.emplace_back(loc_nodes[4], loc_nodes[1], loc_nodes[5]);
444 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[9]);
445 tris.emplace_back(loc_nodes[6], loc_nodes[2], loc_nodes[7]);
446 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[9]);
447 tris.emplace_back(loc_nodes[8], loc_nodes[3], loc_nodes[9]);
448 tris.emplace_back(loc_nodes[9], loc_nodes[4], loc_nodes[5]);
449 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[9]);
450 }
451 else if (loc_nodes.size() == 15)
452 {
453 tris.emplace_back(loc_nodes[0], loc_nodes[3], loc_nodes[11]);
454 tris.emplace_back(loc_nodes[3], loc_nodes[4], loc_nodes[12]);
455 tris.emplace_back(loc_nodes[3], loc_nodes[12], loc_nodes[11]);
456 tris.emplace_back(loc_nodes[12], loc_nodes[10], loc_nodes[11]);
457 tris.emplace_back(loc_nodes[4], loc_nodes[5], loc_nodes[13]);
458 tris.emplace_back(loc_nodes[4], loc_nodes[13], loc_nodes[12]);
459 tris.emplace_back(loc_nodes[12], loc_nodes[13], loc_nodes[14]);
460 tris.emplace_back(loc_nodes[12], loc_nodes[14], loc_nodes[10]);
461 tris.emplace_back(loc_nodes[14], loc_nodes[9], loc_nodes[10]);
462 tris.emplace_back(loc_nodes[5], loc_nodes[1], loc_nodes[6]);
463 tris.emplace_back(loc_nodes[5], loc_nodes[6], loc_nodes[13]);
464 tris.emplace_back(loc_nodes[6], loc_nodes[7], loc_nodes[13]);
465 tris.emplace_back(loc_nodes[13], loc_nodes[7], loc_nodes[14]);
466 tris.emplace_back(loc_nodes[7], loc_nodes[8], loc_nodes[14]);
467 tris.emplace_back(loc_nodes[14], loc_nodes[8], loc_nodes[9]);
468 tris.emplace_back(loc_nodes[8], loc_nodes[2], loc_nodes[9]);
469 }
470 else
471 {
472 print_warning << loc_nodes.size() << " ";
473 // assert(false);
474 }
475
476 if (!is_simplicial)
477 {
478 for (int k = 0; k < loc_nodes.size(); ++k)
479 {
480 if (!visited_node[loc_nodes[k]])
481 displacement_map_entries.emplace_back(loc_nodes[k], loc_nodes[k], 1);
482
483 visited_node[loc_nodes[k]] = true;
484 }
485 }
486 }
487 }
488
489 if (print_warning.str().size() > 0)
490 logger().warn("Skipping faces as theys have {} nodes, boundary export supported up to p4", print_warning.str());
491
492 boundary_triangles.resize(tris.size(), 3);
493 for (int i = 0; i < tris.size(); ++i)
494 {
495 boundary_triangles.row(i) << std::get<0>(tris[i]), std::get<2>(tris[i]), std::get<1>(tris[i]);
496 }
497
498 if (boundary_triangles.rows() > 0)
499 {
500 igl::edges(boundary_triangles, boundary_edges);
501 }
502 }
503 else
504 {
505 node_positions.resize(n_bases, 2);
506 node_positions.setZero();
507 const Mesh2D &mesh2d = dynamic_cast<const Mesh2D &>(mesh);
508
509 std::vector<std::pair<int, int>> edges;
510
511 for (const LocalBoundary &lb : total_local_boundary)
512 {
513 const basis::ElementBases &b = bases[lb.element_id()];
514
515 for (int j = 0; j < lb.size(); ++j)
516 {
517 const int eid = lb.global_primitive_id(j);
518 const int lid = lb[j];
519 const Eigen::VectorXi nodes = b.local_nodes_for_primitive(eid, mesh2d);
520
521 int prev_node = -1;
522
523 for (long n = 0; n < nodes.size(); ++n)
524 {
525 const basis::Basis &bs = b.bases[nodes(n)];
526 const std::vector<basis::Local2Global> &glob = bs.global();
527 if (glob.size() != 1)
528 continue;
529
530 int gindex = glob.front().index;
531 node_positions.row(gindex) = glob.front().node.head<2>();
532
533 if (prev_node >= 0)
534 edges.emplace_back(prev_node, gindex);
535
536 prev_node = gindex;
537 }
538 }
539 }
540
541 boundary_triangles.resize(0, 0);
542 boundary_edges.resize(edges.size(), 2);
543 for (int i = 0; i < edges.size(); ++i)
544 {
545 boundary_edges.row(i) << edges[i].first, edges[i].second;
546 }
547 }
548 }
549
551 const mesh::Mesh &mesh,
552 const std::vector<basis::ElementBases> &bases,
553 const std::vector<basis::ElementBases> &gbases,
554 const std::vector<mesh::LocalBoundary> &total_local_boundary,
555 const Eigen::MatrixXd &solution,
556 const int problem_dim,
557 Eigen::MatrixXd &boundary_vis_vertices,
558 Eigen::MatrixXd &boundary_vis_local_vertices,
559 Eigen::MatrixXi &boundary_vis_elements,
560 Eigen::MatrixXi &boundary_vis_elements_ids,
561 Eigen::MatrixXi &boundary_vis_primitive_ids,
562 Eigen::MatrixXd &boundary_vis_normals,
563 Eigen::MatrixXd &displaced_boundary_vis_normals) const
564 {
565 using namespace polyfem::mesh;
566
567 std::vector<Eigen::MatrixXd> lv, vertices, allnormals, displaced_allnormals;
568 std::vector<int> el_ids, global_primitive_ids;
569 Eigen::MatrixXd uv, local_pts, tmp_n, normals, displaced_normals, trafo, deform_mat;
571 const auto &sampler = ref_element_sampler;
572 const int n_samples = sampler.num_samples();
573 int size = 0;
574
575 std::vector<std::pair<int, int>> edges;
576 std::vector<std::tuple<int, int, int>> tris;
577
578 for (auto it = total_local_boundary.begin(); it != total_local_boundary.end(); ++it)
579 {
580 const auto &lb = *it;
581 const auto &gbs = gbases[lb.element_id()];
582 const auto &bs = bases[lb.element_id()];
583
584 for (int k = 0; k < lb.size(); ++k)
585 {
586 switch (lb.type())
587 {
588 case BoundaryType::TRI_LINE:
590 utils::BoundarySampler::sample_parametric_tri_edge(lb[k], n_samples, uv, local_pts);
591 break;
592 case BoundaryType::QUAD_LINE:
594 utils::BoundarySampler::sample_parametric_quad_edge(lb[k], n_samples, uv, local_pts);
595 break;
596 case BoundaryType::QUAD:
598 utils::BoundarySampler::sample_parametric_quad_face(lb[k], n_samples, uv, local_pts);
599 break;
600 case BoundaryType::TRI:
602 utils::BoundarySampler::sample_parametric_tri_face(lb[k], n_samples, uv, local_pts);
603 break;
604 case BoundaryType::PRISM:
606 utils::BoundarySampler::sample_parametric_prism_face(lb[k], n_samples, uv, local_pts);
607 break;
608 case BoundaryType::PYRAMID:
610 utils::BoundarySampler::sample_parametric_pyramid_face(lb[k], n_samples, uv, local_pts);
611 break;
612 case BoundaryType::POLYGON:
613 utils::BoundarySampler::normal_for_polygon_edge(lb.element_id(), lb.global_primitive_id(k), mesh, tmp_n);
614 utils::BoundarySampler::sample_polygon_edge(lb.element_id(), lb.global_primitive_id(k), n_samples, mesh, uv, local_pts);
615 break;
616 case BoundaryType::POLYHEDRON:
617 assert(false);
618 break;
619 case BoundaryType::INVALID:
620 assert(false);
621 break;
622 default:
623 assert(false);
624 }
625
626 vertices.emplace_back();
627 lv.emplace_back(local_pts);
628 el_ids.push_back(lb.element_id());
629 global_primitive_ids.push_back(lb.global_primitive_id(k));
630 gbs.eval_geom_mapping(local_pts, vertices.back());
631 vals.compute(lb.element_id(), mesh.is_volume(), local_pts, bs, gbs);
632 const int tris_start = tris.size();
633
634 if (mesh.is_volume())
635 {
636 const bool prism_quad = lb.type() == BoundaryType::PRISM && lb[k] >= 2;
637 const bool prism_tri = lb.type() == BoundaryType::PRISM && lb[k] < 2;
638
639 const bool pyramid_quad = lb.type() == BoundaryType::PYRAMID && lb[k] == 0;
640 const bool pyramid_tri = lb.type() == BoundaryType::PYRAMID && lb[k] > 0;
641
642 if (lb.type() == BoundaryType::QUAD || prism_quad || pyramid_quad)
643 {
644 const auto map = [n_samples, size](int i, int j) { return j * n_samples + i + size; };
645
646 for (int j = 0; j < n_samples - 1; ++j)
647 {
648 for (int i = 0; i < n_samples - 1; ++i)
649 {
650 tris.emplace_back(map(i, j), map(i + 1, j), map(i, j + 1));
651 tris.emplace_back(map(i + 1, j + 1), map(i, j + 1), map(i + 1, j));
652 }
653 }
654 }
655 else if (lb.type() == BoundaryType::TRI || prism_tri || pyramid_tri)
656 {
657 int index = 0;
658 std::vector<int> mapp(n_samples * n_samples, -1);
659 for (int j = 0; j < n_samples; ++j)
660 {
661 for (int i = 0; i < n_samples - j; ++i)
662 {
663 mapp[j * n_samples + i] = index;
664 ++index;
665 }
666 }
667 const auto map = [mapp, n_samples](int i, int j) {
668 if (j * n_samples + i >= mapp.size())
669 return -1;
670 return mapp[j * n_samples + i];
671 };
672
673 for (int j = 0; j < n_samples - 1; ++j)
674 {
675 for (int i = 0; i < n_samples - j; ++i)
676 {
677 if (map(i, j) >= 0 && map(i + 1, j) >= 0 && map(i, j + 1) >= 0)
678 tris.emplace_back(map(i, j) + size, map(i + 1, j) + size, map(i, j + 1) + size);
679
680 if (map(i + 1, j + 1) >= 0 && map(i, j + 1) >= 0 && map(i + 1, j) >= 0)
681 tris.emplace_back(map(i + 1, j + 1) + size, map(i, j + 1) + size, map(i + 1, j) + size);
682 }
683 }
684 }
685 else
686 {
687 assert(false);
688 }
689 }
690 else
691 {
692 for (int i = 0; i < vertices.back().rows() - 1; ++i)
693 edges.emplace_back(i + size, i + size + 1);
694 }
695
696 normals.resize(vals.jac_it.size(), tmp_n.cols());
697 displaced_normals.resize(vals.jac_it.size(), tmp_n.cols());
698
699 for (int n = 0; n < vals.jac_it.size(); ++n)
700 {
701 trafo = vals.jac_it[n].inverse();
702
703 if (problem_dim == 2 || problem_dim == 3)
704 {
705
706 if (solution.size() > 0)
707 {
708 deform_mat.resize(problem_dim, problem_dim);
709 deform_mat.setZero();
710 for (const auto &b : vals.basis_values)
711 for (const auto &g : b.global)
712 for (int d = 0; d < problem_dim; ++d)
713 deform_mat.row(d) += solution(g.index * problem_dim + d) * b.grad.row(n);
714
715 trafo += deform_mat;
716 }
717 }
718
719 normals.row(n) = tmp_n * vals.jac_it[n];
720 normals.row(n).normalize();
721
722 displaced_normals.row(n) = tmp_n * trafo.inverse();
723 displaced_normals.row(n).normalize();
724 }
725
726 allnormals.push_back(normals);
727 displaced_allnormals.push_back(displaced_normals);
728
729 tmp_n.setZero();
730 for (int n = 0; n < vals.jac_it.size(); ++n)
731 {
732 tmp_n += normals.row(n);
733 }
734
735 if (mesh.is_volume())
736 {
737 Eigen::Vector3d e1 = vertices.back().row(std::get<1>(tris.back()) - size) - vertices.back().row(std::get<0>(tris.back()) - size);
738 Eigen::Vector3d e2 = vertices.back().row(std::get<2>(tris.back()) - size) - vertices.back().row(std::get<0>(tris.back()) - size);
739
740 Eigen::Vector3d n = e1.cross(e2);
741 Eigen::Vector3d nn = tmp_n.transpose();
742
743 if (n.dot(nn) < 0)
744 {
745 for (int i = tris_start; i < tris.size(); ++i)
746 {
747 tris[i] = std::tuple<int, int, int>(std::get<0>(tris[i]), std::get<2>(tris[i]), std::get<1>(tris[i]));
748 }
749 }
750 }
751
752 size += vertices.back().rows();
753 }
754 }
755
756 boundary_vis_vertices.resize(size, vertices.front().cols());
757 boundary_vis_local_vertices.resize(size, vertices.front().cols());
758 boundary_vis_elements_ids.resize(size, 1);
759 boundary_vis_primitive_ids.resize(size, 1);
760 boundary_vis_normals.resize(size, vertices.front().cols());
761 displaced_boundary_vis_normals.resize(size, vertices.front().cols());
762
763 if (mesh.is_volume())
764 boundary_vis_elements.resize(tris.size(), 3);
765 else
766 boundary_vis_elements.resize(edges.size(), 2);
767
768 int index = 0;
769 int ii = 0;
770 for (const auto &v : vertices)
771 {
772 boundary_vis_vertices.block(index, 0, v.rows(), v.cols()) = v;
773 boundary_vis_local_vertices.block(index, 0, v.rows(), v.cols()) = lv[ii];
774 boundary_vis_elements_ids.block(index, 0, v.rows(), 1).setConstant(el_ids[ii]);
775 boundary_vis_primitive_ids.block(index, 0, v.rows(), 1).setConstant(global_primitive_ids[ii++]);
776 index += v.rows();
777 }
778
779 index = 0;
780 for (const auto &n : allnormals)
781 {
782 boundary_vis_normals.block(index, 0, n.rows(), n.cols()) = n;
783 index += n.rows();
784 }
785
786 index = 0;
787 for (const auto &n : displaced_allnormals)
788 {
789 displaced_boundary_vis_normals.block(index, 0, n.rows(), n.cols()) = n;
790 index += n.rows();
791 }
792
793 index = 0;
794 if (mesh.is_volume())
795 {
796 for (const auto &t : tris)
797 {
798 boundary_vis_elements.row(index) << std::get<0>(t), std::get<1>(t), std::get<2>(t);
799 ++index;
800 }
801 }
802 else
803 {
804 for (const auto &e : edges)
805 {
806 boundary_vis_elements.row(index) << e.first, e.second;
807 ++index;
808 }
809 }
810 }
811
813 const mesh::Mesh &mesh,
814 const Eigen::VectorXi &disc_orders,
815 const std::vector<basis::ElementBases> &gbases,
816 const std::map<int, Eigen::MatrixXd> &polys,
817 const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> &polys_3d,
818 const bool boundary_only,
819 Eigen::MatrixXd &points,
820 Eigen::MatrixXi &tets,
821 Eigen::MatrixXi &el_id,
822 Eigen::MatrixXd &discr) const
823 {
824 const auto &sampler = ref_element_sampler;
825
826 const auto &current_bases = gbases;
827 int tet_total_size = 0;
828 int pts_total_size = 0;
829
830 Eigen::MatrixXd vis_pts_poly;
831 Eigen::MatrixXi vis_faces_poly, vis_edges_poly;
832
833 for (size_t i = 0; i < current_bases.size(); ++i)
834 {
835 const auto &bs = current_bases[i];
836
837 if (boundary_only && mesh.is_volume() && !mesh.is_boundary_element(i))
838 continue;
839
840 if (mesh.is_simplex(i))
841 {
842 tet_total_size += sampler.simplex_volume().rows();
843 pts_total_size += sampler.simplex_points().rows();
844 }
845 else if (mesh.is_cube(i))
846 {
847 tet_total_size += sampler.cube_volume().rows();
848 pts_total_size += sampler.cube_points().rows();
849 }
850 else if (mesh.is_prism(i))
851 {
852 tet_total_size += sampler.prism_volume().rows();
853 pts_total_size += sampler.prism_points().rows();
854 }
855 else if (mesh.is_pyramid(i))
856 {
857 tet_total_size += sampler.pyramid_volume().rows();
858 pts_total_size += sampler.pyramid_points().rows();
859 }
860 else
861 {
862 if (mesh.is_volume())
863 {
864 sampler.sample_polyhedron(polys_3d.at(i).first, polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
865
866 tet_total_size += vis_faces_poly.rows();
867 pts_total_size += vis_pts_poly.rows();
868 }
869 else
870 {
871 sampler.sample_polygon(polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
872
873 tet_total_size += vis_faces_poly.rows();
874 pts_total_size += vis_pts_poly.rows();
875 }
876 }
877 }
878
879 points.resize(pts_total_size, mesh.dimension());
880 tets.resize(tet_total_size, mesh.is_volume() ? 4 : 3);
881
882 el_id.resize(pts_total_size, 1);
883 discr.resize(pts_total_size, 1);
884
885 Eigen::MatrixXd mapped, tmp;
886 int tet_index = 0, pts_index = 0;
887
888 for (size_t i = 0; i < current_bases.size(); ++i)
889 {
890 const auto &bs = current_bases[i];
891
892 if (boundary_only && mesh.is_volume() && !mesh.is_boundary_element(i))
893 continue;
894
895 if (mesh.is_simplex(i))
896 {
897 bs.eval_geom_mapping(sampler.simplex_points(), mapped);
898
899 tets.block(tet_index, 0, sampler.simplex_volume().rows(), tets.cols()) = sampler.simplex_volume().array() + pts_index;
900 tet_index += sampler.simplex_volume().rows();
901
902 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
903 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
904 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
905 pts_index += mapped.rows();
906 }
907 else if (mesh.is_cube(i))
908 {
909 bs.eval_geom_mapping(sampler.cube_points(), mapped);
910
911 tets.block(tet_index, 0, sampler.cube_volume().rows(), tets.cols()) = sampler.cube_volume().array() + pts_index;
912 tet_index += sampler.cube_volume().rows();
913
914 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
915 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
916 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
917 pts_index += mapped.rows();
918 }
919 else if (mesh.is_prism(i))
920 {
921 bs.eval_geom_mapping(sampler.prism_points(), mapped);
922
923 tets.block(tet_index, 0, sampler.prism_volume().rows(), tets.cols()) = sampler.prism_volume().array() + pts_index;
924 tet_index += sampler.prism_volume().rows();
925
926 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
927 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
928 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
929 pts_index += mapped.rows();
930 }
931 else if (mesh.is_pyramid(i))
932 {
933 bs.eval_geom_mapping(sampler.pyramid_points(), mapped);
934
935 tets.block(tet_index, 0, sampler.pyramid_volume().rows(), tets.cols()) = sampler.pyramid_volume().array() + pts_index;
936 tet_index += sampler.pyramid_volume().rows();
937
938 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
939 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(disc_orders(i));
940 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
941 pts_index += mapped.rows();
942 }
943 else
944 {
945 if (mesh.is_volume())
946 {
947 sampler.sample_polyhedron(polys_3d.at(i).first, polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
948 bs.eval_geom_mapping(vis_pts_poly, mapped);
949
950 tets.block(tet_index, 0, vis_faces_poly.rows(), tets.cols()) = vis_faces_poly.array() + pts_index;
951 tet_index += vis_faces_poly.rows();
952
953 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
954 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(-1);
955 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
956 pts_index += mapped.rows();
957 }
958 else
959 {
960 sampler.sample_polygon(polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
961 bs.eval_geom_mapping(vis_pts_poly, mapped);
962
963 tets.block(tet_index, 0, vis_faces_poly.rows(), tets.cols()) = vis_faces_poly.array() + pts_index;
964 tet_index += vis_faces_poly.rows();
965
966 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
967 discr.block(pts_index, 0, mapped.rows(), 1).setConstant(-1);
968 el_id.block(pts_index, 0, mapped.rows(), 1).setConstant(i);
969 pts_index += mapped.rows();
970 }
971 }
972 }
973
974 assert(pts_index == points.rows());
975 assert(tet_index == tets.rows());
976 }
977
979 const mesh::Mesh &mesh,
980 const Eigen::VectorXi &disc_orders,
981 const Eigen::VectorXi &disc_ordersq,
982 const std::vector<basis::ElementBases> &bases,
983 Eigen::MatrixXd &points,
984 std::vector<CellElement> &elements,
985 Eigen::MatrixXi &el_id,
986 Eigen::MatrixXd &discr) const
987 {
988 // if (!mesh)
989 // {
990 // logger().error("Load the mesh first!");
991 // return;
992 // }
993 // if (n_bases <= 0)
994 // {
995 // logger().error("Build the bases first!");
996 // return;
997 // }
998 // assert(mesh.is_linear());
999
1000 std::vector<RowVectorNd> nodes;
1001 int pts_total_size = 0;
1002 elements.resize(bases.size());
1003 Eigen::MatrixXd ref_pts;
1004
1005 for (size_t i = 0; i < bases.size(); ++i)
1006 {
1007 const auto &bs = bases[i];
1008 if (mesh.is_volume())
1009 {
1010 if (mesh.is_simplex(i))
1011 autogen::p_nodes_3d(disc_orders(i), ref_pts);
1012 else if (mesh.is_cube(i))
1013 autogen::q_nodes_3d(disc_orders(i), ref_pts);
1014 else if (mesh.is_prism(i))
1015 {
1016 int max_order = std::max(disc_orders(i), disc_ordersq(i));
1017 autogen::prism_nodes_3d(max_order, max_order, ref_pts);
1018 }
1019 else if (mesh.is_pyramid(i))
1020 {
1021 autogen::pyramid_nodes_3d(disc_orders(i) == 2 ? -1 : disc_orders(i), ref_pts);
1022 }
1023 else
1024 continue;
1025 }
1026 else
1027 {
1028 if (mesh.is_simplex(i))
1029 autogen::p_nodes_2d(disc_orders(i), ref_pts);
1030 else if (mesh.is_cube(i))
1031 autogen::q_nodes_2d(disc_orders(i), ref_pts);
1032 else
1033 {
1034 const int n_v = static_cast<const mesh::Mesh2D &>(mesh).n_face_vertices(i);
1035 ref_pts.resize(n_v, 2);
1036 }
1037 }
1038
1039 pts_total_size += ref_pts.rows();
1040 }
1041
1042 points.resize(pts_total_size, mesh.dimension());
1043
1044 el_id.resize(pts_total_size, 1);
1045 discr.resize(pts_total_size, 1);
1046
1047 Eigen::MatrixXd mapped;
1048 int pts_index = 0;
1049
1050 std::string error_msg = "";
1051
1052 for (size_t i = 0; i < bases.size(); ++i)
1053 {
1054 const auto &bs = bases[i];
1055 if (mesh.is_volume())
1056 {
1057 if (mesh.is_simplex(i))
1058 autogen::p_nodes_3d(disc_orders(i), ref_pts);
1059 else if (mesh.is_cube(i))
1060 autogen::q_nodes_3d(disc_orders(i), ref_pts);
1061 else if (mesh.is_prism(i))
1062 {
1063 int max_order = std::max(disc_orders(i), disc_ordersq(i));
1064 autogen::prism_nodes_3d(max_order, max_order, ref_pts);
1065 }
1066 else if (mesh.is_pyramid(i))
1067 {
1068 autogen::pyramid_nodes_3d(disc_orders(i) == 2 ? -1 : disc_orders(i), ref_pts);
1069 }
1070 else
1071 continue;
1072 }
1073 else
1074 {
1075 if (mesh.is_simplex(i))
1076 autogen::p_nodes_2d(disc_orders(i), ref_pts);
1077 else if (mesh.is_cube(i))
1078 autogen::q_nodes_2d(disc_orders(i), ref_pts);
1079 else
1080 continue;
1081 }
1082
1083 bs.eval_geom_mapping(ref_pts, mapped);
1084
1085 for (int j = 0; j < mapped.rows(); ++j)
1086 {
1087 points.row(pts_index) = mapped.row(j);
1088 el_id(pts_index) = i;
1089 discr(pts_index) = disc_orders(i);
1090 elements[i].vertices.push_back(pts_index);
1091
1092 pts_index++;
1093 }
1094
1095 if (mesh.is_simplex(i))
1096 {
1097 if (mesh.is_volume())
1098 {
1099 const int n_nodes = elements[i].vertices.size();
1100 if (disc_orders(i) >= 3)
1101 {
1102 std::swap(elements[i].vertices[16], elements[i].vertices[17]);
1103 std::swap(elements[i].vertices[17], elements[i].vertices[18]);
1104 std::swap(elements[i].vertices[18], elements[i].vertices[19]);
1105 }
1106 if (disc_orders(i) > 4)
1107 error_msg = "Saving high-order meshes not implemented for P5+ elements!";
1108 }
1109 else
1110 {
1111 if (disc_orders(i) == 4)
1112 {
1113 const int n_nodes = elements[i].vertices.size();
1114 std::swap(elements[i].vertices[n_nodes - 1], elements[i].vertices[n_nodes - 2]);
1115 }
1116 if (disc_orders(i) > 4)
1117 error_msg = "Saving high-order meshes not implemented for P5+ elements!";
1118 }
1119 }
1120 else if (mesh.is_cube(i) && mesh.is_volume())
1121 {
1122 const int n_nodes = elements[i].vertices.size();
1123 if (disc_orders(i) == 2) // Lagrange hex, order=2
1124 {
1125 std::swap(elements[i].vertices[12], elements[i].vertices[16]);
1126 std::swap(elements[i].vertices[13], elements[i].vertices[17]);
1127 std::swap(elements[i].vertices[14], elements[i].vertices[18]);
1128 std::swap(elements[i].vertices[15], elements[i].vertices[19]);
1129 std::swap(elements[i].vertices[18], elements[i].vertices[19]); // a hack fix
1130 }
1131 // if (disc_orders(i) == 3) // Incomplete fix, need to fix order on the edge
1132 // {
1133 // std::swap(elements[i].vertices[24], elements[i].vertices[16]);
1134 // std::swap(elements[i].vertices[25], elements[i].vertices[17]);
1135 // std::swap(elements[i].vertices[26], elements[i].vertices[18]);
1136 // std::swap(elements[i].vertices[27], elements[i].vertices[19]);
1137 // std::swap(elements[i].vertices[28], elements[i].vertices[20]);
1138 // std::swap(elements[i].vertices[29], elements[i].vertices[21]);
1139 // std::swap(elements[i].vertices[30], elements[i].vertices[22]);
1140 // std::swap(elements[i].vertices[31], elements[i].vertices[23]);
1141 // std::swap(elements[i].vertices[28], elements[i].vertices[30]); // hack
1142 // std::swap(elements[i].vertices[29], elements[i].vertices[31]); // hack
1143 // }
1144 if (disc_orders(i) > 2)
1145 error_msg = "Saving high-order meshes not implemented for P2+ elements!";
1146 }
1147 else if (disc_orders(i) > 1)
1148 error_msg = "Saving high-order meshes not implemented for Q2+ elements!";
1149 }
1150
1151 if (!error_msg.empty())
1152 logger().warn(error_msg);
1153
1154 for (size_t i = 0; i < bases.size(); ++i)
1155 {
1156 if (mesh.is_volume() || !mesh.is_polytope(i))
1157 continue;
1158
1159 const auto &mesh2d = static_cast<const mesh::Mesh2D &>(mesh);
1160 const int n_v = mesh2d.n_face_vertices(i);
1161
1162 for (int j = 0; j < n_v; ++j)
1163 {
1164 points.row(pts_index) = mesh2d.point(mesh2d.face_vertex(i, j));
1165 el_id(pts_index) = i;
1166 discr(pts_index) = disc_orders(i);
1167 elements[i].vertices.push_back(pts_index);
1168
1169 pts_index++;
1170 }
1171 }
1172
1173 for (size_t i = 0; i < bases.size(); ++i)
1174 {
1175 if (!mesh.is_volume())
1176 {
1177 if (elements[i].vertices.size() == 1)
1178 elements[i].ctype = CellType::Vertex;
1179 else if (elements[i].vertices.size() == 2)
1180 elements[i].ctype = CellType::Line;
1181 else if (mesh.is_simplex(i))
1182 elements[i].ctype = CellType::Triangle;
1183 else if (mesh.is_cube(i))
1184 elements[i].ctype = CellType::Quadrilateral;
1185 else
1186 elements[i].ctype = CellType::Polygon;
1187 }
1188 else
1189 {
1190 if (mesh.is_simplex(i))
1191 elements[i].ctype = CellType::Tetrahedron;
1192 else if (mesh.is_cube(i))
1193 elements[i].ctype = CellType::Hexahedron;
1194 else if (mesh.is_prism(i))
1195 elements[i].ctype = CellType::Wedge;
1196 else if (mesh.is_pyramid(i))
1197 elements[i].ctype = CellType::Pyramid;
1198 }
1199 }
1200
1201 assert(pts_index == points.rows());
1202 }
1203
1205 const State &state,
1206 const Eigen::MatrixXd &sol,
1207 const Eigen::MatrixXd &pressure,
1208 const bool is_time_dependent,
1209 const double tend_in,
1210 const double dt,
1211 const ExportOptions &opts,
1212 const std::string &vis_mesh_path,
1213 const std::string &nodes_path,
1214 const std::string &solution_path,
1215 const std::string &stress_path,
1216 const std::string &mises_path,
1217 const bool is_contact_enabled) const
1218 {
1219 if (!state.mesh)
1220 {
1221 logger().error("Load the mesh first!");
1222 return;
1223 }
1224 const int n_bases = state.n_bases;
1225 const std::vector<basis::ElementBases> &bases = state.bases;
1226 const std::vector<basis::ElementBases> &gbases = state.geom_bases();
1227 const mesh::Mesh &mesh = *state.mesh;
1228 const Eigen::VectorXi &in_node_to_node = state.in_node_to_node;
1229 const Eigen::MatrixXd &rhs = state.rhs;
1230 const assembler::Problem &problem = *state.problem;
1231
1232 if (n_bases <= 0)
1233 {
1234 logger().error("Build the bases first!");
1235 return;
1236 }
1237 // if (rhs.size() <= 0)
1238 // {
1239 // logger().error("Assemble the rhs first!");
1240 // return;
1241 // }
1242 if (sol.size() <= 0)
1243 {
1244 logger().error("Solve the problem first!");
1245 return;
1246 }
1247
1248 if (!solution_path.empty())
1249 {
1250 logger().info("Saving solution to {}", solution_path);
1251
1252 std::ofstream out(solution_path);
1253 out.precision(100);
1254 out << std::scientific;
1255 if (opts.reorder_output)
1256 {
1257 int problem_dim = (problem.is_scalar() ? 1 : mesh.dimension());
1258 Eigen::VectorXi reordering(n_bases);
1259 reordering.setConstant(-1);
1260
1261 for (int i = 0; i < in_node_to_node.size(); ++i)
1262 {
1263 reordering[in_node_to_node[i]] = i;
1264 }
1265 Eigen::MatrixXd tmp_sol = utils::unflatten(sol, problem_dim);
1266 Eigen::MatrixXd tmp(tmp_sol.rows(), tmp_sol.cols());
1267
1268 for (int i = 0; i < reordering.size(); ++i)
1269 {
1270 if (reordering[i] < 0)
1271 continue;
1272
1273 tmp.row(reordering[i]) = tmp_sol.row(i);
1274 }
1275
1276 for (int i = 0; i < tmp.rows(); ++i)
1277 {
1278 for (int j = 0; j < tmp.cols(); ++j)
1279 out << tmp(i, j) << " ";
1280
1281 out << std::endl;
1282 }
1283 }
1284 else
1285 out << sol << std::endl;
1286 out.close();
1287 }
1288
1289 double tend = tend_in;
1290 if (tend <= 0)
1291 tend = 1;
1292
1293 if (!vis_mesh_path.empty() && !is_time_dependent)
1294 {
1295 save_vtu(
1296 vis_mesh_path, state, sol, pressure,
1297 tend, dt, opts,
1298 is_contact_enabled);
1299 }
1300 if (!nodes_path.empty())
1301 {
1302 Eigen::MatrixXd nodes(n_bases, mesh.dimension());
1303 for (const basis::ElementBases &eb : bases)
1304 {
1305 for (const basis::Basis &b : eb.bases)
1306 {
1307 // for(const auto &lg : b.global())
1308 for (size_t ii = 0; ii < b.global().size(); ++ii)
1309 {
1310 const auto &lg = b.global()[ii];
1311 nodes.row(lg.index) = lg.node;
1312 }
1313 }
1314 }
1315 std::ofstream out(nodes_path);
1316 out.precision(100);
1317 out << nodes;
1318 out.close();
1319 }
1320 if (!stress_path.empty())
1321 {
1322 Eigen::MatrixXd result;
1323 Eigen::VectorXd mises;
1325 mesh, problem.is_scalar(),
1326 bases, gbases, state.disc_orders, state.disc_ordersq, *state.assembler,
1327 sol, tend, result, mises);
1328 std::ofstream out(stress_path);
1329 out.precision(20);
1330 out << result;
1331 }
1332 if (!mises_path.empty())
1333 {
1334 Eigen::MatrixXd result;
1335 Eigen::VectorXd mises;
1337 mesh, problem.is_scalar(),
1338 bases, gbases, state.disc_orders, state.disc_ordersq, *state.assembler,
1339 sol, tend, result, mises);
1340 std::ofstream out(mises_path);
1341 out.precision(20);
1342 out << mises;
1343 }
1344 }
1345
1346 bool OutGeometryData::ExportOptions::export_field(const std::string &field) const
1347 {
1348 return fields.empty() || std::find(fields.begin(), fields.end(), field) != fields.end();
1349 }
1350
1351 OutGeometryData::ExportOptions::ExportOptions(const json &args, const bool is_mesh_linear, const bool mesh_has_prisms, const bool is_problem_scalar)
1352 {
1353 fields = args["output"]["paraview"]["fields"];
1354
1355 volume = args["output"]["paraview"]["volume"];
1356 surface = args["output"]["paraview"]["surface"];
1357 wire = args["output"]["paraview"]["wireframe"];
1358 points = args["output"]["paraview"]["points"];
1359 contact_forces = args["output"]["paraview"]["options"]["contact_forces"] && !is_problem_scalar;
1360 friction_forces = args["output"]["paraview"]["options"]["friction_forces"] && !is_problem_scalar;
1361 normal_adhesion_forces = args["output"]["paraview"]["options"]["normal_adhesion_forces"] && !is_problem_scalar;
1362 tangential_adhesion_forces = args["output"]["paraview"]["options"]["tangential_adhesion_forces"] && !is_problem_scalar;
1363
1364 if (args["output"]["paraview"]["options"]["force_high_order"])
1365 use_sampler = false;
1366 else
1367 use_sampler = !(is_mesh_linear && args["output"]["paraview"]["high_order_mesh"]);
1368 boundary_only = use_sampler && args["output"]["advanced"]["vis_boundary_only"];
1369 material_params = args["output"]["paraview"]["options"]["material"];
1370 body_ids = args["output"]["paraview"]["options"]["body_ids"];
1371 sol_on_grid = args["output"]["advanced"]["sol_on_grid"] > 0;
1372 velocity = args["output"]["paraview"]["options"]["velocity"];
1373 acceleration = args["output"]["paraview"]["options"]["acceleration"];
1374 forces = args["output"]["paraview"]["options"]["forces"] && !is_problem_scalar;
1375 jacobian_validity = args["output"]["paraview"]["options"]["jacobian_validity"] && !is_problem_scalar;
1376
1377 scalar_values = args["output"]["paraview"]["options"]["scalar_values"];
1378 tensor_values = args["output"]["paraview"]["options"]["tensor_values"] && !is_problem_scalar;
1379 discretization_order = args["output"]["paraview"]["options"]["discretization_order"];
1380 nodes = args["output"]["paraview"]["options"]["nodes"] && !is_problem_scalar;
1381
1382 use_spline = args["space"]["basis_type"] == "Spline";
1383
1384 reorder_output = args["output"]["data"]["advanced"]["reorder_nodes"];
1385
1386 use_hdf5 = args["output"]["paraview"]["options"]["use_hdf5"];
1387 }
1388
1390 const std::string &path,
1391 const State &state,
1392 const Eigen::MatrixXd &sol,
1393 const Eigen::MatrixXd &pressure,
1394 const double t,
1395 const double dt,
1396 const ExportOptions &opts,
1397 const bool is_contact_enabled) const
1398 {
1399 if (!state.mesh)
1400 {
1401 logger().error("Load the mesh first!");
1402 return;
1403 }
1404 const mesh::Mesh &mesh = *state.mesh;
1405 const Eigen::MatrixXd &rhs = state.rhs;
1406
1407 if (state.n_bases <= 0)
1408 {
1409 logger().error("Build the bases first!");
1410 return;
1411 }
1412 // if (rhs.size() <= 0)
1413 // {
1414 // logger().error("Assemble the rhs first!");
1415 // return;
1416 // }
1417 if (sol.size() <= 0)
1418 {
1419 logger().error("Solve the problem first!");
1420 return;
1421 }
1422
1423 const bool save_contact = is_contact_enabled && (opts.contact_forces || opts.friction_forces);
1424
1425 logger().info("Saving vtu to {}; volume={}, surface={}, contact={}, points={}, wireframe={}",
1426 path, opts.volume, opts.surface, save_contact, opts.points, opts.wire);
1427
1428 const std::filesystem::path fs_path(path);
1429 const std::string path_stem = fs_path.stem().string();
1430 const std::string base_path = (fs_path.parent_path() / path_stem).string();
1431
1432 if (opts.volume)
1433 {
1434 save_volume(base_path + opts.file_extension(), state, sol, pressure, t, dt, opts);
1435 }
1436
1437 if (opts.surface)
1438 {
1439 save_surface(base_path + "_surf" + opts.file_extension(), state, sol, pressure, t, dt, opts,
1440 is_contact_enabled);
1441 }
1442
1443 if (is_contact_enabled && (opts.contact_forces || opts.friction_forces || opts.normal_adhesion_forces || opts.tangential_adhesion_forces))
1444 {
1445 save_contact_surface(base_path + "_surf" + opts.file_extension(), state, sol, pressure, t, dt, opts,
1446 is_contact_enabled);
1447 }
1448
1449 if (opts.wire)
1450 {
1451 save_wire(base_path + "_wire" + opts.file_extension(), state, sol, t, opts);
1452 }
1453
1454 if (opts.points)
1455 {
1456 save_points(base_path + "_points" + opts.file_extension(), state, sol, opts);
1457 }
1458
1459 paraviewo::VTMWriter vtm(t);
1460 if (opts.volume)
1461 vtm.add_dataset("Volume", "data", path_stem + opts.file_extension());
1462 if (opts.surface)
1463 vtm.add_dataset("Surface", "data", path_stem + "_surf" + opts.file_extension());
1464 if (is_contact_enabled && (opts.contact_forces || opts.friction_forces || opts.normal_adhesion_forces || opts.tangential_adhesion_forces))
1465 vtm.add_dataset("Contact", "data", path_stem + "_surf_contact" + opts.file_extension());
1466 if (opts.wire)
1467 vtm.add_dataset("Wireframe", "data", path_stem + "_wire" + opts.file_extension());
1468 if (opts.points)
1469 vtm.add_dataset("Points", "data", path_stem + "_points" + opts.file_extension());
1470 vtm.save(base_path + ".vtm");
1471 }
1472
1474 const std::string &path,
1475 const State &state,
1476 const Eigen::MatrixXd &sol,
1477 const Eigen::MatrixXd &pressure,
1478 const double t,
1479 const double dt,
1480 const ExportOptions &opts) const
1481 {
1482 const Eigen::VectorXi &disc_orders = state.disc_orders;
1483 const Eigen::VectorXi &disc_ordersq = state.disc_ordersq;
1484 const auto &density = state.mass_matrix_assembler->density();
1485 const std::vector<basis::ElementBases> &bases = state.bases;
1486 const std::vector<basis::ElementBases> &pressure_bases = state.pressure_bases;
1487 const std::vector<basis::ElementBases> &gbases = state.geom_bases();
1488 const std::map<int, Eigen::MatrixXd> &polys = state.polys;
1489 const std::map<int, std::pair<Eigen::MatrixXd, Eigen::MatrixXi>> &polys_3d = state.polys_3d;
1490 const assembler::Assembler &assembler = *state.assembler;
1491 const std::shared_ptr<time_integrator::ImplicitTimeIntegrator> time_integrator = state.solve_data.time_integrator;
1492 const mesh::Mesh &mesh = *state.mesh;
1493 const mesh::Obstacle &obstacle = state.obstacle;
1494 const assembler::Problem &problem = *state.problem;
1495
1496 Eigen::MatrixXd points;
1497 Eigen::MatrixXi tets;
1498 Eigen::MatrixXi el_id;
1499 Eigen::MatrixXd discr;
1500 std::vector<CellElement> elements;
1501
1502 if (opts.use_sampler)
1503 build_vis_mesh(mesh, disc_orders, gbases,
1504 state.polys, state.polys_3d, opts.boundary_only,
1505 points, tets, el_id, discr);
1506 else
1507 build_high_order_vis_mesh(mesh, disc_orders, disc_ordersq, bases,
1508 points, elements, el_id, discr);
1509
1510 Eigen::MatrixXd fun, exact_fun, err, node_fun;
1511
1512 if (opts.sol_on_grid)
1513 {
1514 const int problem_dim = problem.is_scalar() ? 1 : mesh.dimension();
1515 Eigen::MatrixXd tmp, tmp_grad;
1516 Eigen::MatrixXd tmp_p, tmp_grad_p;
1517 Eigen::MatrixXd res(grid_points_to_elements.size(), problem_dim);
1518 res.setConstant(std::numeric_limits<double>::quiet_NaN());
1519 Eigen::MatrixXd res_grad(grid_points_to_elements.size(), problem_dim * problem_dim);
1520 res_grad.setConstant(std::numeric_limits<double>::quiet_NaN());
1521
1522 Eigen::MatrixXd res_p(grid_points_to_elements.size(), 1);
1523 res_p.setConstant(std::numeric_limits<double>::quiet_NaN());
1524 Eigen::MatrixXd res_grad_p(grid_points_to_elements.size(), problem_dim);
1525 res_grad_p.setConstant(std::numeric_limits<double>::quiet_NaN());
1526
1527 for (int i = 0; i < grid_points_to_elements.size(); ++i)
1528 {
1529 const int el_id = grid_points_to_elements(i);
1530 if (el_id < 0)
1531 continue;
1532 assert(mesh.is_simplex(el_id));
1533 const Eigen::MatrixXd bc = grid_points_bc.row(i);
1534 Eigen::MatrixXd pt(1, bc.cols() - 1);
1535 for (int d = 1; d < bc.cols(); ++d)
1536 pt(d - 1) = bc(d);
1538 mesh, problem.is_scalar(), bases, gbases,
1539 el_id, pt, sol, tmp, tmp_grad);
1540
1541 res.row(i) = tmp;
1542 res_grad.row(i) = tmp_grad;
1543
1544 if (state.mixed_assembler != nullptr)
1545 {
1547 mesh, 1, pressure_bases, gbases,
1548 el_id, pt, pressure, tmp_p, tmp_grad_p);
1549 res_p.row(i) = tmp_p;
1550 res_grad_p.row(i) = tmp_grad_p;
1551 }
1552 }
1553
1554 std::ofstream os(path + "_sol.txt");
1555 os << res;
1556
1557 std::ofstream osg(path + "_grad.txt");
1558 osg << res_grad;
1559
1560 std::ofstream osgg(path + "_grid.txt");
1561 osgg << grid_points;
1562
1563 if (state.mixed_assembler != nullptr)
1564 {
1565 std::ofstream osp(path + "_p_sol.txt");
1566 osp << res_p;
1567
1568 std::ofstream osgp(path + "_p_grad.txt");
1569 osgp << res_grad_p;
1570 }
1571 }
1572
1573 Eigen::Vector<bool, -1> validity;
1574 if (opts.jacobian_validity)
1576 mesh, gbases, bases, state.disc_orders,
1577 state.polys, state.polys_3d, ref_element_sampler,
1578 points.rows(), sol, validity, opts.use_sampler, opts.boundary_only);
1579
1581 mesh, problem.is_scalar(), bases, disc_orders, disc_ordersq,
1582 state.polys, state.polys_3d, ref_element_sampler,
1583 points.rows(), sol, fun, opts.use_sampler, opts.boundary_only);
1584
1585 {
1586 Eigen::MatrixXd tmp = Eigen::VectorXd::LinSpaced(sol.size(), 0, sol.size() - 1);
1587
1589 mesh, problem.is_scalar(), bases, disc_orders, disc_ordersq,
1590 state.polys, state.polys_3d, ref_element_sampler,
1591 points.rows(), tmp, node_fun, opts.use_sampler, opts.boundary_only);
1592 }
1593
1594 if (obstacle.n_vertices() > 0)
1595 {
1596 fun.conservativeResize(fun.rows() + obstacle.n_vertices(), fun.cols());
1597 node_fun.conservativeResize(node_fun.rows() + obstacle.n_vertices(), node_fun.cols());
1598 node_fun.bottomRows(obstacle.n_vertices()).setZero();
1599 // obstacle.update_displacement(t, fun);
1600 // NOTE: Assuming the obstacle displacement is the last part of the solution
1601 fun.bottomRows(obstacle.n_vertices()) = utils::unflatten(sol.bottomRows(obstacle.ndof()), fun.cols());
1602 }
1603
1604 if (problem.has_exact_sol())
1605 {
1606 problem.exact(points, t, exact_fun);
1607 err = (fun - exact_fun).eval().rowwise().norm();
1608
1609 if (obstacle.n_vertices() > 0)
1610 {
1611 exact_fun.conservativeResize(exact_fun.rows() + obstacle.n_vertices(), exact_fun.cols());
1612 // obstacle.update_displacement(t, exact_fun);
1613 exact_fun.bottomRows(obstacle.n_vertices()) = utils::unflatten(sol.bottomRows(obstacle.ndof()), fun.cols());
1614
1615 err.conservativeResize(err.rows() + obstacle.n_vertices(), 1);
1616 err.bottomRows(obstacle.n_vertices()).setZero();
1617 }
1618 }
1619
1620 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
1621 if (opts.use_hdf5)
1622 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
1623 else
1624 tmpw = std::make_shared<paraviewo::VTUWriter>();
1625 paraviewo::ParaviewWriter &writer = *tmpw;
1626
1627 if (validity.size() && opts.export_field("validity"))
1628 writer.add_field("validity", validity.cast<double>());
1629
1630 if (opts.nodes && opts.export_field("nodes"))
1631 writer.add_field("nodes", node_fun);
1632
1633 if (problem.is_time_dependent())
1634 {
1635 bool is_time_integrator_valid = time_integrator != nullptr;
1636
1637 if (opts.velocity || opts.export_field("velocity"))
1638 {
1639 const Eigen::VectorXd velocity =
1640 is_time_integrator_valid ? (time_integrator->v_prev()) : Eigen::VectorXd::Zero(sol.size());
1641 save_volume_vector_field(state, points, opts, "velocity", velocity, writer);
1642 }
1643
1644 if (opts.acceleration || opts.export_field("acceleration"))
1645 {
1646 const Eigen::VectorXd acceleration =
1647 is_time_integrator_valid ? (time_integrator->a_prev()) : Eigen::VectorXd::Zero(sol.size());
1648 save_volume_vector_field(state, points, opts, "acceleration", acceleration, writer);
1649 }
1650 }
1651
1652 if (opts.forces)
1653 {
1654 const double s = state.solve_data.time_integrator
1655 ? state.solve_data.time_integrator->acceleration_scaling()
1656 : 1;
1657
1658 for (const auto &[name, form] : state.solve_data.named_forms())
1659 {
1660 // NOTE: Assumes this form will be null for the entire sim
1661 if (form == nullptr)
1662 continue;
1663
1664 Eigen::VectorXd force;
1665 if (form->enabled())
1666 {
1667 form->first_derivative(sol, force);
1668 force *= -1.0 / s; // Divide by acceleration scaling to get units of force
1669 }
1670 else
1671 {
1672 force.setZero(sol.size());
1673 }
1674 if (opts.export_field(name + "_forces"))
1675 save_volume_vector_field(state, points, opts, name + "_forces", force, writer);
1676 }
1677 }
1678
1679 // if(problem->is_mixed())
1680 if (state.mixed_assembler != nullptr && opts.export_field("pressure"))
1681 {
1682 Eigen::MatrixXd interp_p;
1684 mesh, 1, // FIXME: disc_orders should use pressure discr orders, works only with sampler
1685 pressure_bases, disc_orders, disc_ordersq, state.polys, state.polys_3d, ref_element_sampler,
1686 points.rows(), pressure, interp_p, opts.use_sampler, opts.boundary_only);
1687
1688 if (obstacle.n_vertices() > 0)
1689 {
1690 interp_p.conservativeResize(interp_p.size() + obstacle.n_vertices(), 1);
1691 interp_p.bottomRows(obstacle.n_vertices()).setZero();
1692 }
1693
1694 writer.add_field("pressure", interp_p);
1695 }
1696
1697 if (obstacle.n_vertices() > 0)
1698 {
1699 discr.conservativeResize(discr.size() + obstacle.n_vertices(), 1);
1700 discr.bottomRows(obstacle.n_vertices()).setZero();
1701 }
1702
1703 if (opts.discretization_order && opts.export_field("discr"))
1704 writer.add_field("discr", discr);
1705
1706 if (problem.has_exact_sol())
1707 {
1708 if (opts.export_field("exact"))
1709 writer.add_field("exact", exact_fun);
1710 if (opts.export_field("error"))
1711 writer.add_field("error", err);
1712 }
1713
1714 if (fun.cols() != 1 && (opts.scalar_values || opts.tensor_values || (!opts.use_spline && (opts.scalar_values || opts.tensor_values))))
1715 {
1716 std::vector<assembler::Assembler::NamedMatrix> vals, tvals;
1718 mesh, problem.is_scalar(), bases, gbases,
1719 disc_orders, disc_ordersq, state.polys, state.polys_3d,
1720 *state.assembler,
1721 ref_element_sampler, points.rows(), sol, t, vals, opts.use_sampler, opts.boundary_only);
1722
1723 for (auto &[_, v] : vals)
1725
1726 if (opts.scalar_values)
1727 {
1728 for (const auto &[name, v] : vals)
1729 {
1730 if (opts.export_field(name))
1731 writer.add_field(name, v);
1732 }
1733 }
1734
1735 if (opts.tensor_values)
1736 {
1738 mesh, problem.is_scalar(), bases, gbases, disc_orders, disc_ordersq,
1739 state.polys, state.polys_3d, *state.assembler, ref_element_sampler,
1740 points.rows(), sol, t, tvals, opts.use_sampler, opts.boundary_only);
1741
1742 for (auto &[_, v] : tvals)
1744
1745 for (const auto &[name, v] : tvals)
1746 {
1747 const int stride = mesh.dimension();
1748 assert(v.cols() % stride == 0);
1749
1750 if (!opts.export_field(name))
1751 continue;
1752
1753 for (int i = 0; i < v.cols(); i += stride)
1754 {
1755 const Eigen::MatrixXd tmp = v.middleCols(i, stride);
1756 assert(tmp.cols() == stride);
1757
1758 const int ii = (i / stride) + 1;
1759 writer.add_field(fmt::format("{:s}_{:d}", name, ii), tmp);
1760 }
1761 }
1762 }
1763
1764 if (!opts.use_spline && (opts.scalar_values || opts.tensor_values))
1765 {
1767 mesh, problem.is_scalar(), state.n_bases, bases, gbases,
1768 disc_orders, disc_ordersq, state.polys, state.polys_3d, *state.assembler,
1769 ref_element_sampler, t, points.rows(), sol, vals, tvals,
1770 opts.use_sampler, opts.boundary_only);
1771
1772 if (obstacle.n_vertices() > 0)
1773 {
1774 for (auto &v : vals)
1775 {
1776 v.second.conservativeResize(v.second.size() + obstacle.n_vertices(), 1);
1777 v.second.bottomRows(obstacle.n_vertices()).setZero();
1778 }
1779 }
1780
1781 if (opts.scalar_values)
1782 {
1783 for (const auto &v : vals)
1784 {
1785 if (opts.export_field(fmt::format("{:s}_avg", v.first)))
1786 writer.add_field(fmt::format("{:s}_avg", v.first), v.second);
1787 }
1788 }
1789 if (opts.tensor_values)
1790 {
1791 for (const auto &v : tvals)
1792 {
1793 const int stride = mesh.dimension();
1794 assert(v.second.cols() % stride == 0);
1795
1796 if (!opts.export_field(fmt::format("{:s}_avg", v.first)))
1797 continue;
1798
1799 for (int i = 0; i < v.second.cols(); i += stride)
1800 {
1801 const Eigen::MatrixXd tmp = v.second.middleCols(i, stride);
1802 assert(tmp.cols() == stride);
1803
1804 const int ii = (i / stride) + 1;
1805 writer.add_field(
1806 fmt::format("{:s}_avg_{:d}", v.first, ii), tmp);
1807 }
1808 }
1809 }
1810 }
1811 }
1812
1813 if (opts.material_params)
1814 {
1815 const auto &params = assembler.parameters();
1816
1817 std::map<std::string, Eigen::MatrixXd> param_val;
1818 for (const auto &[p, _] : params)
1819 param_val[p] = Eigen::MatrixXd(points.rows(), 1);
1820 Eigen::MatrixXd rhos(points.rows(), 1);
1821
1822 Eigen::MatrixXd local_pts;
1823 Eigen::MatrixXi vis_faces_poly, vis_edges_poly;
1824
1825 int index = 0;
1826 const auto &sampler = ref_element_sampler;
1827 for (int e = 0; e < int(bases.size()); ++e)
1828 {
1829 const basis::ElementBases &gbs = gbases[e];
1830 const basis::ElementBases &bs = bases[e];
1831
1832 if (opts.use_sampler)
1833 {
1834 if (mesh.is_simplex(e))
1835 local_pts = sampler.simplex_points();
1836 else if (mesh.is_cube(e))
1837 local_pts = sampler.cube_points();
1838 else if (mesh.is_prism(e))
1839 local_pts = sampler.prism_points();
1840 else if (mesh.is_pyramid(e))
1841 local_pts = sampler.pyramid_points();
1842 else
1843 {
1844 if (mesh.is_volume())
1845 sampler.sample_polyhedron(polys_3d.at(e).first, polys_3d.at(e).second, local_pts, vis_faces_poly, vis_edges_poly);
1846 else
1847 sampler.sample_polygon(polys.at(e), local_pts, vis_faces_poly, vis_edges_poly);
1848 }
1849 }
1850 else
1851 {
1852 if (mesh.is_volume())
1853 {
1854 if (mesh.is_simplex(e))
1855 autogen::p_nodes_3d(disc_orders(e), local_pts);
1856 else if (mesh.is_cube(e))
1857 autogen::q_nodes_3d(disc_orders(e), local_pts);
1858 else if (mesh.is_prism(e))
1859 {
1860 const auto o = std::max(disc_orders(e), disc_ordersq(e));
1861 autogen::prism_nodes_3d(o, o, local_pts);
1862 }
1863 else if (mesh.is_pyramid(e))
1864 autogen::pyramid_nodes_3d(disc_orders(e) == 2 ? -1 : disc_orders(e), local_pts);
1865
1866 else
1867 continue;
1868 }
1869 else
1870 {
1871 if (mesh.is_simplex(e))
1872 autogen::p_nodes_2d(disc_orders(e), local_pts);
1873 else if (mesh.is_cube(e))
1874 autogen::q_nodes_2d(disc_orders(e), local_pts);
1875 else
1876 {
1877 const auto &mesh2d = static_cast<const mesh::Mesh2D &>(mesh);
1878 const int n_v = mesh2d.n_face_vertices(e);
1879 local_pts.resize(n_v, 2);
1880
1881 for (int j = 0; j < n_v; ++j)
1882 {
1883 local_pts.row(j) = mesh2d.point(mesh2d.face_vertex(e, j));
1884 }
1885 }
1886 }
1887 }
1888
1890 vals.compute(e, mesh.is_volume(), local_pts, bs, gbs);
1891
1892 for (int j = 0; j < vals.val.rows(); ++j)
1893 {
1894 for (const auto &[p, func] : params)
1895 param_val.at(p)(index) = func(local_pts.row(j), vals.val.row(j), t, e);
1896
1897 rhos(index) = density(local_pts.row(j), vals.val.row(j), t, e);
1898
1899 ++index;
1900 }
1901 }
1902
1903 assert(index == points.rows());
1904
1905 if (obstacle.n_vertices() > 0)
1906 {
1907 for (auto &[_, tmp] : param_val)
1908 {
1909 tmp.conservativeResize(tmp.size() + obstacle.n_vertices(), 1);
1910 tmp.bottomRows(obstacle.n_vertices()).setZero();
1911 }
1912
1913 rhos.conservativeResize(rhos.size() + obstacle.n_vertices(), 1);
1914 rhos.bottomRows(obstacle.n_vertices()).setZero();
1915 }
1916 for (const auto &[p, tmp] : param_val)
1917 {
1918 if (opts.export_field(p))
1919 writer.add_field(p, tmp);
1920 }
1921 if (opts.export_field("rho"))
1922 writer.add_field("rho", rhos);
1923 }
1924
1925 if (opts.body_ids || opts.export_field("body_ids"))
1926 {
1927
1928 Eigen::MatrixXd ids(points.rows(), 1);
1929
1930 for (int i = 0; i < points.rows(); ++i)
1931 {
1932 ids(i) = mesh.get_body_id(el_id(i));
1933 }
1934
1935 if (obstacle.n_vertices() > 0)
1936 {
1937 ids.conservativeResize(ids.size() + obstacle.n_vertices(), 1);
1938 ids.bottomRows(obstacle.n_vertices()).setZero();
1939 }
1940
1941 writer.add_field("body_ids", ids);
1942 }
1943
1944 // if (opts.export_field("rhs"))
1945 // {
1946 // interpolate_function(pts_index, rhs, fun, opts.boundary_only);
1947 // writer.add_field("rhs", fun);
1948 // }
1949
1950 if (fun.cols() != 1 && state.mixed_assembler == nullptr && opts.export_field("traction_force"))
1951 {
1952 Eigen::MatrixXd traction_forces, traction_forces_fun;
1953 compute_traction_forces(state, sol, t, traction_forces, false);
1954
1956 mesh, problem.is_scalar(), bases, disc_orders, disc_ordersq,
1957 state.polys, state.polys_3d, ref_element_sampler,
1958 points.rows(), traction_forces, traction_forces_fun, opts.use_sampler, opts.boundary_only);
1959
1960 if (obstacle.n_vertices() > 0)
1961 {
1962 traction_forces_fun.conservativeResize(traction_forces_fun.rows() + obstacle.n_vertices(), traction_forces_fun.cols());
1963 traction_forces_fun.bottomRows(obstacle.n_vertices()).setZero();
1964 }
1965
1966 writer.add_field("traction_force", traction_forces_fun);
1967 }
1968
1969 if (fun.cols() != 1 && state.mixed_assembler == nullptr && opts.export_field("gradient_of_elastic_potential"))
1970 {
1971 try
1972 {
1973 Eigen::VectorXd potential_grad;
1974 Eigen::MatrixXd potential_grad_fun;
1975 if (state.solve_data.elastic_form)
1976 state.solve_data.elastic_form->first_derivative(sol, potential_grad);
1977
1979 mesh, problem.is_scalar(), bases, disc_orders, disc_ordersq,
1980 state.polys, state.polys_3d, ref_element_sampler,
1981 points.rows(), potential_grad, potential_grad_fun, opts.use_sampler, opts.boundary_only);
1982
1983 if (obstacle.n_vertices() > 0)
1984 {
1985 potential_grad_fun.conservativeResize(potential_grad_fun.rows() + obstacle.n_vertices(), potential_grad_fun.cols());
1986 potential_grad_fun.bottomRows(obstacle.n_vertices()).setZero();
1987 }
1988
1989 writer.add_field("gradient_of_elastic_potential", potential_grad_fun);
1990 }
1991 catch (std::exception &)
1992 {
1993 }
1994 }
1995
1996 if (fun.cols() != 1 && state.mixed_assembler == nullptr && opts.export_field("gradient_of_contact_potential"))
1997 {
1998 try
1999 {
2000 Eigen::VectorXd potential_grad;
2001 Eigen::MatrixXd potential_grad_fun;
2002 if (state.solve_data.contact_form && state.solve_data.contact_form->weight() > 0)
2003 {
2004 state.solve_data.contact_form->first_derivative(sol, potential_grad);
2005 potential_grad *= -state.solve_data.contact_form->barrier_stiffness() / state.solve_data.contact_form->weight();
2006
2008 mesh, problem.is_scalar(), bases, state.disc_orders, state.disc_ordersq,
2009 state.polys, state.polys_3d, ref_element_sampler,
2010 points.rows(), potential_grad, potential_grad_fun, opts.use_sampler, opts.boundary_only);
2011
2012 if (obstacle.n_vertices() > 0)
2013 {
2014 potential_grad_fun.conservativeResize(potential_grad_fun.rows() + obstacle.n_vertices(), potential_grad_fun.cols());
2015 potential_grad_fun.bottomRows(obstacle.n_vertices()).setZero();
2016 }
2017
2018 writer.add_field("gradient_of_contact_potential", potential_grad_fun);
2019 }
2020 }
2021 catch (std::exception &)
2022 {
2023 }
2024 }
2025
2026 // Write the solution last so it is the default for warp-by-vector
2027 writer.add_field("solution", fun);
2028
2029 if (obstacle.n_vertices() > 0)
2030 {
2031 const int orig_p = points.rows();
2032 points.conservativeResize(points.rows() + obstacle.n_vertices(), points.cols());
2033 points.bottomRows(obstacle.n_vertices()) = obstacle.v();
2034
2035 if (elements.empty())
2036 {
2037 for (int i = 0; i < tets.rows(); ++i)
2038 {
2039 elements.emplace_back();
2040 elements.back().ctype = CellType::Tetrahedron;
2041 for (int j = 0; j < tets.cols(); ++j)
2042 elements.back().vertices.push_back(tets(i, j));
2043 }
2044 }
2045
2046 for (int i = 0; i < obstacle.get_face_connectivity().rows(); ++i)
2047 {
2048 elements.emplace_back();
2049 elements.back().ctype = CellType::Tetrahedron;
2050 for (int j = 0; j < obstacle.get_face_connectivity().cols(); ++j)
2051 elements.back().vertices.push_back(obstacle.get_face_connectivity()(i, j) + orig_p);
2052 }
2053
2054 for (int i = 0; i < obstacle.get_edge_connectivity().rows(); ++i)
2055 {
2056 elements.emplace_back();
2057 elements.back().ctype = CellType::Tetrahedron;
2058 for (int j = 0; j < obstacle.get_edge_connectivity().cols(); ++j)
2059 elements.back().vertices.push_back(obstacle.get_edge_connectivity()(i, j) + orig_p);
2060 }
2061
2062 for (int i = 0; i < obstacle.get_vertex_connectivity().size(); ++i)
2063 {
2064 elements.emplace_back();
2065 elements.back().ctype = CellType::Tetrahedron;
2066 elements.back().vertices.push_back(obstacle.get_vertex_connectivity()(i) + orig_p);
2067 }
2068 }
2069
2070 if (elements.empty())
2071 writer.write_mesh(path, points, tets, mesh.is_volume() ? CellType::Tetrahedron : CellType::Triangle);
2072 else
2073 writer.write_mesh(path, points, elements);
2074 }
2075
2077 const State &state,
2078 const Eigen::MatrixXd &points,
2079 const ExportOptions &opts,
2080 const std::string &name,
2081 const Eigen::VectorXd &field,
2082 paraviewo::ParaviewWriter &writer) const
2083 {
2084 Eigen::MatrixXd inerpolated_field;
2086 *state.mesh, state.problem->is_scalar(), state.bases, state.disc_orders, state.disc_ordersq,
2087 state.polys, state.polys_3d, ref_element_sampler,
2088 points.rows(), field, inerpolated_field, opts.use_sampler, opts.boundary_only);
2089
2090 if (state.obstacle.n_vertices() > 0)
2091 {
2092 inerpolated_field.conservativeResize(
2093 inerpolated_field.rows() + state.obstacle.n_vertices(), inerpolated_field.cols());
2094 inerpolated_field.bottomRows(state.obstacle.n_vertices()) =
2095 utils::unflatten(field.tail(state.obstacle.ndof()), inerpolated_field.cols());
2096 }
2097 if (opts.export_field(name))
2098 writer.add_field(name, inerpolated_field);
2099 }
2100
2102 const std::string &export_surface,
2103 const State &state,
2104 const Eigen::MatrixXd &sol,
2105 const Eigen::MatrixXd &pressure,
2106 const double t,
2107 const double dt_in,
2108 const ExportOptions &opts,
2109 const bool is_contact_enabled) const
2110 {
2111
2112 const Eigen::VectorXi &disc_orders = state.disc_orders;
2113 const auto &density = state.mass_matrix_assembler->density();
2114 const std::vector<basis::ElementBases> &bases = state.bases;
2115 const std::vector<basis::ElementBases> &pressure_bases = state.pressure_bases;
2116 const std::vector<basis::ElementBases> &gbases = state.geom_bases();
2117 const assembler::Assembler &assembler = *state.assembler;
2118 const assembler::Problem &problem = *state.problem;
2119 const mesh::Mesh &mesh = *state.mesh;
2120 int problem_dim = (problem.is_scalar() ? 1 : mesh.dimension());
2121
2122 Eigen::MatrixXd boundary_vis_vertices;
2123 Eigen::MatrixXd boundary_vis_local_vertices;
2124 Eigen::MatrixXi boundary_vis_elements;
2125 Eigen::MatrixXi boundary_vis_elements_ids;
2126 Eigen::MatrixXi boundary_vis_primitive_ids;
2127 Eigen::MatrixXd boundary_vis_normals;
2128 Eigen::MatrixXd displaced_boundary_vis_normals;
2129
2130 build_vis_boundary_mesh(mesh, bases, gbases, state.total_local_boundary, sol, problem_dim,
2131 boundary_vis_vertices, boundary_vis_local_vertices, boundary_vis_elements,
2132 boundary_vis_elements_ids, boundary_vis_primitive_ids, boundary_vis_normals,
2133 displaced_boundary_vis_normals);
2134
2135 Eigen::MatrixXd fun, interp_p, discr, vect, b_sidesets;
2136
2137 Eigen::MatrixXd lsol, lp, lgrad, lpgrad;
2138
2139 int actual_dim = 1;
2140 if (!problem.is_scalar())
2141 actual_dim = mesh.dimension();
2142
2143 discr.resize(boundary_vis_vertices.rows(), 1);
2144 fun.resize(boundary_vis_vertices.rows(), actual_dim);
2145 interp_p.resize(boundary_vis_vertices.rows(), 1);
2146 vect.resize(boundary_vis_vertices.rows(), mesh.dimension());
2147
2148 b_sidesets.resize(boundary_vis_vertices.rows(), 1);
2149 b_sidesets.setZero();
2150
2151 for (int i = 0; i < boundary_vis_vertices.rows(); ++i)
2152 {
2153 const auto s_id = mesh.get_boundary_id(boundary_vis_primitive_ids(i));
2154 if (s_id > 0)
2155 {
2156 b_sidesets(i) = s_id;
2157 }
2158
2159 const int el_index = boundary_vis_elements_ids(i);
2161 mesh, problem.is_scalar(), bases, gbases,
2162 el_index, boundary_vis_local_vertices.row(i), sol, lsol, lgrad);
2163 assert(lsol.size() == actual_dim);
2164 if (state.mixed_assembler != nullptr)
2165 {
2167 mesh, 1, pressure_bases, gbases,
2168 el_index, boundary_vis_local_vertices.row(i), pressure, lp, lpgrad);
2169 assert(lp.size() == 1);
2170 interp_p(i) = lp(0);
2171 }
2172
2173 discr(i) = disc_orders(el_index);
2174 for (int j = 0; j < actual_dim; ++j)
2175 {
2176 fun(i, j) = lsol(j);
2177 }
2178
2179 if (actual_dim == 1)
2180 {
2181 assert(lgrad.size() == mesh.dimension());
2182 for (int j = 0; j < mesh.dimension(); ++j)
2183 {
2184 vect(i, j) = lgrad(j);
2185 }
2186 }
2187 else
2188 {
2189 assert(lgrad.size() == actual_dim * actual_dim);
2190 std::vector<assembler::Assembler::NamedMatrix> tensor_flat;
2191 const basis::ElementBases &gbs = gbases[el_index];
2192 const basis::ElementBases &bs = bases[el_index];
2193 assembler.compute_tensor_value(assembler::OutputData(t, el_index, bs, gbs, boundary_vis_local_vertices.row(i), sol), tensor_flat);
2194 // TF computed only from cauchy stress
2195 assert(tensor_flat[0].first == "cauchy_stess");
2196 assert(tensor_flat[0].second.size() == actual_dim * actual_dim);
2197
2198 Eigen::Map<Eigen::MatrixXd> tensor(tensor_flat[0].second.data(), actual_dim, actual_dim);
2199 vect.row(i) = displaced_boundary_vis_normals.row(i) * tensor;
2200
2201 double area = 0;
2202 if (mesh.is_volume())
2203 {
2204 if (mesh.is_simplex(el_index))
2205 area = mesh.tri_area(boundary_vis_primitive_ids(i));
2206 else if (mesh.is_cube(el_index))
2207 area = mesh.quad_area(boundary_vis_primitive_ids(i));
2208 else if (mesh.is_prism(el_index))
2209 {
2210 const int tmp = boundary_vis_primitive_ids(i);
2211 area = mesh.n_face_vertices(tmp) == 4 ? mesh.quad_area(tmp) : mesh.tri_area(tmp);
2212 }
2213 else if (mesh.is_pyramid(el_index))
2214 {
2215 const int tmp = boundary_vis_primitive_ids(i);
2216 area = mesh.n_face_vertices(tmp) == 4 ? mesh.quad_area(tmp) : mesh.tri_area(tmp);
2217 }
2218 }
2219 else
2220 area = mesh.edge_length(boundary_vis_primitive_ids(i));
2221
2222 vect.row(i) *= area;
2223 }
2224 }
2225
2226 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
2227 if (opts.use_hdf5)
2228 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
2229 else
2230 tmpw = std::make_shared<paraviewo::VTUWriter>();
2231 paraviewo::ParaviewWriter &writer = *tmpw;
2232
2233 if (opts.export_field("normals"))
2234 writer.add_field("normals", boundary_vis_normals);
2235 if (opts.export_field("displaced_normals"))
2236 writer.add_field("displaced_normals", displaced_boundary_vis_normals);
2237 if (state.mixed_assembler != nullptr && opts.export_field("pressure"))
2238 writer.add_field("pressure", interp_p);
2239 if (opts.export_field("discr"))
2240 writer.add_field("discr", discr);
2241 if (opts.export_field("sidesets"))
2242 writer.add_field("sidesets", b_sidesets);
2243
2244 if (actual_dim == 1 && opts.export_field("solution_grad"))
2245 writer.add_field("solution_grad", vect);
2246 else if (opts.export_field("traction_force"))
2247 {
2248 writer.add_field("traction_force", vect);
2249 }
2250
2251 if (opts.material_params)
2252 {
2253 const auto &params = assembler.parameters();
2254
2255 std::map<std::string, Eigen::MatrixXd> param_val;
2256 for (const auto &[p, _] : params)
2257 param_val[p] = Eigen::MatrixXd(boundary_vis_vertices.rows(), 1);
2258 Eigen::MatrixXd rhos(boundary_vis_vertices.rows(), 1);
2259
2260 for (int i = 0; i < boundary_vis_vertices.rows(); ++i)
2261 {
2262 double lambda, mu;
2263
2264 for (const auto &[p, func] : params)
2265 param_val.at(p)(i) = func(boundary_vis_local_vertices.row(i), boundary_vis_vertices.row(i), t, boundary_vis_elements_ids(i));
2266
2267 rhos(i) = density(boundary_vis_local_vertices.row(i), boundary_vis_vertices.row(i), t, boundary_vis_elements_ids(i));
2268 }
2269
2270 for (const auto &[p, tmp] : param_val)
2271 {
2272 if (opts.export_field(p))
2273 writer.add_field(p, tmp);
2274 }
2275 if (opts.export_field("rho"))
2276 writer.add_field("rho", rhos);
2277 }
2278
2279 if (opts.body_ids || opts.export_field("body_ids"))
2280 {
2281
2282 Eigen::MatrixXd ids(boundary_vis_vertices.rows(), 1);
2283
2284 for (int i = 0; i < boundary_vis_vertices.rows(); ++i)
2285 {
2286 ids(i) = mesh.get_body_id(boundary_vis_elements_ids(i));
2287 }
2288
2289 writer.add_field("body_ids", ids);
2290 }
2291
2292 // Write the solution last so it is the default for warp-by-vector
2293 writer.add_field("solution", fun);
2294 writer.write_mesh(export_surface, boundary_vis_vertices, boundary_vis_elements, mesh.is_volume() ? CellType::Triangle : CellType::Line);
2295 }
2296
2298 const std::string &export_surface,
2299 const State &state,
2300 const Eigen::MatrixXd &sol,
2301 const Eigen::MatrixXd &pressure,
2302 const double t,
2303 const double dt_in,
2304 const ExportOptions &opts,
2305 const bool is_contact_enabled) const
2306 {
2307 const mesh::Mesh &mesh = *state.mesh;
2308 const ipc::CollisionMesh &collision_mesh = state.collision_mesh;
2309 const double dhat = state.args["contact"]["dhat"];
2310 const double friction_coefficient = state.args["contact"]["friction_coefficient"];
2311 const double epsv = state.args["contact"]["epsv"];
2312 const double dhat_a = state.args["contact"]["adhesion"]["dhat_a"];
2313 const double dhat_p = state.args["contact"]["adhesion"]["dhat_p"];
2314 const double Y = state.args["contact"]["adhesion"]["adhesion_strength"];
2315 const double epsa = state.args["contact"]["adhesion"]["epsa"];
2316 const double tangential_adhesion_coefficient = state.args["contact"]["adhesion"]["tangential_adhesion_coefficient"];
2317 const std::shared_ptr<solver::ContactForm> &contact_form = state.solve_data.contact_form;
2318 const std::shared_ptr<solver::FrictionForm> &friction_form = state.solve_data.friction_form;
2319 const std::shared_ptr<solver::NormalAdhesionForm> &normal_adhesion_form = state.solve_data.normal_adhesion_form;
2320 const std::shared_ptr<solver::TangentialAdhesionForm> &tangential_adhesion_form = state.solve_data.tangential_adhesion_form;
2321
2322 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
2323 if (opts.use_hdf5)
2324 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
2325 else
2326 tmpw = std::make_shared<paraviewo::VTUWriter>();
2327 paraviewo::ParaviewWriter &writer = *tmpw;
2328
2329 const int problem_dim = mesh.dimension();
2330 const Eigen::MatrixXd full_displacements = utils::unflatten(sol, problem_dim);
2331 const Eigen::MatrixXd surface_displacements = collision_mesh.map_displacements(full_displacements);
2332
2333 const Eigen::MatrixXd displaced_surface = collision_mesh.displace_vertices(full_displacements);
2334
2335 ipc::NormalCollisions collision_set;
2336 // collision_set.set_use_convergent_formulation(state.args["contact"]["use_convergent_formulation"]);
2337 if (state.args["contact"]["use_convergent_formulation"])
2338 {
2339 collision_set.set_use_area_weighting(state.args["contact"]["use_area_weighting"]);
2340 collision_set.set_use_improved_max_approximator(state.args["contact"]["use_improved_max_operator"]);
2341 }
2342
2343 collision_set.build(
2344 collision_mesh, displaced_surface, dhat,
2345 /*dmin=*/0, ipc::create_broad_phase(state.args["solver"]["contact"]["CCD"]["broad_phase"]).get());
2346
2347 const double barrier_stiffness = contact_form != nullptr ? contact_form->barrier_stiffness() : 1;
2348 ipc::BarrierPotential barrier_potential(dhat, barrier_stiffness);
2349 if (state.args["contact"]["use_convergent_formulation"])
2350 {
2351 barrier_potential.set_use_physical_barrier(state.args["contact"]["use_physical_barrier"]);
2352 }
2353
2354 if (opts.contact_forces || opts.export_field("contact_forces"))
2355 {
2356 Eigen::MatrixXd forces = -barrier_potential.gradient(collision_set, collision_mesh, displaced_surface);
2357
2358 Eigen::MatrixXd forces_reshaped = utils::unflatten(forces, problem_dim);
2359
2360 assert(forces_reshaped.rows() == surface_displacements.rows());
2361 assert(forces_reshaped.cols() == surface_displacements.cols());
2362 writer.add_field("contact_forces", forces_reshaped);
2363 }
2364
2365 if (contact_form && state.args["contact"]["use_gcp_formulation"] && state.args["contact"]["use_adaptive_dhat"] && opts.export_field("adaptive_dhat"))
2366 {
2367 const auto form = std::dynamic_pointer_cast<solver::SmoothContactForm>(contact_form);
2368 assert(form);
2369 const auto &set = form->collision_set();
2370
2371 if (problem_dim == 2)
2372 {
2373 Eigen::VectorXd dhats(collision_mesh.num_edges());
2374 dhats.setConstant(dhat);
2375 for (int e = 0; e < dhats.size(); e++)
2376 dhats(e) = set.get_edge_dhat(e);
2377
2378 writer.add_cell_field("dhat", dhats);
2379 }
2380 else
2381 {
2382 Eigen::VectorXd fdhats(collision_mesh.num_faces());
2383 fdhats.setConstant(dhat);
2384 for (int e = 0; e < fdhats.size(); e++)
2385 fdhats(e) = set.get_face_dhat(e);
2386
2387 writer.add_cell_field("dhat_face", fdhats);
2388
2389 Eigen::VectorXd vdhats(collision_mesh.num_vertices());
2390 vdhats.setConstant(dhat);
2391 for (int i = 0; i < vdhats.size(); i++)
2392 vdhats(i) = set.get_vert_dhat(i);
2393
2394 writer.add_field("dhat_vert", vdhats);
2395 }
2396 }
2397
2398 if (opts.friction_forces || opts.export_field("friction_forces"))
2399 {
2400 ipc::TangentialCollisions friction_collision_set;
2401 friction_collision_set.build(
2402 collision_mesh, displaced_surface, collision_set,
2403 barrier_potential, friction_coefficient);
2404
2405 ipc::FrictionPotential friction_potential(epsv);
2406
2407 Eigen::MatrixXd velocities;
2408 if (state.solve_data.time_integrator != nullptr)
2409 velocities = state.solve_data.time_integrator->v_prev();
2410 else
2411 velocities = sol;
2412 velocities = collision_mesh.map_displacements(utils::unflatten(velocities, collision_mesh.dim()));
2413
2414 Eigen::MatrixXd forces = -friction_potential.gradient(
2415 friction_collision_set, collision_mesh, velocities);
2416
2417 Eigen::MatrixXd forces_reshaped = utils::unflatten(forces, problem_dim);
2418
2419 assert(forces_reshaped.rows() == surface_displacements.rows());
2420 assert(forces_reshaped.cols() == surface_displacements.cols());
2421 writer.add_field("friction_forces", forces_reshaped);
2422 }
2423
2424 ipc::NormalCollisions adhesion_collision_set;
2425 adhesion_collision_set.build(
2426 collision_mesh, displaced_surface, dhat_a,
2427 /*dmin=*/0, ipc::create_broad_phase(state.args["solver"]["contact"]["CCD"]["broad_phase"]).get());
2428
2429 ipc::NormalAdhesionPotential normal_adhesion_potential(dhat_p, dhat_a, Y, 1);
2430
2431 if (opts.normal_adhesion_forces || opts.export_field("normal_adhesion_forces"))
2432 {
2433 Eigen::MatrixXd forces = -1 * normal_adhesion_potential.gradient(adhesion_collision_set, collision_mesh, displaced_surface);
2434
2435 Eigen::MatrixXd forces_reshaped = utils::unflatten(forces, problem_dim);
2436
2437 assert(forces_reshaped.rows() == surface_displacements.rows());
2438 assert(forces_reshaped.cols() == surface_displacements.cols());
2439 writer.add_field("normal_adhesion_forces", forces_reshaped);
2440 }
2441
2442 if (opts.tangential_adhesion_forces || opts.export_field("tangential_adhesion_forces"))
2443 {
2444 ipc::TangentialCollisions tangential_collision_set;
2445 tangential_collision_set.build(
2446 collision_mesh, displaced_surface, adhesion_collision_set,
2447 normal_adhesion_potential, tangential_adhesion_coefficient);
2448
2449 ipc::TangentialAdhesionPotential tangential_adhesion_potential(epsa);
2450
2451 Eigen::MatrixXd velocities;
2452 if (state.solve_data.time_integrator != nullptr)
2453 velocities = state.solve_data.time_integrator->v_prev();
2454 else
2455 velocities = sol;
2456 velocities = collision_mesh.map_displacements(utils::unflatten(velocities, collision_mesh.dim()));
2457
2458 Eigen::MatrixXd forces = -tangential_adhesion_potential.gradient(
2459 tangential_collision_set, collision_mesh, velocities);
2460
2461 Eigen::MatrixXd forces_reshaped = utils::unflatten(forces, problem_dim);
2462
2463 assert(forces_reshaped.rows() == surface_displacements.rows());
2464 assert(forces_reshaped.cols() == surface_displacements.cols());
2465 writer.add_field("tangential_adhesion_forces", forces_reshaped);
2466 }
2467
2468 assert(collision_mesh.rest_positions().rows() == surface_displacements.rows());
2469 assert(collision_mesh.rest_positions().cols() == surface_displacements.cols());
2470
2471 // Write the solution last so it is the default for warp-by-vector
2472 writer.add_field("solution", surface_displacements);
2473
2474 writer.write_mesh(
2475 export_surface.substr(0, export_surface.length() - 4) + "_contact.vtu",
2476 collision_mesh.rest_positions(),
2477 problem_dim == 3 ? collision_mesh.faces() : collision_mesh.edges(),
2478 problem_dim == 3 ? CellType::Triangle : CellType::Line);
2479 }
2480
2482 const std::string &name,
2483 const State &state,
2484 const Eigen::MatrixXd &sol,
2485 const double t,
2486 const ExportOptions &opts) const
2487 {
2488 const std::vector<basis::ElementBases> &gbases = state.geom_bases();
2489 const mesh::Mesh &mesh = *state.mesh;
2490 const assembler::Problem &problem = *state.problem;
2491
2492 const auto &sampler = ref_element_sampler;
2493
2494 Eigen::MatrixXi vis_faces_poly, vis_edges_poly;
2495 Eigen::MatrixXd vis_pts_poly;
2496
2497 const auto &current_bases = gbases;
2498 int seg_total_size = 0;
2499 int pts_total_size = 0;
2500 int faces_total_size = 0;
2501
2502 for (size_t i = 0; i < current_bases.size(); ++i)
2503 {
2504 const auto &bs = current_bases[i];
2505
2506 if (mesh.is_simplex(i))
2507 {
2508 pts_total_size += sampler.simplex_points().rows();
2509 seg_total_size += sampler.simplex_edges().rows();
2510 faces_total_size += sampler.simplex_faces().rows();
2511 }
2512 else if (mesh.is_cube(i))
2513 {
2514 pts_total_size += sampler.cube_points().rows();
2515 seg_total_size += sampler.cube_edges().rows();
2516 faces_total_size += sampler.cube_faces().rows();
2517 }
2518 else if (mesh.is_prism(i))
2519 {
2520 pts_total_size += sampler.prism_points().rows();
2521 seg_total_size += sampler.prism_edges().rows();
2522 faces_total_size += sampler.prism_faces().rows();
2523 }
2524 else if (mesh.is_pyramid(i))
2525 {
2526 pts_total_size += sampler.pyramid_points().rows();
2527 seg_total_size += sampler.pyramid_edges().rows();
2528 faces_total_size += sampler.pyramid_faces().rows();
2529 }
2530 else
2531 {
2532 if (mesh.is_volume())
2533 sampler.sample_polyhedron(state.polys_3d.at(i).first, state.polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
2534 else
2535 sampler.sample_polygon(state.polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
2536
2537 pts_total_size += vis_pts_poly.rows();
2538 seg_total_size += vis_edges_poly.rows();
2539 faces_total_size += vis_faces_poly.rows();
2540 }
2541 }
2542
2543 Eigen::MatrixXd points(pts_total_size, mesh.dimension());
2544 Eigen::MatrixXi edges(seg_total_size, 2);
2545 Eigen::MatrixXi faces(faces_total_size, 3);
2546 points.setZero();
2547
2548 Eigen::MatrixXd mapped, tmp;
2549 int seg_index = 0, pts_index = 0, face_index = 0;
2550 for (size_t i = 0; i < current_bases.size(); ++i)
2551 {
2552 const auto &bs = current_bases[i];
2553
2554 if (mesh.is_simplex(i))
2555 {
2556 bs.eval_geom_mapping(sampler.simplex_points(), mapped);
2557 edges.block(seg_index, 0, sampler.simplex_edges().rows(), edges.cols()) = sampler.simplex_edges().array() + pts_index;
2558 seg_index += sampler.simplex_edges().rows();
2559
2560 faces.block(face_index, 0, sampler.simplex_faces().rows(), 3) = sampler.simplex_faces().array() + pts_index;
2561 face_index += sampler.simplex_faces().rows();
2562
2563 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
2564 pts_index += mapped.rows();
2565 }
2566 else if (mesh.is_cube(i))
2567 {
2568 bs.eval_geom_mapping(sampler.cube_points(), mapped);
2569 edges.block(seg_index, 0, sampler.cube_edges().rows(), edges.cols()) = sampler.cube_edges().array() + pts_index;
2570 seg_index += sampler.cube_edges().rows();
2571
2572 faces.block(face_index, 0, sampler.cube_faces().rows(), 3) = sampler.cube_faces().array() + pts_index;
2573 face_index += sampler.cube_faces().rows();
2574
2575 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
2576 pts_index += mapped.rows();
2577 }
2578 else if (mesh.is_prism(i))
2579 {
2580 bs.eval_geom_mapping(sampler.prism_points(), mapped);
2581 edges.block(seg_index, 0, sampler.prism_edges().rows(), edges.cols()) = sampler.prism_edges().array() + pts_index;
2582 seg_index += sampler.prism_edges().rows();
2583
2584 faces.block(face_index, 0, sampler.prism_faces().rows(), 3) = sampler.prism_faces().array() + pts_index;
2585 face_index += sampler.prism_faces().rows();
2586
2587 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
2588 pts_index += mapped.rows();
2589 }
2590 else if (mesh.is_pyramid(i))
2591 {
2592 bs.eval_geom_mapping(sampler.pyramid_points(), mapped);
2593 edges.block(seg_index, 0, sampler.pyramid_edges().rows(), edges.cols()) = sampler.pyramid_edges().array() + pts_index;
2594 seg_index += sampler.pyramid_edges().rows();
2595
2596 faces.block(face_index, 0, sampler.pyramid_faces().rows(), 3) = sampler.pyramid_faces().array() + pts_index;
2597 face_index += sampler.pyramid_faces().rows();
2598
2599 points.block(pts_index, 0, mapped.rows(), points.cols()) = mapped;
2600 pts_index += mapped.rows();
2601 }
2602 else
2603 {
2604 if (mesh.is_volume())
2605 sampler.sample_polyhedron(state.polys_3d.at(i).first, state.polys_3d.at(i).second, vis_pts_poly, vis_faces_poly, vis_edges_poly);
2606 else
2607 sampler.sample_polygon(state.polys.at(i), vis_pts_poly, vis_faces_poly, vis_edges_poly);
2608
2609 edges.block(seg_index, 0, vis_edges_poly.rows(), edges.cols()) = vis_edges_poly.array() + pts_index;
2610 seg_index += vis_edges_poly.rows();
2611
2612 faces.block(face_index, 0, vis_faces_poly.rows(), 3) = vis_faces_poly.array() + pts_index;
2613 face_index += vis_faces_poly.rows();
2614
2615 points.block(pts_index, 0, vis_pts_poly.rows(), points.cols()) = vis_pts_poly;
2616 pts_index += vis_pts_poly.rows();
2617 }
2618 }
2619
2620 assert(pts_index == points.rows());
2621 assert(face_index == faces.rows());
2622
2623 if (mesh.is_volume())
2624 {
2625 // reverse all faces
2626 for (long i = 0; i < faces.rows(); ++i)
2627 {
2628 const int v0 = faces(i, 0);
2629 const int v1 = faces(i, 1);
2630 const int v2 = faces(i, 2);
2631
2632 int tmpc = faces(i, 2);
2633 faces(i, 2) = faces(i, 1);
2634 faces(i, 1) = tmpc;
2635 }
2636 }
2637 else
2638 {
2639 Eigen::Matrix2d mmat;
2640 for (long i = 0; i < faces.rows(); ++i)
2641 {
2642 const int v0 = faces(i, 0);
2643 const int v1 = faces(i, 1);
2644 const int v2 = faces(i, 2);
2645
2646 mmat.row(0) = points.row(v2) - points.row(v0);
2647 mmat.row(1) = points.row(v1) - points.row(v0);
2648
2649 if (mmat.determinant() > 0)
2650 {
2651 int tmpc = faces(i, 2);
2652 faces(i, 2) = faces(i, 1);
2653 faces(i, 1) = tmpc;
2654 }
2655 }
2656 }
2657
2658 Eigen::MatrixXd fun;
2660 mesh, problem.is_scalar(), state.bases, state.disc_orders, state.disc_ordersq,
2661 state.polys, state.polys_3d, ref_element_sampler,
2662 pts_index, sol, fun, /*use_sampler*/ true, false);
2663
2664 Eigen::MatrixXd exact_fun, err;
2665
2666 if (problem.has_exact_sol())
2667 {
2668 problem.exact(points, t, exact_fun);
2669 err = (fun - exact_fun).eval().rowwise().norm();
2670 }
2671
2672 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
2673 if (opts.use_hdf5)
2674 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
2675 else
2676 tmpw = std::make_shared<paraviewo::VTUWriter>();
2677 paraviewo::ParaviewWriter &writer = *tmpw;
2678
2679 if (problem.has_exact_sol())
2680 {
2681 if (opts.export_field("exact"))
2682 writer.add_field("exact", exact_fun);
2683 if (opts.export_field("error"))
2684 writer.add_field("error", err);
2685 }
2686
2687 if (fun.cols() != 1)
2688 {
2689 std::vector<assembler::Assembler::NamedMatrix> scalar_val;
2691 mesh, problem.is_scalar(), state.bases, gbases,
2692 state.disc_orders, state.disc_ordersq, state.polys, state.polys_3d,
2693 *state.assembler,
2694 ref_element_sampler, pts_index, sol, t, scalar_val, /*use_sampler*/ true, false);
2695 for (const auto &v : scalar_val)
2696 {
2697 if (opts.export_field(v.first))
2698 writer.add_field(v.first, v.second);
2699 }
2700 }
2701 // Write the solution last so it is the default for warp-by-vector
2702 writer.add_field("solution", fun);
2703
2704 writer.write_mesh(name, points, edges, CellType::Line);
2705 }
2706
2708 const std::string &path,
2709 const State &state,
2710 const Eigen::MatrixXd &sol,
2711 const ExportOptions &opts) const
2712 {
2713 const auto &dirichlet_nodes = state.dirichlet_nodes;
2714 const auto &dirichlet_nodes_position = state.dirichlet_nodes_position;
2715 const mesh::Mesh &mesh = *state.mesh;
2716 const assembler::Problem &problem = *state.problem;
2717
2718 int actual_dim = 1;
2719 if (!problem.is_scalar())
2720 actual_dim = mesh.dimension();
2721
2722 Eigen::MatrixXd fun(dirichlet_nodes_position.size(), actual_dim);
2723 Eigen::MatrixXd b_sidesets(dirichlet_nodes_position.size(), 1);
2724 b_sidesets.setZero();
2725 Eigen::MatrixXd points(dirichlet_nodes_position.size(), mesh.dimension());
2726 std::vector<CellElement> cells(dirichlet_nodes_position.size());
2727
2728 for (int i = 0; i < dirichlet_nodes_position.size(); ++i)
2729 {
2730 const int n_id = dirichlet_nodes[i];
2731 const auto s_id = mesh.get_node_id(n_id);
2732 if (s_id > 0)
2733 {
2734 b_sidesets(i) = s_id;
2735 }
2736
2737 for (int j = 0; j < actual_dim; ++j)
2738 {
2739 fun(i, j) = sol(n_id * actual_dim + j);
2740 }
2741
2742 points.row(i) = dirichlet_nodes_position[i];
2743 cells[i].vertices.push_back(i);
2744 cells[i].ctype = CellType::Vertex;
2745 }
2746
2747 std::shared_ptr<paraviewo::ParaviewWriter> tmpw;
2748 if (opts.use_hdf5)
2749 tmpw = std::make_shared<paraviewo::HDF5VTUWriter>();
2750 else
2751 tmpw = std::make_shared<paraviewo::VTUWriter>();
2752 paraviewo::ParaviewWriter &writer = *tmpw;
2753
2754 if (opts.export_field("sidesets"))
2755 writer.add_field("sidesets", b_sidesets);
2756 // Write the solution last so it is the default for warp-by-vector
2757 writer.add_field("solution", fun);
2758 writer.write_mesh(path, points, cells);
2759 }
2760
2762 const std::string &name,
2763 const std::function<std::string(int)> &vtu_names,
2764 int time_steps, double t0, double dt, int skip_frame) const
2765 {
2766 paraviewo::PVDWriter::save_pvd(name, vtu_names, time_steps, t0, dt, skip_frame);
2767 }
2768
2769 void OutGeometryData::init_sampler(const polyfem::mesh::Mesh &mesh, const double vismesh_rel_area)
2770 {
2771 ref_element_sampler.init(mesh.is_volume(), mesh.n_elements(), vismesh_rel_area);
2772 }
2773
2774 void OutGeometryData::build_grid(const polyfem::mesh::Mesh &mesh, const double spacing)
2775 {
2776 if (spacing <= 0)
2777 return;
2778
2779 RowVectorNd min, max;
2780 mesh.bounding_box(min, max);
2781 const RowVectorNd delta = max - min;
2782 const int nx = delta[0] / spacing + 1;
2783 const int ny = delta[1] / spacing + 1;
2784 const int nz = delta.cols() >= 3 ? (delta[2] / spacing + 1) : 1;
2785 const int n = nx * ny * nz;
2786
2787 grid_points.resize(n, delta.cols());
2788 int index = 0;
2789 for (int i = 0; i < nx; ++i)
2790 {
2791 const double x = (delta[0] / (nx - 1)) * i + min[0];
2792
2793 for (int j = 0; j < ny; ++j)
2794 {
2795 const double y = (delta[1] / (ny - 1)) * j + min[1];
2796
2797 if (delta.cols() <= 2)
2798 {
2799 grid_points.row(index++) << x, y;
2800 }
2801 else
2802 {
2803 for (int k = 0; k < nz; ++k)
2804 {
2805 const double z = (delta[2] / (nz - 1)) * k + min[2];
2806 grid_points.row(index++) << x, y, z;
2807 }
2808 }
2809 }
2810 }
2811
2812 assert(index == n);
2813
2814 std::vector<std::array<Eigen::Vector3d, 2>> boxes;
2815 mesh.elements_boxes(boxes);
2816
2817 SimpleBVH::BVH bvh;
2818 bvh.init(boxes);
2819
2820 const double eps = 1e-6;
2821
2822 grid_points_to_elements.resize(grid_points.rows(), 1);
2823 grid_points_to_elements.setConstant(-1);
2824
2825 grid_points_bc.resize(grid_points.rows(), mesh.is_volume() ? 4 : 3);
2826
2827 for (int i = 0; i < grid_points.rows(); ++i)
2828 {
2829 const Eigen::Vector3d min(
2830 grid_points(i, 0) - eps,
2831 grid_points(i, 1) - eps,
2832 (mesh.is_volume() ? grid_points(i, 2) : 0) - eps);
2833
2834 const Eigen::Vector3d max(
2835 grid_points(i, 0) + eps,
2836 grid_points(i, 1) + eps,
2837 (mesh.is_volume() ? grid_points(i, 2) : 0) + eps);
2838
2839 std::vector<unsigned int> candidates;
2840
2841 bvh.intersect_box(min, max, candidates);
2842
2843 for (const auto cand : candidates)
2844 {
2845 if (!mesh.is_simplex(cand))
2846 {
2847 logger().warn("Element {} is not simplex, skipping", cand);
2848 continue;
2849 }
2850
2851 Eigen::MatrixXd coords;
2852 mesh.barycentric_coords(grid_points.row(i), cand, coords);
2853
2854 for (int d = 0; d < coords.size(); ++d)
2855 {
2856 if (fabs(coords(d)) < 1e-8)
2857 coords(d) = 0;
2858 else if (fabs(coords(d) - 1) < 1e-8)
2859 coords(d) = 1;
2860 }
2861
2862 if (coords.array().minCoeff() >= 0 && coords.array().maxCoeff() <= 1)
2863 {
2864 grid_points_to_elements(i) = cand;
2865 grid_points_bc.row(i) = coords;
2866 break;
2867 }
2868 }
2869 }
2870 }
2871
2872 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)
2873 {
2874 Eigen::MatrixXd samples_simplex, samples_cube, mapped, p0, p1, p;
2875
2876 mesh_size = 0;
2877 average_edge_length = 0;
2878 min_edge_length = std::numeric_limits<double>::max();
2879
2880 if (!use_curved_mesh_size)
2881 {
2882 mesh_in.get_edges(p0, p1);
2883 p = p0 - p1;
2884 min_edge_length = p.rowwise().norm().minCoeff();
2885 average_edge_length = p.rowwise().norm().mean();
2886 mesh_size = p.rowwise().norm().maxCoeff();
2887
2888 logger().info("hmin: {}", min_edge_length);
2889 logger().info("hmax: {}", mesh_size);
2890 logger().info("havg: {}", average_edge_length);
2891
2892 return;
2893 }
2894
2895 if (mesh_in.is_volume())
2896 {
2897 utils::EdgeSampler::sample_3d_simplex(n_samples, samples_simplex);
2898 utils::EdgeSampler::sample_3d_cube(n_samples, samples_cube);
2899 }
2900 else
2901 {
2902 utils::EdgeSampler::sample_2d_simplex(n_samples, samples_simplex);
2903 utils::EdgeSampler::sample_2d_cube(n_samples, samples_cube);
2904 }
2905
2906 int n = 0;
2907 for (size_t i = 0; i < bases_in.size(); ++i)
2908 {
2909 if (mesh_in.is_polytope(i))
2910 continue;
2911 int n_edges;
2912
2913 if (mesh_in.is_simplex(i))
2914 {
2915 n_edges = mesh_in.is_volume() ? 6 : 3;
2916 bases_in[i].eval_geom_mapping(samples_simplex, mapped);
2917 }
2918 else
2919 {
2920 n_edges = mesh_in.is_volume() ? 12 : 4;
2921 bases_in[i].eval_geom_mapping(samples_cube, mapped);
2922 }
2923
2924 for (int j = 0; j < n_edges; ++j)
2925 {
2926 double current_edge = 0;
2927 for (int k = 0; k < n_samples - 1; ++k)
2928 {
2929 p0 = mapped.row(j * n_samples + k);
2930 p1 = mapped.row(j * n_samples + k + 1);
2931 p = p0 - p1;
2932
2933 current_edge += p.norm();
2934 }
2935
2936 mesh_size = std::max(current_edge, mesh_size);
2937 min_edge_length = std::min(current_edge, min_edge_length);
2938 average_edge_length += current_edge;
2939 ++n;
2940 }
2941 }
2942
2943 average_edge_length /= n;
2944
2945 logger().info("hmin: {}", min_edge_length);
2946 logger().info("hmax: {}", mesh_size);
2947 logger().info("havg: {}", average_edge_length);
2948 }
2949
2951 {
2952 sigma_avg = 0;
2953 sigma_max = 0;
2954 sigma_min = 0;
2955
2956 n_flipped = 0;
2957 }
2958
2959 void OutStatsData::count_flipped_elements(const polyfem::mesh::Mesh &mesh, const std::vector<polyfem::basis::ElementBases> &gbases)
2960 {
2961 using namespace mesh;
2962
2963 logger().info("Counting flipped elements...");
2964 const auto &els_tag = mesh.elements_tag();
2965
2966 // flipped_elements.clear();
2967 for (size_t i = 0; i < gbases.size(); ++i)
2968 {
2969 if (mesh.is_polytope(i))
2970 continue;
2971
2973 if (!vals.is_geom_mapping_positive(mesh.is_volume(), gbases[i]))
2974 {
2975 ++n_flipped;
2976
2977 static const std::vector<std::string> element_type_names{{
2978 "Simplex",
2979 "RegularInteriorCube",
2980 "RegularBoundaryCube",
2981 "SimpleSingularInteriorCube",
2982 "MultiSingularInteriorCube",
2983 "SimpleSingularBoundaryCube",
2984 "InterfaceCube",
2985 "MultiSingularBoundaryCube",
2986 "BoundaryPolytope",
2987 "InteriorPolytope",
2988 "Undefined",
2989 }};
2990
2991 log_and_throw_error("element {} is flipped, type {}", i, element_type_names[static_cast<int>(els_tag[i])]);
2992 }
2993 }
2994
2995 logger().info(" done");
2996
2997 // dynamic_cast<Mesh3D *>(mesh.get())->save({56}, 1, "mesh.HYBRID");
2998
2999 // std::sort(flipped_elements.begin(), flipped_elements.end());
3000 // auto it = std::unique(flipped_elements.begin(), flipped_elements.end());
3001 // flipped_elements.resize(std::distance(flipped_elements.begin(), it));
3002 }
3003
3005 const int n_bases,
3006 const std::vector<polyfem::basis::ElementBases> &bases,
3007 const std::vector<polyfem::basis::ElementBases> &gbases,
3008 const polyfem::mesh::Mesh &mesh,
3009 const assembler::Problem &problem,
3010 const double tend,
3011 const Eigen::MatrixXd &sol)
3012 {
3013 if (n_bases <= 0)
3014 {
3015 logger().error("Build the bases first!");
3016 return;
3017 }
3018 if (sol.size() <= 0)
3019 {
3020 logger().error("Solve the problem first!");
3021 return;
3022 }
3023
3024 int actual_dim = 1;
3025 if (!problem.is_scalar())
3026 actual_dim = mesh.dimension();
3027
3028 igl::Timer timer;
3029 timer.start();
3030 logger().info("Computing errors...");
3031 using std::max;
3032
3033 const int n_el = int(bases.size());
3034
3035 Eigen::MatrixXd v_exact, v_approx;
3036 Eigen::MatrixXd v_exact_grad(0, 0), v_approx_grad;
3037
3038 l2_err = 0;
3039 h1_err = 0;
3040 grad_max_err = 0;
3041 h1_semi_err = 0;
3042 linf_err = 0;
3043 lp_err = 0;
3044 // double pred_norm = 0;
3045
3046 static const int p = 8;
3047
3048 // Eigen::MatrixXd err_per_el(n_el, 5);
3050
3051 for (int e = 0; e < n_el; ++e)
3052 {
3053 vals.compute(e, mesh.is_volume(), bases[e], gbases[e]);
3054
3055 if (problem.has_exact_sol())
3056 {
3057 problem.exact(vals.val, tend, v_exact);
3058 problem.exact_grad(vals.val, tend, v_exact_grad);
3059 }
3060
3061 v_approx.resize(vals.val.rows(), actual_dim);
3062 v_approx.setZero();
3063
3064 v_approx_grad.resize(vals.val.rows(), mesh.dimension() * actual_dim);
3065 v_approx_grad.setZero();
3066
3067 const int n_loc_bases = int(vals.basis_values.size());
3068
3069 for (int i = 0; i < n_loc_bases; ++i)
3070 {
3071 const auto &val = vals.basis_values[i];
3072
3073 for (size_t ii = 0; ii < val.global.size(); ++ii)
3074 {
3075 for (int d = 0; d < actual_dim; ++d)
3076 {
3077 v_approx.col(d) += val.global[ii].val * sol(val.global[ii].index * actual_dim + d) * val.val;
3078 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;
3079 }
3080 }
3081 }
3082
3083 const auto err = problem.has_exact_sol() ? (v_exact - v_approx).eval().rowwise().norm().eval() : (v_approx).eval().rowwise().norm().eval();
3084 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();
3085
3086 // for(long i = 0; i < err.size(); ++i)
3087 // errors.push_back(err(i));
3088
3089 linf_err = std::max(linf_err, err.maxCoeff());
3090 grad_max_err = std::max(linf_err, err_grad.maxCoeff());
3091
3092 // {
3093 // const auto &mesh3d = *dynamic_cast<Mesh3D *>(mesh.get());
3094 // const auto v0 = mesh3d.point(mesh3d.cell_vertex(e, 0));
3095 // const auto v1 = mesh3d.point(mesh3d.cell_vertex(e, 1));
3096 // const auto v2 = mesh3d.point(mesh3d.cell_vertex(e, 2));
3097 // const auto v3 = mesh3d.point(mesh3d.cell_vertex(e, 3));
3098
3099 // Eigen::Matrix<double, 6, 3> ee;
3100 // ee.row(0) = v0 - v1;
3101 // ee.row(1) = v1 - v2;
3102 // ee.row(2) = v2 - v0;
3103
3104 // ee.row(3) = v0 - v3;
3105 // ee.row(4) = v1 - v3;
3106 // ee.row(5) = v2 - v3;
3107
3108 // Eigen::Matrix<double, 6, 1> en = ee.rowwise().norm();
3109
3110 // // Eigen::Matrix<double, 3*4, 1> alpha;
3111 // // 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));
3112 // // 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));
3113 // // 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));
3114 // // 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));
3115
3116 // 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;
3117 // const double V = std::abs(ee.row(3).dot(ee.row(2).cross(-ee.row(0))))/6;
3118 // const double rho = 3 * V / S;
3119 // const double hp = en.maxCoeff();
3120 // const int pp = disc_orders(e);
3121 // const int p_ref = args["space"]["discr_order"];
3122
3123 // err_per_el(e, 0) = err.mean();
3124 // err_per_el(e, 1) = err.maxCoeff();
3125 // err_per_el(e, 2) = std::pow(hp, pp+1)/(rho/hp); // /std::pow(average_edge_length, p_ref+1) * (sqrt(6)/12);
3126 // err_per_el(e, 3) = rho/hp;
3127 // err_per_el(e, 4) = (vals.det.array() * vals.quadrature.weights.array()).sum();
3128
3129 // // pred_norm += (pow(std::pow(hp, pp+1)/(rho/hp),p) * vals.det.array() * vals.quadrature.weights.array()).sum();
3130 // }
3131
3132 l2_err += (err.array() * err.array() * vals.det.array() * vals.quadrature.weights.array()).sum();
3133 h1_err += (err_grad.array() * err_grad.array() * vals.det.array() * vals.quadrature.weights.array()).sum();
3134 lp_err += (err.array().pow(p) * vals.det.array() * vals.quadrature.weights.array()).sum();
3135 }
3136
3137 h1_semi_err = sqrt(fabs(h1_err));
3138 h1_err = sqrt(fabs(l2_err) + fabs(h1_err));
3139 l2_err = sqrt(fabs(l2_err));
3140
3141 lp_err = pow(fabs(lp_err), 1. / p);
3142
3143 // pred_norm = pow(fabs(pred_norm), 1./p);
3144
3145 timer.stop();
3146 const double computing_errors_time = timer.getElapsedTime();
3147 logger().info(" took {}s", computing_errors_time);
3148
3149 logger().info("-- L2 error: {}", l2_err);
3150 logger().info("-- Lp error: {}", lp_err);
3151 logger().info("-- H1 error: {}", h1_err);
3152 logger().info("-- H1 semi error: {}", h1_semi_err);
3153 // logger().info("-- Perd norm: {}", pred_norm);
3154
3155 logger().info("-- Linf error: {}", linf_err);
3156 logger().info("-- grad max error: {}", grad_max_err);
3157
3158 // {
3159 // std::ofstream out("errs.txt");
3160 // out<<err_per_el;
3161 // out.close();
3162 // }
3163 }
3164
3166 {
3167 using namespace polyfem::mesh;
3168
3169 simplex_count = 0;
3170 prism_count = 0;
3171 pyramid_count = 0;
3172 regular_count = 0;
3173 regular_boundary_count = 0;
3174 simple_singular_count = 0;
3175 multi_singular_count = 0;
3176 boundary_count = 0;
3177 non_regular_boundary_count = 0;
3178 non_regular_count = 0;
3179 undefined_count = 0;
3180 multi_singular_boundary_count = 0;
3181
3182 const auto &els_tag = mesh.elements_tag();
3183
3184 for (size_t i = 0; i < els_tag.size(); ++i)
3185 {
3186 const ElementType type = els_tag[i];
3187
3188 switch (type)
3189 {
3190 case ElementType::SIMPLEX:
3191 simplex_count++;
3192 break;
3193 case ElementType::PRISM:
3194 prism_count++;
3195 break;
3196 case ElementType::PYRAMID:
3197 pyramid_count++;
3198 break;
3199 case ElementType::REGULAR_INTERIOR_CUBE:
3200 regular_count++;
3201 break;
3202 case ElementType::REGULAR_BOUNDARY_CUBE:
3203 regular_boundary_count++;
3204 break;
3205 case ElementType::SIMPLE_SINGULAR_INTERIOR_CUBE:
3206 simple_singular_count++;
3207 break;
3208 case ElementType::MULTI_SINGULAR_INTERIOR_CUBE:
3209 multi_singular_count++;
3210 break;
3211 case ElementType::SIMPLE_SINGULAR_BOUNDARY_CUBE:
3212 boundary_count++;
3213 break;
3214 case ElementType::INTERFACE_CUBE:
3215 case ElementType::MULTI_SINGULAR_BOUNDARY_CUBE:
3216 multi_singular_boundary_count++;
3217 break;
3218 case ElementType::BOUNDARY_POLYTOPE:
3219 non_regular_boundary_count++;
3220 break;
3221 case ElementType::INTERIOR_POLYTOPE:
3222 non_regular_count++;
3223 break;
3224 case ElementType::UNDEFINED:
3225 undefined_count++;
3226 break;
3227 default:
3228 throw std::runtime_error("Unknown element type");
3229 }
3230 }
3231
3232 logger().info("simplex_count: \t{}", simplex_count);
3233 logger().info("prism_count: \t{}", prism_count);
3234 logger().info("pyramid_count: \t{}", pyramid_count);
3235 logger().info("regular_count: \t{}", regular_count);
3236 logger().info("regular_boundary_count: \t{}", regular_boundary_count);
3237 logger().info("simple_singular_count: \t{}", simple_singular_count);
3238 logger().info("multi_singular_count: \t{}", multi_singular_count);
3239 logger().info("boundary_count: \t{}", boundary_count);
3240 logger().info("multi_singular_boundary_count: \t{}", multi_singular_boundary_count);
3241 logger().info("non_regular_count: \t{}", non_regular_count);
3242 logger().info("non_regular_boundary_count: \t{}", non_regular_boundary_count);
3243 logger().info("undefined_count: \t{}", undefined_count);
3244 logger().info("total count:\t {}", mesh.n_elements());
3245 }
3246
3248 const nlohmann::json &args,
3249 const int n_bases, const int n_pressure_bases,
3250 const Eigen::MatrixXd &sol,
3251 const mesh::Mesh &mesh,
3252 const Eigen::VectorXi &disc_orders,
3253 const Eigen::VectorXi &disc_ordersq,
3254 const assembler::Problem &problem,
3255 const OutRuntimeData &runtime,
3256 const std::string &formulation,
3257 const bool isoparametric,
3258 const int sol_at_node_id,
3259 nlohmann::json &j)
3260 {
3261
3262 j["args"] = args;
3263
3264 j["geom_order"] = mesh.orders().size() > 0 ? mesh.orders().maxCoeff() : 1;
3265 j["geom_order_min"] = mesh.orders().size() > 0 ? mesh.orders().minCoeff() : 1;
3266 j["discr_order_min"] = disc_orders.minCoeff();
3267 j["discr_order_max"] = disc_orders.maxCoeff();
3268 j["discr_orderq_min"] = disc_ordersq.minCoeff();
3269 j["discr_orderq_max"] = disc_ordersq.maxCoeff();
3270 j["iso_parametric"] = isoparametric;
3271 j["problem"] = problem.name();
3272 j["mat_size"] = mat_size;
3273 j["num_bases"] = n_bases;
3274 j["num_pressure_bases"] = n_pressure_bases;
3275 j["num_non_zero"] = nn_zero;
3276 j["num_flipped"] = n_flipped;
3277 j["num_dofs"] = num_dofs;
3278 j["num_vertices"] = mesh.n_vertices();
3279 j["num_elements"] = mesh.n_elements();
3280
3281 j["num_p1"] = (disc_orders.array() == 1).count();
3282 j["num_p2"] = (disc_orders.array() == 2).count();
3283 j["num_p3"] = (disc_orders.array() == 3).count();
3284 j["num_p4"] = (disc_orders.array() == 4).count();
3285 j["num_p5"] = (disc_orders.array() == 5).count();
3286
3287 j["mesh_size"] = mesh_size;
3288 j["max_angle"] = max_angle;
3289
3290 j["sigma_max"] = sigma_max;
3291 j["sigma_min"] = sigma_min;
3292 j["sigma_avg"] = sigma_avg;
3293
3294 j["min_edge_length"] = min_edge_length;
3295 j["average_edge_length"] = average_edge_length;
3296
3297 j["err_l2"] = l2_err;
3298 j["err_h1"] = h1_err;
3299 j["err_h1_semi"] = h1_semi_err;
3300 j["err_linf"] = linf_err;
3301 j["err_linf_grad"] = grad_max_err;
3302 j["err_lp"] = lp_err;
3303
3304 j["spectrum"] = {spectrum(0), spectrum(1), spectrum(2), spectrum(3)};
3305 j["spectrum_condest"] = std::abs(spectrum(3)) / std::abs(spectrum(0));
3306
3307 // j["errors"] = errors;
3308
3309 j["time_building_basis"] = runtime.building_basis_time;
3310 j["time_loading_mesh"] = runtime.loading_mesh_time;
3311 j["time_computing_poly_basis"] = runtime.computing_poly_basis_time;
3312 j["time_assembling_stiffness_mat"] = runtime.assembling_stiffness_mat_time;
3313 j["time_assembling_mass_mat"] = runtime.assembling_mass_mat_time;
3314 j["time_assigning_rhs"] = runtime.assigning_rhs_time;
3315 j["time_solving"] = runtime.solving_time;
3316 // j["time_computing_errors"] = runtime.computing_errors_time;
3317
3318 j["solver_info"] = solver_info;
3319
3320 j["count_simplex"] = simplex_count;
3321 j["count_prism"] = prism_count;
3322 j["count_pyramid"] = pyramid_count;
3323 j["count_regular"] = regular_count;
3324 j["count_regular_boundary"] = regular_boundary_count;
3325 j["count_simple_singular"] = simple_singular_count;
3326 j["count_multi_singular"] = multi_singular_count;
3327 j["count_boundary"] = boundary_count;
3328 j["count_non_regular_boundary"] = non_regular_boundary_count;
3329 j["count_non_regular"] = non_regular_count;
3330 j["count_undefined"] = undefined_count;
3331 j["count_multi_singular_boundary"] = multi_singular_boundary_count;
3332
3333 j["is_simplicial"] = mesh.n_elements() == simplex_count;
3334
3335 j["peak_memory"] = getPeakRSS() / (1024 * 1024);
3336
3337 const int actual_dim = problem.is_scalar() ? 1 : mesh.dimension();
3338
3339 std::vector<double> mmin(actual_dim);
3340 std::vector<double> mmax(actual_dim);
3341
3342 for (int d = 0; d < actual_dim; ++d)
3343 {
3344 mmin[d] = std::numeric_limits<double>::max();
3345 mmax[d] = -std::numeric_limits<double>::max();
3346 }
3347
3348 for (int i = 0; i < sol.size(); i += actual_dim)
3349 {
3350 for (int d = 0; d < actual_dim; ++d)
3351 {
3352 mmin[d] = std::min(mmin[d], sol(i + d));
3353 mmax[d] = std::max(mmax[d], sol(i + d));
3354 }
3355 }
3356
3357 std::vector<double> sol_at_node(actual_dim);
3358
3359 if (sol_at_node_id >= 0)
3360 {
3361 const int node_id = sol_at_node_id;
3362
3363 for (int d = 0; d < actual_dim; ++d)
3364 {
3365 sol_at_node[d] = sol(node_id * actual_dim + d);
3366 }
3367 }
3368
3369 j["sol_at_node"] = sol_at_node;
3370 j["sol_min"] = mmin;
3371 j["sol_max"] = mmax;
3372
3373#if defined(POLYFEM_WITH_CPP_THREADS)
3374 j["num_threads"] = utils::get_n_threads();
3375#elif defined(POLYFEM_WITH_TBB)
3376 j["num_threads"] = utils::get_n_threads();
3377#else
3378 j["num_threads"] = 1;
3379#endif
3380
3381 j["formulation"] = formulation;
3382
3383 logger().info("done");
3384 }
3385
3386 EnergyCSVWriter::EnergyCSVWriter(const std::string &path, const solver::SolveData &solve_data)
3387 : file(path), solve_data(solve_data)
3388 {
3389 file << "i,";
3390 for (const auto &[name, _] : solve_data.named_forms())
3391 {
3392 file << name << ",";
3393 }
3394 file << "total_energy" << std::endl;
3395 }
3396
3398 {
3399 file.close();
3400 }
3401
3402 void EnergyCSVWriter::write(const int i, const Eigen::MatrixXd &sol)
3403 {
3404 const double s = solve_data.time_integrator
3405 ? solve_data.time_integrator->acceleration_scaling()
3406 : 1;
3407 file << i << ",";
3408 for (const auto &[_, form] : solve_data.named_forms())
3409 {
3410 // Divide by acceleration scaling to get the energy (units of J)
3411 file << ((form && form->enabled()) ? form->value(sol) : 0) / s << ",";
3412 }
3413 file << solve_data.nl_problem->value(sol) / s << "\n";
3414 file.flush();
3415 }
3416
3417 RuntimeStatsCSVWriter::RuntimeStatsCSVWriter(const std::string &path, const State &state, const double t0, const double dt)
3418 : file(path), state(state), t0(t0), dt(dt)
3419 {
3420 file << "step,time,forward,remeshing,global_relaxation,peak_mem,#V,#T" << std::endl;
3421 }
3422
3427
3428 void RuntimeStatsCSVWriter::write(const int t, const double forward, const double remeshing, const double global_relaxation, const Eigen::MatrixXd &sol)
3429 {
3430 total_forward_solve_time += forward;
3431 total_remeshing_time += remeshing;
3432 total_global_relaxation_time += global_relaxation;
3433
3434 // logger().debug(
3435 // "Forward (cur, avg, total): {} s, {} s, {} s",
3436 // forward, total_forward_solve_time / t, total_forward_solve_time);
3437 // logger().debug(
3438 // "Remeshing (cur, avg, total): {} s, {} s, {} s",
3439 // remeshing, total_remeshing_time / t, total_remeshing_time);
3440 // logger().debug(
3441 // "Global relaxation (cur, avg, total): {} s, {} s, {} s",
3442 // global_relaxation, total_global_relaxation_time / t, total_global_relaxation_time);
3443
3444 const double peak_mem = getPeakRSS() / double(1 << 30);
3445 // logger().debug("Peak mem: {} GiB", peak_mem);
3446
3447 file << fmt::format(
3448 "{},{},{},{},{},{},{},{}\n",
3449 t, t0 + dt * t, forward, remeshing, global_relaxation, peak_mem,
3450 state.n_bases, state.mesh->n_elements());
3451 file.flush();
3452 }
3453
3454} // namespace polyfem::io
double val
Definition Assembler.cpp:86
ElementAssemblyValues vals
Definition Assembler.cpp:22
std::vector< Eigen::VectorXi > faces
int y
int z
int x
main class that contains the polyfem solver and all its state
Definition State.hpp:113
int n_bases
number of bases
Definition State.hpp:212
const std::vector< basis::ElementBases > & geom_bases() const
Get a constant reference to the geometry mapping bases.
Definition State.hpp:257
Eigen::VectorXi in_node_to_node
Inpute nodes (including high-order) to polyfem nodes, only for isoparametric.
Definition State.hpp:571
mesh::Obstacle obstacle
Obstacles used in collisions.
Definition State.hpp:589
std::shared_ptr< assembler::Assembler > assembler
assemblers
Definition State.hpp:189
ipc::CollisionMesh collision_mesh
IPC collision mesh.
Definition State.hpp:663
std::vector< basis::ElementBases > pressure_bases
FE pressure bases for mixed elements, the size is #elements.
Definition State.hpp:207
std::shared_ptr< assembler::Mass > mass_matrix_assembler
Definition State.hpp:191
std::unique_ptr< mesh::Mesh > mesh
current mesh, it can be a Mesh2D or Mesh3D
Definition State.hpp:587
std::vector< int > dirichlet_nodes
per node dirichlet
Definition State.hpp:564
std::shared_ptr< assembler::Problem > problem
current problem, it contains rhs and bc
Definition State.hpp:202
std::vector< RowVectorNd > dirichlet_nodes_position
Definition State.hpp:565
std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > polys_3d
polyhedra, used since poly have no geom mapping
Definition State.hpp:221
json args
main input arguments containing all defaults
Definition State.hpp:135
std::vector< basis::ElementBases > bases
FE bases, the size is #elements.
Definition State.hpp:205
Eigen::VectorXi disc_ordersq
Definition State.hpp:224
std::vector< mesh::LocalBoundary > total_local_boundary
mapping from elements to nodes for all mesh
Definition State.hpp:552
QuadratureOrders n_boundary_samples() const
quadrature used for projecting boundary conditions
Definition State.hpp:297
solver::SolveData solve_data
timedependent stuff cached
Definition State.hpp:375
Eigen::VectorXi disc_orders
vector of discretization orders, used when not all elements have the same degree, one per element
Definition State.hpp:224
std::shared_ptr< assembler::MixedAssembler > mixed_assembler
Definition State.hpp:193
std::map< int, Eigen::MatrixXd > polys
polygons, used since poly have no geom mapping
Definition State.hpp:219
Eigen::MatrixXd rhs
System right-hand side.
Definition State.hpp:241
virtual std::map< std::string, ParamFunc > parameters() const =0
virtual void compute_tensor_value(const OutputData &data, std::vector< NamedMatrix > &result) const
stores per local bases evaluations
std::vector< basis::Local2Global > global
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,...
std::vector< Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, 0, 3, 3 > > jac_it
const std::string & name() const
Definition Problem.hpp:23
virtual void exact_grad(const Eigen::MatrixXd &pts, const double t, Eigen::MatrixXd &val) const
Definition Problem.hpp:46
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:45
virtual bool is_time_dependent() const
Definition Problem.hpp:50
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).
EnergyCSVWriter(const std::string &path, const solver::SolveData &solve_data)
Definition OutData.cpp:3386
const solver::SolveData & solve_data
Definition OutData.hpp:509
void write(const int i, const Eigen::MatrixXd &sol)
Definition OutData.cpp:3402
static void interpolate_at_local_vals(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const int el_index, const Eigen::MatrixXd &local_pts, const Eigen::MatrixXd &fun, Eigen::MatrixXd &result, Eigen::MatrixXd &result_grad)
interpolate solution and gradient at element (calls interpolate_at_local_vals with sol)
static void average_grad_based_function(const mesh::Mesh &mesh, const bool is_problem_scalar, const int n_bases, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const assembler::Assembler &assembler, const utils::RefElementSampler &sampler, const double t, const int n_points, const Eigen::MatrixXd &fun, std::vector< assembler::Assembler::NamedMatrix > &result_scalar, std::vector< assembler::Assembler::NamedMatrix > &result_tensor, const bool use_sampler, const bool boundary_only)
calls compute_scalar_value (i.e von mises for elasticity and norm of velocity for fluid) and compute_...
static void compute_scalar_value(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const assembler::Assembler &assembler, const utils::RefElementSampler &sampler, const int n_points, const Eigen::MatrixXd &fun, const double t, std::vector< assembler::Assembler::NamedMatrix > &result, const bool use_sampler, const bool boundary_only)
computes scalar quantity of funtion (ie von mises for elasticity and norm of velocity for fluid)
static void compute_tensor_value(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const assembler::Assembler &assembler, const utils::RefElementSampler &sampler, const int n_points, const Eigen::MatrixXd &fun, const double t, std::vector< assembler::Assembler::NamedMatrix > &result, const bool use_sampler, const bool boundary_only)
compute tensor quantity (ie stress tensor or velocity)
static void mark_flipped_cells(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &gbasis, const std::vector< basis::ElementBases > &basis, const Eigen::VectorXi &disc_orders, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const utils::RefElementSampler &sampler, const int n_points, const Eigen::MatrixXd &fun, Eigen::Vector< bool, -1 > &result, const bool use_sampler, const bool boundary_only)
static void interpolate_function(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::map< int, Eigen::MatrixXd > &polys, const std::map< int, std::pair< Eigen::MatrixXd, Eigen::MatrixXi > > &polys_3d, const utils::RefElementSampler &sampler, const int n_points, const Eigen::MatrixXd &fun, Eigen::MatrixXd &result, const bool use_sampler, const bool boundary_only)
interpolate the function fun.
static void compute_stress_at_quadrature_points(const mesh::Mesh &mesh, const bool is_problem_scalar, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const assembler::Assembler &assembler, const Eigen::MatrixXd &fun, const double t, Eigen::MatrixXd &result, Eigen::VectorXd &von_mises)
compute von mises stress at quadrature points for the function fun, also compute the interpolated fun...
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) const
builds visualzation mesh, upsampled mesh used for visualization the visualization mesh is a dense mes...
Definition OutData.cpp:812
void save_volume_vector_field(const State &state, const Eigen::MatrixXd &points, const ExportOptions &opts, const std::string &name, const Eigen::VectorXd &field, paraviewo::ParaviewWriter &writer) const
Definition OutData.cpp:2076
Eigen::MatrixXd grid_points_bc
grid mesh boundaries
Definition OutData.hpp:259
Eigen::MatrixXd grid_points
grid mesh points to export solution sampled on a grid
Definition OutData.hpp:255
void build_vis_boundary_mesh(const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const std::vector< mesh::LocalBoundary > &total_local_boundary, const Eigen::MatrixXd &solution, const int problem_dim, 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, Eigen::MatrixXd &displaced_boundary_vis_normals) const
builds the boundary mesh for visualization
Definition OutData.cpp:550
void save_points(const std::string &path, const State &state, const Eigen::MatrixXd &sol, const ExportOptions &opts) const
saves the nodal values
Definition OutData.cpp:2707
void build_grid(const polyfem::mesh::Mesh &mesh, const double spacing)
builds the grid to export the solution
Definition OutData.cpp:2774
void save_contact_surface(const std::string &export_surface, const State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure, const double t, const double dt_in, const ExportOptions &opts, const bool is_contact_enabled) const
saves the surface vtu file for for constact quantites, eg contact or friction forces
Definition OutData.cpp:2297
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:150
void save_volume(const std::string &path, const State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure, const double t, const double dt, const ExportOptions &opts) const
saves the volume vtu file
Definition OutData.cpp:1473
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:2761
void save_wire(const std::string &name, const State &state, const Eigen::MatrixXd &sol, const double t, const ExportOptions &opts) const
saves the wireframe
Definition OutData.cpp:2481
void build_high_order_vis_mesh(const mesh::Mesh &mesh, const Eigen::VectorXi &disc_orders, const Eigen::VectorXi &disc_ordersq, const std::vector< basis::ElementBases > &bases, Eigen::MatrixXd &points, std::vector< paraviewo::CellElement > &elements, Eigen::MatrixXi &el_id, Eigen::MatrixXd &discr) const
builds high-der visualzation mesh per element all disconnected it also retuns the mapping to element ...
Definition OutData.cpp:978
void save_vtu(const std::string &path, const State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure, const double t, const double dt, const ExportOptions &opts, const bool is_contact_enabled) const
saves the vtu file for time t
Definition OutData.cpp:1389
void init_sampler(const polyfem::mesh::Mesh &mesh, const double vismesh_rel_area)
unitalize the ref element sampler
Definition OutData.cpp:2769
void export_data(const State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure, const bool is_time_dependent, const double tend_in, const double dt, const ExportOptions &opts, const std::string &vis_mesh_path, const std::string &nodes_path, const std::string &solution_path, const std::string &stress_path, const std::string &mises_path, const bool is_contact_enabled) const
exports everytihng, txt, vtu, etc
Definition OutData.cpp:1204
void save_surface(const std::string &export_surface, const State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd &pressure, const double t, const double dt_in, const ExportOptions &opts, const bool is_contact_enabled) const
saves the surface vtu file for for surface quantites, eg traction forces
Definition OutData.cpp:2101
Eigen::MatrixXi grid_points_to_elements
grid mesh mapping to fe elements
Definition OutData.hpp:257
utils::RefElementSampler ref_element_sampler
used to sample the solution
Definition OutData.hpp:252
stores all runtime data
Definition OutData.hpp:348
double loading_mesh_time
time to load the mesh
Definition OutData.hpp:353
double assembling_stiffness_mat_time
time to assembly
Definition OutData.hpp:357
double assigning_rhs_time
time to computing the rhs
Definition OutData.hpp:361
double assembling_mass_mat_time
time to assembly mass
Definition OutData.hpp:359
double building_basis_time
time to construct the basis
Definition OutData.hpp:351
double solving_time
time to solve
Definition OutData.hpp:363
double computing_poly_basis_time
time to build the polygonal/polyhedral bases
Definition OutData.hpp:355
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:2959
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)
saves the output statistic to a json object
Definition OutData.cpp:3247
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:3004
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:2872
void reset()
clears all stats
Definition OutData.cpp:2950
void compute_mesh_stats(const polyfem::mesh::Mesh &mesh)
compute stats (counts els type, mesh lenght, etc), step 1 of solve
Definition OutData.cpp:3165
void write(const int t, const double forward, const double remeshing, const double global_relaxation, const Eigen::MatrixXd &sol)
Definition OutData.cpp:3428
RuntimeStatsCSVWriter(const std::string &path, const State &state, const double t0, const double dt)
Definition OutData.cpp:3417
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
virtual int get_body_id(const int primitive) const
Get the volume selection of an element (cell in 3d, face in 2d)
Definition Mesh.hpp:514
virtual double edge_length(const int gid) const
edge length
Definition Mesh.hpp:308
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.
virtual double tri_area(const int gid) const
area of a tri face of a tet mesh
Definition Mesh.hpp:326
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
virtual double quad_area(const int gid) const
area of a quad face of an hex mesh
Definition Mesh.hpp:317
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:47
const Eigen::MatrixXi & get_face_connectivity() const
Definition Obstacle.hpp:46
const Eigen::MatrixXd & v() const
Definition Obstacle.hpp:41
const Eigen::VectorXi & get_vertex_connectivity() const
Definition Obstacle.hpp:48
class to store time stepping data
Definition SolveData.hpp:59
std::shared_ptr< solver::FrictionForm > friction_form
std::shared_ptr< solver::NLProblem > nl_problem
std::shared_ptr< solver::NormalAdhesionForm > normal_adhesion_form
std::shared_ptr< solver::ContactForm > contact_form
std::vector< std::pair< std::string, std::shared_ptr< solver::Form > > > named_forms() const
std::shared_ptr< solver::ElasticForm > elastic_form
std::shared_ptr< time_integrator::ImplicitTimeIntegrator > time_integrator
std::shared_ptr< solver::TangentialAdhesionForm > tangential_adhesion_form
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 bool boundary_quadrature(const mesh::LocalBoundary &local_boundary, const QuadratureOrders &order, const mesh::Mesh &mesh, const bool skip_computation, Eigen::MatrixXd &uv, Eigen::MatrixXd &points, Eigen::MatrixXd &normals, Eigen::VectorXd &weights, Eigen::VectorXi &global_primitive_ids)
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::MatrixXd & simplex_points() const
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
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)
paraviewo::CellElement CellElement
Definition OutData.cpp:63
paraviewo::CellType CellType
Definition OutData.cpp:62
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
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
void append_rows_of_zeros(DstMat &dst, const size_t n_zero_rows)
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:80
std::vector< std::string > fields
Definition OutData.hpp:38
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:1351
bool export_field(const std::string &field) const
Definition OutData.cpp:1346