PolyFEM
Loading...
Searching...
No Matches
NonlinearElasticVarForm.cpp
Go to the documentation of this file.
2
5
9
16
21
33
34#include <igl/Timer.h>
35#include <igl/edges.h>
36
37#include <ipc/ipc.hpp>
38
39#include <polysolve/linear/Solver.hpp>
40#include <polysolve/nonlinear/Solver.hpp>
41
42#include <algorithm>
43#include <cmath>
44#include <limits>
45
46namespace polyfem::varform
47{
48 using namespace solver;
49 using namespace time_integrator;
50
51 void NonlinearElasticVarForm::init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
52 {
53 json clean_args = args;
54 const bool contact_dhat_was_explicit = clean_args["contact"].value("_dhat_was_explicit", false);
55 clean_args["contact"].erase("_dhat_was_explicit");
56 ElasticVarForm::init(formulation, units, clean_args, out_path);
57 contact_dhat_was_explicit_ = contact_dhat_was_explicit;
58 }
59
76
78 {
79 return args.contains("/constraints/macro_displacement_gradient"_json_pointer);
80 }
81
82 void NonlinearElasticVarForm::load_mesh(const mesh::Mesh &mesh, const json &args)
83 {
85 }
86
88 {
89 obstacle = std::move(loaded_obstacle);
90 }
91
93 {
94 auto space = ElasticVarForm::output_space();
95 space.collision_mesh = is_contact_enabled() ? &collision_mesh_ : nullptr;
96 space.obstacle = &obstacle;
97 return space;
98 }
99
100 std::vector<io::OutputField> NonlinearElasticVarForm::output_fields(
101 const io::OutputSample &sample,
102 const Eigen::MatrixXd &solution,
103 const io::OutputFieldOptions &options) const
104 {
105 std::vector<io::OutputField> fields = elastic_output_fields(
106 sample, solution, options, &obstacle, solve_data_.time_integrator.get(),
108 if (!mesh_ || !problem || solution.size() <= 0)
109 return fields;
111 return fields;
112
113 const int actual_dim = problem->is_scalar() ? 1 : mesh_->dimension();
114 const auto &paraview_options = args["output"]["paraview"]["options"];
115 const bool explicit_fields = !options.fields.empty();
116
117 const auto has_field = [&](const std::string &name) {
118 return std::any_of(fields.begin(), fields.end(), [&](const io::OutputField &field) {
119 return field.association == io::OutputField::Association::Point && field.name == name;
120 });
121 };
122
123 const auto append_collision_dof_field = [&](const std::string &name, const Eigen::MatrixXd &dof_values) {
124 if (has_field(name) || dof_values.size() <= 0)
125 return;
126
127 Eigen::MatrixXd values = collision_mesh_.map_displacements(utils::unflatten(dof_values, actual_dim));
128 if (values.rows() == sample.points.rows())
129 fields.push_back({name, values, io::OutputField::Association::Point});
130 };
131
132 const auto append_collision_form_force = [&](const std::string &name, const std::shared_ptr<solver::Form> &form) {
133 if (!form || !form->enabled() || sample.points.rows() != collision_mesh_.rest_positions().rows())
134 return;
135
136 Eigen::VectorXd force;
137 form->first_derivative(solution.col(0), force);
138 const double acceleration_scaling =
139 solve_data_.time_integrator ? solve_data_.time_integrator->acceleration_scaling() : 1;
140 force *= -1.0 / acceleration_scaling;
141 append_collision_dof_field(name, force);
142 };
143
144 if (paraview_options["forces"] && !problem->is_scalar())
145 {
146 const double s = solve_data_.time_integrator ? solve_data_.time_integrator->acceleration_scaling() : 1;
147 for (const auto &[name, form] : solve_data_.named_forms())
148 {
149 const std::string field_name = name + "_forces";
150 if (!options.export_field(field_name))
151 continue;
152
153 Eigen::VectorXd force;
154 if (form && form->enabled())
155 {
156 form->first_derivative(solution, force);
157 force *= -1.0 / s;
158 }
159 else
160 {
161 force.setZero(solution.size());
162 }
163 append_collision_dof_field(field_name, force);
164 }
165 }
166
167 if (options.export_field("gradient_of_elastic_potential") && solve_data_.elastic_form)
168 {
169 Eigen::VectorXd potential_grad;
170 solve_data_.elastic_form->first_derivative(solution, potential_grad);
171 append_collision_dof_field("gradient_of_elastic_potential", potential_grad);
172 }
173
174 if (options.export_field("gradient_of_contact_potential") && solve_data_.contact_form && solve_data_.contact_form->weight() > 0)
175 {
176 Eigen::VectorXd potential_grad;
177 solve_data_.contact_form->first_derivative(solution, potential_grad);
178 potential_grad *= -solve_data_.contact_form->barrier_stiffness() / solve_data_.contact_form->weight();
179 append_collision_dof_field("gradient_of_contact_potential", potential_grad);
180 }
181
182 if (options.export_field("displacement"))
183 append_collision_dof_field("displacement", solution);
184 if (options.export_field("solution"))
185 append_collision_dof_field("solution", solution);
186
187 if ((paraview_options["contact_forces"] || explicit_fields) && options.export_field("contact_forces"))
188 append_collision_form_force("contact_forces", solve_data_.contact_form);
189 if ((paraview_options["friction_forces"] || explicit_fields) && options.export_field("friction_forces"))
190 append_collision_form_force("friction_forces", solve_data_.friction_form);
191 if ((paraview_options["normal_adhesion_forces"] || explicit_fields) && options.export_field("normal_adhesion_forces"))
192 append_collision_form_force("normal_adhesion_forces", solve_data_.normal_adhesion_form);
193 if ((paraview_options["tangential_adhesion_forces"] || explicit_fields) && options.export_field("tangential_adhesion_forces"))
194 append_collision_form_force("tangential_adhesion_forces", solve_data_.tangential_adhesion_form);
195
196 if (explicit_fields
197 && options.export_field("adaptive_dhat")
198 && args["contact"]["use_gcp_formulation"]
199 && args["contact"]["use_adaptive_dhat"])
200 {
201 const auto gcp = std::dynamic_pointer_cast<solver::GCPContactForm>(solve_data_.contact_form);
202 if (gcp)
203 {
204 const auto &set = gcp->collision_set();
205 if (actual_dim == 2)
206 {
207 Eigen::VectorXd dhats(collision_mesh_.num_edges());
208 for (int e = 0; e < dhats.size(); ++e)
209 dhats(e) = set.get_edge_dhat(e);
210 fields.push_back({"dhat", dhats, io::OutputField::Association::Cell});
211 }
212 else
213 {
214 Eigen::VectorXd dhats(collision_mesh_.num_faces());
215 for (int f = 0; f < dhats.size(); ++f)
216 dhats(f) = set.get_face_dhat(f);
217 fields.push_back({"dhat_face", dhats, io::OutputField::Association::Cell});
218
219 Eigen::VectorXd vertex_dhats(collision_mesh_.num_vertices());
220 for (int v = 0; v < vertex_dhats.size(); ++v)
221 vertex_dhats(v) = set.get_vert_dhat(v);
222 fields.push_back({"dhat_vert", vertex_dhats, io::OutputField::Association::Point});
223 }
224 }
225 }
226
227 return fields;
228 }
229
230 void NonlinearElasticVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
231 {
232 ElasticVarForm::build_basis(mesh, iso_parametric, args);
233
234 problem->setup_bc(
235 mesh, assembler::BoundaryKind::NormalAlignedNeumann, /*fe_space_id=*/-1,
236 boundary_.total_local_boundary, boundary_.local_pressure_boundary);
237 problem->setup_bc(
238 mesh, assembler::BoundaryKind::PressureCavity, /*fe_space_id=*/-1,
239 boundary_.total_local_boundary, boundary_.local_pressure_cavity);
240
241 // Legacy nonlinear/contact code assumes the displacement space includes obstacle vertices.
242 // The shared build path only counts FE bases, so extend it here
243 // before constructing collision/contact state.
244 const int n_fe_bases = space_.n_bases;
245 space_.n_bases += obstacle.n_vertices();
246
247 if (is_contact_enabled())
248 {
249 logger().info("Building collision mesh...");
250 build_collision_mesh(mesh, args);
251 preprocess_contact_parameters();
252
253 if (args["contact"]["periodic"])
254 build_periodic_collision_mesh();
255 }
256
257 logger().info("Done!");
258
259 for (int i = n_fe_bases; i < space_.n_bases; ++i)
260 {
261 for (int d = 0; d < mesh.dimension(); ++d)
262 boundary_.boundary_nodes.push_back(i * mesh.dimension() + d);
263 }
264
265 boundary_.normalize_boundary_nodes();
266 }
267
268 void NonlinearElasticVarForm::preprocess_contact_parameters()
269 {
270 if (!is_contact_enabled())
271 return;
272
273 double min_boundary_edge_length = std::numeric_limits<double>::max();
274 for (const auto &edge : collision_mesh_.edges().rowwise())
275 {
276 const VectorNd v0 = collision_mesh_.rest_positions().row(edge(0));
277 const VectorNd v1 = collision_mesh_.rest_positions().row(edge(1));
278 min_boundary_edge_length = std::min(min_boundary_edge_length, (v1 - v0).norm());
279 }
280
281 double dhat = Units::convert(args["contact"]["dhat"], units.length());
282 args["contact"]["epsv"] = Units::convert(args["contact"]["epsv"], units.velocity());
283
284 if (!contact_dhat_was_explicit_
285 && std::isfinite(min_boundary_edge_length)
286 && dhat > min_boundary_edge_length)
287 {
288 dhat = args["contact"]["dhat_percentage"].get<double>() * min_boundary_edge_length;
289 logger().info("dhat set to {}", dhat);
290 }
291 else if (std::isfinite(min_boundary_edge_length) && dhat > min_boundary_edge_length)
292 {
293 logger().warn("dhat larger than min boundary edge, {} > {}", dhat, min_boundary_edge_length);
294 }
295
296 args["contact"]["dhat"] = dhat;
297 }
298
299 void NonlinearElasticVarForm::build_rhs_assembler()
300 {
301 json rhs_solver_params = args["solver"]["linear"];
302 if (!rhs_solver_params.contains("Pardiso"))
303 rhs_solver_params["Pardiso"] = {};
304 rhs_solver_params["Pardiso"]["mtype"] = -2;
305
306 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
307
308 solve_data_.rhs_assembler = std::make_shared<assembler::RhsAssembler>(
309 *primary_assembler_, *mesh_, &obstacle,
310 boundary_.dirichlet_nodes, boundary_.neumann_nodes,
311 boundary_.dirichlet_nodes_position, boundary_.neumann_nodes_position,
312 space_.n_bases, size, space_.basis_list(), space_.geometry_basis_list(), mass_ass_vals_cache_, *problem, resources_,
313 args["space"]["advanced"]["bc_method"],
314 rhs_solver_params,
315 /*fe_space_id=*/-1);
316 rhs_assembler_ = solve_data_.rhs_assembler;
317 }
318
319 void NonlinearElasticVarForm::build_collision_mesh(
320 const mesh::Mesh &mesh,
321 const json &args)
322 {
323 build_collision_mesh(
324 mesh, space_.n_bases, space_.basis_list(), space_.geometry_basis_list(), boundary_.total_local_boundary, obstacle,
325 args, resources_,
326 space_.space_in_node_to_node, collision_mesh_);
327 }
328
329 void NonlinearElasticVarForm::build_collision_mesh(
330 const mesh::Mesh &mesh,
331 const int n_bases,
332 const std::vector<basis::ElementBases> &bases,
333 const std::vector<basis::ElementBases> &geom_bases,
334 const std::vector<mesh::LocalBoundary> &total_local_boundary,
335 const mesh::Obstacle &obstacle,
336 const json &args,
337 const io::ResourceIO &resources,
338 const Eigen::VectorXi &in_node_to_node,
339 ipc::CollisionMesh &collision_mesh_)
340 {
341 Eigen::MatrixXd collision_vertices;
342 Eigen::VectorXi collision_codim_vids;
343 Eigen::MatrixXi collision_edges, collision_triangles;
344 std::vector<Eigen::Triplet<double>> displacement_map_entries;
345
346 const auto extract_default_collision_mesh = [&]() {
347 if (args.at("/space/basis_type"_json_pointer) == "Spline")
348 {
350 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
351 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
352 }
353 else
354 {
356 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
357 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
358 }
359 };
360
361 if (args.contains("/contact/collision_mesh"_json_pointer)
362 && args.at("/contact/collision_mesh/enabled"_json_pointer).get<bool>())
363 {
364 const json collision_mesh_args = args.at("/contact/collision_mesh"_json_pointer);
365 if (collision_mesh_args.contains("linear_map"))
366 {
367 assert(displacement_map_entries.empty());
368 assert(collision_mesh_args.contains("mesh"));
369 // TODO: handle transformation per geometry
370 const json transformation = utils::json_as_array(args["geometry"])[0]["transformation"];
372 resources,
373 collision_mesh_args["mesh"].get<std::string>(),
374 collision_mesh_args["linear_map"].get<std::string>(),
375 in_node_to_node, transformation, collision_vertices, collision_codim_vids,
376 collision_edges, collision_triangles, displacement_map_entries);
377 }
378 else if (collision_mesh_args.contains("tessellation_type")
379 && collision_mesh_args["tessellation_type"] == "max_order")
380 {
382 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
383 collision_vertices, collision_edges, collision_triangles, displacement_map_entries,
384 utils::json_value<int>(collision_mesh_args, "sampling_order", 0));
385 }
386 else if (collision_mesh_args.contains("max_edge_length"))
387 {
388 logger().debug(
389 "Building collision proxy with max edge length={} ...",
390 collision_mesh_args["max_edge_length"].get<double>());
391 igl::Timer timer;
392 timer.start();
394 bases, geom_bases, total_local_boundary, n_bases, mesh.dimension(),
395 collision_mesh_args["max_edge_length"], collision_vertices,
396 collision_triangles, displacement_map_entries,
397 collision_mesh_args["tessellation_type"]);
398 if (collision_triangles.size())
399 igl::edges(collision_triangles, collision_edges);
400 timer.stop();
401 logger().debug(fmt::format(
402 std::locale("en_US.UTF-8"),
403 "Done (took {:g}s, {:L} vertices, {:L} triangles)",
404 timer.getElapsedTime(),
405 collision_vertices.rows(), collision_triangles.rows()));
406 }
407 else
408 {
409 extract_default_collision_mesh();
410 }
411 }
412 else
413 {
414 extract_default_collision_mesh();
415 }
416
417 std::vector<bool> is_orientable_vertex(collision_vertices.rows(), true);
418 std::vector<bool> is_fixed_vertex(collision_vertices.rows(), false);
419
420 // n_bases already contains the obstacle vertices
421 const int num_fe_nodes = n_bases - obstacle.n_vertices();
422 const int num_fe_collision_vertices = collision_vertices.rows();
423 assert(collision_edges.size() == 0 || collision_edges.maxCoeff() < num_fe_collision_vertices);
424 assert(collision_triangles.size() == 0 || collision_triangles.maxCoeff() < num_fe_collision_vertices);
425
426 // Append the obstacles to the collision mesh
427 if (obstacle.n_vertices() > 0)
428 {
429 utils::append_rows(collision_vertices, obstacle.v());
430 utils::append_rows(collision_codim_vids, obstacle.codim_v().array() + num_fe_collision_vertices);
431 utils::append_rows(collision_edges, obstacle.e().array() + num_fe_collision_vertices);
432 utils::append_rows(collision_triangles, obstacle.f().array() + num_fe_collision_vertices);
433
434 for (int i = 0; i < obstacle.n_vertices(); i++)
435 {
436 is_orientable_vertex.push_back(false);
437 is_fixed_vertex.push_back(true);
438 }
439
440 if (!displacement_map_entries.empty())
441 {
442 displacement_map_entries.reserve(displacement_map_entries.size() + obstacle.n_vertices());
443 for (int i = 0; i < obstacle.n_vertices(); i++)
444 {
445 displacement_map_entries.emplace_back(num_fe_collision_vertices + i, num_fe_nodes + i, 1.0);
446 }
447 }
448 }
449
450 std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(
451 collision_vertices.rows(), collision_edges);
452 for (const int vid : collision_codim_vids)
453 {
454 is_on_surface[vid] = true;
455 }
456
457 Eigen::SparseMatrix<double> displacement_map;
458 if (!displacement_map_entries.empty())
459 {
460 displacement_map.resize(collision_vertices.rows(), n_bases);
461 displacement_map.setFromTriplets(displacement_map_entries.begin(), displacement_map_entries.end());
462 }
463
464 collision_mesh_ = ipc::CollisionMesh(
465 is_on_surface, is_orientable_vertex, is_fixed_vertex, collision_vertices, collision_edges, collision_triangles,
466 displacement_map);
467
468 collision_mesh_.can_collide = [&collision_mesh_, num_fe_collision_vertices](size_t vi, size_t vj) {
469 // obstacles do not collide with other obstacles
470 return collision_mesh_.to_full_vertex_id(vi) < num_fe_collision_vertices
471 || collision_mesh_.to_full_vertex_id(vj) < num_fe_collision_vertices;
472 };
473
474 collision_mesh_.init_area_jacobians();
475 }
476
477 void NonlinearElasticVarForm::build_periodic_collision_mesh()
478 {
479 const int dim = mesh_->dimension();
480 const int n_tiles = 2;
481 const int tile_count = 1 << dim;
482
483 if (obstacle.n_vertices() != 0)
484 {
485 log_and_throw_error("Periodic contact does not support obstacles.");
486 }
487
488 const int n_bases = space_.n_bases;
489 const json &conditions = args["boundary_conditions"]["periodic"];
490 Eigen::VectorXi periodic_dof_mask = Eigen::VectorXi::Zero(n_bases);
491 Eigen::VectorXi periodic_face_mask = Eigen::VectorXi::Zero(n_bases);
492 Eigen::MatrixXd periodic_tile_offsets(dim, conditions.size());
493 for (int i = 0; i < int(conditions.size()); ++i)
494 {
495 const json &condition = conditions[i];
496 const std::array<int, 2> boundary_ids = {{condition["boundary_ids"][0].get<int>(),
497 condition["boundary_ids"][1].get<int>()}};
499 n_bases * dim, dim, *mesh_, space_.basis_list(), boundary_.total_local_boundary,
500 boundary_ids, condition.value("tolerance", 1e-5));
501 periodic_tile_offsets.col(i) = mapping.translation.transpose();
502 for (const int dof : mapping.boundary_dofs)
503 {
504 periodic_dof_mask(dof) = 1;
505 }
506 for (const auto &boundary : boundary_.total_local_boundary)
507 {
508 const auto &bases = space_.basis_list()[boundary.element_id()];
509 for (int primitive = 0; primitive < boundary.size(); ++primitive)
510 {
511 const int primitive_id = boundary.global_primitive_id(primitive);
512 for (int side = 0; side < 2; ++side)
513 {
514 if (mesh_->get_boundary_id(primitive_id) != boundary_ids[side])
515 {
516 continue;
517 }
518 for (const int node : bases.local_nodes_for_primitive(primitive_id, *mesh_))
519 {
520 for (const auto &global : bases.bases[node].global())
521 {
522 periodic_face_mask(global.index) |= 1 << (2 * i + side);
523 }
524 }
525 }
526 }
527 }
528 }
529
530 Eigen::MatrixXd V(n_bases, dim);
531 for (const auto &bs : space_.basis_list())
532 for (const auto &b : bs.bases)
533 for (const auto &g : b.global())
534 V.row(g.index) = g.node;
535
536 Eigen::MatrixXi primitives = dim == 2 ? collision_mesh_.edges() : collision_mesh_.faces();
537 for (int entry = 0; entry < primitives.size(); ++entry)
538 {
539 primitives(entry) = collision_mesh_.to_full_vertex_id(primitives(entry));
540 if (primitives(entry) < 0 || primitives(entry) >= n_bases)
541 {
542 log_and_throw_error("Periodic contact requires collision vertices to map to FE basis nodes.");
543 }
544 }
545
546 Eigen::MatrixXd bbox(V.cols(), 2);
547 bbox.col(0) = V.colwise().minCoeff();
548 bbox.col(1) = V.colwise().maxCoeff();
549
550 // Exclude artificial cell cuts, not physical faces whose vertices lie on different periodic boundaries.
551 {
552 std::vector<int> ind;
553 for (int primitive = 0; primitive < primitives.rows(); ++primitive)
554 {
555 int common_faces = periodic_face_mask(primitives(primitive, 0));
556 for (int corner = 1; corner < primitives.cols(); ++corner)
557 {
558 common_faces &= periodic_face_mask(primitives(primitive, corner));
559 }
560 if (common_faces == 0)
561 {
562 ind.push_back(primitive);
563 }
564 }
565
566 primitives = primitives(ind, Eigen::all).eval();
567 }
568
569 Eigen::MatrixXd Vtmp(V.rows() * tile_count, dim);
570 Eigen::MatrixXi tiled_primitives(primitives.rows() * tile_count, primitives.cols());
571
572 if (periodic_tile_offsets.rows() != dim || periodic_tile_offsets.cols() != dim
573 || Eigen::FullPivLU<Eigen::MatrixXd>(periodic_tile_offsets).rank() != dim)
574 log_and_throw_error("Periodic contact requires {} linearly independent periodic boundary pairs", dim);
575 const Eigen::MatrixXd &tile_offset = periodic_tile_offsets;
576
577 for (int tile = 0; tile < tile_count; ++tile)
578 {
579 Eigen::VectorXd block_id(dim);
580 for (int axis = 0; axis < dim; ++axis)
581 {
582 block_id(axis) = (tile >> (dim - axis - 1)) % n_tiles;
583 }
584 Vtmp.middleRows(tile * V.rows(), V.rows()) = V.rowwise() + (tile_offset * block_id).transpose();
585 tiled_primitives.middleRows(tile * primitives.rows(), primitives.rows()) = primitives.array() + tile * V.rows();
586 }
587
588 // clean duplicated vertices
589 Eigen::VectorXi indices;
590 {
591 std::vector<int> tmp;
592 for (int i = 0; i < V.rows(); i++)
593 {
594 if (periodic_dof_mask(i))
595 tmp.push_back(i);
596 }
597
598 indices.resize(tmp.size() * tile_count);
599 for (int i = 0; i < tile_count; i++)
600 {
601 indices.segment(i * tmp.size(), tmp.size()) = Eigen::Map<Eigen::VectorXi, Eigen::Unaligned>(tmp.data(), tmp.size());
602 indices.segment(i * tmp.size(), tmp.size()).array() += i * V.rows();
603 }
604 }
605
606 Eigen::VectorXi potentially_duplicate_mask(Vtmp.rows());
607 potentially_duplicate_mask.setZero();
608 potentially_duplicate_mask(indices).array() = 1;
609
610 Eigen::MatrixXd candidates = Vtmp(indices, Eigen::all);
611
612 Eigen::VectorXi SVI;
613 std::vector<int> SVJ;
614 SVI.setConstant(Vtmp.rows(), -1);
615 int id = 0;
616 double relative_tolerance = 1e-5;
617 for (const json &condition : conditions)
618 relative_tolerance = std::max(relative_tolerance, condition.value("tolerance", 1e-5));
619 const double eps = (bbox.col(1) - bbox.col(0)).maxCoeff() * relative_tolerance;
620 for (int i = 0; i < Vtmp.rows(); i++)
621 {
622 if (SVI[i] < 0)
623 {
624 SVI[i] = id;
625 SVJ.push_back(i);
626 if (potentially_duplicate_mask(i))
627 {
628 Eigen::VectorXd diffs = (candidates.rowwise() - Vtmp.row(i)).rowwise().norm();
629 for (int j = 0; j < diffs.size(); j++)
630 if (diffs(j) < eps)
631 SVI[indices[j]] = id;
632 }
633 id++;
634 }
635 }
636
637 const Eigen::MatrixXd Vnew = Vtmp(SVJ, Eigen::all);
638 for (int entry = 0; entry < tiled_primitives.size(); ++entry)
639 {
640 tiled_primitives(entry) = SVI(tiled_primitives(entry));
641 }
642 Eigen::MatrixXi Enew;
643 Eigen::MatrixXi boundary_triangles(0, 3);
644 if (dim == 3)
645 {
646 boundary_triangles = std::move(tiled_primitives);
647 igl::edges(boundary_triangles, Enew);
648 }
649 else
650 {
651 Enew = std::move(tiled_primitives);
652 }
653
654 std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(Vnew.rows(), Enew);
655
656 Eigen::SparseMatrix<double> displacement_map;
657 periodic_collision_mesh_ = ipc::CollisionMesh(is_on_surface,
658 std::vector<bool>(Vnew.rows(), false),
659 Vnew,
660 Enew,
661 boundary_triangles,
662 displacement_map);
663
664 periodic_collision_mesh_.init_area_jacobians();
665
666 periodic_collision_mesh_to_basis_.setConstant(Vnew.rows(), -1);
667 for (int i = 0; i < V.rows(); i++)
668 for (int j = 0; j < tile_count; j++)
669 periodic_collision_mesh_to_basis_(SVI[j * V.rows() + i]) = i;
670
671 if (periodic_collision_mesh_to_basis_.maxCoeff() + 1 != V.rows())
672 log_and_throw_error("Failed to tile mesh!");
673 }
674
675 std::shared_ptr<assembler::PressureAssembler> NonlinearElasticVarForm::build_pressure_assembler() const
676 {
677 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
678
679 return std::make_shared<assembler::PressureAssembler>(
680 *primary_assembler_, *mesh_, obstacle,
681 boundary_.local_pressure_boundary,
682 boundary_.local_pressure_cavity,
683 boundary_.boundary_nodes,
684 elastic_primitive_to_node(), elastic_node_to_primitive(),
685 space_.n_bases, size, space_.basis_list(), space_.geometry_basis_list(), *problem);
686 }
687
688 void NonlinearElasticStaticVarForm::solve_problem(
689 Eigen::MatrixXd &sol,
690 const InitialConditionOverride *initial_condition_override,
691 const ForwardStepCallback &post_step)
692 {
693 assert((!initial_condition_override || (initial_condition_override->velocity.size() == 0 && initial_condition_override->acceleration.size() == 0))
694 && "Static elasticity does not accept initial velocity or acceleration overrides");
695
696 stats.spectrum.setZero();
697
698 igl::Timer timer;
699 timer.start();
700 logger().info("Solving {}", primary_assembler_->name());
701
702 {
703 POLYFEM_SCOPED_TIMER("Setup RHS");
704
705 if (initial_condition_override && initial_condition_override->solution.size() != 0)
706 initial_solution(sol, initial_condition_override);
707 else if (sol.size() <= 0)
708 initial_solution(sol, initial_condition_override);
709
710 if (initial_condition_override && initial_condition_override->solution.size() != 0)
711 assert(sol.cols() == 1 && "Static initial solution override must have exactly one column");
712 else if (sol.cols() != 1)
713 log_and_throw_error("Static elasticity requires exactly one initial solution column.");
714 }
715 if (has_macro_strain())
716 {
717 Eigen::VectorXd extended_solution = init_homogenization_solve(sol, 0.0, initial_condition_override);
718 solve_homogenization_step(0, 0.0, extended_solution, sol, post_step);
719 }
720 else
721 {
722 init_solve(sol, 1.0, initial_condition_override);
723 solve_tensor_nonlinear(0, sol, true);
724 if (post_step)
725 post_step(0, sol);
726 }
727
728 timer.stop();
729 timings.solving_time = timer.getElapsedTime();
730 logger().info(" took {}s", timings.solving_time);
731 }
732
733 void NonlinearElasticTransientVarForm::solve_problem(
734 Eigen::MatrixXd &sol,
735 const InitialConditionOverride *initial_condition_override,
736 const ForwardStepCallback &post_step)
737 {
738 const bool save_stats = args["output"]["stats"];
739 stats.spectrum.setZero();
740
741 igl::Timer timer;
742 timer.start();
743 logger().info("Solving {}", primary_assembler_->name());
744
745 {
746 POLYFEM_SCOPED_TIMER("Setup RHS");
747
748 if (initial_condition_override && initial_condition_override->solution.size() != 0)
749 initial_solution(sol, initial_condition_override);
750 else if (sol.size() <= 0)
751 initial_solution(sol, initial_condition_override);
752
753 if (sol.cols() > 1) // ignore previous solutions
754 sol.conservativeResize(Eigen::NoChange, 1);
755 }
756 if (has_macro_strain())
757 {
758 Eigen::VectorXd extended_solution = init_homogenization_solve(sol, t0, initial_condition_override);
759 for (int step = 0; step <= time_steps; ++step)
760 {
761 const double time = t0 + step * dt;
762 solve_homogenization_step(step, time, extended_solution, sol, post_step);
763 save_timestep(time, step, t0, dt, sol);
764 solve_data_.update_barrier_stiffness(sol);
765 logger().info("{}/{} t={}", step, time_steps, time);
766 notify_time_step(step, time_steps, t0, dt);
767 save_elastic_step_state(t0, dt, step, sol, nullptr);
768 }
769 timer.stop();
770 timings.solving_time = timer.getElapsedTime();
771 logger().info(" took {}s", timings.solving_time);
772 return;
773 }
774 init_solve(sol, t0 + dt, initial_condition_override);
775 if (post_step)
776 post_step(0, sol);
777
778 // Write the total energy to a CSV file
779 int save_i = 0;
780
781 std::unique_ptr<io::EnergyCSVWriter> energy_csv = nullptr;
782 std::unique_ptr<io::RuntimeStatsCSVWriter> stats_csv = nullptr;
783
784 if (save_stats)
785 {
786 logger().debug("Saving nl stats to {} and {}", resolve_output_path("energy.csv"), resolve_output_path("stats.csv"));
787 energy_csv = std::make_unique<io::EnergyCSVWriter>(resolve_output_path("energy.csv"), solve_data_);
788 const io::OutputSpace space = output_space();
789 stats_csv = std::make_unique<io::RuntimeStatsCSVWriter>(
790 resolve_output_path("stats.csv"),
791 space_.n_bases,
792 space.mesh ? space.mesh->n_elements() : 0,
793 t0, dt);
794 }
795
796 // Save the initial solution
797 if (energy_csv)
798 energy_csv->write(save_i, sol);
799 save_timestep(t0, 0, t0, dt, sol);
800
801 save_i++;
802
803 for (int t = 1; t <= time_steps; ++t)
804 {
805 double forward_solve_time = 0, remeshing_time = 0, global_relaxation_time = 0;
806
807 {
808 POLYFEM_SCOPED_TIMER(forward_solve_time);
809 solve_tensor_nonlinear(t, sol, true);
810 }
811 if (post_step)
812 post_step(t, sol);
813
814 // Always save the solution for consistency
815 if (energy_csv)
816 energy_csv->write(save_i, sol);
817 save_timestep(t0 + dt * t, t, t0, dt, sol);
818 save_i++;
819
820 {
821 POLYFEM_SCOPED_TIMER("Update quantities");
822
823 if (solve_data_.time_integrator)
824 solve_data_.time_integrator->update_quantities(sol);
825
826 solve_data_.nl_problem->update_quantities(t0 + (t + 1) * dt, sol);
827
828 solve_data_.update_dt();
829 solve_data_.update_barrier_stiffness(sol);
830 }
831
832 logger().info("{}/{} t={}", t, time_steps, t0 + dt * t);
833 notify_time_step(t, time_steps, t0, dt);
834
835 save_elastic_step_state(t0, dt, t, sol, solve_data_.time_integrator.get());
836 if (stats_csv)
837 stats_csv->write(t, forward_solve_time, remeshing_time, global_relaxation_time);
838 }
839
840 timer.stop();
841 timings.solving_time = timer.getElapsedTime();
842 logger().info(" took {}s", timings.solving_time);
843 }
844
845 void NonlinearElasticVarForm::init_forms(const json &args, const int dim, Eigen::MatrixXd &sol, const double t)
846 {
847 damping_assembler_ = std::make_shared<assembler::ViscousDamping>();
848 set_materials(*damping_assembler_, mesh_->dimension());
849
850 elasticity_pressure_assembler = build_pressure_assembler();
851
852 // for backward solve
853 damping_prev_assembler_ = std::make_shared<assembler::ViscousDampingPrev>();
854 set_materials(*damping_prev_assembler_, mesh_->dimension());
855
856 const ElementInversionCheck check_inversion = args["solver"]["advanced"]["check_inversion"];
857
858 forms = solve_data_.init_forms(
859 // General
860 units,
861 resources_,
862 dim, t, space_.space_in_node_to_node,
863 // Elastic form
864 space_.n_bases, *space_.bases, space_.geometry_basis_list(), *primary_assembler_, ass_vals_cache_, mass_ass_vals_cache_, args["solver"]["advanced"]["jacobian_threshold"], check_inversion,
865 args["solver"]["advanced"]["conservative_max_iter"],
866 // Body form
867 0, boundary_.boundary_nodes, boundary_.local_boundary,
868 boundary_.local_neumann_boundary,
869 elastic_boundary_samples(), rhs_, sol, mass_assembler_->density(),
870 // Pressure form
871 boundary_.local_pressure_boundary, boundary_.local_pressure_cavity, elasticity_pressure_assembler,
872 // Inertia form
873 args.value("/time/quasistatic"_json_pointer, true), mass_,
874 damping_assembler_->is_valid() ? damping_assembler_ : nullptr,
875 // Lagged regularization form
876 args["solver"]["advanced"]["lagged_regularization_weight"],
877 args["solver"]["advanced"]["lagged_regularization_iterations"],
878 // Augmented lagrangian form
879 obstacle.ndof(), args["constraints"]["hard"], args["constraints"]["soft"], args["constraints"]["zero_mean"],
880 // Contact form
881 args["contact"]["enabled"],
882 args["contact"]["periodic"] ? periodic_collision_mesh_ : collision_mesh_, args["contact"]["dhat"],
883 avg_mass_, args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_area_weighting"]) : false,
884 args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_improved_max_operator"]) : false,
885 args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_physical_barrier"]) : false,
886 args["contact"]["collision_set_type"].get<std::string>(),
887 args["contact"]["skip_obstacles"].get<bool>(),
888 args["contact"]["barrier"].get<std::string>(),
889 args["solver"]["contact"]["barrier_stiffness"],
890 args["solver"]["contact"]["initial_barrier_stiffness"],
891 args["solver"]["contact"]["dhat_epsilon_scale"],
892 args["solver"]["contact"]["CCD"]["broad_phase"],
893 args["solver"]["contact"]["CCD"]["tolerance"],
894 args["solver"]["contact"]["CCD"]["max_iterations"],
895 false,
896 // Smooth Contact Form
897 args["contact"]["use_gcp_formulation"],
898 args["contact"]["alpha_t"],
899 args["contact"]["alpha_n"],
900 args["contact"]["use_adaptive_dhat"],
901 args["contact"]["min_distance_ratio"],
902 // ESP contact form
903 args["contact"]["use_esp_formulation"],
904 args["contact"]["esp_params"],
905 // Normal Adhesion Form
906 args["contact"]["adhesion"]["adhesion_enabled"],
907 args["contact"]["adhesion"]["dhat_p"],
908 args["contact"]["adhesion"]["dhat_a"],
909 args["contact"]["adhesion"]["adhesion_strength"],
910 // Tangential Adhesion Form
911 args["contact"]["adhesion"]["tangential_adhesion_coefficient"],
912 args["contact"]["adhesion"]["epsa"],
913 args["solver"]["contact"]["tangential_adhesion_iterations"],
914 // Homogenization
915 macro_strain_constraint_,
916 // Periodic contact
917 args["contact"]["periodic"], periodic_collision_mesh_to_basis_,
918 // Friction form
919 args["contact"]["friction_coefficient"],
920 args["contact"]["epsv"],
921 args["solver"]["contact"]["friction_iterations"],
922 // Rayleigh damping form
923 args["solver"]["rayleigh_damping"],
924
925 // BC AL lumping
926 args["solver"]["augmented_lagrangian"]["lumping"],
927
928 // Boundary-ID periodic constraints
929 mesh_.get(), &boundary_.total_local_boundary,
930 args["boundary_conditions"]["periodic"], /*fe_space_id=*/-1);
931
932 for (const auto &form : forms)
933 form->set_output_dir(output_path);
934
935 if (solve_data_.contact_form != nullptr)
936 solve_data_.contact_form->save_ccd_debug_meshes = args["output"]["advanced"]["save_ccd_debug_meshes"];
937 }
938
940 Eigen::VectorXd NonlinearElasticVarForm::init_homogenization_solve(
941 Eigen::MatrixXd &solution,
942 const double time,
943 const InitialConditionOverride *initial_condition_override)
944 {
945 assert(has_macro_strain());
946 forms.clear();
947 solve_data_ = solver::SolveData();
948 // Retain the RHS assembler so rebuilding forms also retains the Dirichlet constraints that remove translation modes.
949 solve_data_.rhs_assembler = rhs_assembler_;
950 displacement_gradient_.resize(0, 0);
951 macro_strain_constraint_ = assembler::MacroStrainValue();
952 macro_strain_constraint_.init(
953 mesh_->dimension(), args["constraints"]["macro_displacement_gradient"], resources_);
954 init_solve_data(solution, time, "", initial_condition_override);
955
956 for (const auto &[name, form] : solve_data_.named_forms())
957 {
958 if (name == "augmented_lagrangian")
959 {
960 form->set_weight(0);
961 form->disable();
962 }
963 }
964
965 // See chap 3.4 of https://dl.acm.org/doi/10.1145/3687765.
966 // Since rotation does not affect elastic behavior, enforce symmetry of macro strain tensor G.
967 bool solve_symmetric_macro_strain = false;
968 const Eigen::VectorXi &fixed_entries = macro_strain_constraint_.get_fixed_entry();
969 const int dim = mesh_->dimension();
970 for (int i = 0; i < dim && !solve_symmetric_macro_strain; ++i)
971 {
972 for (int j = 0; j < i; ++j)
973 {
974 const bool ij_fixed = std::find(
975 fixed_entries.data(), fixed_entries.data() + fixed_entries.size(), i + j * dim)
976 != fixed_entries.data() + fixed_entries.size();
977 const bool ji_fixed = std::find(
978 fixed_entries.data(), fixed_entries.data() + fixed_entries.size(), j + i * dim)
979 != fixed_entries.data() + fixed_entries.size();
980 if (!ij_fixed && !ji_fixed)
981 solve_symmetric_macro_strain = true;
982 }
983 }
984
985 double characteristic_length = args["solver"]["advanced"]["characteristic_length"];
986 if (characteristic_length <= 0)
987 {
988 RowVectorNd min, max;
989 mesh_->bounding_box(min, max);
990 characteristic_length = (max - min).norm();
991 }
992 double characteristic_force_density = args["solver"]["advanced"]["characteristic_force_density"];
993 if (characteristic_force_density <= 0)
994 characteristic_force_density = 10000;
995
996 const int ndof = space_.n_bases * dim;
997 auto homo_problem = std::make_shared<solver::NLHomoProblem>(
998 ndof, macro_strain_constraint_, space_.n_bases, space_.mesh_nodes,
999 time, forms, solve_data_.al_form, solve_symmetric_macro_strain,
1000 polysolve::linear::Solver::create(args["solver"]["linear"], logger()),
1001 characteristic_length, characteristic_force_density, pure_mass_, dim);
1002 if (solve_data_.periodic_contact_form)
1003 homo_problem->add_form(solve_data_.periodic_contact_form);
1004 if (solve_data_.strain_al_lagr_form)
1005 homo_problem->add_form(solve_data_.strain_al_lagr_form);
1006
1007 solve_data_.nl_problem = homo_problem;
1008 const Eigen::VectorXd initial_reduced = Eigen::VectorXd::Zero(
1009 homo_problem->reduced_size() + homo_problem->macro_reduced_size());
1010 homo_problem->init(initial_reduced);
1011 homo_problem->update_quantities(time, initial_reduced);
1012 stats.solver_info = json::array();
1013
1014 if (!initial_condition_override || initial_condition_override->displacement_gradient.size() == 0)
1015 {
1016 return Eigen::VectorXd::Zero(ndof + dim * dim);
1017 }
1018
1019 const Eigen::MatrixXd &gradient = initial_condition_override->displacement_gradient;
1020 Eigen::VectorXd extended(ndof + dim * dim);
1021 // The continuation state stores total displacement; the homogenization unknown stores u - G X.
1022 extended.head(ndof) = solution - io::Evaluator::generate_linear_field(space_.n_bases, space_.mesh_nodes, gradient);
1023 extended.tail(dim * dim) = utils::flatten(gradient);
1024 return extended;
1025 }
1026
1027 void NonlinearElasticVarForm::solve_homogenization_step(
1028 const int step,
1029 const double time,
1030 Eigen::VectorXd &extended_solution,
1031 Eigen::MatrixXd &solution,
1032 const ForwardStepCallback &post_step)
1033 {
1034
1035 // Approach the prescribed macro strain with AL, then eliminate fixed macro components to enforce it exactly.
1036
1037 auto homo_problem = std::dynamic_pointer_cast<solver::NLHomoProblem>(solve_data_.nl_problem);
1038 assert(homo_problem && solve_data_.strain_al_lagr_form);
1039
1040 const int dim = mesh_->dimension();
1041 if (extended_solution.size() == 0)
1042 extended_solution.setZero(homo_problem->full_size() + dim * dim);
1043 assert(extended_solution.size() == homo_problem->full_size() + dim * dim);
1044 const Eigen::VectorXi &fixed_entries = macro_strain_constraint_.get_fixed_entry();
1045 homo_problem->set_fixed_entry({});
1046
1047 auto lagrangian_form = solve_data_.strain_al_lagr_form;
1048 lagrangian_form->enable();
1049 Eigen::VectorXd reduced_solution = homo_problem->extended_to_reduced(extended_solution);
1050 homo_problem->update_quantities(time, reduced_solution);
1051 reduced_solution = homo_problem->extended_to_reduced(extended_solution);
1052 const Eigen::VectorXd initial_solution = reduced_solution;
1053 const Eigen::VectorXi fixed_indices = fixed_entries.array() + homo_problem->full_size();
1054 const Eigen::VectorXd fixed_values =
1055 utils::flatten(macro_strain_constraint_.eval(time))(fixed_entries);
1056 const double initial_error = lagrangian_form->compute_error(extended_solution);
1057
1058 double al_weight = args["solver"]["augmented_lagrangian"]["initial_weight"];
1059 const double max_weight = args["solver"]["augmented_lagrangian"]["max_weight"];
1060 const double eta_tolerance = args["solver"]["augmented_lagrangian"]["eta"];
1061 const double scaling = args["solver"]["augmented_lagrangian"]["scaling"];
1062 lagrangian_form->set_initial_weight(al_weight);
1063 homo_problem->init(reduced_solution);
1064 homo_problem->solution_changed(reduced_solution);
1065
1066 // Adjust the initial AL weight so that negative-gradient descent points toward the prescribed macro strain.
1067 while (initial_error > 0 && al_weight < max_weight && scaling > 1)
1068 {
1069 Eigen::VectorXd gradient;
1070 homo_problem->gradient(reduced_solution, gradient);
1071 const Eigen::VectorXd macro_gradient = utils::flatten(homo_problem->reduced_to_disp_grad(gradient, true));
1072 if ((macro_gradient(fixed_entries).array() * (extended_solution(fixed_indices) - fixed_values).array()).minCoeff() >= 0)
1073 break;
1074 al_weight = std::min(al_weight * scaling, max_weight);
1075 lagrangian_form->set_initial_weight(al_weight);
1076 }
1077 logger().debug("Macro-strain AL initial penalty: {}", al_weight);
1078
1079 // Try enforcing the prescribed macro strain exactly to check whether the projection is feasible.
1080 extended_solution(fixed_indices) = fixed_values;
1081 Eigen::VectorXd constrained_solution = homo_problem->extended_to_reduced(extended_solution);
1082 homo_problem->line_search_begin(reduced_solution, constrained_solution);
1083 bool force_al_solve = true;
1084
1085 // AL supplies a feasible starting point for the exact constrained solve.
1086 // Once projection is feasible, eliminate the fixed macro components rather
1087 // than continuing to minimize a different, softly constrained problem.
1088 while (force_al_solve
1089 || !std::isfinite(homo_problem->value(constrained_solution))
1090 || !homo_problem->is_step_valid(reduced_solution, constrained_solution)
1091 || !homo_problem->is_step_collision_free(reduced_solution, constrained_solution))
1092 {
1093 force_al_solve = false;
1094 homo_problem->line_search_end();
1095 // Apply any penalty increase from the previous iteration before minimizing again.
1096 lagrangian_form->set_initial_weight(al_weight);
1097 homo_problem->init(reduced_solution);
1098 auto nonlinear_solver = polysolve::nonlinear::Solver::create(
1099 args["solver"]["augmented_lagrangian"]["nonlinear"],
1100 args["solver"]["linear"], units.characteristic_length(), logger());
1101 homo_problem->normalize_forms();
1102 nonlinear_solver->minimize(*homo_problem, reduced_solution);
1103
1104 extended_solution = homo_problem->reduced_to_extended(reduced_solution);
1105 const double current_error = lagrangian_form->compute_error(extended_solution);
1106 const double eta = initial_error > 0 ? 1 - std::sqrt(current_error / initial_error) : 1;
1107 logger().debug("Macro-strain AL penalty {}, squared error {}, progress {}", al_weight, current_error, eta);
1108 if (eta < eta_tolerance && al_weight < max_weight)
1109 al_weight = std::min(al_weight * scaling, max_weight);
1110 else
1111 lagrangian_form->update_lagrangian(extended_solution, al_weight);
1112 if (eta <= 0)
1113 reduced_solution = initial_solution;
1114
1115 extended_solution(fixed_indices) = fixed_values;
1116 constrained_solution = homo_problem->extended_to_reduced(extended_solution);
1117 homo_problem->line_search_begin(reduced_solution, constrained_solution);
1118 }
1119 homo_problem->line_search_end();
1120 lagrangian_form->disable();
1121
1122 homo_problem->set_fixed_entry(fixed_entries);
1123 reduced_solution = homo_problem->extended_to_reduced(extended_solution);
1124 homo_problem->init(reduced_solution);
1125 auto nonlinear_solver = polysolve::nonlinear::Solver::create(
1126 args["solver"]["nonlinear"], args["solver"]["linear"],
1127 units.characteristic_length(), logger());
1128 homo_problem->normalize_forms();
1129 nonlinear_solver->minimize(*homo_problem, reduced_solution);
1130
1131 extended_solution = homo_problem->reduced_to_extended(reduced_solution);
1132 displacement_gradient_ = homo_problem->reduced_to_disp_grad(reduced_solution);
1133 solution = homo_problem->reduced_to_full(reduced_solution);
1134 if (post_step)
1135 post_step(step, solution);
1136 }
1137 void NonlinearElasticVarForm::init_solve(
1138 Eigen::MatrixXd &sol,
1139 const double t,
1140 const InitialConditionOverride *initial_condition_override)
1141 {
1142 init_solve_data(sol, t, "", initial_condition_override);
1143
1144 double characteristic_length = 0;
1145 if (args["solver"]["advanced"]["characteristic_length"] > 0)
1146 {
1147 characteristic_length = args["solver"]["advanced"]["characteristic_length"];
1148 }
1149 else
1150 {
1151 RowVectorNd min, max;
1152 mesh_->bounding_box(min, max);
1153 characteristic_length = (max - min).norm();
1154 }
1155
1156 double characteristic_force_density = 0;
1157 if (args["solver"]["advanced"]["characteristic_force_density"] <= 0)
1158 {
1159 logger().warn("No user-specified force density was provided, defaulting to 10000.");
1160 characteristic_force_density = 10000;
1161 }
1162 else
1163 {
1164 characteristic_force_density = args["solver"]["advanced"]["characteristic_force_density"];
1165 }
1166
1167 const int ndof = space_.n_bases * mesh_->dimension();
1168 solve_data_.nl_problem = std::make_shared<solver::NLProblem>(
1169 ndof, t, forms, solve_data_.al_form,
1170 polysolve::linear::Solver::create(args["solver"]["linear"], logger()),
1171 characteristic_length, characteristic_force_density, pure_mass_, mesh_->dimension());
1172 solve_data_.nl_problem->init(sol);
1173 solve_data_.nl_problem->update_quantities(t, sol);
1174
1175 stats.solver_info = json::array();
1176 }
1177
1178 void NonlinearElasticVarForm::init_solve_data(
1179 Eigen::MatrixXd &sol,
1180 const double t,
1181 const std::string &state_prefix,
1182 const InitialConditionOverride *initial_condition_override)
1183 {
1184 assert(sol.cols() == 1);
1185 assert(!problem->is_scalar()); // tensor
1186
1187 // FIXME
1188 // if (optimization_enabled != solver::CacheLevel::None)
1189 // {
1190 // if (initial_sol_update.size() == ndof())
1191 // sol = initial_sol_update;
1192 // else
1193 // initial_sol_update = sol;
1194 // }
1195
1196 // --------------------------------------------------------------------
1197 // Check for initial intersections
1198 if (args["contact"]["enabled"])
1199 {
1200 POLYFEM_SCOPED_TIMER("Check for initial intersections");
1201
1202 const Eigen::MatrixXd displaced = collision_mesh_.displace_vertices(
1203 utils::unflatten(sol, mesh_->dimension()));
1204
1205 if (ipc::has_intersections(collision_mesh_, displaced, ipc::create_broad_phase(args["solver"]["contact"]["CCD"]["broad_phase"]).get()))
1206 {
1208 resolve_output_path("intersection.obj"), displaced,
1209 collision_mesh_.edges(), collision_mesh_.faces());
1210 log_and_throw_error("Unable to solve, initial solution has intersections!");
1211 }
1212 }
1213
1214 // --------------------------------------------------------------------
1215
1216 if (problem->is_time_dependent() && !has_macro_strain())
1217 {
1218 POLYFEM_SCOPED_TIMER("Initialize time integrator");
1219 solve_data_.time_integrator = ImplicitTimeIntegrator::construct_time_integrator(args["time"]["integrator"]);
1220
1221 Eigen::MatrixXd solution, velocity, acceleration;
1222 initial_solution(solution, initial_condition_override, state_prefix); // Reload this because we need all previous solutions
1223 solution.col(0) = sol; // Make sure the current solution is the same as `sol`
1224 assert(solution.rows() == sol.size());
1225 initial_velocity(velocity, initial_condition_override, state_prefix);
1226 assert(velocity.rows() == sol.size());
1227 initial_acceleration(acceleration, initial_condition_override, state_prefix);
1228 assert(acceleration.rows() == sol.size());
1229 if (solution.cols() != velocity.cols() || solution.cols() != acceleration.cols())
1230 {
1232 "Incompatible initial-condition history for transient solve: "
1233 "solution has {} columns, velocity has {}, acceleration has {}.",
1234 solution.cols(), velocity.cols(), acceleration.cols());
1235 }
1236
1237 solve_data_.time_integrator->init(solution, velocity, acceleration, dt);
1238 restore_checkpoint_integrator(
1239 solve_data_.time_integrator, "/checkpoint/state/primary_integrator", dt,
1240 "primary", space_, mesh_->dimension());
1241 assert(solve_data_.time_integrator != nullptr && "Transient nonlinear elasticity requires an initialized time integrator");
1242 }
1243 else
1244 {
1245 solve_data_.time_integrator = nullptr;
1246 }
1247
1248 // --------------------------------------------------------------------
1249 // Initialize forms
1250
1251 // --------------------------------------------------------------------
1252 // Initialize nonlinear problems
1253
1254 init_forms(args, mesh_->dimension(), sol, t);
1255
1256 if (pure_mass_.size() == 0)
1257 pure_mass_assembler_->assemble(mesh_->is_volume(), space_.n_bases, space_.basis_list(), space_.geometry_basis_list(), pure_mass_ass_vals_cache_, 0, pure_mass_, true);
1258 }
1259
1260 void NonlinearElasticVarForm::prepare_for_embedding()
1261 {
1262 prepare();
1263 }
1264
1265 void NonlinearElasticVarForm::initial_solution_for_embedding(
1266 Eigen::MatrixXd &solution, const std::string &state_prefix) const
1267 {
1268 initial_solution(solution, nullptr, state_prefix);
1269 if (solution.cols() > 1)
1270 solution.conservativeResize(Eigen::NoChange, 1);
1271 }
1272
1273 void NonlinearElasticVarForm::init_forms_for_embedding(
1274 Eigen::MatrixXd &solution, const double t, const std::string &state_prefix)
1275 {
1276 prepare();
1277 init_solve_data(solution, t, state_prefix);
1278 }
1279
1280 void NonlinearElasticVarForm::advance_for_embedding(const Eigen::VectorXd &solution)
1281 {
1282 assert(solve_data_.time_integrator);
1283 solve_data_.time_integrator->update_quantities(solution);
1284 solve_data_.update_dt();
1285 }
1286
1287 void NonlinearElasticVarForm::update_barrier_stiffness_for_embedding(
1288 const Eigen::VectorXd &solution)
1289 {
1290 solve_data_.update_barrier_stiffness(solution);
1291 }
1292
1293 bool NonlinearElasticVarForm::save_timestep_for_embedding(
1294 const double time, const int step, const double dt,
1295 const Eigen::MatrixXd &solution, paraviewo::VTMWriter &vtm,
1296 const std::string &block_prefix) const
1297 {
1298 return save_timestep_to_vtm(time, step, dt, solution, vtm, block_prefix);
1299 }
1300
1301 int NonlinearElasticVarForm::embedding_ndof() const
1302 {
1303 return mesh_ ? space_.n_bases * mesh_->dimension() : 0;
1304 }
1305
1306 void NonlinearElasticVarForm::solve_tensor_nonlinear(
1307 const int step,
1308 Eigen::MatrixXd &sol,
1309 const bool init_lagging)
1310 {
1311 assert(solve_data_.nl_problem != nullptr && "Nonlinear forms must initialize the nonlinear problem before solving");
1312 solver::NLProblem &nl_problem = *(solve_data_.nl_problem);
1313
1314 assert(sol.size() == rhs_.size());
1315
1316 if (nl_problem.uses_lagging())
1317 {
1318 if (init_lagging)
1319 {
1320 POLYFEM_SCOPED_TIMER("Initializing lagging");
1321 nl_problem.init_lagging(sol);
1322 }
1323 logger().info("Lagging iteration 1:");
1324 }
1325
1326 save_subsolve(0, step, sol);
1327
1328 std::shared_ptr<polysolve::nonlinear::Solver> nl_solver =
1329 polysolve::nonlinear::Solver::create(args["solver"]["augmented_lagrangian"]["nonlinear"], args["solver"]["linear"], units.characteristic_length(), logger());
1330
1331 ALSolver al_solver(
1332 solve_data_.al_form,
1333 args["solver"]["augmented_lagrangian"]["initial_weight"],
1334 args["solver"]["augmented_lagrangian"]["scaling"],
1335 args["solver"]["augmented_lagrangian"]["max_weight"],
1336 args["solver"]["augmented_lagrangian"]["eta"],
1337 [&](const Eigen::VectorXd &x) {
1338 this->solve_data_.update_barrier_stiffness(sol);
1339 });
1340
1341 al_solver.post_subsolve = [&](const double al_weight) {
1342 stats.solver_info.push_back(
1343 {{"type", al_weight > 0 ? "al" : "rc"},
1344 {"t", step},
1345 {"info", nl_solver->info()}});
1346 if (al_weight > 0)
1347 stats.solver_info.back()["weight"] = al_weight;
1348 save_subsolve(stats.solver_info.size(), step, sol);
1349 };
1350
1351 Eigen::MatrixXd prev_sol = sol;
1352 al_solver.solve_al(nl_problem, sol,
1353 args["solver"]["augmented_lagrangian"]["nonlinear"], args["solver"]["linear"], units.characteristic_length());
1354
1355 al_solver.solve_reduced(nl_problem, sol,
1356 args["solver"]["nonlinear"], args["solver"]["linear"], units.characteristic_length());
1357
1358 if (args["space"]["advanced"]["count_flipped_els_continuous"])
1359 {
1360 const auto invalidList = utils::count_invalid(mesh_->dimension(), space_.basis_list(), space_.geometry_basis_list(), sol);
1361 logger().debug("Flipped elements (cnt {}) : {}", invalidList.size(), invalidList);
1362 }
1363
1364 const double lagging_tol = args["solver"]["contact"].value("friction_convergence_tol", 1e-2) * units.characteristic_length();
1365
1366 bool lagging_converged = !nl_problem.uses_lagging();
1367 for (int lag_i = 1; !lagging_converged; lag_i++)
1368 {
1369 Eigen::VectorXd tmp_sol = nl_problem.full_to_reduced(sol);
1370
1371 nl_problem.update_lagging(tmp_sol, lag_i);
1372
1373 Eigen::VectorXd grad;
1374 nl_problem.gradient(tmp_sol, grad);
1375 const double delta_x_norm = (prev_sol - sol).lpNorm<Eigen::Infinity>();
1376 logger().debug("Lagging convergence grad_norm={:g} tol={:g} (||Δx||={:g})", grad.norm(), lagging_tol, delta_x_norm);
1377 if (grad.norm() <= lagging_tol)
1378 {
1379 logger().info(
1380 "Lagging converged in {:d} iteration(s) (grad_norm={:g} tol={:g})",
1381 lag_i, grad.norm(), lagging_tol);
1382 lagging_converged = true;
1383 break;
1384 }
1385
1386 if (delta_x_norm <= 1e-12)
1387 {
1388 logger().warn(
1389 "Lagging produced tiny update between iterations {:d} and {:d} (grad_norm={:g} grad_tol={:g} ||Δx||={:g} Δx_tol={:g}); stopping early",
1390 lag_i - 1, lag_i, grad.norm(), lagging_tol, delta_x_norm, 1e-6);
1391 lagging_converged = false;
1392 break;
1393 }
1394
1395 if (lag_i >= nl_problem.max_lagging_iterations())
1396 {
1397 logger().warn(
1398 "Lagging failed to converge with {:d} iteration(s) (grad_norm={:g} tol={:g})",
1399 lag_i, grad.norm(), lagging_tol);
1400 lagging_converged = false;
1401 break;
1402 }
1403
1404 logger().info("Lagging iteration {:d}:", lag_i + 1);
1405 nl_problem.init(sol);
1406 solve_data_.update_barrier_stiffness(sol);
1407 nl_problem.normalize_forms();
1408 nl_solver->minimize(nl_problem, tmp_sol);
1409 nl_problem.finish();
1410 prev_sol = sol;
1411 sol = nl_problem.reduced_to_full(tmp_sol);
1412
1413 stats.solver_info.push_back(
1414 {{"type", "rc"},
1415 {"t", step},
1416 {"lag_i", lag_i},
1417 {"info", nl_solver->info()}});
1418 save_subsolve(stats.solver_info.size(), step, sol);
1419 }
1420 }
1421
1422} // namespace polyfem::varform
int V
int x
std::array< Matrix< int, 3, 3 >, 3 > space_
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
static double convert(const json &val, const std::string &unit_type)
Definition Units.cpp:35
static Eigen::MatrixXd generate_linear_field(const int n_bases, const std::shared_ptr< mesh::MeshNodes > mesh_nodes, const Eigen::MatrixXd &grad)
static bool write(const std::string &path, const Eigen::MatrixXd &v, const Eigen::MatrixXi &e, const Eigen::MatrixXi &f)
Definition OBJWriter.cpp:18
static void extract_boundary_mesh_sampled(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, const int sampling_order=0)
extracts a collision proxy sampling every boundary face on a uniform lattice of the globally maximal ...
Definition OutData.cpp:434
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:737
Read-only interface to a hierarchical collection of simulation resources.
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:50
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
Definition Mesh.hpp:148
int dimension() const
utily for dimension
Definition Mesh.hpp:138
const Eigen::MatrixXi & e() const
Definition Obstacle.hpp:48
const Eigen::MatrixXi & f() const
Definition Obstacle.hpp:47
const Eigen::MatrixXd & v() const
Definition Obstacle.hpp:45
const Eigen::VectorXi & codim_v() const
Definition Obstacle.hpp:46
void solve_reduced(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
Definition ALSolver.hpp:41
std::function< void(const double)> post_subsolve
Definition ALSolver.hpp:53
void solve_al(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
Definition ALSolver.hpp:29
virtual void init(const TVector &x0) override
double normalize_forms() override
TVector full_to_reduced(const TVector &full) const
virtual void gradient(const TVector &x, TVector &gradv) override
void init_lagging(const TVector &x) override
virtual TVector reduced_to_full(const TVector &reduced) const
void update_lagging(const TVector &x, const int iter_num) override
static Mapping build_mapping(int ndof, int value_dim, const mesh::Mesh &mesh, const std::vector< basis::ElementBases > &bases, const std::vector< mesh::LocalBoundary > &local_boundary, const std::array< int, 2 > &boundary_ids, double relative_tolerance)
class to store time stepping data
Definition SolveData.hpp:56
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
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
void init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path) override
Initialize the variational formulation with the given parameters.
std::vector< io::OutputField > elastic_output_fields(const io::OutputSample &sample, const Eigen::MatrixXd &solution, const io::OutputFieldOptions &options, const mesh::Obstacle *obstacle, const time_integrator::ImplicitTimeIntegrator *time_integrator, const std::vector< std::pair< std::string, std::shared_ptr< solver::Form > > > &named_forms, const solver::Form *elastic_form, const solver::ContactForm *contact_form=nullptr) const
void load_mesh(const mesh::Mesh &mesh, const json &args) override
void set_obstacle(mesh::Obstacle &&obstacle) override
assembler::MacroStrainValue macro_strain_constraint_
Prescribed macro displacement-gradient values and fixed components.
std::shared_ptr< assembler::PressureAssembler > elasticity_pressure_assembler
std::vector< io::OutputField > output_fields(const io::OutputSample &sample, const Eigen::MatrixXd &solution, const io::OutputFieldOptions &options) const override
Get the output fields of the variational formulation, for output purposes.
Eigen::MatrixXd displacement_gradient_
For periodic structure, displacement u = periodic fluctuation ũ + macro strain GX.
std::shared_ptr< assembler::ViscousDampingPrev > damping_prev_assembler_
void init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path) override
Initialize the variational formulation with the given parameters.
std::shared_ptr< assembler::ViscousDamping > damping_assembler_
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
void load_mesh(const mesh::Mesh &mesh, const json &args) override
bool is_contact_enabled() const override
Check if contact is enabled for the variational formulation, for output purposes.
std::vector< std::shared_ptr< solver::Form > > forms
std::shared_ptr< assembler::Problem > problem
current problem, it contains rhs and bc
Definition VarForm.hpp:263
virtual std::string name() const =0
Get the name of the variational formulation.
std::unique_ptr< mesh::Mesh > mesh_
Definition VarForm.hpp:275
void build_collision_proxy(const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &geom_bases, const std::vector< LocalBoundary > &total_local_boundary, const int n_bases, const int dim, const double max_edge_length, Eigen::MatrixXd &proxy_vertices, Eigen::MatrixXi &proxy_faces, std::vector< Eigen::Triplet< double > > &displacement_map_entries, const CollisionProxyTessellation tessellation)
void load_collision_proxy(const io::ResourceIO &resources, const std::string &mesh_filename, const std::string &weights_filename, const Eigen::VectorXi &in_node_to_node, const json &transformation, Eigen::MatrixXd &vertices, Eigen::VectorXi &codim_vertices, Eigen::MatrixXi &edges, Eigen::MatrixXi &faces, std::vector< Eigen::Triplet< double > > &displacement_map_entries)
Load a collision proxy mesh and displacement map from resources.
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:49
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
void append_rows(DstMat &dst, const SrcMat &src)
std::vector< int > count_invalid(const int dim, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXd &u, const unsigned max_iter)
Definition Jacobian.cpp:122
Eigen::VectorXd flatten(const Eigen::MatrixXd &X)
Flatten rowwises.
std::function< void(int step, const Eigen::MatrixXd &solution)> ForwardStepCallback
Definition VarForm.hpp:55
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 3, 1 > VectorNd
Definition Types.hpp:11
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::vector< std::string > fields
const mesh::Mesh * mesh
Eigen::MatrixXd displacement_gradient
Macro G for decomposing solution as u = fluctuation + G X; empty when absent.
Definition VarForm.hpp:51