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