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