PolyFEM
Loading...
Searching...
No Matches
ContactForm.cpp
Go to the documentation of this file.
1#include "ContactForm.hpp"
2
10
12
13#include <ipc/barrier/adaptive_stiffness.hpp>
14#include <ipc/utils/world_bbox_diagonal_length.hpp>
15
16#include <igl/writePLY.h>
17
18namespace polyfem::solver
19{
20 ContactForm::ContactForm(const ipc::CollisionMesh &collision_mesh,
21 const double dhat,
22 const double avg_mass,
23 const bool use_convergent_formulation,
24 const bool use_adaptive_barrier_stiffness,
25 const bool is_time_dependent,
26 const bool enable_shape_derivatives,
27 const ipc::BroadPhaseMethod broad_phase_method,
28 const double ccd_tolerance,
29 const int ccd_max_iterations)
30 : collision_mesh_(collision_mesh),
31 dhat_(dhat),
32 use_adaptive_barrier_stiffness_(use_adaptive_barrier_stiffness),
33 avg_mass_(avg_mass),
34 is_time_dependent_(is_time_dependent),
35 enable_shape_derivatives_(enable_shape_derivatives),
36 broad_phase_method_(broad_phase_method),
37 ccd_tolerance_(ccd_tolerance),
38 ccd_max_iterations_(ccd_max_iterations),
39 barrier_potential_(dhat)
40 {
41 assert(dhat_ > 0);
42 assert(ccd_tolerance > 0);
43
44 prev_distance_ = -1;
45 collision_set_.set_use_convergent_formulation(use_convergent_formulation);
46 collision_set_.set_are_shape_derivatives_enabled(enable_shape_derivatives);
47 }
48
49 void ContactForm::init(const Eigen::VectorXd &x)
50 {
52 }
53
54 void ContactForm::force_shape_derivative(const ipc::Collisions &collision_set, const Eigen::MatrixXd &solution, const Eigen::VectorXd &adjoint_sol, Eigen::VectorXd &term)
55 {
56 // Eigen::MatrixXd U = collision_mesh_.vertices(utils::unflatten(solution, collision_mesh_.dim()));
57 // Eigen::MatrixXd X = collision_mesh_.vertices(boundary_nodes_pos_);
58 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(solution);
59
60 StiffnessMatrix dq_h = collision_mesh_.to_full_dof(barrier_potential_.shape_derivative(collision_set, collision_mesh_, displaced_surface));
61 term = barrier_stiffness() * dq_h.transpose() * adjoint_sol;
62 }
63
64 void ContactForm::update_quantities(const double t, const Eigen::VectorXd &x)
65 {
67 }
68
69 Eigen::MatrixXd ContactForm::compute_displaced_surface(const Eigen::VectorXd &x) const
70 {
71 return collision_mesh_.displace_vertices(utils::unflatten(x, collision_mesh_.dim()));
72 }
73
74 void ContactForm::update_barrier_stiffness(const Eigen::VectorXd &x, const Eigen::MatrixXd &grad_energy)
75 {
77 return;
78
79 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(x);
80
81 // The adative stiffness is designed for the non-convergent formulation,
82 // so we need to compute the gradient of the non-convergent barrier.
83 // After we can map it to a good value for the convergent formulation.
84 ipc::Collisions nonconvergent_constraints;
85 nonconvergent_constraints.set_use_convergent_formulation(false);
86 nonconvergent_constraints.build(
87 collision_mesh_, displaced_surface, dhat_, dmin_, broad_phase_method_);
88 Eigen::VectorXd grad_barrier = barrier_potential_.gradient(
89 nonconvergent_constraints, collision_mesh_, displaced_surface);
90 grad_barrier = collision_mesh_.to_full_dof(grad_barrier);
91
92 barrier_stiffness_ = ipc::initial_barrier_stiffness(
93 ipc::world_bbox_diagonal_length(displaced_surface), barrier_potential_.barrier(), dhat_, avg_mass_,
94 grad_energy, grad_barrier, max_barrier_stiffness_);
95
97 {
98 double scaling_factor = 0;
99 if (!nonconvergent_constraints.empty())
100 {
101 const double nonconvergent_potential = barrier_potential_(
102 nonconvergent_constraints, collision_mesh_, displaced_surface);
103
104 update_collision_set(displaced_surface);
105 const double convergent_potential = barrier_potential_(
106 collision_set_, collision_mesh_, displaced_surface);
107
108 scaling_factor = nonconvergent_potential / convergent_potential;
109 }
110 else
111 {
112 // Hardcoded difference between the non-convergent and convergent barrier
113 scaling_factor = dhat_ * std::pow(dhat_ + 2 * dmin_, 2);
114 }
115 barrier_stiffness_ *= scaling_factor;
116 max_barrier_stiffness_ *= scaling_factor;
117 }
118
119 // The barrier stiffness is choosen based on including the acceleration scaling,
120 // but the acceleration scaling will be applied later. Therefore, we need to remove it.
123
124 logger().debug(
125 "Setting adaptive barrier stiffness to {} (max barrier stiffness: {})",
127 }
128
129 void ContactForm::update_collision_set(const Eigen::MatrixXd &displaced_surface)
130 {
131 // Store the previous value used to compute the constraint set to avoid duplicate computation.
132 static Eigen::MatrixXd cached_displaced_surface;
133 if (cached_displaced_surface.size() == displaced_surface.size() && cached_displaced_surface == displaced_surface)
134 return;
135
137 collision_set_.build(
138 candidates_, collision_mesh_, displaced_surface, dhat_);
139 else
140 collision_set_.build(
141 collision_mesh_, displaced_surface, dhat_, dmin_, broad_phase_method_);
142 cached_displaced_surface = displaced_surface;
143 }
144
145 double ContactForm::value_unweighted(const Eigen::VectorXd &x) const
146 {
148 }
149
150 Eigen::VectorXd ContactForm::value_per_element_unweighted(const Eigen::VectorXd &x) const
151 {
152 const Eigen::MatrixXd V = compute_displaced_surface(x);
153 assert(V.rows() == collision_mesh_.num_vertices());
154
155 const size_t num_vertices = collision_mesh_.num_vertices();
156
157 if (collision_set_.empty())
158 {
159 return Eigen::VectorXd::Zero(collision_mesh_.full_num_vertices());
160 }
161
162 const Eigen::MatrixXi &E = collision_mesh_.edges();
163 const Eigen::MatrixXi &F = collision_mesh_.faces();
164
165 auto storage = utils::create_thread_storage<Eigen::VectorXd>(Eigen::VectorXd::Zero(num_vertices));
166
167 utils::maybe_parallel_for(collision_set_.size(), [&](int start, int end, int thread_id) {
168 Eigen::VectorXd &local_storage = utils::get_local_thread_storage(storage, thread_id);
169
170 for (size_t i = start; i < end; i++)
171 {
172 // Quadrature weight is premultiplied by compute_potential
173 const double potential = barrier_potential_(collision_set_[i], collision_set_[i].dof(V, E, F));
174
175 const int n_v = collision_set_[i].num_vertices();
176 const std::array<long, 4> vis = collision_set_[i].vertex_ids(E, F);
177 for (int j = 0; j < n_v; j++)
178 {
179 assert(0 <= vis[j] && vis[j] < num_vertices);
180 local_storage[vis[j]] += potential / n_v;
181 }
182 }
183 });
184
185 Eigen::VectorXd out = Eigen::VectorXd::Zero(num_vertices);
186 for (const auto &local_potential : storage)
187 {
188 out += local_potential;
189 }
190
191 Eigen::VectorXd out_full = Eigen::VectorXd::Zero(collision_mesh_.full_num_vertices());
192 for (int i = 0; i < out.size(); i++)
193 out_full[collision_mesh_.to_full_vertex_id(i)] = out[i];
194
195 assert(std::abs(value_unweighted(x) - out_full.sum()) < std::max(1e-10 * out_full.sum(), 1e-10));
196
197 return out_full;
198 }
199
200 void ContactForm::first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
201 {
203 gradv = collision_mesh_.to_full_dof(gradv);
204 }
205
206 void ContactForm::second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const
207 {
208 POLYFEM_SCOPED_TIMER("barrier hessian");
210 hessian = collision_mesh_.to_full_dof(hessian);
211 }
212
213 void ContactForm::solution_changed(const Eigen::VectorXd &new_x)
214 {
216 }
217
218 double ContactForm::max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
219 {
220 // Extract surface only
221 const Eigen::MatrixXd V0 = compute_displaced_surface(x0);
222 const Eigen::MatrixXd V1 = compute_displaced_surface(x1);
223
225 {
226 const Eigen::MatrixXi E = collision_mesh_.dim() == 2 ? Eigen::MatrixXi() : collision_mesh_.edges();
227 const Eigen::MatrixXi &F = collision_mesh_.faces();
228 igl::writePLY(resolve_output_path("debug_ccd_0.ply"), V0, F, E);
229 igl::writePLY(resolve_output_path("debug_ccd_1.ply"), V1, F, E);
230 }
231
232 double max_step;
233 if (use_cached_candidates_ && broad_phase_method_ != ipc::BroadPhaseMethod::SWEEP_AND_TINIEST_QUEUE)
234 max_step = candidates_.compute_collision_free_stepsize(
236 else
237 max_step = ipc::compute_collision_free_stepsize(
239
240 if (save_ccd_debug_meshes && ipc::has_intersections(collision_mesh_, (V1 - V0) * max_step + V0, broad_phase_method_))
241 {
242 log_and_throw_error("Taking max_step results in intersections (max_step={})", max_step);
243 }
244
245#ifndef NDEBUG
246 // This will check for static intersections as a failsafe. Not needed if we use our conservative CCD.
247 Eigen::MatrixXd V_toi = (V1 - V0) * max_step + V0;
248
249 while (ipc::has_intersections(collision_mesh_, V_toi, broad_phase_method_))
250 {
251 logger().error("Taking max_step results in intersections (max_step={:g})", max_step);
252 max_step /= 2.0;
253
254 const double Linf = (V_toi - V0).lpNorm<Eigen::Infinity>();
255 if (max_step <= 0 || Linf == 0)
256 log_and_throw_error("Unable to find an intersection free step size (max_step={:g} L∞={:g})", max_step, Linf);
257
258 V_toi = (V1 - V0) * max_step + V0;
259 }
260#endif
261
262 return max_step;
263 }
264
265 void ContactForm::line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
266 {
267 candidates_.build(
271 /*inflation_radius=*/dhat_ / 2,
273
275 }
276
278 {
279 candidates_.clear();
281 }
282
283 void ContactForm::post_step(const polysolve::nonlinear::PostStepData &data)
284 {
285 if (data.iter_num == 0)
286 return;
287
288 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(data.x);
289
290 const double curr_distance = collision_set_.compute_minimum_distance(collision_mesh_, displaced_surface);
291
293 {
295 {
296 const double prev_barrier_stiffness = barrier_stiffness();
297
298 barrier_stiffness_ = ipc::update_barrier_stiffness(
300 barrier_stiffness(), ipc::world_bbox_diagonal_length(displaced_surface));
301
302 if (barrier_stiffness() != prev_barrier_stiffness)
303 {
304 polyfem::logger().debug(
305 "updated barrier stiffness from {:g} to {:g} (max barrier stiffness: )",
306 prev_barrier_stiffness, barrier_stiffness(), max_barrier_stiffness_);
307 }
308 }
309 else
310 {
311 // TODO: missing feature
312 // update_barrier_stiffness(data.x);
313 }
314 }
315
316 prev_distance_ = curr_distance;
317 }
318
319 bool ContactForm::is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
320 {
321 const auto displaced0 = compute_displaced_surface(x0);
322 const auto displaced1 = compute_displaced_surface(x1);
323
324 // Skip CCD if the displacement is zero.
325 if ((displaced1 - displaced0).lpNorm<Eigen::Infinity>() == 0.0)
326 {
327 // Assumes initially intersection-free
328 return true;
329 }
330
331 bool is_valid;
333 is_valid = candidates_.is_step_collision_free(
334 collision_mesh_, displaced0, displaced1, dmin_,
336 else
337 is_valid = ipc::is_step_collision_free(
338 collision_mesh_, displaced0, displaced1, broad_phase_method_,
340
341 return is_valid;
342 }
343} // namespace polyfem::solver
int V
int x
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
ipc::Collisions collision_set_
Cached constraint set for the current solution.
const double avg_mass_
Average mass of the mesh (used for adaptive barrier stiffness)
const bool use_adaptive_barrier_stiffness_
If true, use an adaptive barrier stiffness.
virtual void force_shape_derivative(const ipc::Collisions &collision_set, const Eigen::MatrixXd &solution, const Eigen::VectorXd &adjoint_sol, Eigen::VectorXd &term)
const double dmin_
Minimum distance between elements.
void init(const Eigen::VectorXd &x) override
Initialize the form.
bool use_cached_candidates_
If true, use the cached candidate set for the current solution.
Eigen::MatrixXd compute_displaced_surface(const Eigen::VectorXd &x) const
Compute the displaced positions of the surface nodes.
double barrier_stiffness() const
Get the current barrier stiffness.
void post_step(const polysolve::nonlinear::PostStepData &data) override
Update fields after a step in the optimization.
virtual void second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const override
Compute the second derivative of the value wrt x.
double max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const override
Determine the maximum step size allowable between the current and next solution.
double barrier_stiffness_
Barrier stiffness.
double max_barrier_stiffness_
Maximum barrier stiffness to use when using adaptive barrier stiffness.
void solution_changed(const Eigen::VectorXd &new_x) override
Update cached fields upon a change in the solution.
double prev_distance_
Previous minimum distance between all elements.
const ipc::BarrierPotential barrier_potential_
const ipc::BroadPhaseMethod broad_phase_method_
Broad phase method to use for distance and CCD evaluations.
void line_search_end() override
Clear variables used during the line search.
Eigen::VectorXd value_per_element_unweighted(const Eigen::VectorXd &x) const override
Compute the value of the form multiplied per element.
virtual double value_unweighted(const Eigen::VectorXd &x) const override
Compute the contact barrier potential value.
const ipc::Collisions & collision_set() const
const ipc::CollisionMesh & collision_mesh_
Collision mesh.
void line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
Initialize variables used during the line search.
bool use_adaptive_barrier_stiffness() const
Get use_adaptive_barrier_stiffness.
ipc::Candidates candidates_
Cached candidate set for the current solution.
const int ccd_max_iterations_
Continuous collision detection maximum iterations.
ContactForm(const ipc::CollisionMesh &collision_mesh, const double dhat, const double avg_mass, const bool use_convergent_formulation, const bool use_adaptive_barrier_stiffness, const bool is_time_dependent, const bool enable_shape_derivatives, const ipc::BroadPhaseMethod broad_phase_method, const double ccd_tolerance, const int ccd_max_iterations)
Construct a new Contact Form object.
virtual void update_barrier_stiffness(const Eigen::VectorXd &x, const Eigen::MatrixXd &grad_energy)
Update the barrier stiffness based on the current elasticity energy.
void update_collision_set(const Eigen::MatrixXd &displaced_surface)
Update the cached candidate set for the current solution.
virtual void first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const override
Compute the first derivative of the value wrt x.
const double ccd_tolerance_
Continuous collision detection tolerance.
void update_quantities(const double t, const Eigen::VectorXd &x) override
Update time-dependent fields.
bool save_ccd_debug_meshes
If true, output debug files.
const double dhat_
Barrier activation distance.
bool is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const override
Checks if the step is collision free.
bool use_convergent_formulation() const
Get use_convergent_formulation.
const bool is_time_dependent_
Is the simulation time dependent?
double weight_
weight of the form (e.g., AL penalty weight or Δt²)
Definition Form.hpp:148
std::string resolve_output_path(const std::string &path) const
Definition Form.hpp:154
bool project_to_psd_
If true, the form's second derivative is projected to be positive semidefinite.
Definition Form.hpp:146
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
void maybe_parallel_for(int size, const std::function< void(int, int, int)> &partial_for)
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:42
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:71
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:20