35#include <polysolve/linear/Solver.hpp>
36#include <polysolve/nonlinear/Solver.hpp>
44 using namespace solver;
45 using namespace time_integrator;
50 const bool contact_dhat_was_explicit = clean_args[
"contact"].value(
"_dhat_was_explicit",
false);
51 clean_args[
"contact"].erase(
"_dhat_was_explicit");
78 obstacle = std::move(loaded_obstacle);
91 const Eigen::MatrixXd &solution,
102 const int actual_dim =
problem->is_scalar() ? 1 :
mesh_->dimension();
103 const auto ¶view_options =
args[
"output"][
"paraview"][
"options"];
104 const bool explicit_fields = !options.
fields.empty();
106 const auto has_field = [&](
const std::string &
name) {
107 return std::any_of(fields.begin(), fields.end(), [&](
const io::OutputField &field) {
108 return field.association == io::OutputField::Association::Point && field.name == name;
112 const auto append_collision_dof_field = [&](
const std::string &
name,
const Eigen::MatrixXd &dof_values) {
113 if (has_field(
name) || dof_values.size() <= 0)
117 if (values.rows() == sample.
points.rows())
121 const auto append_collision_form_force = [&](
const std::string &name,
const std::shared_ptr<solver::Form> &form) {
122 if (!form || !form->enabled() || sample.points.rows() != collision_mesh_.rest_positions().rows())
125 Eigen::VectorXd force;
126 form->first_derivative(solution.col(0), force);
127 const double acceleration_scaling =
128 solve_data_.time_integrator ? solve_data_.time_integrator->acceleration_scaling() : 1;
129 force *= -1.0 / acceleration_scaling;
130 append_collision_dof_field(name, force);
133 if (paraview_options[
"forces"] && !problem->is_scalar())
135 const double s = solve_data_.time_integrator ? solve_data_.time_integrator->acceleration_scaling() : 1;
136 for (
const auto &[name, form] : solve_data_.named_forms())
138 const std::string field_name = name +
"_forces";
139 if (!options.export_field(field_name))
142 Eigen::VectorXd force;
143 if (form && form->enabled())
145 form->first_derivative(solution, force);
150 force.setZero(solution.size());
152 append_collision_dof_field(field_name, force);
156 if (options.export_field(
"gradient_of_elastic_potential") && solve_data_.elastic_form)
158 Eigen::VectorXd potential_grad;
159 solve_data_.elastic_form->first_derivative(solution, potential_grad);
160 append_collision_dof_field(
"gradient_of_elastic_potential", potential_grad);
163 if (options.export_field(
"gradient_of_contact_potential") && solve_data_.contact_form && solve_data_.contact_form->weight() > 0)
165 Eigen::VectorXd potential_grad;
166 solve_data_.contact_form->first_derivative(solution, potential_grad);
167 potential_grad *= -solve_data_.contact_form->barrier_stiffness() / solve_data_.contact_form->weight();
168 append_collision_dof_field(
"gradient_of_contact_potential", potential_grad);
171 if (options.export_field(
"displacement"))
172 append_collision_dof_field(
"displacement", solution);
173 if (options.export_field(
"solution"))
174 append_collision_dof_field(
"solution", solution);
176 if ((paraview_options[
"contact_forces"] || explicit_fields) && options.export_field(
"contact_forces"))
177 append_collision_form_force(
"contact_forces", solve_data_.contact_form);
178 if ((paraview_options[
"friction_forces"] || explicit_fields) && options.export_field(
"friction_forces"))
179 append_collision_form_force(
"friction_forces", solve_data_.friction_form);
180 if ((paraview_options[
"normal_adhesion_forces"] || explicit_fields) && options.export_field(
"normal_adhesion_forces"))
181 append_collision_form_force(
"normal_adhesion_forces", solve_data_.normal_adhesion_form);
182 if ((paraview_options[
"tangential_adhesion_forces"] || explicit_fields) && options.export_field(
"tangential_adhesion_forces"))
183 append_collision_form_force(
"tangential_adhesion_forces", solve_data_.tangential_adhesion_form);
186 && options.export_field(
"adaptive_dhat")
187 && args[
"contact"][
"use_gcp_formulation"]
188 && args[
"contact"][
"use_adaptive_dhat"])
190 const auto smooth_contact = std::dynamic_pointer_cast<solver::SmoothContactForm>(solve_data_.contact_form);
193 const auto &set = smooth_contact->collision_set();
196 Eigen::VectorXd dhats(collision_mesh_.num_edges());
197 for (
int e = 0;
e < dhats.size(); ++
e)
198 dhats(e) = set.get_edge_dhat(e);
203 Eigen::VectorXd dhats(collision_mesh_.num_faces());
204 for (
int f = 0;
f < dhats.size(); ++
f)
205 dhats(f) = set.get_face_dhat(f);
208 Eigen::VectorXd vertex_dhats(collision_mesh_.num_vertices());
209 for (
int v = 0; v < vertex_dhats.size(); ++v)
210 vertex_dhats(v) = set.get_vert_dhat(v);
219 void NonlinearElasticVarForm::build_basis(
mesh::Mesh &mesh,
const bool iso_parametric,
const json &args)
221 ElasticVarForm::build_basis(mesh, iso_parametric, args);
226 const int n_fe_bases =
space_.n_bases;
227 space_.n_bases += obstacle.n_vertices();
229 if (is_contact_enabled())
231 logger().info(
"Building collision mesh...");
232 build_collision_mesh(mesh, args);
233 preprocess_contact_parameters();
235 if (args[
"contact"][
"periodic"])
236 build_periodic_collision_mesh();
241 for (
int i = n_fe_bases; i <
space_.n_bases; ++i)
243 for (
int d = 0; d < mesh.
dimension(); ++d)
244 boundary_.boundary_nodes.push_back(i * mesh.
dimension() + d);
247 boundary_.normalize_boundary_nodes();
250 void NonlinearElasticVarForm::preprocess_contact_parameters()
252 if (!is_contact_enabled())
255 double min_boundary_edge_length = std::numeric_limits<double>::max();
256 for (
const auto &edge : collision_mesh_.edges().rowwise())
258 const VectorNd v0 = collision_mesh_.rest_positions().row(edge(0));
259 const VectorNd v1 = collision_mesh_.rest_positions().row(edge(1));
260 min_boundary_edge_length = std::min(min_boundary_edge_length, (v1 - v0).norm());
263 double dhat =
Units::convert(args[
"contact"][
"dhat"], units.length());
264 args[
"contact"][
"epsv"] =
Units::convert(args[
"contact"][
"epsv"], units.velocity());
266 if (!contact_dhat_was_explicit_
267 && std::isfinite(min_boundary_edge_length)
268 && dhat > min_boundary_edge_length)
270 dhat = args[
"contact"][
"dhat_percentage"].get<
double>() * min_boundary_edge_length;
271 logger().info(
"dhat set to {}", dhat);
273 else if (std::isfinite(min_boundary_edge_length) && dhat > min_boundary_edge_length)
275 logger().warn(
"dhat larger than min boundary edge, {} > {}", dhat, min_boundary_edge_length);
278 args[
"contact"][
"dhat"] = dhat;
281 void NonlinearElasticVarForm::build_rhs_assembler()
283 json rhs_solver_params = args[
"solver"][
"linear"];
284 if (!rhs_solver_params.contains(
"Pardiso"))
285 rhs_solver_params[
"Pardiso"] = {};
286 rhs_solver_params[
"Pardiso"][
"mtype"] = -2;
288 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
290 solve_data_.rhs_assembler = std::make_shared<assembler::RhsAssembler>(
291 *primary_assembler_, *mesh_, &obstacle,
292 boundary_.dirichlet_nodes, boundary_.neumann_nodes,
293 boundary_.dirichlet_nodes_position, boundary_.neumann_nodes_position,
294 space_.n_bases, size,
space_.basis_list(),
space_.geometry_basis_list(), mass_ass_vals_cache_, *problem, resources_,
295 args[
"space"][
"advanced"][
"bc_method"],
298 rhs_assembler_ = solve_data_.rhs_assembler;
301 void NonlinearElasticVarForm::build_collision_mesh(
305 build_collision_mesh(
306 mesh,
space_.n_bases,
space_.basis_list(),
space_.geometry_basis_list(), boundary_.total_local_boundary, obstacle,
308 space_.space_in_node_to_node, collision_mesh_);
311 void NonlinearElasticVarForm::build_collision_mesh(
314 const std::vector<basis::ElementBases> &bases,
315 const std::vector<basis::ElementBases> &geom_bases,
316 const std::vector<mesh::LocalBoundary> &total_local_boundary,
320 const Eigen::VectorXi &in_node_to_node,
321 ipc::CollisionMesh &collision_mesh_)
323 Eigen::MatrixXd collision_vertices;
324 Eigen::VectorXi collision_codim_vids;
325 Eigen::MatrixXi collision_edges, collision_triangles;
326 std::vector<Eigen::Triplet<double>> displacement_map_entries;
328 const auto extract_default_collision_mesh = [&]() {
329 if (args.at(
"/space/basis_type"_json_pointer) ==
"Spline")
332 mesh, n_bases - obstacle.
n_vertices(), bases, total_local_boundary,
333 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
338 mesh, n_bases - obstacle.
n_vertices(), bases, total_local_boundary,
339 collision_vertices, collision_edges, collision_triangles, displacement_map_entries);
343 if (args.contains(
"/contact/collision_mesh"_json_pointer)
344 && args.at(
"/contact/collision_mesh/enabled"_json_pointer).get<
bool>())
346 const json collision_mesh_args = args.at(
"/contact/collision_mesh"_json_pointer);
347 if (collision_mesh_args.contains(
"linear_map"))
349 assert(displacement_map_entries.empty());
350 assert(collision_mesh_args.contains(
"mesh"));
355 collision_mesh_args[
"mesh"].get<std::string>(),
356 collision_mesh_args[
"linear_map"].get<std::string>(),
357 in_node_to_node, transformation, collision_vertices, collision_codim_vids,
358 collision_edges, collision_triangles, displacement_map_entries);
360 else if (collision_mesh_args.contains(
"tessellation_type")
361 && collision_mesh_args[
"tessellation_type"] ==
"max_order")
364 mesh, n_bases - obstacle.
n_vertices(), bases, total_local_boundary,
365 collision_vertices, collision_edges, collision_triangles, displacement_map_entries,
366 utils::json_value<int>(collision_mesh_args,
"sampling_order", 0));
368 else if (collision_mesh_args.contains(
"max_edge_length"))
371 "Building collision proxy with max edge length={} ...",
372 collision_mesh_args[
"max_edge_length"].get<double>());
376 bases, geom_bases, total_local_boundary, n_bases, mesh.
dimension(),
377 collision_mesh_args[
"max_edge_length"], collision_vertices,
378 collision_triangles, displacement_map_entries,
379 collision_mesh_args[
"tessellation_type"]);
380 if (collision_triangles.size())
381 igl::edges(collision_triangles, collision_edges);
383 logger().debug(fmt::format(
384 std::locale(
"en_US.UTF-8"),
385 "Done (took {:g}s, {:L} vertices, {:L} triangles)",
386 timer.getElapsedTime(),
387 collision_vertices.rows(), collision_triangles.rows()));
391 extract_default_collision_mesh();
396 extract_default_collision_mesh();
399 std::vector<bool> is_orientable_vertex(collision_vertices.rows(),
true);
402 const int num_fe_nodes = n_bases - obstacle.
n_vertices();
403 const int num_fe_collision_vertices = collision_vertices.rows();
404 assert(collision_edges.size() == 0 || collision_edges.maxCoeff() < num_fe_collision_vertices);
405 assert(collision_triangles.size() == 0 || collision_triangles.maxCoeff() < num_fe_collision_vertices);
415 for (
int i = 0; i < obstacle.
n_vertices(); i++)
417 is_orientable_vertex.push_back(
false);
420 if (!displacement_map_entries.empty())
422 displacement_map_entries.reserve(displacement_map_entries.size() + obstacle.
n_vertices());
423 for (
int i = 0; i < obstacle.
n_vertices(); i++)
425 displacement_map_entries.emplace_back(num_fe_collision_vertices + i, num_fe_nodes + i, 1.0);
430 std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(
431 collision_vertices.rows(), collision_edges);
432 for (
const int vid : collision_codim_vids)
434 is_on_surface[vid] =
true;
437 Eigen::SparseMatrix<double> displacement_map;
438 if (!displacement_map_entries.empty())
440 displacement_map.resize(collision_vertices.rows(), n_bases);
441 displacement_map.setFromTriplets(displacement_map_entries.begin(), displacement_map_entries.end());
444 collision_mesh_ = ipc::CollisionMesh(
445 is_on_surface, is_orientable_vertex, collision_vertices, collision_edges, collision_triangles,
448 collision_mesh_.can_collide = [&collision_mesh_, num_fe_collision_vertices](
size_t vi,
size_t vj) {
450 return collision_mesh_.to_full_vertex_id(vi) < num_fe_collision_vertices
451 || collision_mesh_.to_full_vertex_id(vj) < num_fe_collision_vertices;
454 collision_mesh_.init_area_jacobians();
457 void NonlinearElasticVarForm::build_periodic_collision_mesh()
459 assert(!mesh_->is_volume());
460 const int dim = mesh_->dimension();
461 const int n_tiles = 2;
463 if (mesh_->dimension() != 2)
465 if (obstacle.n_vertices() != 0)
468 const int n_bases =
space_.n_bases;
469 const json &conditions = args[
"boundary_conditions"][
"periodic"];
470 Eigen::VectorXi periodic_dof_mask = Eigen::VectorXi::Zero(n_bases);
471 Eigen::MatrixXd periodic_tile_offsets(dim, conditions.size());
472 for (
int i = 0; i < int(conditions.size()); ++i)
474 const json &condition = conditions[i];
475 const std::array<int, 2> boundary_ids = {{condition[
"boundary_ids"][0].get<
int>(),
476 condition[
"boundary_ids"][1].get<int>()}};
478 n_bases * dim, dim, *mesh_,
space_.basis_list(), boundary_.total_local_boundary,
479 boundary_ids, condition.value(
"tolerance", 1e-5));
480 periodic_tile_offsets.col(i) = mapping.translation.transpose();
481 for (
const int dof : mapping.boundary_dofs)
482 periodic_dof_mask(dof) = 1;
485 Eigen::MatrixXd
V(n_bases, dim);
486 for (
const auto &bs :
space_.basis_list())
487 for (
const auto &b : bs.bases)
488 for (
const auto &g : b.global())
489 V.row(g.index) = g.node;
491 Eigen::MatrixXi E = collision_mesh_.edges();
492 for (
int i = 0; i < E.size(); i++)
494 E(i) = collision_mesh_.to_full_vertex_id(E(i));
495 if (E(i) < 0 || E(i) >= n_bases)
496 log_and_throw_error(
"Periodic contact requires collision vertices to map to FE basis nodes.");
499 Eigen::MatrixXd bbox(
V.cols(), 2);
500 bbox.col(0) =
V.colwise().minCoeff();
501 bbox.col(1) =
V.colwise().maxCoeff();
505 std::vector<int> ind;
506 for (
int i = 0; i < E.rows(); i++)
508 if (!periodic_dof_mask(E(i, 0)) || !periodic_dof_mask(E(i, 1)))
512 E = E(ind, Eigen::all).eval();
515 Eigen::MatrixXd Vtmp, Vnew;
516 Eigen::MatrixXi Etmp, Enew;
517 Vtmp.setZero(
V.rows() * n_tiles * n_tiles,
V.cols());
518 Etmp.setZero(E.rows() * n_tiles * n_tiles, E.cols());
520 if (periodic_tile_offsets.rows() != dim || periodic_tile_offsets.cols() != dim
521 || Eigen::FullPivLU<Eigen::MatrixXd>(periodic_tile_offsets).rank() != dim)
522 log_and_throw_error(
"Periodic contact requires {} linearly independent periodic boundary pairs", dim);
523 const Eigen::MatrixXd &tile_offset = periodic_tile_offsets;
525 for (
int i = 0, idx = 0; i < n_tiles; i++)
527 for (
int j = 0; j < n_tiles; j++)
529 Eigen::Vector2d block_id;
532 Vtmp.middleRows(idx *
V.rows(),
V.rows()) =
V;
533 for (
int vid = 0; vid <
V.rows(); vid++)
534 Vtmp.block(idx *
V.rows() + vid, 0, 1, 2) += (tile_offset * block_id).transpose();
536 Etmp.middleRows(idx * E.rows(), E.rows()) = E.array() + idx *
V.rows();
542 Eigen::VectorXi indices;
544 std::vector<int> tmp;
545 for (
int i = 0; i <
V.rows(); i++)
547 if (periodic_dof_mask(i))
551 indices.resize(tmp.size() * n_tiles * n_tiles);
552 for (
int i = 0; i < n_tiles * n_tiles; i++)
554 indices.segment(i * tmp.size(), tmp.size()) = Eigen::Map<Eigen::VectorXi, Eigen::Unaligned>(tmp.data(), tmp.size());
555 indices.segment(i * tmp.size(), tmp.size()).array() += i *
V.rows();
559 Eigen::VectorXi potentially_duplicate_mask(Vtmp.rows());
560 potentially_duplicate_mask.setZero();
561 potentially_duplicate_mask(indices).array() = 1;
563 Eigen::MatrixXd candidates = Vtmp(indices, Eigen::all);
566 std::vector<int> SVJ;
567 SVI.setConstant(Vtmp.rows(), -1);
569 double relative_tolerance = 1e-5;
570 for (
const json &condition : conditions)
571 relative_tolerance = std::max(relative_tolerance, condition.value(
"tolerance", 1e-5));
572 const double eps = (bbox.col(1) - bbox.col(0)).maxCoeff() * relative_tolerance;
573 for (
int i = 0; i < Vtmp.rows(); i++)
579 if (potentially_duplicate_mask(i))
581 Eigen::VectorXd diffs = (candidates.rowwise() - Vtmp.row(i)).rowwise().norm();
582 for (
int j = 0; j < diffs.size(); j++)
584 SVI[indices[j]] =
id;
590 Vnew = Vtmp(SVJ, Eigen::all);
592 Enew.resizeLike(Etmp);
593 for (
int d = 0; d < Etmp.cols(); d++)
594 Enew.col(d) = SVI(Etmp.col(d));
596 std::vector<bool> is_on_surface = ipc::CollisionMesh::construct_is_on_surface(Vnew.rows(), Enew);
598 Eigen::MatrixXi boundary_triangles;
599 Eigen::SparseMatrix<double> displacement_map;
600 periodic_collision_mesh_ = ipc::CollisionMesh(is_on_surface,
601 std::vector<bool>(Vnew.rows(),
false),
607 periodic_collision_mesh_.init_area_jacobians();
609 periodic_collision_mesh_to_basis_.setConstant(Vnew.rows(), -1);
610 for (
int i = 0; i <
V.rows(); i++)
611 for (
int j = 0; j < n_tiles * n_tiles; j++)
612 periodic_collision_mesh_to_basis_(SVI[j *
V.rows() + i]) = i;
614 if (periodic_collision_mesh_to_basis_.maxCoeff() + 1 !=
V.rows())
618 std::shared_ptr<assembler::PressureAssembler> NonlinearElasticVarForm::build_pressure_assembler()
const
620 const int size = problem->is_scalar() ? 1 : mesh_->dimension();
622 return std::make_shared<assembler::PressureAssembler>(
623 *primary_assembler_, *mesh_, obstacle,
624 boundary_.local_pressure_boundary,
625 boundary_.local_pressure_cavity,
626 boundary_.boundary_nodes,
627 elastic_primitive_to_node(), elastic_node_to_primitive(),
631 void NonlinearElasticStaticVarForm::solve_problem(
632 Eigen::MatrixXd &sol,
636 assert((!initial_condition_override || (initial_condition_override->
velocity.size() == 0 && initial_condition_override->
acceleration.size() == 0))
637 &&
"Static elasticity does not accept initial velocity or acceleration overrides");
639 stats.spectrum.setZero();
643 logger().info(
"Solving {}", primary_assembler_->name());
648 if (initial_condition_override && initial_condition_override->
solution.size() != 0)
649 initial_solution(sol, initial_condition_override);
650 else if (sol.size() <= 0)
651 initial_solution(sol, initial_condition_override);
653 if (initial_condition_override && initial_condition_override->
solution.size() != 0)
654 assert(sol.cols() == 1 &&
"Static initial solution override must have exactly one column");
655 else if (sol.cols() != 1)
658 init_solve(sol, 1.0, initial_condition_override);
660 solve_tensor_nonlinear(0, sol,
true);
665 timings.solving_time = timer.getElapsedTime();
666 logger().info(
" took {}s", timings.solving_time);
669 void NonlinearElasticTransientVarForm::solve_problem(
670 Eigen::MatrixXd &sol,
674 const bool save_stats = args[
"output"][
"stats"];
675 stats.spectrum.setZero();
679 logger().info(
"Solving {}", primary_assembler_->name());
684 if (initial_condition_override && initial_condition_override->
solution.size() != 0)
685 initial_solution(sol, initial_condition_override);
686 else if (sol.size() <= 0)
687 initial_solution(sol, initial_condition_override);
690 sol.conservativeResize(Eigen::NoChange, 1);
692 init_solve(sol, t0 + dt, initial_condition_override);
699 std::unique_ptr<io::EnergyCSVWriter> energy_csv =
nullptr;
700 std::unique_ptr<io::RuntimeStatsCSVWriter> stats_csv =
nullptr;
704 logger().debug(
"Saving nl stats to {} and {}", resolve_output_path(
"energy.csv"), resolve_output_path(
"stats.csv"));
705 energy_csv = std::make_unique<io::EnergyCSVWriter>(resolve_output_path(
"energy.csv"), solve_data_);
707 stats_csv = std::make_unique<io::RuntimeStatsCSVWriter>(
708 resolve_output_path(
"stats.csv"),
716 energy_csv->write(save_i, sol);
717 save_timestep(t0, 0, t0, dt, sol);
721 for (
int t = 1; t <= time_steps; ++t)
723 double forward_solve_time = 0, remeshing_time = 0, global_relaxation_time = 0;
727 solve_tensor_nonlinear(t, sol,
true);
734 energy_csv->write(save_i, sol);
735 save_timestep(t0 + dt * t, t, t0, dt, sol);
741 if (solve_data_.time_integrator)
742 solve_data_.time_integrator->update_quantities(sol);
744 solve_data_.nl_problem->update_quantities(t0 + (t + 1) * dt, sol);
746 solve_data_.update_dt();
747 solve_data_.update_barrier_stiffness(sol);
750 logger().info(
"{}/{} t={}", t, time_steps, t0 + dt * t);
751 notify_time_step(t, time_steps, t0, dt);
753 save_elastic_step_state(t0, dt, t, sol, solve_data_.time_integrator.get());
755 stats_csv->write(t, forward_solve_time, remeshing_time, global_relaxation_time);
759 timings.solving_time = timer.getElapsedTime();
760 logger().info(
" took {}s", timings.solving_time);
763 void NonlinearElasticVarForm::init_forms(
const json &args,
const int dim, Eigen::MatrixXd &sol,
const double t)
765 damping_assembler_ = std::make_shared<assembler::ViscousDamping>();
766 set_materials(*damping_assembler_, mesh_->dimension());
768 elasticity_pressure_assembler = build_pressure_assembler();
771 damping_prev_assembler_ = std::make_shared<assembler::ViscousDampingPrev>();
772 set_materials(*damping_prev_assembler_, mesh_->dimension());
777 forms = solve_data_.init_forms(
781 dim, t,
space_.space_in_node_to_node,
783 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,
784 args[
"solver"][
"advanced"][
"conservative_max_iter"],
786 0, boundary_.boundary_nodes, boundary_.local_boundary,
787 boundary_.local_neumann_boundary,
788 elastic_boundary_samples(), rhs_, sol, mass_assembler_->density(),
790 boundary_.local_pressure_boundary, boundary_.local_pressure_cavity, elasticity_pressure_assembler,
792 args.value(
"/time/quasistatic"_json_pointer,
true), mass_,
793 damping_assembler_->is_valid() ? damping_assembler_ :
nullptr,
795 args[
"solver"][
"advanced"][
"lagged_regularization_weight"],
796 args[
"solver"][
"advanced"][
"lagged_regularization_iterations"],
798 obstacle.ndof(), args[
"constraints"][
"hard"], args[
"constraints"][
"soft"], args[
"constraints"][
"zero_mean"],
800 args[
"contact"][
"enabled"], collision_mesh_, args[
"contact"][
"dhat"],
801 avg_mass_, args[
"contact"][
"use_convergent_formulation"] ? bool(args[
"contact"][
"use_area_weighting"]) :
false,
802 args[
"contact"][
"use_convergent_formulation"] ? bool(args[
"contact"][
"use_improved_max_operator"]) :
false,
803 args[
"contact"][
"use_convergent_formulation"] ? bool(args[
"contact"][
"use_physical_barrier"]) :
false,
804 args[
"solver"][
"contact"][
"barrier_stiffness"],
805 args[
"solver"][
"contact"][
"initial_barrier_stiffness"],
806 args[
"solver"][
"contact"][
"CCD"][
"broad_phase"],
807 args[
"solver"][
"contact"][
"CCD"][
"tolerance"],
808 args[
"solver"][
"contact"][
"CCD"][
"max_iterations"],
811 args[
"contact"][
"use_gcp_formulation"],
812 args[
"contact"][
"alpha_t"],
813 args[
"contact"][
"alpha_n"],
814 args[
"contact"][
"use_adaptive_dhat"],
815 args[
"contact"][
"min_distance_ratio"],
817 args[
"contact"][
"adhesion"][
"adhesion_enabled"],
818 args[
"contact"][
"adhesion"][
"dhat_p"],
819 args[
"contact"][
"adhesion"][
"dhat_a"],
820 args[
"contact"][
"adhesion"][
"adhesion_strength"],
822 args[
"contact"][
"adhesion"][
"tangential_adhesion_coefficient"],
823 args[
"contact"][
"adhesion"][
"epsa"],
824 args[
"solver"][
"contact"][
"tangential_adhesion_iterations"],
828 false, Eigen::VectorXi(),
830 args[
"contact"][
"friction_coefficient"],
831 args[
"contact"][
"epsv"],
832 args[
"solver"][
"contact"][
"friction_iterations"],
834 args[
"solver"][
"rayleigh_damping"],
837 args[
"solver"][
"augmented_lagrangian"][
"lumping"],
840 mesh_.get(), &boundary_.total_local_boundary,
841 args[
"boundary_conditions"][
"periodic"], -1);
843 for (
const auto &form : forms)
844 form->set_output_dir(output_path);
846 if (solve_data_.contact_form !=
nullptr)
847 solve_data_.contact_form->save_ccd_debug_meshes = args[
"output"][
"advanced"][
"save_ccd_debug_meshes"];
850 void NonlinearElasticVarForm::init_solve(
851 Eigen::MatrixXd &sol,
855 init_solve_data(sol, t,
"", initial_condition_override);
857 double characteristic_length = 0;
858 if (args[
"solver"][
"advanced"][
"characteristic_length"] > 0)
860 characteristic_length = args[
"solver"][
"advanced"][
"characteristic_length"];
865 mesh_->bounding_box(min, max);
866 characteristic_length = (max - min).norm();
869 double characteristic_force_density = 0;
870 if (args[
"solver"][
"advanced"][
"characteristic_force_density"] <= 0)
872 logger().warn(
"No user-specified force density was provided, defaulting to 10000.");
873 characteristic_force_density = 10000;
877 characteristic_force_density = args[
"solver"][
"advanced"][
"characteristic_force_density"];
880 const int ndof =
space_.n_bases * mesh_->dimension();
881 solve_data_.nl_problem = std::make_shared<solver::NLProblem>(
882 ndof, t, forms, solve_data_.al_form,
883 polysolve::linear::Solver::create(args[
"solver"][
"linear"],
logger()),
884 characteristic_length, characteristic_force_density, pure_mass_, mesh_->dimension());
885 solve_data_.nl_problem->init(sol);
886 solve_data_.nl_problem->update_quantities(t, sol);
888 stats.solver_info = json::array();
891 void NonlinearElasticVarForm::init_solve_data(
892 Eigen::MatrixXd &sol,
894 const std::string &state_prefix,
897 assert(sol.cols() == 1);
898 assert(!problem->is_scalar());
911 if (args[
"contact"][
"enabled"])
915 const Eigen::MatrixXd displaced = collision_mesh_.displace_vertices(
918 if (ipc::has_intersections(collision_mesh_, displaced, ipc::create_broad_phase(args[
"solver"][
"contact"][
"CCD"][
"broad_phase"]).get()))
921 resolve_output_path(
"intersection.obj"), displaced,
922 collision_mesh_.edges(), collision_mesh_.faces());
929 if (problem->is_time_dependent())
932 solve_data_.time_integrator = ImplicitTimeIntegrator::construct_time_integrator(args[
"time"][
"integrator"]);
934 Eigen::MatrixXd solution, velocity, acceleration;
935 initial_solution(solution, initial_condition_override, state_prefix);
936 solution.col(0) = sol;
937 assert(solution.rows() == sol.size());
938 initial_velocity(velocity, initial_condition_override, state_prefix);
939 assert(velocity.rows() == sol.size());
940 initial_acceleration(acceleration, initial_condition_override, state_prefix);
941 assert(acceleration.rows() == sol.size());
942 if (solution.cols() != velocity.cols() || solution.cols() != acceleration.cols())
945 "Incompatible initial-condition history for transient solve: "
946 "solution has {} columns, velocity has {}, acceleration has {}.",
947 solution.cols(), velocity.cols(), acceleration.cols());
950 solve_data_.time_integrator->init(solution, velocity, acceleration, dt);
951 restore_checkpoint_integrator(
952 solve_data_.time_integrator,
"/checkpoint/state/primary_integrator", dt,
953 "primary",
space_, mesh_->dimension());
954 assert(solve_data_.time_integrator !=
nullptr &&
"Transient nonlinear elasticity requires an initialized time integrator");
958 solve_data_.time_integrator =
nullptr;
967 init_forms(args, mesh_->dimension(), sol, t);
969 if (pure_mass_.size() == 0)
970 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);
973 void NonlinearElasticVarForm::prepare_for_embedding()
978 void NonlinearElasticVarForm::initial_solution_for_embedding(
979 Eigen::MatrixXd &solution,
const std::string &state_prefix)
const
981 initial_solution(solution,
nullptr, state_prefix);
982 if (solution.cols() > 1)
983 solution.conservativeResize(Eigen::NoChange, 1);
986 void NonlinearElasticVarForm::init_forms_for_embedding(
987 Eigen::MatrixXd &solution,
const double t,
const std::string &state_prefix)
990 init_solve_data(solution, t, state_prefix);
993 void NonlinearElasticVarForm::advance_for_embedding(
const Eigen::VectorXd &solution)
995 assert(solve_data_.time_integrator);
996 solve_data_.time_integrator->update_quantities(solution);
997 solve_data_.update_dt();
1000 void NonlinearElasticVarForm::update_barrier_stiffness_for_embedding(
1001 const Eigen::VectorXd &solution)
1003 solve_data_.update_barrier_stiffness(solution);
1006 bool NonlinearElasticVarForm::save_timestep_for_embedding(
1007 const double time,
const int step,
const double dt,
1008 const Eigen::MatrixXd &solution, paraviewo::VTMWriter &vtm,
1009 const std::string &block_prefix)
const
1011 return save_timestep_to_vtm(time, step, dt, solution, vtm, block_prefix);
1014 int NonlinearElasticVarForm::embedding_ndof()
const
1016 return mesh_ ?
space_.n_bases * mesh_->dimension() : 0;
1019 void NonlinearElasticVarForm::solve_tensor_nonlinear(
1021 Eigen::MatrixXd &sol,
1022 const bool init_lagging)
1024 assert(solve_data_.nl_problem !=
nullptr &&
"Nonlinear forms must initialize the nonlinear problem before solving");
1027 assert(sol.size() == rhs_.size());
1036 logger().info(
"Lagging iteration 1:");
1039 save_subsolve(0, step, sol);
1041 std::shared_ptr<polysolve::nonlinear::Solver> nl_solver =
1042 polysolve::nonlinear::Solver::create(args[
"solver"][
"augmented_lagrangian"][
"nonlinear"], args[
"solver"][
"linear"], units.characteristic_length(),
logger());
1045 solve_data_.al_form,
1046 args[
"solver"][
"augmented_lagrangian"][
"initial_weight"],
1047 args[
"solver"][
"augmented_lagrangian"][
"scaling"],
1048 args[
"solver"][
"augmented_lagrangian"][
"max_weight"],
1049 args[
"solver"][
"augmented_lagrangian"][
"eta"],
1050 [&](
const Eigen::VectorXd &
x) {
1051 this->solve_data_.update_barrier_stiffness(sol);
1055 stats.solver_info.push_back(
1056 {{
"type", al_weight > 0 ?
"al" :
"rc"},
1058 {
"info", nl_solver->info()}});
1060 stats.solver_info.back()[
"weight"] = al_weight;
1061 save_subsolve(stats.solver_info.size(), step, sol);
1064 Eigen::MatrixXd prev_sol = sol;
1065 al_solver.
solve_al(nl_problem, sol,
1066 args[
"solver"][
"augmented_lagrangian"][
"nonlinear"], args[
"solver"][
"linear"], units.characteristic_length());
1069 args[
"solver"][
"nonlinear"], args[
"solver"][
"linear"], units.characteristic_length());
1071 if (args[
"space"][
"advanced"][
"count_flipped_els_continuous"])
1074 logger().debug(
"Flipped elements (cnt {}) : {}", invalidList.size(), invalidList);
1077 const double lagging_tol = args[
"solver"][
"contact"].value(
"friction_convergence_tol", 1e-2) * units.characteristic_length();
1080 for (
int lag_i = 1; !lagging_converged; lag_i++)
1086 Eigen::VectorXd grad;
1087 nl_problem.
gradient(tmp_sol, grad);
1088 const double delta_x_norm = (prev_sol - sol).lpNorm<Eigen::Infinity>();
1089 logger().debug(
"Lagging convergence grad_norm={:g} tol={:g} (||Δx||={:g})", grad.norm(), lagging_tol, delta_x_norm);
1090 if (grad.norm() <= lagging_tol)
1093 "Lagging converged in {:d} iteration(s) (grad_norm={:g} tol={:g})",
1094 lag_i, grad.norm(), lagging_tol);
1095 lagging_converged =
true;
1099 if (delta_x_norm <= 1e-12)
1102 "Lagging produced tiny update between iterations {:d} and {:d} (grad_norm={:g} grad_tol={:g} ||Δx||={:g} Δx_tol={:g}); stopping early",
1103 lag_i - 1, lag_i, grad.norm(), lagging_tol, delta_x_norm, 1e-6);
1104 lagging_converged =
false;
1111 "Lagging failed to converge with {:d} iteration(s) (grad_norm={:g} tol={:g})",
1112 lag_i, grad.norm(), lagging_tol);
1113 lagging_converged =
false;
1117 logger().info(
"Lagging iteration {:d}:", lag_i + 1);
1118 nl_problem.
init(sol);
1119 solve_data_.update_barrier_stiffness(sol);
1121 nl_solver->minimize(nl_problem, tmp_sol);
1126 stats.solver_info.push_back(
1130 {
"info", nl_solver->info()}});
1131 save_subsolve(stats.solver_info.size(), step, sol);
std::array< Matrix< int, 3, 3 >, 3 > space_
#define POLYFEM_SCOPED_TIMER(...)
static double convert(const json &val, const std::string &unit_type)
static bool write(const std::string &path, const Eigen::MatrixXd &v, const Eigen::MatrixXi &e, const Eigen::MatrixXi &f)
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 ...
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
Read-only interface to a hierarchical collection of simulation resources.
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
int n_elements() const
utitlity to return the number of elements, cells or faces in 3d and 2d
int dimension() const
utily for dimension
const Eigen::MatrixXi & e() const
const Eigen::MatrixXi & f() const
const Eigen::MatrixXd & v() const
const Eigen::VectorXi & codim_v() const
void solve_reduced(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
std::function< void(const double)> post_subsolve
void solve_al(NLProblem &nl_problem, Eigen::MatrixXd &sol, std::shared_ptr< polysolve::nonlinear::Solver > nl_solver)
bool uses_lagging() const
int max_lagging_iterations() const
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
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
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.
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)
spdlog::logger & logger()
Retrieves the current logger.
Eigen::Matrix< double, Eigen::Dynamic, 1, 0, 3, 1 > VectorNd
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
void log_and_throw_error(const std::string &msg)
std::vector< std::string > fields