PolyFEM
Loading...
Searching...
No Matches
Jacobian.cpp
Go to the documentation of this file.
1#include <numeric>
4#include "Jacobian.hpp"
7
8#ifdef POLYFEM_WITH_MISO
9#include "EigenAdapters.hpp"
10#include <algorithms/solve.hpp>
11#include <algorithms/minimize.hpp>
12#include <algorithms/batch-minimize.hpp>
13using namespace miso;
14#endif
15
16using namespace polyfem::assembler;
17
18namespace polyfem::utils
19{
20 Eigen::MatrixXd extract_nodes(const int dim, const std::vector<basis::ElementBases> &bases, const std::vector<basis::ElementBases> &gbases, const Eigen::VectorXd &u, int order, int n_elem)
21 {
22 if (n_elem < 0)
23 n_elem = bases.size();
24 Eigen::MatrixXd local_pts;
25 if (dim == 3)
26 autogen::p_nodes_3d(order, local_pts);
27 else
28 autogen::p_nodes_2d(order, local_pts);
29 const int n_basis_per_cell = local_pts.rows();
30 Eigen::MatrixXd cp = Eigen::MatrixXd::Zero(n_elem * n_basis_per_cell, dim);
31 for (int e = 0; e < n_elem; ++e)
32 {
34 vals.compute(e, dim == 3, local_pts, bases[e], gbases[e]);
35
36 for (std::size_t j = 0; j < vals.basis_values.size(); ++j)
37 for (const auto &g : vals.basis_values[j].global)
38 cp.middleRows(e * n_basis_per_cell, n_basis_per_cell) += g.val * vals.basis_values[j].val * u.segment(g.index * dim, dim).transpose();
39
40 Eigen::MatrixXd mapped;
41 gbases[e].eval_geom_mapping(local_pts, mapped);
42 cp.middleRows(e * n_basis_per_cell, n_basis_per_cell) += mapped;
43 }
44 return cp;
45 }
46
47 Eigen::MatrixXd extract_nodes(const int dim, const basis::ElementBases &basis, const basis::ElementBases &gbasis, const Eigen::VectorXd &u, int order)
48 {
49 Eigen::MatrixXd local_pts;
50 if (dim == 3)
51 autogen::p_nodes_3d(order, local_pts);
52 else
53 autogen::p_nodes_2d(order, local_pts);
54
55 Eigen::MatrixXd cp;
56 gbasis.eval_geom_mapping(local_pts, cp);
57
59 vals.compute(0, dim == 3, local_pts, basis, gbasis);
60 for (std::size_t j = 0; j < vals.basis_values.size(); ++j)
61 for (const auto &g : vals.basis_values[j].global)
62 cp += g.val * vals.basis_values[j].val * u.segment(g.index * dim, dim).transpose();
63
64 return cp;
65 }
66
67 Eigen::VectorXd robust_evaluate_jacobian(
68 const int order,
69 const Eigen::MatrixXd &cp,
70 const Eigen::MatrixXd &uv)
71 {
72#ifdef POLYFEM_WITH_MISO
73 const int dim = cp.cols();
74 Eigen::VectorXd result(uv.rows());
75
76 // TODO: replace with constant function evaluator (same as used for P=1 static validity)
77 std::vector<Eigen::MatrixXd> grads(cp.rows(), Eigen::MatrixXd::Zero(uv.rows(), dim));
78 for (int bid = 0; bid < cp.rows(); bid++)
79 {
80 if (dim == 2)
81 autogen::p_grad_basis_value_2d(false, order, bid, uv, grads[bid]);
82 else
83 autogen::p_grad_basis_value_3d(false, order, bid, uv, grads[bid]);
84 }
85 for (int k = 0; k < uv.rows(); k++)
86 {
87 Eigen::MatrixXd jac_mat = Eigen::MatrixXd::Zero(dim, dim);
88 for (int bid = 0; bid < cp.rows(); bid++)
89 jac_mat += cp.row(bid).transpose() * grads[bid].row(k);
90 result(k) = jac_mat.determinant();
91 }
92 return result;
93#else
94 log_and_throw_error("Enable Bezier or Miso library to allow robust Jacobian evaluation!");
95 return Eigen::VectorXd::Zero(uv.rows());
96#endif
97 }
98
99#ifdef POLYFEM_WITH_MISO
100 namespace
101 {
102 void build_tree(Tree &tree, const std::vector<unsigned> &path, unsigned n_spatial)
103 {
104 // Space-time child i maps to spatial child i/2 (even=lower t, odd=upper t).
105 // Children beyond 2*n_spatial are time-only subdivisions and are skipped.
106 Tree *dst = &tree;
107 for (const auto idx : path)
108 {
109 if (idx < 2 * n_spatial)
110 {
111 dst->add_children(n_spatial);
112 dst = &(dst->child(idx / 2));
113 }
114 // else: time-only subdivision, skip
115 }
116 }
117 } // anonymous namespace
118#endif // POLYFEM_WITH_MISO
119
120 // Debug utility: counts elements with invalid Jacobian in a given configuration.
121 // Not used in the solve loop; called for diagnostics and visualization.
122 std::vector<int> count_invalid(
123 const int dim,
124 const std::vector<basis::ElementBases> &bases,
125 const std::vector<basis::ElementBases> &gbases,
126 const Eigen::VectorXd &u,
127 const unsigned max_iter)
128 {
129 std::vector<int> invalidList;
130#ifdef POLYFEM_WITH_MISO
131 RealInterval::init();
132 const int order = std::max(bases[0].bases.front().order(), gbases[0].bases.front().order());
133 const int n_per = std::max(bases[0].bases.size(), gbases[0].bases.size());
134 const Eigen::MatrixXd cp = extract_nodes(dim, bases, gbases, u, order);
135 const int n_elem = static_cast<int>(bases.size());
136
137 auto run_solve = [&](auto problem, int e) {
138 Parameters params;
139 params.constraintEpsilon = {0.0};
140 params.maxIter = max_iter;
141 params.findOne = true;
142 Info info;
143 auto sols = solve(std::move(problem), params, &info);
144 if (!info.success())
145 logger().warn("Jacobian solve gave up at element {} after {} iterations", e, info.numIterations);
146 return !sols.empty();
147 };
148
149 for (int e = 0; e < n_elem; ++e)
150 {
151 const int o = e * n_per;
152 bool invalid = false;
153 if (dim == 2)
154 {
155 switch (order)
156 {
157 case 1:
158 invalid = JacEval_P1Tri(rv<3>(cp, o, 0, tri1_perm), rv<3>(cp, o, 1, tri1_perm)) <= 0;
159 break;
160 case 2:
161 invalid = run_solve(make_p2tri_val(cp, o), e);
162 break;
163 case 3:
164 invalid = run_solve(make_p3tri_val(cp, o), e);
165 break;
166 case 4:
167 invalid = run_solve(make_p4tri_val(cp, o), e);
168 break;
169 default:
170 throw std::invalid_argument("Order not supported");
171 }
172 }
173 else
174 {
175 switch (order)
176 {
177 case 1:
178 invalid = JacEval_P1Tet(rv<4>(cp, o, 0, tet1_perm), rv<4>(cp, o, 1, tet1_perm), rv<4>(cp, o, 2, tet1_perm)) <= 0;
179 break;
180 case 2:
181 invalid = run_solve(make_p2tet_val(cp, o), e);
182 break;
183 case 3:
184 invalid = run_solve(make_p3tet_val(cp, o), e);
185 break;
186 default:
187 throw std::invalid_argument("Order not supported");
188 }
189 }
190 if (invalid)
191 invalidList.push_back(e);
192 }
193 RealInterval::deinit();
194#else
195 log_and_throw_error("Enable Bezier or Miso library to allow robust Jacobian check!");
196#endif
197 return invalidList;
198 }
199
200 std::tuple<bool, int, Tree>
202 const int dim,
203 const std::vector<basis::ElementBases> &bases,
204 const std::vector<basis::ElementBases> &gbases,
205 const Eigen::VectorXd &u,
206 const double threshold,
207 const unsigned max_iter)
208 {
209#ifdef POLYFEM_WITH_MISO
210 RealInterval::init();
211 const int order = std::max(bases[0].bases.front().order(), gbases[0].bases.front().order());
212 const int n_per = std::max(bases[0].bases.size(), gbases[0].bases.size());
213 const Eigen::MatrixXd cp = extract_nodes(dim, bases, gbases, u, order);
214 const int n_elem = static_cast<int>(bases.size());
215
216 auto run_solve = [threshold, max_iter](auto problem, int e) {
217 Parameters params;
218 params.constraintEpsilon = {threshold};
219 params.maxIter = max_iter;
220 params.findOne = true;
221 Info info;
222 auto sols = solve(std::move(problem), params, &info);
223 if (!info.success())
224 {
225 logger().warn("Jacobian solve gave up at element {} after {} iterations", e, info.numIterations);
226 return true; // ambiguous counts as invalid
227 }
228 return !sols.empty();
229 };
230
231 for (int e = 0; e < n_elem; ++e)
232 {
233 const int o = e * n_per;
234 bool invalid = false;
235 if (dim == 2)
236 {
237 switch (order)
238 {
239 case 1:
240 invalid = JacEval_P1Tri(rv<3>(cp, o, 0, tri1_perm), rv<3>(cp, o, 1, tri1_perm)) <= 0;
241 break;
242 case 2:
243 invalid = run_solve(make_p2tri_val(cp, o), e);
244 break;
245 case 3:
246 invalid = run_solve(make_p3tri_val(cp, o), e);
247 break;
248 case 4:
249 invalid = run_solve(make_p4tri_val(cp, o), e);
250 break;
251 default:
252 throw std::invalid_argument("Order not supported");
253 }
254 }
255 else
256 {
257 switch (order)
258 {
259 case 1:
260 invalid = JacEval_P1Tet(rv<4>(cp, o, 0, tet1_perm), rv<4>(cp, o, 1, tet1_perm), rv<4>(cp, o, 2, tet1_perm)) <= 0;
261 break;
262 case 2:
263 invalid = run_solve(make_p2tet_val(cp, o), e);
264 break;
265 case 3:
266 invalid = run_solve(make_p3tet_val(cp, o), e);
267 break;
268 default:
269 throw std::invalid_argument("Order not supported");
270 }
271 }
272 if (invalid)
273 {
274 RealInterval::deinit();
275 return {false, e, Tree{}};
276 }
277 }
278 RealInterval::deinit();
279 return {true, -1, Tree{}};
280#else
281 log_and_throw_error("Enable Bezier or Miso library to allow robust Jacobian check!");
282 return {false, -1, Tree{}};
283#endif
284 }
285
286 std::tuple<double, int, double, Tree> max_time_step(
287 const int dim,
288 const std::vector<basis::ElementBases> &bases,
289 const std::vector<basis::ElementBases> &gbases,
290 const Eigen::VectorXd &u1,
291 const Eigen::VectorXd &u2,
292 double precision,
293 double threshold,
294 const unsigned max_iter)
295 {
296#ifdef POLYFEM_WITH_MISO
297 RealInterval::init();
298 const int order = std::max(bases[0].bases.front().order(), gbases[0].bases.front().order());
299 const int n_per = std::max(bases[0].bases.size(), gbases[0].bases.size());
300 const Eigen::MatrixXd cp1 = extract_nodes(dim, bases, gbases, u1, order);
301 const Eigen::MatrixXd cp2 = extract_nodes(dim, bases, gbases, u2, order);
302 const int n_elem = static_cast<int>(bases.size());
303
304 double step = 1.0;
305 int invalid_id = -1;
306 double invalid_step = 1.0;
307 Tree tree;
308
309 // Spatial split: 2^dim children (ignore time subdivisions)
310 const unsigned n_children = (dim == 2) ? 4u : 8u;
311
312 // Build a vector of all per-element problems (same type for all elements) and
313 // run a single batch_minimize over the entire mesh.
314 auto run_batch = [&](auto factory) {
315 using ProblemType = decltype(factory(0));
316 std::vector<ProblemType> problems;
317 problems.reserve(n_elem);
318 for (int e = 0; e < n_elem; ++e)
319 problems.push_back(factory(e * n_per));
320
321 Parameters params;
322 params.targetPrecision = precision;
323 params.constraintEpsilon = {threshold};
324 params.maxIter = max_iter;
325 params.requiredMinimum = 0.;
326 params.minBoxWidth = 0.;
327 Info info;
328 auto result = batch_minimize(std::move(problems), params, &info);
329 const double t_lo = lower(result);
330 if (!info.success())
331 {
332 if (t_lo <= 0)
333 logger().error("Jacobian batch_minimize gave up with step size 0 after {} iterations", info.numIterations);
334 else
335 logger().warn("Jacobian batch_minimize gave up after {} iterations (step={})", info.numIterations, t_lo);
336 }
337 if (t_lo < step)
338 {
339 step = t_lo;
340 invalid_id = static_cast<int>(info.pathToFeasibleId);
341 invalid_step = upper(result);
342 tree = Tree{};
343 build_tree(tree, info.pathToFeasible, n_children);
344 }
345 };
346
347 if (dim == 2)
348 {
349 switch (order)
350 {
351 case 1:
352 run_batch([&](int o) { return make_p1tri_cgv(cp1, cp2, o); });
353 break;
354 case 2:
355 run_batch([&](int o) { return make_p2tri_cgv(cp1, cp2, o); });
356 break;
357 case 3:
358 run_batch([&](int o) { return make_p3tri_cgv(cp1, cp2, o); });
359 break;
360 case 4:
361 run_batch([&](int o) { return make_p4tri_cgv(cp1, cp2, o); });
362 break;
363 default:
364 throw std::invalid_argument("Order not supported");
365 }
366 }
367 else
368 {
369 switch (order)
370 {
371 case 1:
372 run_batch([&](int o) { return make_p1tet_cgv(cp1, cp2, o); });
373 break;
374 case 2:
375 run_batch([&](int o) { return make_p2tet_cgv(cp1, cp2, o); });
376 break;
377 case 3:
378 run_batch([&](int o) { return make_p3tet_cgv(cp1, cp2, o); });
379 break;
380 default:
381 throw std::invalid_argument("Order not supported");
382 }
383 }
384
385 RealInterval::deinit();
386 return {step, invalid_id, invalid_step, tree};
387#else
388 log_and_throw_error("Enable Bezier or Miso library to allow robust Jacobian check!");
389 return {1.0, -1, 1.0, Tree{}};
390#endif
391 }
392} // namespace polyfem::utils
ElementAssemblyValues vals
Definition Assembler.cpp:25
stores per element basis values at given quadrature points and geometric mapping
void compute(const int el_index, const bool is_volume, const Eigen::MatrixXd &pts, const basis::ElementBases &basis, const basis::ElementBases &gbasis)
computes the per element values at the local (ref el) points (pts) sets basis_values,...
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
void eval_geom_mapping(const Eigen::MatrixXd &samples, Eigen::MatrixXd &mapped) const
Map the sample positions in the parametric domain to the object domain (if the element has no paramet...
void add_children(int n)
Definition Jacobian.hpp:86
Tree & child(int i)
Definition Jacobian.hpp:84
Used for test only.
void p_grad_basis_value_2d(const bool bernstein, const int p, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void p_grad_basis_value_3d(const bool bernstein, const int p, const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val)
void p_nodes_2d(const int p, Eigen::MatrixXd &val)
void p_nodes_3d(const int p, Eigen::MatrixXd &val)
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
Eigen::VectorXd robust_evaluate_jacobian(const int order, const Eigen::MatrixXd &cp, const Eigen::MatrixXd &uv)
Definition Jacobian.cpp:67
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)
Definition Jacobian.cpp:122
Eigen::MatrixXd extract_nodes(const int dim, const std::vector< basis::ElementBases > &bases, const std::vector< basis::ElementBases > &gbases, const Eigen::VectorXd &u, int order, int n_elem)
Definition Jacobian.cpp:20
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73