3#include <wmtk/optimization/AMIPSEnergy.hpp>
4#include <wmtk/optimization/EnergySum.hpp>
5#include <wmtk/optimization/EnvelopeEnergy.hpp>
6#include <wmtk/optimization/solver.hpp>
8#include <wmtk/Types.hpp>
9#include <wmtk/envelope/Envelope.hpp>
10#include <wmtk/simplex/SimplexCollection.hpp>
11#include <wmtk/utils/Logger.hpp>
12#include <wmtk/utils/TetraQualityUtils.hpp>
20namespace wmtk::optimization {
36 std::atomic<size_t> accepted{0};
47 std::string to_string()
const
50 "accepted {} | rejected: pre-inverted {}, envelope {}, inverted {}, quality {}",
81 int(polysolve::nonlinear::Status::UpdateDirectionFailed) == int(
kStatuses) - 2,
82 "polysolve's Status enum changed; update kStatuses and status_name()");
84 std::array<std::atomic<size_t>,
kIterBuckets> iterations{};
85 std::array<std::atomic<size_t>,
kStatuses> status{};
86 std::atomic<size_t> iterations_total{0};
87 std::atomic<size_t> threw{0};
90 void record(
const polysolve::nonlinear::Solver& s,
const bool did_throw)
92 const size_t it = s.current_criteria().iterations;
93 iterations[std::min(it,
kIterBuckets - 1)].fetch_add(1, std::memory_order_relaxed);
94 iterations_total.fetch_add(it, std::memory_order_relaxed);
95 const int st = int(s.status()) + 1;
97 status[size_t(st)].fetch_add(1, std::memory_order_relaxed);
99 if (did_throw) threw.fetch_add(1, std::memory_order_relaxed);
102 size_t solves()
const
105 for (
const auto& c : iterations) n += c.load();
111 for (
auto& c : iterations) c = 0;
112 for (
auto& c : status) c = 0;
113 iterations_total = 0;
117 static const char* status_name(
const size_t i)
119 static constexpr std::array<const char*, kStatuses> names = {
124 "RelXDeltaTolerance",
127 "RelGradNormTolerance",
128 "NewtonDecrementTolerance",
129 "ObjectiveCustomStop",
131 "NotDescentDirection",
133 "UpdateDirectionFailed"}};
142 const size_t n = solves();
143 std::string out = fmt::format(
144 "solves={} mean_iterations={:.3g} | iterations",
146 n > 0 ?
double(iterations_total.load()) /
double(n) : 0.);
148 out += i + 1 <
kIterBuckets ? fmt::format(
" {}={}", i, iterations[i].load())
149 : fmt::format(
" >{}={}", i - 1, iterations[i].load());
153 if (status[i].load() != 0)
154 out += fmt::format(
" {}={}", status_name(i), status[i].load());
156 out += fmt::format(
" | threw={}", threw.load());
173 double w_amips = 1e-4;
174 double w_envelope = 1.0 - 1e-4;
175 double s_amips = 1.0;
176 double s_envelope = 1.0;
296bool smooth_vertex_3d(
298 const typename Mesh::Tuple& t,
300 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
304 using Tuple =
typename Mesh::Tuple;
306 const size_t vid = t.vid(m);
307 auto& VA = m.m_vertex_attribute;
308 const auto locs = m.get_one_ring_tets_for_vertex(t);
309 assert(!locs.empty());
311 double max_quality = 0.;
312 for (
const Tuple& tet : locs) {
313 max_quality = std::max(max_quality, m.cell_quality(tet.tid(m)));
314 if (m.is_inverted_f(tet)) {
317 if (counters) ++counters->already_inverted;
324 std::vector<std::array<double, 12>> assembles(locs.size());
325 for (
size_t i = 0; i < locs.size(); ++i) {
326 std::array<size_t, 4> local_verts = m.oriented_tet_vids(locs[i].tid(m));
327 local_verts = wmtk::orient_preserve_tet_reorder(local_verts, vid);
328 for (
int k = 0; k < 4; k++) {
329 for (
int j = 0; j < 3; j++) {
330 assembles[i][k * 3 + j] = VA[local_verts[k]].m_posf[j];
336 solver = create_basic_solver();
339 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
340 auto amips_energy = std::make_shared<AMIPSEnergy3D>(assembles, amips_w);
346 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
347 m.smoothing_extra_energy(vid);
348 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
350 auto sum = std::make_shared<EnergySum>();
351 if (opts.w_amips > 0) sum->add_energy(amips_energy);
352 sum->add_energy(extra_energy);
355 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
358 VectorXd x = VA[vid].m_posf;
361 solver->minimize(*total_energy, x);
362 }
catch (
const std::exception&) {
367 if (newton) newton->record(*solver, threw);
371 const std::shared_ptr<SampleEnvelope> pull_env =
372 VA[vid].m_is_on_surface ? m.smoothing_energy_envelope(vid) :
nullptr;
374 if (pull_env && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
376 const Vector3d x_orig = VA[vid].m_posf;
377 total_energy = base_energy;
379 const Vector3d x_new = VA[vid].m_posf;
383 const auto worst_at = [&](
const Vector3d& p) {
385 VA[vid].m_pos = to_rational(p);
387 for (
const Tuple& loc : locs) {
388 if (m.is_inverted(loc)) {
389 return std::numeric_limits<double>::infinity();
391 mq = std::max(mq, m.get_quality(loc));
396 bool accepted =
false;
397 std::vector<Vector3d> interp, proj;
401 const Vector3d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
403 pull_env->nearest_point(p, q);
406 if (worst_at(q) < max_quality) {
420 for (
size_t k = 0; k < proj.size() && !accepted; ++k) {
421 double lo = 0.0, hi = 1.0;
425 const double mid = 0.5 * (lo + hi);
426 const Vector3d cand = interp[k] + mid * (proj[k] - interp[k]);
427 if (worst_at(cand) < max_quality) {
444 VA[vid].m_posf = x_orig;
445 VA[vid].m_pos = to_rational(x_orig);
446 if (counters) ++counters->quality;
449 }
else if (pull_env) {
450 auto envelope_energy =
451 std::make_shared<ExactDistanceEnergy3D>(pull_env, opts.s_envelope * opts.w_envelope);
454 auto warmup = std::make_shared<EnergySum>();
455 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
456 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
457 if (extra_energy) warmup->add_energy(extra_energy);
458 total_energy = warmup;
462 auto weighted = std::make_shared<EnergySum>();
463 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
464 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
465 if (extra_energy) weighted->add_energy(extra_energy);
466 total_energy = weighted;
474 const std::shared_ptr<SampleEnvelope> check_env =
475 VA[vid].m_is_on_surface ? m.smoothing_containment_envelope(vid) :
nullptr;
477 const simplex::SimplexCollection surf = m.get_surface_faces_for_vertex(vid);
478 for (
const simplex::Face& f : surf.faces()) {
479 const std::array<Eigen::Vector3d, 3> face = {
480 {VA[f.vertices()[0]].m_posf,
481 VA[f.vertices()[1]].m_posf,
482 VA[f.vertices()[2]].m_posf}};
483 if (check_env->is_outside(face)) {
484 if (counters) ++counters->envelope;
491 VA[vid].m_pos = to_rational(VA[vid].m_posf);
493 double max_after_quality = 0.;
494 for (
const Tuple& loc : locs) {
495 if (m.is_inverted(loc)) {
496 if (counters) ++counters->inverted;
499 const size_t tid = loc.tid(m);
500 const double quality = m.get_quality(loc);
501 m.set_cell_quality(tid, quality);
502 max_after_quality = std::max(max_after_quality, quality);
506 if (max_after_quality > max_quality) {
507 if (counters) ++counters->quality;
512 if (counters) ++counters->accepted;
535bool smooth_vertex_2d(
537 const typename Mesh::Tuple& t,
538 const SmoothVertexOptions& opts,
539 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
540 SmoothRejectCounters* counters =
nullptr)
542 const size_t vid = t.vid(m);
543 auto& VA = m.m_vertex_attribute;
544 auto& FA = m.m_face_attribute;
546 const std::vector<size_t>& locs = m.get_one_ring_fids_for_vertex(t);
547 assert(!locs.empty());
549 double max_quality = 0.;
550 for (
const size_t fid : locs) {
551 max_quality = std::max(max_quality, FA[fid].m_quality);
552 if (m.is_inverted_f(fid)) {
555 if (counters) ++counters->already_inverted;
561 std::vector<std::array<double, 6>> assembles;
562 assembles.reserve(locs.size());
563 for (
const size_t fid : locs) {
564 std::array<size_t, 3> vs = m.oriented_tri_vids(fid);
566 for (
size_t i = 0; i < 3; ++i) {
572 const std::array<size_t, 3> buf = vs;
574 vs[1] = buf[(v_loc + 1) % 3];
575 vs[2] = buf[(v_loc + 2) % 3];
577 std::array<double, 6> T;
578 for (
int i = 0; i < 3; i++) {
579 const Vector2d p = m.smoothing_position(vs[i]);
583 assembles.push_back(T);
587 solver = create_basic_solver();
590 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
591 auto amips_energy = std::make_shared<AMIPSEnergy2D>(assembles, amips_w);
597 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
598 m.smoothing_extra_energy(vid);
599 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
601 auto sum = std::make_shared<EnergySum>();
602 if (opts.w_amips > 0) sum->add_energy(amips_energy);
603 sum->add_energy(extra_energy);
606 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
609 VectorXd x = m.smoothing_position(vid);
611 solver->minimize(*total_energy, x);
612 }
catch (
const std::exception&) {
616 m.set_smoothing_position(vid, Vector2d(x));
622 std::vector<Vector2d> surf_neighbors;
623 if (VA[vid].m_is_on_surface) {
624 const simplex::SimplexCollection es = m.get_surface_edges_for_vertex(vid);
625 surf_neighbors.reserve(es.edges().size());
626 for (
const simplex::Edge& e : es.edges()) {
627 const auto& evs = e.vertices();
628 surf_neighbors.push_back(m.smoothing_position(evs[0] != vid ? evs[0] : evs[1]));
630 assert(!surf_neighbors.empty());
636 const std::shared_ptr<SampleEnvelope> envelope = m.smoothing_energy_envelope(vid);
638 if (envelope && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
639 const Vector2d x_orig = m.smoothing_position(vid);
640 total_energy = base_energy;
642 const Vector2d x_new = m.smoothing_position(vid);
646 const auto worst_at = [&](
const Vector2d& p) {
647 m.set_smoothing_position(vid, p);
649 for (
const size_t fid : locs) {
650 if (m.is_inverted(fid)) {
651 return std::numeric_limits<double>::infinity();
653 mq = std::max(mq, m.get_quality(fid));
658 bool accepted =
false;
659 std::vector<Vector2d> interp, proj;
660 interp.reserve(opts.project_line_search_steps);
661 proj.reserve(opts.project_line_search_steps);
662 for (
int k = 0; k < opts.project_line_search_steps; ++k) {
663 const Vector2d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
665 envelope->nearest_point(p, q);
668 if (worst_at(q) < max_quality) {
681 if (!accepted && opts.project_line_search_nested_steps > 0) {
682 for (
size_t k = 0; k < proj.size() && !accepted; ++k) {
683 double lo = 0.0, hi = 1.0;
686 for (
int j = 0; j < opts.project_line_search_nested_steps; ++j) {
687 const double mid = 0.5 * (lo + hi);
688 const Vector2d cand = interp[k] + mid * (proj[k] - interp[k]);
689 if (worst_at(cand) < max_quality) {
706 m.set_smoothing_position(vid, x_orig);
707 if (counters) ++counters->quality;
710 }
else if (envelope) {
711 auto envelope_energy =
712 std::make_shared<ExactDistanceEnergy2D>(envelope, opts.s_envelope * opts.w_envelope);
714 if (opts.two_stage) {
715 auto warmup = std::make_shared<EnergySum>();
716 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
717 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
718 if (extra_energy) warmup->add_energy(extra_energy);
719 total_energy = warmup;
723 auto weighted = std::make_shared<EnergySum>();
724 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
725 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
726 if (extra_energy) weighted->add_energy(extra_energy);
727 total_energy = weighted;
737 if (!m.smoothing_position_is_allowed(vid, m.smoothing_position(vid))) {
738 if (counters) ++counters->envelope;
744 if (
const std::shared_ptr<SampleEnvelope> hold_env = m.smoothing_containment_envelope(vid)) {
745 const Vector2d p = m.smoothing_position(vid);
746 for (
const Vector2d& q : surf_neighbors) {
747 const std::array<Eigen::Vector2d, 2> edge = {{p, q}};
748 if (hold_env->is_outside(edge)) {
749 if (counters) ++counters->envelope;
755 double max_after_quality = 0.;
756 for (
const size_t fid : locs) {
757 if (m.is_inverted(fid)) {
758 if (counters) ++counters->inverted;
761 const double q = m.get_quality(fid);
762 FA[fid].m_quality = q;
763 max_after_quality = std::max(max_after_quality, q);
766 if (opts.quality_veto && (!VA[vid].m_is_on_surface || opts.quality_veto_on_surface)) {
767 if (max_after_quality > max_quality) {
768 if (counters) ++counters->quality;
773 if (counters) ++counters->accepted;
How the vertex solves of a smoothing pass ended: Newton iterations taken and the status polysolve sto...
Definition SmoothVertex.hpp:75
void record(const polysolve::nonlinear::Solver &s, const bool did_throw)
One solve that just returned from minimize(), or threw out of it (did_throw).
Definition SmoothVertex.hpp:90
std::string to_string() const
Definition SmoothVertex.hpp:140
static constexpr size_t kStatuses
polysolve::nonlinear::Status, NotStarted (-1) .. UpdateDirectionFailed (12), shifted by 1.
Definition SmoothVertex.hpp:79
static constexpr size_t kIterBuckets
0 .. 10 iterations, and one bucket for more than 10.
Definition SmoothVertex.hpp:77
Why a smoothing attempt was refused, counted per pass.
Definition SmoothVertex.hpp:31
std::atomic< size_t > envelope
a surface triangle left the envelope
Definition SmoothVertex.hpp:33
std::atomic< size_t > already_inverted
incident tet inverted in floats on entry
Definition SmoothVertex.hpp:32
std::atomic< size_t > quality
the move made the worst incident tet worse
Definition SmoothVertex.hpp:35
std::atomic< size_t > inverted
the move inverted a tet (exact predicate)
Definition SmoothVertex.hpp:34
Weights and policy for smooth_vertex_3d.
Definition SmoothVertex.hpp:172
bool quality_veto_on_surface
Definition SmoothVertex.hpp:209
int project_line_search_steps
Definition SmoothVertex.hpp:230
int project_line_search_nested_steps
Definition SmoothVertex.hpp:267
bool quality_veto
Definition SmoothVertex.hpp:249
bool two_stage
Definition SmoothVertex.hpp:180
SmoothingMode
Definition SmoothVertex.hpp:225