Loading [MathJax]/extensions/tex2jax.js
PolyFEM
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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_area_weighting,
24 const bool use_improved_max_operator,
25 const bool use_physical_barrier,
26 const bool use_adaptive_barrier_stiffness,
27 const bool is_time_dependent,
28 const bool enable_shape_derivatives,
29 const ipc::BroadPhaseMethod broad_phase_method,
30 const double ccd_tolerance,
31 const int ccd_max_iterations)
32 : collision_mesh_(collision_mesh),
33 dhat_(dhat),
34 use_adaptive_barrier_stiffness_(use_adaptive_barrier_stiffness),
35 avg_mass_(avg_mass),
36 is_time_dependent_(is_time_dependent),
37 enable_shape_derivatives_(enable_shape_derivatives),
38 broad_phase_method_(broad_phase_method),
39 tight_inclusion_ccd_(ccd_tolerance, ccd_max_iterations),
40 barrier_potential_(dhat, use_physical_barrier)
41 {
42 assert(dhat_ > 0);
43 assert(ccd_tolerance > 0);
44
45 prev_distance_ = -1;
46 //collision_set_.set_use_convergent_formulation(use_convergent_formulation);
47 collision_set_.set_use_improved_max_approximator(use_improved_max_operator);
48 collision_set_.set_use_area_weighting(use_area_weighting);
49 collision_set_.set_enable_shape_derivatives(enable_shape_derivatives);
50 }
51
52 void ContactForm::init(const Eigen::VectorXd &x)
53 {
55 }
56
57 void ContactForm::force_shape_derivative(const ipc::NormalCollisions &collision_set, const Eigen::MatrixXd &solution, const Eigen::VectorXd &adjoint_sol, Eigen::VectorXd &term)
58 {
59 // Eigen::MatrixXd U = collision_mesh_.vertices(utils::unflatten(solution, collision_mesh_.dim()));
60 // Eigen::MatrixXd X = collision_mesh_.vertices(boundary_nodes_pos_);
61 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(solution);
62
63 StiffnessMatrix dq_h = collision_mesh_.to_full_dof(barrier_potential_.shape_derivative(collision_set, collision_mesh_, displaced_surface));
64 term = barrier_stiffness() * dq_h.transpose() * adjoint_sol;
65 }
66
67 void ContactForm::update_quantities(const double t, const Eigen::VectorXd &x)
68 {
70 }
71
72 Eigen::MatrixXd ContactForm::compute_displaced_surface(const Eigen::VectorXd &x) const
73 {
74 return collision_mesh_.displace_vertices(utils::unflatten(x, collision_mesh_.dim()));
75 }
76
77 void ContactForm::update_barrier_stiffness(const Eigen::VectorXd &x, const Eigen::MatrixXd &grad_energy)
78 {
80 return;
81
82 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(x);
83
84 // The adative stiffness is designed for the non-convergent formulation,
85 // so we need to compute the gradient of the non-convergent barrier.
86 // After we can map it to a good value for the convergent formulation.
87 ipc::NormalCollisions nonconvergent_constraints;
88 //nonconvergent_constraints.set_use_convergent_formulation(false);
89 nonconvergent_constraints.set_use_area_weighting(false);
90 nonconvergent_constraints.set_use_improved_max_approximator(false);
91 nonconvergent_constraints.build(
92 collision_mesh_, displaced_surface, dhat_, dmin_, broad_phase_method_);
93 Eigen::VectorXd grad_barrier = barrier_potential_.gradient(
94 nonconvergent_constraints, collision_mesh_, displaced_surface);
95 grad_barrier = collision_mesh_.to_full_dof(grad_barrier);
96
97 barrier_stiffness_ = ipc::initial_barrier_stiffness(
98 ipc::world_bbox_diagonal_length(displaced_surface), barrier_potential_.barrier(), dhat_, avg_mass_,
99 grad_energy, grad_barrier, max_barrier_stiffness_);
100
102 {
103 double scaling_factor = 0;
104 if (!nonconvergent_constraints.empty())
105 {
106 const double nonconvergent_potential = barrier_potential_(
107 nonconvergent_constraints, collision_mesh_, displaced_surface);
108
109 update_collision_set(displaced_surface);
110 const double convergent_potential = barrier_potential_(
111 collision_set_, collision_mesh_, displaced_surface);
112
113 scaling_factor = nonconvergent_potential / convergent_potential;
114 }
115 else
116 {
117 // Hardcoded difference between the non-convergent and convergent barrier
118 scaling_factor = dhat_ * std::pow(dhat_ + 2 * dmin_, 2);
119 }
120 barrier_stiffness_ *= scaling_factor;
121 max_barrier_stiffness_ *= scaling_factor;
122 }
123
124 // The barrier stiffness is choosen based on including the acceleration scaling,
125 // but the acceleration scaling will be applied later. Therefore, we need to remove it.
128
129 logger().debug(
130 "Setting adaptive barrier stiffness to {} (max barrier stiffness: {})",
132 }
133
134 void ContactForm::update_collision_set(const Eigen::MatrixXd &displaced_surface)
135 {
136 // Store the previous value used to compute the constraint set to avoid duplicate computation.
137 static Eigen::MatrixXd cached_displaced_surface;
138 if (cached_displaced_surface.size() == displaced_surface.size() && cached_displaced_surface == displaced_surface)
139 return;
140
142 collision_set_.build(
143 candidates_, collision_mesh_, displaced_surface, dhat_);
144 else
145 collision_set_.build(
146 collision_mesh_, displaced_surface, dhat_, dmin_, broad_phase_method_);
147 cached_displaced_surface = displaced_surface;
148 }
149
150 double ContactForm::value_unweighted(const Eigen::VectorXd &x) const
151 {
153 }
154
155 Eigen::VectorXd ContactForm::value_per_element_unweighted(const Eigen::VectorXd &x) const
156 {
157 const Eigen::MatrixXd V = compute_displaced_surface(x);
158 assert(V.rows() == collision_mesh_.num_vertices());
159
160 const size_t num_vertices = collision_mesh_.num_vertices();
161
162 if (collision_set_.empty())
163 {
164 return Eigen::VectorXd::Zero(collision_mesh_.full_num_vertices());
165 }
166
167 const Eigen::MatrixXi &E = collision_mesh_.edges();
168 const Eigen::MatrixXi &F = collision_mesh_.faces();
169
170 auto storage = utils::create_thread_storage<Eigen::VectorXd>(Eigen::VectorXd::Zero(num_vertices));
171
172 utils::maybe_parallel_for(collision_set_.size(), [&](int start, int end, int thread_id) {
173 Eigen::VectorXd &local_storage = utils::get_local_thread_storage(storage, thread_id);
174
175 for (size_t i = start; i < end; i++)
176 {
177 // Quadrature weight is premultiplied by compute_potential
178 const double potential = barrier_potential_(collision_set_[i], collision_set_[i].dof(V, E, F));
179
180 const int n_v = collision_set_[i].num_vertices();
181 const std::array<long, 4> vis = collision_set_[i].vertex_ids(E, F);
182 for (int j = 0; j < n_v; j++)
183 {
184 assert(0 <= vis[j] && vis[j] < num_vertices);
185 local_storage[vis[j]] += potential / n_v;
186 }
187 }
188 });
189
190 Eigen::VectorXd out = Eigen::VectorXd::Zero(num_vertices);
191 for (const auto &local_potential : storage)
192 {
193 out += local_potential;
194 }
195
196 Eigen::VectorXd out_full = Eigen::VectorXd::Zero(collision_mesh_.full_num_vertices());
197 for (int i = 0; i < out.size(); i++)
198 out_full[collision_mesh_.to_full_vertex_id(i)] = out[i];
199
200 assert(std::abs(value_unweighted(x) - out_full.sum()) < std::max(1e-10 * out_full.sum(), 1e-10));
201
202 return out_full;
203 }
204
205 void ContactForm::first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
206 {
208 gradv = collision_mesh_.to_full_dof(gradv);
209 }
210
211 void ContactForm::second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const
212 {
213 POLYFEM_SCOPED_TIMER("barrier hessian");
214
215 ipc::PSDProjectionMethod psd_projection_method;
216
217 if (project_to_psd_) {
218 psd_projection_method = ipc::PSDProjectionMethod::CLAMP;
219 } else {
220 psd_projection_method = ipc::PSDProjectionMethod::NONE;
221 }
222
223 hessian = barrier_potential_.hessian(collision_set_, collision_mesh_, compute_displaced_surface(x), psd_projection_method);
224 hessian = collision_mesh_.to_full_dof(hessian);
225 }
226
227 void ContactForm::solution_changed(const Eigen::VectorXd &new_x)
228 {
230 }
231
232 double ContactForm::max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
233 {
234 // Extract surface only
235 const Eigen::MatrixXd V0 = compute_displaced_surface(x0);
236 const Eigen::MatrixXd V1 = compute_displaced_surface(x1);
237
239 {
240 const Eigen::MatrixXi E = collision_mesh_.dim() == 2 ? Eigen::MatrixXi() : collision_mesh_.edges();
241 const Eigen::MatrixXi &F = collision_mesh_.faces();
242 igl::writePLY(resolve_output_path("debug_ccd_0.ply"), V0, F, E);
243 igl::writePLY(resolve_output_path("debug_ccd_1.ply"), V1, F, E);
244 }
245
246 double max_step;
247 if (use_cached_candidates_ && broad_phase_method_ != ipc::BroadPhaseMethod::SWEEP_AND_TINIEST_QUEUE)
248 max_step = candidates_.compute_collision_free_stepsize(
250 else
251 max_step = ipc::compute_collision_free_stepsize(
253
254 if (save_ccd_debug_meshes && ipc::has_intersections(collision_mesh_, (V1 - V0) * max_step + V0, broad_phase_method_))
255 {
256 log_and_throw_error("Taking max_step results in intersections (max_step={})", max_step);
257 }
258
259#ifndef NDEBUG
260 // This will check for static intersections as a failsafe. Not needed if we use our conservative CCD.
261 Eigen::MatrixXd V_toi = (V1 - V0) * max_step + V0;
262
263 while (ipc::has_intersections(collision_mesh_, V_toi, broad_phase_method_))
264 {
265 logger().error("Taking max_step results in intersections (max_step={:g})", max_step);
266 max_step /= 2.0;
267
268 const double Linf = (V_toi - V0).lpNorm<Eigen::Infinity>();
269 if (max_step <= 0 || Linf == 0)
270 log_and_throw_error("Unable to find an intersection free step size (max_step={:g} L∞={:g})", max_step, Linf);
271
272 V_toi = (V1 - V0) * max_step + V0;
273 }
274#endif
275
276 return max_step;
277 }
278
279 void ContactForm::line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
280 {
281 candidates_.build(
285 /*inflation_radius=*/dhat_ / 2,
287
289 }
290
292 {
293 candidates_.clear();
295 }
296
297 void ContactForm::post_step(const polysolve::nonlinear::PostStepData &data)
298 {
299 if (data.iter_num == 0)
300 return;
301
302 const Eigen::MatrixXd displaced_surface = compute_displaced_surface(data.x);
303
304 const double curr_distance = collision_set_.compute_minimum_distance(collision_mesh_, displaced_surface);
305
307 {
309 {
310 const double prev_barrier_stiffness = barrier_stiffness();
311
312 barrier_stiffness_ = ipc::update_barrier_stiffness(
314 barrier_stiffness(), ipc::world_bbox_diagonal_length(displaced_surface));
315
316 if (barrier_stiffness() != prev_barrier_stiffness)
317 {
318 polyfem::logger().debug(
319 "updated barrier stiffness from {:g} to {:g} (max barrier stiffness: )",
320 prev_barrier_stiffness, barrier_stiffness(), max_barrier_stiffness_);
321 }
322 }
323 else
324 {
325 // TODO: missing feature
326 // update_barrier_stiffness(data.x);
327 }
328 }
329
330 prev_distance_ = curr_distance;
331 }
332
333 bool ContactForm::is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
334 {
335 const auto displaced0 = compute_displaced_surface(x0);
336 const auto displaced1 = compute_displaced_surface(x1);
337
338 // Skip CCD if the displacement is zero.
339 if ((displaced1 - displaced0).lpNorm<Eigen::Infinity>() == 0.0)
340 {
341 // Assumes initially intersection-free
342 return true;
343 }
344
345 bool is_valid;
347 is_valid = candidates_.is_step_collision_free(
348 collision_mesh_, displaced0, displaced1, dmin_,
350 else
351 is_valid = ipc::is_step_collision_free(
352 collision_mesh_, displaced0, displaced1, dmin_, broad_phase_method_,
354
355 return is_valid;
356 }
357} // namespace polyfem::solver
int V
int x
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
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.
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.
virtual void force_shape_derivative(const ipc::NormalCollisions &collision_set, const Eigen::MatrixXd &solution, const Eigen::VectorXd &adjoint_sol, Eigen::VectorXd &term)
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.
bool use_area_weighting() const
Get use_area_weighting.
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::CollisionMesh & collision_mesh_
Collision mesh.
const ipc::TightInclusionCCD tight_inclusion_ccd_
Continuous collision detection specification object.
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.
ContactForm(const ipc::CollisionMesh &collision_mesh, const double dhat, const double avg_mass, const bool use_area_weighting, const bool use_improved_max_operator, const bool use_physical_barrier, 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.
ipc::Candidates candidates_
Cached candidate set for the current solution.
bool use_improved_max_operator() const
Get use_improved_max_operator.
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 ipc::NormalCollisions & collision_set() const
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.
ipc::NormalCollisions collision_set_
Cached constraint set for the current solution.
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:145
std::string resolve_output_path(const std::string &path) const
Definition Form.hpp:151
bool project_to_psd_
If true, the form's second derivative is projected to be positive semidefinite.
Definition Form.hpp:143
std::tuple< bool, int, Tree > is_valid(const int dim, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXd &u, const double threshold)
Definition Jacobian.cpp:241
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:22