PolyFEM
Loading...
Searching...
No Matches
ElasticForm.cpp
Go to the documentation of this file.
1#include "ElasticForm.hpp"
2
11
12#include <algorithm>
13#include <cassert>
14#include <cmath>
15#include <memory>
16#include <stdexcept>
17#include <tuple>
18#include <vector>
19
20using namespace polyfem::assembler;
21using namespace polyfem::utils;
22using namespace polyfem::quadrature;
23
24namespace polyfem::solver
25{
26 namespace
27 {
28 Eigen::MatrixXd refined_nodes(const int dim, const int i)
29 {
30 Eigen::MatrixXd A(dim + 1, dim);
31 if (dim == 2)
32 {
33 A << 0., 0.,
34 1., 0.,
35 0., 1.;
36 switch (i)
37 {
38 case 0:
39 break;
40 case 1:
41 A.col(0).array() += 1;
42 break;
43 case 2:
44 A.col(1).array() += 1;
45 break;
46 case 3:
47 A.array() -= 1;
48 A *= -1;
49 break;
50 default:
51 throw std::runtime_error("Invalid node index");
52 }
53 }
54 else
55 {
56 A << 0, 0, 0,
57 1, 0, 0,
58 0, 1, 0,
59 0, 0, 1;
60 switch (i)
61 {
62 case 0:
63 break;
64 case 1:
65 A.col(0).array() += 1;
66 break;
67 case 2:
68 A.col(1).array() += 1;
69 break;
70 case 3:
71 A.col(2).array() += 1;
72 break;
73 case 4:
74 {
75 Eigen::VectorXd tmp = 1. - A.col(0).array() - A.col(1).array();
76 A.col(2) += A.col(1);
77 A.col(1) = A.col(0);
78 A.col(0) = tmp;
79 break;
80 }
81 case 5:
82 {
83 Eigen::VectorXd tmp = 1. - A.col(0).array();
84 A.col(1) += A.col(0);
85 A.col(0) = tmp;
86 break;
87 }
88 case 6:
89 {
90 Eigen::VectorXd tmp0 = A.col(0);
91 Eigen::VectorXd tmp1 = A.col(1);
92 A.col(0) = tmp1;
93 A.col(1) = 1. - tmp0.array() - tmp1.array();
94 A.col(2) += tmp0 + tmp1;
95 break;
96 }
97 case 7:
98 {
99 Eigen::VectorXd tmp = A.col(1);
100 A.col(0) += tmp;
101 A.col(1) = 1. - tmp.array();
102 A.col(2) += tmp;
103 break;
104 }
105 default:
106 throw std::runtime_error("Invalid node index");
107 }
108 }
109 return A / 2;
110 }
111
116 std::tuple<Eigen::MatrixXd, std::vector<int>> extract_subelement(const Eigen::MatrixXd &pts, const Tree &tree)
117 {
118 if (!tree.has_children())
119 return {pts, std::vector<int>{0}};
120
121 const int dim = pts.cols();
122 Eigen::MatrixXd out;
123 std::vector<int> levels;
124 for (int i = 0; i < tree.n_children(); i++)
125 {
126 Eigen::MatrixXd uv;
127 uv.setZero(dim + 1, dim + 1);
128 uv.rightCols(dim) = refined_nodes(dim, i);
129 if (dim == 2)
130 uv.col(0) = 1. - uv.col(2).array() - uv.col(1).array();
131 else
132 uv.col(0) = 1. - uv.col(3).array() - uv.col(1).array() - uv.col(2).array();
133
134 Eigen::MatrixXd pts_ = uv * pts;
135
136 auto [tmp, L] = extract_subelement(pts_, tree.child(i));
137 if (out.size() == 0)
138 out = tmp;
139 else
140 {
141 out.conservativeResize(out.rows() + tmp.rows(), Eigen::NoChange);
142 out.bottomRows(tmp.rows()) = tmp;
143 }
144 for (int &i : L)
145 ++i;
146 levels.insert(levels.end(), L.begin(), L.end());
147 }
148 return {out, levels};
149 }
150
151 quadrature::Quadrature refine_quadrature(const Tree &tree, const int dim, const int order)
152 {
153 Eigen::MatrixXd pts(dim + 1, dim);
154 if (dim == 2)
155 pts << 0., 0.,
156 1., 0.,
157 0., 1.;
158 else
159 pts << 0, 0, 0,
160 1, 0, 0,
161 0, 1, 0,
162 0, 0, 1;
163 auto [quad_points, levels] = extract_subelement(pts, tree);
164
165 Quadrature tmp, quad;
166 if (dim == 2)
167 {
168 TriQuadrature tri_quadrature(true);
169 tri_quadrature.get_quadrature(order, tmp);
170 tmp.points.conservativeResize(tmp.points.rows(), dim + 1);
171 tmp.points.col(dim) = 1. - tmp.points.col(0).array() - tmp.points.col(1).array();
172 }
173 else
174 {
175 TetQuadrature tet_quadrature(true);
176 // The corner rule for order 4 (Liu-Vinokur 9) has a large negative
177 // weight at the centroid, which corrupts non-polynomial integrands
178 // like NeoHookean near inversion. Order 5 has all-positive weights.
179 const int safe_order = (order == 4) ? 5 : order;
180 tet_quadrature.get_quadrature(safe_order, tmp);
181 tmp.points.conservativeResize(tmp.points.rows(), dim + 1);
182 tmp.points.col(dim) = 1. - tmp.points.col(0).array() - tmp.points.col(1).array() - tmp.points.col(2).array();
183 }
184
185 quad.points.resize(tmp.size() * levels.size(), dim);
186 quad.weights.resize(tmp.size() * levels.size());
187
188 for (int i = 0; i < levels.size(); i++)
189 {
190 quad.points.middleRows(i * tmp.size(), tmp.size()) = tmp.points * quad_points.middleRows(i * (dim + 1), dim + 1);
191 quad.weights.segment(i * tmp.size(), tmp.size()) = tmp.weights / pow(2, dim * levels[i]);
192 }
193 assert(fabs(quad.weights.sum() - tmp.weights.sum()) < 1e-8);
194
195 return quad;
196 }
197
198 void update_quadrature(const int invalidID, const int dim, Tree &tree, const int quad_order, basis::ElementBases &bs, const basis::ElementBases &gbs, assembler::AssemblyValsCache &ass_vals_cache)
199 {
200 // update quadrature to capture the point with negative jacobian
201 const Quadrature quad = refine_quadrature(tree, dim, quad_order);
202
203 // capture the flipped point by refining the quadrature
204 bs.set_quadrature([quad](Quadrature &quad_) {
205 quad_ = quad;
206 });
207 logger().debug("New number of quadrature points: {}, level: {}", quad.size(), tree.depth());
208
209 if (ass_vals_cache.is_initialized())
210 ass_vals_cache.update(invalidID, dim == 3, bs, gbs);
211 }
212 } // namespace
213
214 ElasticForm::ElasticForm(const int n_bases,
215 std::vector<basis::ElementBases> &bases,
216 const std::vector<basis::ElementBases> &geom_bases,
217 const assembler::Assembler &assembler,
218 assembler::AssemblyValsCache &ass_vals_cache,
219 const double t, const double dt,
220 const bool is_volume,
221 const double jacobian_threshold,
222 const ElementInversionCheck check_inversion,
223 const unsigned conservative_max_iter)
224 : n_bases_(n_bases),
225 bases_(bases),
226 geom_bases_(geom_bases),
227 assembler_(assembler),
228 ass_vals_cache_(ass_vals_cache),
229 t_(t),
230 jacobian_threshold_(jacobian_threshold),
231 check_inversion_(check_inversion),
232 conservative_max_iter_(conservative_max_iter),
233 dt_(dt),
234 is_volume_(is_volume)
235 {
236 if (assembler_.is_linear())
237 compute_cached_stiffness();
238 // mat_cache_ = std::make_unique<utils::DenseMatrixCache>();
239 mat_cache_ = std::make_unique<utils::SparseMatrixCache>();
240 quadrature_hierarchy_.resize(bases_.size());
241
242 quadrature_order_ = AssemblerUtils::quadrature_order(assembler_.name(), bases_[0].bases[0].order(), AssemblerUtils::BasisType::SIMPLEX_LAGRANGE, is_volume_ ? 3 : 2);
243
244 if (check_inversion_ != ElementInversionCheck::Discrete)
245 {
246 Eigen::VectorXd x0;
247 x0.setZero(n_bases_ * (is_volume_ ? 3 : 2));
248 if (!is_step_collision_free(x0, x0))
249 log_and_throw_error("Initial state has inverted elements!");
250
251 int basis_order = 0;
252 int gbasis_order = 0;
253 for (int e = 0; e < bases_.size(); e++)
254 {
255 if (basis_order == 0)
256 basis_order = bases_[e].bases.front().order();
257 else if (basis_order != bases_[e].bases.front().order())
258 log_and_throw_error("Non-uniform basis order not supported for conservative Jacobian check!!");
259 if (gbasis_order == 0)
260 gbasis_order = geom_bases_[e].bases.front().order();
261 else if (gbasis_order != geom_bases_[e].bases.front().order())
262 log_and_throw_error("Non-uniform gbasis order not supported for conservative Jacobian check!!");
263 }
264
265 // Replace the corner quadrature set by LagrangeBasis for every element.
266 // The default corner rule for order 4 has a negative centroid weight
267 // (see refine_quadrature); override it here so the base quadrature is
268 // also safe before any adaptive refinement occurs.
269 const int dim = is_volume_ ? 3 : 2;
270 for (int e = 0; e < (int)bases_.size(); e++)
271 update_quadrature(e, dim, quadrature_hierarchy_[e], quadrature_order_, bases_[e], geom_bases_[e], ass_vals_cache_);
272 }
273 }
274
275 double ElasticForm::value_unweighted(const Eigen::VectorXd &x) const
276 {
277 return assembler_.assemble_energy(
278 is_volume_,
279 bases_, geom_bases_, ass_vals_cache_, t_, dt_, x, x_prev_);
280 }
281
282 Eigen::VectorXd ElasticForm::value_per_element_unweighted(const Eigen::VectorXd &x) const
283 {
284 const Eigen::VectorXd out = assembler_.assemble_energy_per_element(
285 is_volume_, bases_, geom_bases_, ass_vals_cache_, t_, dt_, x, x_prev_);
286 assert(abs(out.sum() - value_unweighted(x)) < std::max(1e-10 * out.sum(), 1e-10));
287 return out;
288 }
289
290 void ElasticForm::first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
291 {
292 Eigen::MatrixXd grad;
293 assembler_.assemble_gradient(is_volume_, n_bases_, bases_, geom_bases_,
294 ass_vals_cache_, t_, dt_, x, x_prev_, grad);
295 gradv = grad;
296 }
297
298 void ElasticForm::second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const
299 {
300 POLYFEM_SCOPED_TIMER("elastic hessian");
301
302 hessian.resize(x.size(), x.size());
303
304 if (assembler_.is_linear())
305 {
306 assert(cached_stiffness_.rows() == x.size() && cached_stiffness_.cols() == x.size());
307 hessian = cached_stiffness_;
308 }
309 else
310 {
311 // NOTE: mat_cache_ is marked as mutable so we can modify it here
312 assembler_.assemble_hessian(
313 is_volume_, n_bases_, project_to_psd_, bases_,
314 geom_bases_, ass_vals_cache_, t_, dt_, x, x_prev_, *mat_cache_, hessian);
315 }
316 }
317
318 void ElasticForm::finish()
319 {
320 for (auto &t : quadrature_hierarchy_)
321 t = Tree();
322 pending_refinement_.reset();
323 }
324
325 double ElasticForm::max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
326 {
327 if (check_inversion_ == ElementInversionCheck::Discrete)
328 return 1.;
329
330 const int dim = is_volume_ ? 3 : 2;
331
332 double step, invalidStep;
333 int invalidID;
334
335 Tree subdivision_tree;
336 {
337 double transient_check_time = 0;
338 {
339 POLYFEM_SCOPED_TIMER("Transient Jacobian Check", transient_check_time);
340 std::tie(step, invalidID, invalidStep, subdivision_tree) = max_time_step(dim, bases_, geom_bases_, x0, x1, .25, 0., conservative_max_iter_);
341 }
342
343 logger().log(step == 0 ? spdlog::level::err : (step == 1. ? spdlog::level::trace : spdlog::level::debug),
344 "Jacobian max step size: {} at element {}, invalid step size: {}, runtime {} sec, tree depth {}", step, invalidID, invalidStep, transient_check_time, subdivision_tree.depth());
345 }
346
347 if (invalidID >= 0 && step == 0)
348 {
349 // A step of 0 can never be accepted, so it will never reach
350 // post_step(); commit the refinement candidate immediately so
351 // the next iteration's energy/gradient/Hessian actually sees
352 // this near-degenerate point instead of repeating the same
353 // failure against stale quadrature.
354 commit_refinement(invalidID, subdivision_tree);
355 pending_refinement_.reset();
356 }
357 else if (invalidID >= 0 && step <= 0.5)
358 {
359 // Store as a candidate only; committed in post_step() if/when
360 // this step is accepted (see pending_refinement_ in the header).
361 pending_refinement_ = std::make_pair(invalidID, std::move(subdivision_tree));
362 }
363 else
364 {
365 pending_refinement_.reset();
366 }
367
368 return step;
369 }
370
371 bool ElasticForm::is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
372 {
373 if (check_inversion_ == ElementInversionCheck::Discrete)
374 return true;
375
376 const auto [isvalid, id, tree] = is_valid(is_volume_ ? 3 : 2, bases_, geom_bases_, x1, 0., conservative_max_iter_);
377 return isvalid;
378 }
379
380 bool ElasticForm::is_step_valid(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) const
381 {
382 // check inversion on quadrature points
383 Eigen::VectorXd grad;
384 first_derivative(x1, grad);
385 if (grad.array().isNaN().any())
386 return false;
387
388 return true;
389
390 // Check the scalar field in the output does not contain NANs.
391 // WARNING: Does not work because the energy is not evaluated at the same quadrature points.
392 // This causes small step lengths in the LS.
393 // TVector x1_full;
394 // reduced_to_full(x1, x1_full);
395 // return state_.check_scalar_value(x1_full, true, false);
396 // return true;
397 }
398
399 void ElasticForm::post_step(const polysolve::nonlinear::PostStepData &data)
400 {
401 if (!pending_refinement_.has_value())
402 return;
403
404 auto [id, subdivision_tree] = *pending_refinement_;
405 commit_refinement(id, subdivision_tree);
406
407 pending_refinement_.reset();
408 }
409
410 void ElasticForm::commit_refinement(const int id, utils::Tree &subdivision_tree) const
411 {
412 const int dim = is_volume_ ? 3 : 2;
413
414 // Merge into quadrature_hierarchy_ and rebuild the live quadrature for
415 // this element, if the tree is actually refined.
416 if (quadrature_hierarchy_[id].merge(subdivision_tree))
417 {
418 auto &bs = bases_[id];
419 auto &gbs = geom_bases_[id];
420 update_quadrature(id, dim, quadrature_hierarchy_[id], quadrature_order_, bs, gbs, ass_vals_cache_);
421 }
422 }
423
424 void ElasticForm::compute_cached_stiffness()
425 {
426 if (assembler_.is_linear() && cached_stiffness_.size() == 0)
427 {
428 assembler_.assemble(is_volume_, n_bases_, bases_, geom_bases_,
429 ass_vals_cache_, t_, cached_stiffness_);
430 }
431 }
432} // namespace polyfem::solver
int x
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
static int quadrature_order(const std::string &assembler, const int basis_degree, const BasisType &b_type, const int dim)
utility for retrieving the needed quadrature order to precisely integrate the given form on the given...
Caches basis evaluation and geometric mapping at every element.
void update(const int el_index, const bool is_volume, const basis::ElementBases &basis, const basis::ElementBases &gbasis)
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
void set_quadrature(const QuadratureFunction &fun)
int depth() const
Definition Jacobian.hpp:66
int n_children() const
Definition Jacobian.hpp:65
bool has_children() const
Definition Jacobian.hpp:64
Tree & child(int i)
Definition Jacobian.hpp:84
list tmp
Definition p_bases.py:366
Used for test only.
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, const unsigned max_iter)
Definition Jacobian.cpp:201
std::tuple< double, int, double, Tree > max_time_step(const int dim, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXd &u1, const Eigen::VectorXd &u2, double precision, double threshold, const unsigned max_iter)
Definition Jacobian.cpp:286
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:24