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>
21namespace wmtk::optimization {
37 std::atomic<size_t> accepted{0};
48 std::string to_string()
const
51 "accepted {} | rejected: pre-inverted {}, envelope {}, inverted {}, quality {}",
82 int(polysolve::nonlinear::Status::UpdateDirectionFailed) == int(
kStatuses) - 2,
83 "polysolve's Status enum changed; update kStatuses and status_name()");
85 std::array<std::atomic<size_t>,
kIterBuckets> iterations{};
86 std::array<std::atomic<size_t>,
kStatuses> status{};
87 std::atomic<size_t> iterations_total{0};
88 std::atomic<size_t> threw{0};
91 void record(
const polysolve::nonlinear::Solver& s,
const bool did_throw)
93 const size_t it = s.current_criteria().iterations;
94 iterations[std::min(it,
kIterBuckets - 1)].fetch_add(1, std::memory_order_relaxed);
95 iterations_total.fetch_add(it, std::memory_order_relaxed);
96 const int st = int(s.status()) + 1;
98 status[size_t(st)].fetch_add(1, std::memory_order_relaxed);
100 if (did_throw) threw.fetch_add(1, std::memory_order_relaxed);
103 size_t solves()
const
106 for (
const auto& c : iterations) n += c.load();
112 for (
auto& c : iterations) c = 0;
113 for (
auto& c : status) c = 0;
114 iterations_total = 0;
118 static const char* status_name(
const size_t i)
120 static constexpr std::array<const char*, kStatuses> names = {
125 "RelXDeltaTolerance",
128 "RelGradNormTolerance",
129 "NewtonDecrementTolerance",
130 "ObjectiveCustomStop",
132 "NotDescentDirection",
134 "UpdateDirectionFailed"}};
143 const size_t n = solves();
144 std::string out = fmt::format(
145 "solves={} mean_iterations={:.3g} | iterations",
147 n > 0 ?
double(iterations_total.load()) /
double(n) : 0.);
149 out += i + 1 <
kIterBuckets ? fmt::format(
" {}={}", i, iterations[i].load())
150 : fmt::format(
" >{}={}", i - 1, iterations[i].load());
154 if (status[i].load() != 0)
155 out += fmt::format(
" {}={}", status_name(i), status[i].load());
157 out += fmt::format(
" | threw={}", threw.load());
174 double w_amips = 1e-4;
175 double w_envelope = 1.0 - 1e-4;
176 double s_amips = 1.0;
177 double s_envelope = 1.0;
277 double max_step = std::numeric_limits<double>::infinity();
311bool smooth_vertex_3d(
313 const typename Mesh::Tuple& t,
315 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
319 using Tuple =
typename Mesh::Tuple;
321 const size_t vid = t.vid(m);
322 auto& VA = m.m_vertex_attribute;
328 const auto& VA_read = std::as_const(m.m_vertex_attribute);
329 const auto locs = m.get_one_ring_tets_for_vertex(t);
330 assert(!locs.empty());
339 for (
const Tuple& tet : locs) {
340 if (m.is_inverted_f(tet)) {
343 if (counters) ++counters->already_inverted;
347 double max_quality = 0.;
348 for (
const Tuple& tet : locs) {
350 std::max(max_quality, m.smoothing_cell_energy(tet.tid(m), m.cell_quality(tet.tid(m))));
355 std::vector<std::array<double, 12>> assembles(locs.size());
356 for (
size_t i = 0; i < locs.size(); ++i) {
357 std::array<size_t, 4> local_verts = m.oriented_tet_vids(locs[i].tid(m));
358 local_verts = wmtk::orient_preserve_tet_reorder(local_verts, vid);
359 for (
int k = 0; k < 4; k++) {
360 for (
int j = 0; j < 3; j++) {
361 assembles[i][k * 3 + j] = VA_read[local_verts[k]].m_posf[j];
367 solver = create_basic_solver();
370 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
371 auto amips_energy = std::make_shared<AMIPSEnergy3D>(assembles, amips_w);
377 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
378 m.smoothing_extra_energy(vid);
379 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
381 auto sum = std::make_shared<EnergySum>();
382 if (opts.w_amips > 0) sum->add_energy(amips_energy);
383 sum->add_energy(extra_energy);
386 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
389 const Vector3d x_entry = VA_read[vid].m_posf;
391 VectorXd x = VA[vid].m_posf;
394 solver->minimize(*total_energy, x);
395 }
catch (
const std::exception&) {
400 if (newton) newton->record(*solver, threw);
401 const Vector3d step = Vector3d(x) - x_entry;
402 const double len = step.norm();
410 const bool on_feature_curve = m.m_track_feature_edges && m.vertex_has_feature_edge(vid);
411 const std::shared_ptr<Envelope> pull_env =
412 (VA[vid].m_is_on_surface || on_feature_curve) ? m.smoothing_energy_envelope(vid) :
nullptr;
414 if (pull_env && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
418 const Vector3d x_orig = VA[vid].m_posf;
419 const double ring_before = m.smoothing_ring_energy(locs,
true);
420 total_energy = base_energy;
422 const Vector3d x_new = VA[vid].m_posf;
427 const auto worst_at = [&](
const Vector3d& p) {
428 if ((p - x_orig).norm() > opts.
max_step)
return std::numeric_limits<double>::infinity();
430 VA[vid].set_pos_to_posf();
431 for (
const Tuple& loc : locs) {
432 if (m.is_inverted(loc)) {
433 return std::numeric_limits<double>::infinity();
436 return m.smoothing_ring_energy(locs,
false);
439 bool accepted =
false;
440 std::vector<Vector3d> interp, proj;
444 const Vector3d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
446 pull_env->nearest_point(p, q);
449 if (worst_at(q) < ring_before) {
463 for (
size_t k = 0; k < proj.size() && !accepted; ++k) {
464 double lo = 0.0, hi = 1.0;
468 const double mid = 0.5 * (lo + hi);
469 const Vector3d cand = interp[k] + mid * (proj[k] - interp[k]);
470 if (worst_at(cand) < ring_before) {
487 VA[vid].m_posf = x_orig;
488 VA[vid].set_pos_to_posf();
489 if (counters) ++counters->quality;
492 }
else if (pull_env) {
493 auto envelope_energy =
494 std::make_shared<ExactDistanceEnergy3D>(pull_env, opts.s_envelope * opts.w_envelope);
497 auto warmup = std::make_shared<EnergySum>();
498 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
499 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
500 if (extra_energy) warmup->add_energy(extra_energy);
501 total_energy = warmup;
505 auto weighted = std::make_shared<EnergySum>();
506 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
507 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
508 if (extra_energy) weighted->add_energy(extra_energy);
509 total_energy = weighted;
517 if (!m.smoothing_position_is_allowed(vid, VA[vid].m_posf)) {
518 if (counters) ++counters->envelope;
524 if (on_feature_curve && !m.feature_edges_at_vertex_inside(vid)) {
525 if (counters) ++counters->envelope;
534 if (!m.boundary_edges_at_vertex_inside(vid)) {
535 if (counters) ++counters->envelope;
542 if (VA[vid].m_is_on_surface) {
543 const auto& FA_read = std::as_const(m.m_face_attribute);
544 const simplex::SimplexCollection surf = m.get_surface_faces_for_vertex(vid);
545 for (
const simplex::Face& f : surf.faces()) {
546 const auto& fv = f.vertices();
547 const size_t fid = std::get<1>(m.tuple_from_face(fv));
548 const std::shared_ptr<Envelope> check_env =
549 m.smoothing_containment_envelope(vid, fv, FA_read[fid]);
550 if (!check_env)
continue;
551 const std::array<Eigen::Vector3d, 3> face = {
552 {VA_read[fv[0]].m_posf, VA_read[fv[1]].m_posf, VA_read[fv[2]].m_posf}};
553 if (check_env->is_outside(face)) {
554 if (counters) ++counters->envelope;
561 VA[vid].set_pos_to_posf();
563 for (
const Tuple& loc : locs) {
564 if (m.is_inverted(loc)) {
565 if (counters) ++counters->inverted;
569 double max_after_quality = 0.;
570 for (
const Tuple& loc : locs) {
571 const size_t tid = loc.tid(m);
572 const double quality = m.get_quality(loc);
573 m.set_cell_quality(tid, quality);
574 max_after_quality = std::max(max_after_quality, m.smoothing_cell_energy(tid, quality));
578 if (max_after_quality > max_quality) {
579 if (counters) ++counters->quality;
584 if (counters) ++counters->accepted;
610bool smooth_vertex_2d(
612 const typename Mesh::Tuple& t,
613 const SmoothVertexOptions& opts,
614 std::unique_ptr<polysolve::nonlinear::Solver>& solver,
615 SmoothRejectCounters* counters =
nullptr)
617 const size_t vid = t.vid(m);
618 auto& VA = m.m_vertex_attribute;
619 auto& FA = m.m_face_attribute;
621 const std::vector<size_t>& locs = m.get_one_ring_fids_for_vertex(t);
622 assert(!locs.empty());
625 for (
const size_t fid : locs) {
626 if (m.is_inverted_f(fid)) {
629 if (counters) ++counters->already_inverted;
633 double max_quality = 0.;
634 for (
const size_t fid : locs) {
635 max_quality = std::max(max_quality, m.smoothing_cell_energy(fid, FA[fid].m_quality));
639 std::vector<std::array<double, 6>> assembles;
640 assembles.reserve(locs.size());
641 for (
const size_t fid : locs) {
642 std::array<size_t, 3> vs = m.oriented_tri_vids(fid);
644 for (
size_t i = 0; i < 3; ++i) {
650 const std::array<size_t, 3> buf = vs;
652 vs[1] = buf[(v_loc + 1) % 3];
653 vs[2] = buf[(v_loc + 2) % 3];
655 std::array<double, 6> T;
656 for (
int i = 0; i < 3; i++) {
657 const Vector2d p = m.smoothing_position(vs[i]);
661 assembles.push_back(T);
665 solver = create_basic_solver();
668 const double amips_w = opts.w_amips > 0 ? opts.s_amips * opts.w_amips : 1.0;
669 auto amips_energy = std::make_shared<AMIPSEnergy2D>(assembles, amips_w);
675 const std::shared_ptr<polysolve::nonlinear::Problem> extra_energy =
676 m.smoothing_extra_energy(vid);
677 std::shared_ptr<polysolve::nonlinear::Problem> base_energy = amips_energy;
679 auto sum = std::make_shared<EnergySum>();
680 if (opts.w_amips > 0) sum->add_energy(amips_energy);
681 sum->add_energy(extra_energy);
684 std::shared_ptr<polysolve::nonlinear::Problem> total_energy = base_energy;
687 VectorXd x = m.smoothing_position(vid);
689 solver->minimize(*total_energy, x);
690 }
catch (
const std::exception&) {
694 m.set_smoothing_position(vid, Vector2d(x));
701 std::vector<Vector2d> surf_neighbors;
702 std::vector<std::array<size_t, 2>> surf_edges;
703 if (VA[vid].m_is_on_surface) {
704 const simplex::SimplexCollection es = m.get_surface_edges_for_vertex(vid);
705 surf_neighbors.reserve(es.edges().size());
706 surf_edges.reserve(es.edges().size());
707 for (
const simplex::Edge& e : es.edges()) {
708 const auto& evs = e.vertices();
709 surf_neighbors.push_back(m.smoothing_position(evs[0] != vid ? evs[0] : evs[1]));
710 surf_edges.push_back(evs);
712 assert(!surf_neighbors.empty());
718 const std::shared_ptr<Envelope> envelope = m.smoothing_energy_envelope(vid);
720 if (envelope && opts.smoothing_mode == SmoothVertexOptions::SmoothingMode::Projected) {
721 const Vector2d x_orig = m.smoothing_position(vid);
722 total_energy = base_energy;
724 const Vector2d x_new = m.smoothing_position(vid);
728 const auto worst_at = [&](
const Vector2d& p) {
729 m.set_smoothing_position(vid, p);
730 for (
const size_t fid : locs) {
731 if (m.is_inverted(fid)) {
732 return std::numeric_limits<double>::infinity();
736 for (
const size_t fid : locs) {
737 mq = std::max(mq, m.smoothing_cell_energy(fid, m.get_quality(fid)));
742 bool accepted =
false;
743 std::vector<Vector2d> interp, proj;
744 interp.reserve(opts.project_line_search_steps);
745 proj.reserve(opts.project_line_search_steps);
746 for (
int k = 0; k < opts.project_line_search_steps; ++k) {
747 const Vector2d p = x_orig + std::pow(0.5, k) * (x_new - x_orig);
749 envelope->nearest_point(p, q);
752 if (worst_at(q) < max_quality) {
765 if (!accepted && opts.project_line_search_nested_steps > 0) {
766 for (
size_t k = 0; k < proj.size() && !accepted; ++k) {
767 double lo = 0.0, hi = 1.0;
770 for (
int j = 0; j < opts.project_line_search_nested_steps; ++j) {
771 const double mid = 0.5 * (lo + hi);
772 const Vector2d cand = interp[k] + mid * (proj[k] - interp[k]);
773 if (worst_at(cand) < max_quality) {
790 m.set_smoothing_position(vid, x_orig);
791 if (counters) ++counters->quality;
794 }
else if (envelope) {
795 auto envelope_energy =
796 std::make_shared<ExactDistanceEnergy2D>(envelope, opts.s_envelope * opts.w_envelope);
798 if (opts.two_stage) {
799 auto warmup = std::make_shared<EnergySum>();
800 if (opts.w_amips > 0) warmup->add_energy(amips_energy, 1. / opts.w_amips);
801 if (opts.w_envelope > 0) warmup->add_energy(envelope_energy, 1. / opts.w_envelope);
802 if (extra_energy) warmup->add_energy(extra_energy);
803 total_energy = warmup;
807 auto weighted = std::make_shared<EnergySum>();
808 if (opts.w_amips > 0) weighted->add_energy(amips_energy);
809 if (opts.w_envelope > 0) weighted->add_energy(envelope_energy);
810 if (extra_energy) weighted->add_energy(extra_energy);
811 total_energy = weighted;
821 if (!m.smoothing_position_is_allowed(vid, m.smoothing_position(vid))) {
822 if (counters) ++counters->envelope;
829 const auto& EA_read = std::as_const(m.m_edge_attribute);
830 const Vector2d p = m.smoothing_position(vid);
831 for (
size_t i = 0; i < surf_neighbors.size(); ++i) {
832 const size_t eid = std::get<1>(m.tuple_from_edge(surf_edges[i]));
833 const std::shared_ptr<Envelope> hold_env =
834 m.smoothing_containment_envelope(vid, surf_edges[i], EA_read[eid]);
835 if (!hold_env)
continue;
836 const std::array<Eigen::Vector2d, 2> edge = {{p, surf_neighbors[i]}};
837 if (hold_env->is_outside(edge)) {
838 if (counters) ++counters->envelope;
844 for (
const size_t fid : locs) {
845 if (m.is_inverted(fid)) {
846 if (counters) ++counters->inverted;
850 double max_after_quality = 0.;
851 for (
const size_t fid : locs) {
852 const double q = m.get_quality(fid);
853 FA[fid].m_quality = q;
854 max_after_quality = std::max(max_after_quality, m.smoothing_cell_energy(fid, q));
857 if (opts.quality_veto && (!VA[vid].m_is_on_surface || opts.quality_veto_on_surface)) {
858 if (max_after_quality > max_quality) {
859 if (counters) ++counters->quality;
864 if (counters) ++counters->accepted;
How the vertex solves of a smoothing pass ended: Newton iterations taken and the status polysolve sto...
Definition SmoothVertex.hpp:76
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:91
std::string to_string() const
Definition SmoothVertex.hpp:141
static constexpr size_t kStatuses
polysolve::nonlinear::Status, NotStarted (-1) .. UpdateDirectionFailed (12), shifted by 1.
Definition SmoothVertex.hpp:80
static constexpr size_t kIterBuckets
0 .. 10 iterations, and one bucket for more than 10.
Definition SmoothVertex.hpp:78
Why a smoothing attempt was refused, counted per pass.
Definition SmoothVertex.hpp:32
std::atomic< size_t > envelope
a surface triangle left the envelope
Definition SmoothVertex.hpp:34
std::atomic< size_t > already_inverted
incident tet inverted in floats on entry
Definition SmoothVertex.hpp:33
std::atomic< size_t > quality
the move made the worst incident tet worse
Definition SmoothVertex.hpp:36
std::atomic< size_t > inverted
the move inverted a tet (exact predicate)
Definition SmoothVertex.hpp:35
Weights and policy for smooth_vertex_3d.
Definition SmoothVertex.hpp:173
bool quality_veto_on_surface
Definition SmoothVertex.hpp:210
double max_step
Definition SmoothVertex.hpp:277
int project_line_search_steps
Definition SmoothVertex.hpp:231
int project_line_search_nested_steps
Definition SmoothVertex.hpp:268
bool quality_veto
Definition SmoothVertex.hpp:250
bool two_stage
Definition SmoothVertex.hpp:181
SmoothingMode
Definition SmoothVertex.hpp:226