PolyFEM
Loading...
Searching...
No Matches
NonlinearElasticVarForm.cpp
Go to the documentation of this file.
2
5
10
17
21
29
30#include <igl/Timer.h>
31#include <igl/edges.h>
32
33#include <ipc/ipc.hpp>
34
35#include <polysolve/linear/Solver.hpp>
36#include <polysolve/nonlinear/Solver.hpp>
37
38#include <algorithm>
39#include <cmath>
40#include <limits>
41
42namespace polyfem::varform
43{
44 using namespace solver;
45 using namespace time_integrator;
46
47 void NonlinearElasticVarForm::init(const std::string &formulation, const Units &units, const json &args, const std::string &out_path)
48 {
49 json clean_args = args;
50 const bool contact_dhat_was_explicit = clean_args["contact"].value("_dhat_was_explicit", false);
51 clean_args["contact"].erase("_dhat_was_explicit");
52 ElasticVarForm::init(formulation, units, clean_args, out_path);
53 contact_dhat_was_explicit_ = contact_dhat_was_explicit;
54 }
55
57 {
59 collision_mesh = ipc::CollisionMesh();
62 forms.clear();
64 damping_assembler = nullptr;
65 damping_prev_assembler = nullptr;
67 }
68
69 void NonlinearElasticVarForm::load_mesh(const mesh::Mesh &mesh, const json &args)
70 {
72
73 logger().info("Loading obstacles...");
75 units,
76 args["geometry"],
77 utils::json_as_array(args["boundary_conditions"]["obstacle_displacements"]),
78 utils::json_as_array(args["boundary_conditions"]["dirichlet_boundary"]),
79 root_path, mesh.dimension());
80 }
81
83 {
84 auto space = ElasticVarForm::output_space();
85 space.collision_mesh = is_contact_enabled() ? &collision_mesh : nullptr;
86 space.obstacle = &obstacle;
87 return space;
88 }
89
90 std::vector<io::OutputField> NonlinearElasticVarForm::output_fields(
91 const io::OutputSample &sample,
92 const Eigen::MatrixXd &solution,
93 const io::OutputFieldOptions &options) const
94 {
95 std::vector<io::OutputField> fields = elastic_output_fields(
96 sample, solution, options, &obstacle, solve_data.time_integrator.get(),
98 if (!mesh_ || !problem || solution.size() <= 0)
99 return fields;
101 return fields;
102
103 const int actual_dim = problem->is_scalar() ? 1 : mesh_->dimension();
104 const auto &paraview_options = args["output"]["paraview"]["options"];
105 const bool explicit_fields = !options.fields.empty();
106
107 const auto has_field = [&](const std::string &name) {
108 return std::any_of(fields.begin(), fields.end(), [&](const io::OutputField &field) {
109 return field.association == io::OutputField::Association::Point && field.name == name;
110 });
111 };
112
113 const auto append_collision_dof_field = [&](const std::string &name, const Eigen::MatrixXd &dof_values) {
114 if (has_field(name) || dof_values.size() <= 0)
115 return;
116
117 Eigen::MatrixXd values = collision_mesh.map_displacements(utils::unflatten(dof_values, actual_dim));
118 if (values.rows() == sample.points.rows())
119 fields.push_back({name, values, io::OutputField::Association::Point});
120 };
121
122 const auto append_collision_form_force = [&](const std::string &name, const std::shared_ptr<solver::Form> &form) {
123 if (!form || !form->enabled() || sample.points.rows() != collision_mesh.rest_positions().rows())
124 return;
125
126 Eigen::VectorXd force;
127 form->first_derivative(solution.col(0), force);
128 const double acceleration_scaling =
129 solve_data.time_integrator ? solve_data.time_integrator->acceleration_scaling() : 1;
130 force *= -1.0 / acceleration_scaling;
131 append_collision_dof_field(name, force);
132 };
133
134 if (paraview_options["forces"] && !problem->is_scalar())
135 {
136 const double s = solve_data.time_integrator ? solve_data.time_integrator->acceleration_scaling() : 1;
137 for (const auto &[name, form] : solve_data.named_forms())
138 {
139 const std::string field_name = name + "_forces";
140 if (!options.export_field(field_name))
141 continue;
142
143 Eigen::VectorXd force;
144 if (form && form->enabled())
145 {
146 form->first_derivative(solution, force);
147 force *= -1.0 / s;
148 }
149 else
150 {
151 force.setZero(solution.size());
152 }
153 append_collision_dof_field(field_name, force);
154 }
155 }
156
157 if (options.export_field("gradient_of_elastic_potential") && solve_data.elastic_form)
158 {
159 Eigen::VectorXd potential_grad;
160 solve_data.elastic_form->first_derivative(solution, potential_grad);
161 append_collision_dof_field("gradient_of_elastic_potential", potential_grad);
162 }
163
164 if (options.export_field("gradient_of_contact_potential") && solve_data.contact_form && solve_data.contact_form->weight() > 0)
165 {
166 Eigen::VectorXd potential_grad;
167 solve_data.contact_form->first_derivative(solution, potential_grad);
168 potential_grad *= -solve_data.contact_form->barrier_stiffness() / solve_data.contact_form->weight();
169 append_collision_dof_field("gradient_of_contact_potential", potential_grad);
170 }
171
172 if (options.export_field("displacement"))
173 append_collision_dof_field("displacement", solution);
174 if (options.export_field("solution"))
175 append_collision_dof_field("solution", solution);
176
177 if ((paraview_options["contact_forces"] || explicit_fields) && options.export_field("contact_forces"))
178 append_collision_form_force("contact_forces", solve_data.contact_form);
179 if ((paraview_options["friction_forces"] || explicit_fields) && options.export_field("friction_forces"))
180 append_collision_form_force("friction_forces", solve_data.friction_form);
181 if ((paraview_options["normal_adhesion_forces"] || explicit_fields) && options.export_field("normal_adhesion_forces"))
182 append_collision_form_force("normal_adhesion_forces", solve_data.normal_adhesion_form);
183 if ((paraview_options["tangential_adhesion_forces"] || explicit_fields) && options.export_field("tangential_adhesion_forces"))
184 append_collision_form_force("tangential_adhesion_forces", solve_data.tangential_adhesion_form);
185
186 if (explicit_fields
187 && options.export_field("adaptive_dhat")
188 && args["contact"]["use_gcp_formulation"]
189 && args["contact"]["use_adaptive_dhat"])
190 {
191 const auto smooth_contact = std::dynamic_pointer_cast<solver::SmoothContactForm>(solve_data.contact_form);
192 if (smooth_contact)
193 {
194 const auto &set = smooth_contact->collision_set();
195 if (actual_dim == 2)
196 {
197 Eigen::VectorXd dhats(collision_mesh.num_edges());
198 for (int e = 0; e < dhats.size(); ++e)
199 dhats(e) = set.get_edge_dhat(e);
200 fields.push_back({"dhat", dhats, io::OutputField::Association::Cell});
201 }
202 else
203 {
204 Eigen::VectorXd dhats(collision_mesh.num_faces());
205 for (int f = 0; f < dhats.size(); ++f)
206 dhats(f) = set.get_face_dhat(f);
207 fields.push_back({"dhat_face", dhats, io::OutputField::Association::Cell});
208
209 Eigen::VectorXd vertex_dhats(collision_mesh.num_vertices());
210 for (int v = 0; v < vertex_dhats.size(); ++v)
211 vertex_dhats(v) = set.get_vert_dhat(v);
212 fields.push_back({"dhat_vert", vertex_dhats, io::OutputField::Association::Point});
213 }
214 }
215 }
216
217 return fields;
218 }
219
220 void NonlinearElasticVarForm::build_basis(mesh::Mesh &mesh, const bool iso_parametric, const json &args)
221 {
222 ElasticVarForm::build_basis(mesh, iso_parametric, args);
223
224 // Legacy nonlinear/contact code assumes the displacement space includes obstacle vertices.
225 // The shared build path only counts FE bases, so extend it here
226 // before constructing collision/contact state.
227 const int n_fe_bases = space_.n_bases;
228 space_.n_bases += obstacle.n_vertices();
229
230 if (is_contact_enabled())
231 {
232 logger().info("Building collision mesh...");
233 build_collision_mesh(mesh, args);
234 preprocess_contact_parameters();
235
236 // FIXME!! handle periodic collision mesh
237 // if (periodic_bc && args["contact"]["periodic"])
238 // build_periodic_collision_mesh();
239 }
240
241 logger().info("Done!");
242
243 for (int i = n_fe_bases; i < space_.n_bases; ++i)
244 {
245 for (int d = 0; d < mesh.dimension(); ++d)
246 boundary_.boundary_nodes.push_back(i * mesh.dimension() + d);
247 }
248
249 boundary_.normalize_boundary_nodes();
250 }
251
252 void NonlinearElasticVarForm::preprocess_contact_parameters()
253 {
254 if (!is_contact_enabled())
255 return;
256
257 double min_boundary_edge_length = std::numeric_limits<double>::max();
258 for (const auto &edge : collision_mesh.edges().rowwise())
259 {
260 const VectorNd v0 = collision_mesh.rest_positions().row(edge(0));
261 const VectorNd v1 = collision_mesh.rest_positions().row(edge(1));
262 min_boundary_edge_length = std::min(min_boundary_edge_length, (v1 - v0).norm());
263 }
264
265 double dhat = Units::convert(args["contact"]["dhat"], units.length());
266 args["contact"]["epsv"] = Units::convert(args["contact"]["epsv"], units.velocity());
267
268 if (!contact_dhat_was_explicit_
269 && std::isfinite(min_boundary_edge_length)
270 && dhat > min_boundary_edge_length)
271 {
272 dhat = args["contact"]["dhat_percentage"].get<double>() * min_boundary_edge_length;
273 logger().info("dhat set to {}", dhat);
274 }
275 else if (std::isfinite(min_boundary_edge_length) && dhat > min_boundary_edge_length)
276 {
277 logger().warn("dhat larger than min boundary edge, {} > {}", dhat, min_boundary_edge_length);
278 }
279
280 args["contact"]["dhat"] = dhat;
281 }
282
283 void NonlinearElasticVarForm::build_rhs_assembler()
284 {
285 json rhs_solver_params = args["solver"]["linear"];
286 if (!rhs_solver_params.contains("Pardiso"))
287 rhs_solver_params["Pardiso"] = {};
288 rhs_solver_params["Pardiso"]["mtype"] = -2;
289
290 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
291
292 solve_data.rhs_assembler = std::make_shared<assembler::RhsAssembler>(
293 *primary_assembler_, *mesh_, &obstacle,
294 boundary_.dirichlet_nodes, boundary_.neumann_nodes,
295 boundary_.dirichlet_nodes_position, boundary_.neumann_nodes_position,
296 space_.n_bases, size, space_.basis_list(), space_.geometry_basis_list(), mass_ass_vals_cache_, *problem,
297 args["space"]["advanced"]["bc_method"],
298 rhs_solver_params,
299 /*fe_space_id=*/-1);
300 rhs_assembler_ = solve_data.rhs_assembler;
301 }
302
303 void NonlinearElasticVarForm::build_collision_mesh(
304 const mesh::Mesh &mesh,
305 const json &args)
306 {
307 build_collision_mesh(
308 mesh, space_.n_bases, space_.basis_list(), space_.geometry_basis_list(), boundary_.total_local_boundary, obstacle,
309 args, [this](const std::string &p) { return utils::resolve_path(p, root_path, false); },
310 space_.space_in_node_to_node, collision_mesh);
311 }
312
313 void NonlinearElasticVarForm::build_collision_mesh(
314 const mesh::Mesh &mesh,
315 const int n_bases,
316 const std::vector<basis::ElementBases> &bases,
317 const std::vector<basis::ElementBases> &geom_bases,
318 const std::vector<mesh::LocalBoundary> &total_local_boundary,
319 const mesh::Obstacle &obstacle,
320 const json &args,
321 const std::function<std::string(const std::string &)> &resolve_input_path,
322 const Eigen::VectorXi &in_node_to_node,
323 ipc::CollisionMesh &collision_mesh)
324 {
325 Eigen::MatrixXd collision_vertices;
326 Eigen::VectorXi collision_codim_vids;
327 Eigen::MatrixXi collision_edges, collision_triangles;
328 std::vector<Eigen::Triplet<double>> displacement_map_entries;
329
330 if (args.contains("/contact/collision_mesh"_json_pointer)
331 && args.at("/contact/collision_mesh/enabled"_json_pointer).get<bool>())
332 {
333 const json collision_mesh_args = args.at("/contact/collision_mesh"_json_pointer);
334 if (collision_mesh_args.contains("linear_map"))
335 {
336 assert(displacement_map_entries.empty());
337 assert(collision_mesh_args.contains("mesh"));
338 const std::string root_path = utils::json_value<std::string>(args, "root_path", "");
339 // TODO: handle transformation per geometry
340 const json transformation = utils::json_as_array(args["geometry"])[0]["transformation"];
342 utils::resolve_path(collision_mesh_args["mesh"], root_path),
343 utils::resolve_path(collision_mesh_args["linear_map"], root_path),
344 in_node_to_node, transformation, collision_vertices, collision_codim_vids,
345 collision_edges, collision_triangles, displacement_map_entries);
346 }
347 else if (collision_mesh_args.contains("tessellation_type")
348 && collision_mesh_args["tessellation_type"] == "max_order")
349 {
351 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
352 collision_vertices, collision_edges, collision_triangles, displacement_map_entries,
353 utils::json_value<int>(collision_mesh_args, "sampling_order", 0));
354 }
355 else if (collision_mesh_args.contains("max_edge_length"))
356 {
357 logger().debug(
358 "Building collision proxy with max edge length={} ...",
359 collision_mesh_args["max_edge_length"].get<double>());
360 igl::Timer timer;
361 timer.start();
363 bases, geom_bases, total_local_boundary, n_bases, mesh.dimension(),
364 collision_mesh_args["max_edge_length"], collision_vertices,
365 collision_triangles, displacement_map_entries,
366 collision_mesh_args["tessellation_type"]);
367 if (collision_triangles.size())
368 igl::edges(collision_triangles, collision_edges);
369 timer.stop();
370 logger().debug(fmt::format(
371 std::locale("en_US.UTF-8"),
372 "Done (took {:g}s, {:L} vertices, {:L} triangles)",
373 timer.getElapsedTime(),
374 collision_vertices.rows(), collision_triangles.rows()));
375 }
376 else
377 {
379 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
380 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
381 }
382 }
383 else
384 {
386 mesh, n_bases - obstacle.n_vertices(), bases, total_local_boundary,
387 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
388 }
389
390 std::vector<bool> is_orientable_vertex(collision_vertices.rows(), true);
391
392 // n_bases already contains the obstacle vertices
393 const int num_fe_nodes = n_bases - obstacle.n_vertices();
394 const int num_fe_collision_vertices = collision_vertices.rows();
395 assert(collision_edges.size() == 0 || collision_edges.maxCoeff() < num_fe_collision_vertices);
396 assert(collision_triangles.size() == 0 || collision_triangles.maxCoeff() < num_fe_collision_vertices);
397
398 // Append the obstacles to the collision mesh
399 if (obstacle.n_vertices() > 0)
400 {
401 utils::append_rows(collision_vertices, obstacle.v());
402 utils::append_rows(collision_codim_vids, obstacle.codim_v().array() + num_fe_collision_vertices);
403 utils::append_rows(collision_edges, obstacle.e().array() + num_fe_collision_vertices);
404 utils::append_rows(collision_triangles, obstacle.f().array() + num_fe_collision_vertices);
405
406 for (int i = 0; i < obstacle.n_vertices(); i++)
407 {
408 is_orientable_vertex.push_back(false);
409 }
410
411 if (!displacement_map_entries.empty())
412 {
413 displacement_map_entries.reserve(displacement_map_entries.size() + obstacle.n_vertices());
414 for (int i = 0; i < obstacle.n_vertices(); i++)
415 {
416 displacement_map_entries.emplace_back(num_fe_collision_vertices + i, num_fe_nodes + i, 1.0);
417 }
418 }
419 }
420
421 std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(
422 collision_vertices.rows(), collision_edges);
423 for (const int vid : collision_codim_vids)
424 {
425 is_on_surface[vid] = true;
426 }
427
428 Eigen::SparseMatrix<double> displacement_map;
429 if (!displacement_map_entries.empty())
430 {
431 displacement_map.resize(collision_vertices.rows(), n_bases);
432 displacement_map.setFromTriplets(displacement_map_entries.begin(), displacement_map_entries.end());
433 }
434
435 collision_mesh = ipc::CollisionMesh(
436 is_on_surface, is_orientable_vertex, collision_vertices, collision_edges, collision_triangles,
437 displacement_map);
438
439 collision_mesh.can_collide = [&collision_mesh, num_fe_collision_vertices](size_t vi, size_t vj) {
440 // obstacles do not collide with other obstacles
441 return collision_mesh.to_full_vertex_id(vi) < num_fe_collision_vertices
442 || collision_mesh.to_full_vertex_id(vj) < num_fe_collision_vertices;
443 };
444
445 collision_mesh.init_area_jacobians();
446 }
447
448 std::shared_ptr<assembler::PressureAssembler> NonlinearElasticVarForm::build_pressure_assembler() const
449 {
450 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
451
452 return std::make_shared<assembler::PressureAssembler>(
453 *primary_assembler_, *mesh_, obstacle,
454 boundary_.local_pressure_boundary,
455 boundary_.local_pressure_cavity,
456 boundary_.boundary_nodes,
457 elastic_primitive_to_node(), elastic_node_to_primitive(),
458 space_.n_bases, size, space_.basis_list(), space_.geometry_basis_list(), *problem);
459 }
460
461 void NonlinearElasticStaticVarForm::solve_problem(Eigen::MatrixXd &sol)
462 {
463 stats.spectrum.setZero();
464
465 igl::Timer timer;
466 timer.start();
467 logger().info("Solving {}", primary_assembler_->name());
468
469 {
470 POLYFEM_SCOPED_TIMER("Setup RHS");
471
472 // FIXME
473 // read_initial_x_from_file(
474 // resolve_input_path(args["input"]["data"]["state"]), "u",
475 // args["input"]["data"]["reorder"], in_node_to_node,
476 // mesh->dimension(), solution);
477
478 if (sol.size() <= 0)
479 initial_elastic_solution(sol);
480
481 if (sol.cols() > 1) // ignore previous solutions
482 sol.conservativeResize(Eigen::NoChange, 1);
483 }
484 init_solve(sol, 1.0);
485
486 solve_tensor_nonlinear(0, sol, true);
487
488 const std::string state_path = resolve_output_path(args["output"]["data"]["state"]);
489 if (!state_path.empty())
490 io::write_matrix(state_path, "u", sol);
491
492 timer.stop();
493 timings.solving_time = timer.getElapsedTime();
494 logger().info(" took {}s", timings.solving_time);
495 }
496
497 void NonlinearElasticTransientVarForm::solve_problem(Eigen::MatrixXd &sol)
498 {
499 const bool save_stats = args["output"]["stats"];
500 stats.spectrum.setZero();
501
502 igl::Timer timer;
503 timer.start();
504 logger().info("Solving {}", primary_assembler_->name());
505
506 {
507 POLYFEM_SCOPED_TIMER("Setup RHS");
508
509 // FIXME
510 // read_initial_x_from_file(
511 // resolve_input_path(args["input"]["data"]["state"]), "u",
512 // args["input"]["data"]["reorder"], in_node_to_node,
513 // mesh->dimension(), solution);
514
515 if (sol.size() <= 0)
516 initial_elastic_solution(sol);
517
518 if (sol.cols() > 1) // ignore previous solutions
519 sol.conservativeResize(Eigen::NoChange, 1);
520 }
521 init_solve(sol, t0 + dt);
522
523 // Write the total energy to a CSV file
524 int save_i = 0;
525
526 std::unique_ptr<io::EnergyCSVWriter> energy_csv = nullptr;
527 std::unique_ptr<io::RuntimeStatsCSVWriter> stats_csv = nullptr;
528
529 if (save_stats)
530 {
531 logger().debug("Saving nl stats to {} and {}", resolve_output_path("energy.csv"), resolve_output_path("stats.csv"));
532 energy_csv = std::make_unique<io::EnergyCSVWriter>(resolve_output_path("energy.csv"), solve_data);
533 const io::OutputSpace space = output_space();
534 stats_csv = std::make_unique<io::RuntimeStatsCSVWriter>(
535 resolve_output_path("stats.csv"),
536 space_.n_bases,
537 space.mesh ? space.mesh->n_elements() : 0,
538 t0, dt);
539 }
540
541 // Save the initial solution
542 if (energy_csv)
543 energy_csv->write(save_i, sol);
544 save_timestep(t0, 0, t0, dt, sol);
545
546 save_i++;
547
548 for (int t = 1; t <= time_steps; ++t)
549 {
550 double forward_solve_time = 0, remeshing_time = 0, global_relaxation_time = 0;
551
552 {
553 POLYFEM_SCOPED_TIMER(forward_solve_time);
554 solve_tensor_nonlinear(t, sol, true);
555 }
556
557 // Always save the solution for consistency
558 if (energy_csv)
559 energy_csv->write(save_i, sol);
560 save_timestep(t0 + dt * t, t, t0, dt, sol);
561 save_i++;
562
563 {
564 POLYFEM_SCOPED_TIMER("Update quantities");
565
566 if (solve_data.time_integrator)
567 solve_data.time_integrator->update_quantities(sol);
568
569 solve_data.nl_problem->update_quantities(t0 + (t + 1) * dt, sol);
570
571 solve_data.update_dt();
572 solve_data.update_barrier_stiffness(sol);
573 }
574
575 logger().info("{}/{} t={}", t, time_steps, t0 + dt * t);
576 notify_time_step(t, time_steps, t0, dt);
577
578 save_elastic_step_state(t0, dt, t, solve_data.time_integrator.get());
579 if (stats_csv)
580 stats_csv->write(t, forward_solve_time, remeshing_time, global_relaxation_time);
581 }
582
583 timer.stop();
584 timings.solving_time = timer.getElapsedTime();
585 logger().info(" took {}s", timings.solving_time);
586 }
587
588 void NonlinearElasticVarForm::init_forms(const json &args, const int dim, Eigen::MatrixXd &sol, const double t)
589 {
590 damping_assembler = std::make_shared<assembler::ViscousDamping>();
591 set_materials(*damping_assembler, mesh_->dimension());
592
593 elasticity_pressure_assembler = build_pressure_assembler();
594
595 // for backward solve
596 damping_prev_assembler = std::make_shared<assembler::ViscousDampingPrev>();
597 set_materials(*damping_prev_assembler, mesh_->dimension());
598
599 const ElementInversionCheck check_inversion = args["solver"]["advanced"]["check_inversion"];
600
601 // NOTE: some stuff are legacy and hardcoded to be off
602 forms = solve_data.init_forms(
603 // General
604 units,
605 dim, t, space_.space_in_node_to_node,
606 // Elastic form
607 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,
608 args["solver"]["advanced"]["conservative_max_iter"],
609 // Body form
610 0, boundary_.boundary_nodes, boundary_.local_boundary,
611 boundary_.local_neumann_boundary,
612 elastic_boundary_samples(), rhs_, sol, mass_assembler_->density(),
613 // Pressure form
614 boundary_.local_pressure_boundary, boundary_.local_pressure_cavity, elasticity_pressure_assembler,
615 // Inertia form
616 args.value("/time/quasistatic"_json_pointer, true), mass_,
617 damping_assembler->is_valid() ? damping_assembler : nullptr,
618 // Lagged regularization form
619 args["solver"]["advanced"]["lagged_regularization_weight"],
620 args["solver"]["advanced"]["lagged_regularization_iterations"],
621 // Augmented lagrangian form
622 obstacle.ndof(), args["constraints"]["hard"], args["constraints"]["soft"], args["constraints"]["zero_mean"],
623 // Contact form
624 args["contact"]["enabled"], collision_mesh, args["contact"]["dhat"],
625 avg_mass_, args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_area_weighting"]) : false,
626 args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_improved_max_operator"]) : false,
627 args["contact"]["use_convergent_formulation"] ? bool(args["contact"]["use_physical_barrier"]) : false,
628 args["solver"]["contact"]["barrier_stiffness"],
629 args["solver"]["contact"]["initial_barrier_stiffness"],
630 args["solver"]["contact"]["CCD"]["broad_phase"],
631 args["solver"]["contact"]["CCD"]["tolerance"],
632 args["solver"]["contact"]["CCD"]["max_iterations"],
633 false,
634 // Smooth Contact Form
635 args["contact"]["use_gcp_formulation"],
636 args["contact"]["alpha_t"],
637 args["contact"]["alpha_n"],
638 args["contact"]["use_adaptive_dhat"],
639 args["contact"]["min_distance_ratio"],
640 // Normal Adhesion Form
641 args["contact"]["adhesion"]["adhesion_enabled"],
642 args["contact"]["adhesion"]["dhat_p"],
643 args["contact"]["adhesion"]["dhat_a"],
644 args["contact"]["adhesion"]["adhesion_strength"],
645 // Tangential Adhesion Form
646 args["contact"]["adhesion"]["tangential_adhesion_coefficient"],
647 args["contact"]["adhesion"]["epsa"],
648 args["solver"]["contact"]["tangential_adhesion_iterations"],
649 // Homogenization
651 // Periodic contact
652 false, Eigen::VectorXi(),
653 // Friction form
654 args["contact"]["friction_coefficient"],
655 args["contact"]["epsv"],
656 args["solver"]["contact"]["friction_iterations"],
657 // Rayleigh damping form
658 args["solver"]["rayleigh_damping"],
659
660 // BC AL lumping
661 args["solver"]["augmented_lagrangian"]["lumping"],
662
663 // Boundary-ID periodic constraints
664 mesh_.get(), &boundary_.total_local_boundary,
665 args["boundary_conditions"]["periodic"], /*fe_space_id=*/-1);
666
667 for (const auto &form : forms)
668 form->set_output_dir(output_path);
669
670 if (solve_data.contact_form != nullptr)
671 solve_data.contact_form->save_ccd_debug_meshes = args["output"]["advanced"]["save_ccd_debug_meshes"];
672 }
673
674 void NonlinearElasticVarForm::init_solve(Eigen::MatrixXd &sol, const double t)
675 {
676 init_solve_data(sol, t, "");
677
678 double characteristic_length = 0;
679 if (args["solver"]["advanced"]["characteristic_length"] > 0)
680 {
681 characteristic_length = args["solver"]["advanced"]["characteristic_length"];
682 }
683 else
684 {
685 RowVectorNd min, max;
686 mesh_->bounding_box(min, max);
687 characteristic_length = (max - min).norm();
688 }
689
690 double characteristic_force_density = 0;
691 if (args["solver"]["advanced"]["characteristic_force_density"] <= 0)
692 {
693 logger().warn("No user-specified force density was provided, defaulting to 10000.");
694 characteristic_force_density = 10000;
695 }
696 else
697 {
698 characteristic_force_density = args["solver"]["advanced"]["characteristic_force_density"];
699 }
700
701 const int ndof = space_.n_bases * mesh_->dimension();
702 solve_data.nl_problem = std::make_shared<solver::NLProblem>(
703 ndof, t, forms, solve_data.al_form,
704 polysolve::linear::Solver::create(args["solver"]["linear"], logger()),
705 characteristic_length, characteristic_force_density, pure_mass_, mesh_->dimension());
706 solve_data.nl_problem->init(sol);
707 solve_data.nl_problem->update_quantities(t, sol);
708
709 stats.solver_info = json::array();
710 }
711
712 void NonlinearElasticVarForm::init_solve_data(
713 Eigen::MatrixXd &sol, const double t, const std::string &state_prefix)
714 {
715 assert(sol.cols() == 1);
716 assert(!problem->is_scalar()); // tensor
717
718 // FIXME
719 // if (optimization_enabled != solver::CacheLevel::None)
720 // {
721 // if (initial_sol_update.size() == ndof())
722 // sol = initial_sol_update;
723 // else
724 // initial_sol_update = sol;
725 // }
726
727 // --------------------------------------------------------------------
728 // Check for initial intersections
729 if (args["contact"]["enabled"])
730 {
731 POLYFEM_SCOPED_TIMER("Check for initial intersections");
732
733 const Eigen::MatrixXd displaced = collision_mesh.displace_vertices(
734 utils::unflatten(sol, mesh_->dimension()));
735
736 if (ipc::has_intersections(collision_mesh, displaced, ipc::create_broad_phase(args["solver"]["contact"]["CCD"]["broad_phase"]).get()))
737 {
739 resolve_output_path("intersection.obj"), displaced,
740 collision_mesh.edges(), collision_mesh.faces());
741 log_and_throw_error("Unable to solve, initial solution has intersections!");
742 }
743 }
744
745 // --------------------------------------------------------------------
746
747 if (problem->is_time_dependent())
748 {
749 POLYFEM_SCOPED_TIMER("Initialize time integrator");
750 solve_data.time_integrator = ImplicitTimeIntegrator::construct_time_integrator(args["time"]["integrator"]);
751
752 Eigen::MatrixXd solution, velocity, acceleration;
753 initial_elastic_solution(solution, state_prefix); // Reload this because we need all previous solutions
754 solution.col(0) = sol; // Make sure the current solution is the same as `sol`
755 assert(solution.rows() == sol.size());
756 initial_velocity(velocity, state_prefix);
757 assert(velocity.rows() == sol.size());
758 initial_acceleration(acceleration, state_prefix);
759 assert(acceleration.rows() == sol.size());
760
761 solve_data.time_integrator->init(solution, velocity, acceleration, dt);
762 assert(solve_data.time_integrator != nullptr);
763 }
764 else
765 {
766 solve_data.time_integrator = nullptr;
767 }
768
769 // --------------------------------------------------------------------
770 // Initialize forms
771
772 // --------------------------------------------------------------------
773 // Initialize nonlinear problems
774
775 init_forms(args, mesh_->dimension(), sol, t);
776
777 if (pure_mass_.size() == 0)
778 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);
779 }
780
781 void NonlinearElasticVarForm::prepare_for_embedding()
782 {
783 prepare();
784 }
785
786 void NonlinearElasticVarForm::initial_solution_for_embedding(
787 Eigen::MatrixXd &solution, const std::string &state_prefix) const
788 {
789 initial_elastic_solution(solution, state_prefix);
790 if (solution.cols() > 1)
791 solution.conservativeResize(Eigen::NoChange, 1);
792 }
793
794 void NonlinearElasticVarForm::init_forms_for_embedding(
795 Eigen::MatrixXd &solution, const double t, const std::string &state_prefix)
796 {
797 prepare();
798 init_solve_data(solution, t, state_prefix);
799 }
800
801 void NonlinearElasticVarForm::advance_for_embedding(const Eigen::VectorXd &solution)
802 {
803 assert(solve_data.time_integrator);
804 solve_data.time_integrator->update_quantities(solution);
805 solve_data.update_dt();
806 }
807
808 void NonlinearElasticVarForm::update_barrier_stiffness_for_embedding(
809 const Eigen::VectorXd &solution)
810 {
811 solve_data.update_barrier_stiffness(solution);
812 }
813
814 bool NonlinearElasticVarForm::save_timestep_for_embedding(
815 const double time, const int step, const double dt,
816 const Eigen::MatrixXd &solution, paraviewo::VTMWriter &vtm,
817 const std::string &block_prefix) const
818 {
819 return save_timestep_to_vtm(time, step, dt, solution, vtm, block_prefix);
820 }
821
822 int NonlinearElasticVarForm::embedding_ndof() const
823 {
824 return mesh_ ? space_.n_bases * mesh_->dimension() : 0;
825 }
826
827 void NonlinearElasticVarForm::solve_tensor_nonlinear(int step, Eigen::MatrixXd &sol, const bool init_lagging)
828 {
829 assert(solve_data.nl_problem != nullptr);
830 solver::NLProblem &nl_problem = *(solve_data.nl_problem);
831
832 assert(sol.size() == rhs_.size());
833
834 if (nl_problem.uses_lagging())
835 {
836 if (init_lagging)
837 {
838 POLYFEM_SCOPED_TIMER("Initializing lagging");
839 nl_problem.init_lagging(sol);
840 }
841 logger().info("Lagging iteration 1:");
842 }
843
844 save_subsolve(0, step, sol);
845
846 std::shared_ptr<polysolve::nonlinear::Solver> nl_solver =
847 polysolve::nonlinear::Solver::create(args["solver"]["augmented_lagrangian"]["nonlinear"], args["solver"]["linear"], units.characteristic_length(), logger());
848
849 ALSolver al_solver(
850 solve_data.al_form,
851 args["solver"]["augmented_lagrangian"]["initial_weight"],
852 args["solver"]["augmented_lagrangian"]["scaling"],
853 args["solver"]["augmented_lagrangian"]["max_weight"],
854 args["solver"]["augmented_lagrangian"]["eta"],
855 [&](const Eigen::VectorXd &x) {
856 this->solve_data.update_barrier_stiffness(sol);
857 });
858
859 al_solver.post_subsolve = [&](const double al_weight) {
860 stats.solver_info.push_back(
861 {{"type", al_weight > 0 ? "al" : "rc"},
862 {"t", step},
863 {"info", nl_solver->info()}});
864 if (al_weight > 0)
865 stats.solver_info.back()["weight"] = al_weight;
866 save_subsolve(stats.solver_info.size(), step, sol);
867 };
868
869 Eigen::MatrixXd prev_sol = sol;
870 al_solver.solve_al(nl_problem, sol,
871 args["solver"]["augmented_lagrangian"]["nonlinear"], args["solver"]["linear"], units.characteristic_length());
872
873 al_solver.solve_reduced(nl_problem, sol,
874 args["solver"]["nonlinear"], args["solver"]["linear"], units.characteristic_length());
875
876 if (args["space"]["advanced"]["count_flipped_els_continuous"])
877 {
878 const auto invalidList = utils::count_invalid(mesh_->dimension(), space_.basis_list(), space_.geometry_basis_list(), sol);
879 logger().debug("Flipped elements (cnt {}) : {}", invalidList.size(), invalidList);
880 }
881
882 const double lagging_tol = args["solver"]["contact"].value("friction_convergence_tol", 1e-2) * units.characteristic_length();
883
884 bool lagging_converged = !nl_problem.uses_lagging();
885 for (int lag_i = 1; !lagging_converged; lag_i++)
886 {
887 Eigen::VectorXd tmp_sol = nl_problem.full_to_reduced(sol);
888
889 nl_problem.update_lagging(tmp_sol, lag_i);
890
891 Eigen::VectorXd grad;
892 nl_problem.gradient(tmp_sol, grad);
893 const double delta_x_norm = (prev_sol - sol).lpNorm<Eigen::Infinity>();
894 logger().debug("Lagging convergence grad_norm={:g} tol={:g} (||Δx||={:g})", grad.norm(), lagging_tol, delta_x_norm);
895 if (grad.norm() <= lagging_tol)
896 {
897 logger().info(
898 "Lagging converged in {:d} iteration(s) (grad_norm={:g} tol={:g})",
899 lag_i, grad.norm(), lagging_tol);
900 lagging_converged = true;
901 break;
902 }
903
904 if (delta_x_norm <= 1e-12)
905 {
906 logger().warn(
907 "Lagging produced tiny update between iterations {:d} and {:d} (grad_norm={:g} grad_tol={:g} ||Δx||={:g} Δx_tol={:g}); stopping early",
908 lag_i - 1, lag_i, grad.norm(), lagging_tol, delta_x_norm, 1e-6);
909 lagging_converged = false;
910 break;
911 }
912
913 if (lag_i >= nl_problem.max_lagging_iterations())
914 {
915 logger().warn(
916 "Lagging failed to converge with {:d} iteration(s) (grad_norm={:g} tol={:g})",
917 lag_i, grad.norm(), lagging_tol);
918 lagging_converged = false;
919 break;
920 }
921
922 logger().info("Lagging iteration {:d}:", lag_i + 1);
923 nl_problem.init(sol);
924 solve_data.update_barrier_stiffness(sol);
925 nl_problem.normalize_forms();
926 nl_solver->minimize(nl_problem, tmp_sol);
927 nl_problem.finish();
928 prev_sol = sol;
929 sol = nl_problem.reduced_to_full(tmp_sol);
930
931 stats.solver_info.push_back(
932 {{"type", "rc"},
933 {"t", step},
934 {"lag_i", lag_i},
935 {"info", nl_solver->info()}});
936 save_subsolve(stats.solver_info.size(), step, sol);
937 }
938 }
939
940} // namespace polyfem::varform
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 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:707
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:49
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
Definition Mesh.hpp:174
int dimension() const
utily for dimension
Definition Mesh.hpp:164
const Eigen::MatrixXi & e() const
Definition Obstacle.hpp:45
const Eigen::MatrixXi & f() const
Definition Obstacle.hpp:44
const Eigen::MatrixXd & v() const
Definition Obstacle.hpp:42
const Eigen::VectorXi & codim_v() const
Definition Obstacle.hpp:43
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
TVector reduced_to_full(const TVector &reduced) const
void update_lagging(const TVector &x, const int iter_num) override
class to store time stepping data
Definition SolveData.hpp:55
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
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.
std::shared_ptr< assembler::ViscousDamping > damping_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.
io::OutputSpace output_space() const override
Get the output space of the variational formulation, for output purposes.
std::shared_ptr< assembler::ViscousDampingPrev > damping_prev_assembler
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:195
virtual std::string name() const =0
Get the name of the variational formulation.
std::unique_ptr< mesh::Mesh > mesh_
Definition VarForm.hpp:207
bool write_matrix(const std::string &path, const Mat &mat)
Writes a matrix to a file. Determines the file format based on the path's extension.
Definition MatrixIO.cpp:42
void load_collision_proxy(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 files.
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)
Obstacle read_obstacle_geometry(const Units &units, const json &geometry, const std::vector< json > &displacements, const std::vector< json > &dirichlets, const std::string &root_path, const int dim, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read a FEM mesh from a geometry JSON
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:38
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
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