Wildmeshing Toolkit
Loading...
Searching...
No Matches
Parameters.h
1#pragma once
2#include <wmtk/OptimizerParameters.h>
3#include <nlohmann/json.hpp>
4#include <wmtk/Types.hpp>
5#include <wmtk/components/simwild/expression_parser/Expression.hpp>
6
7using ExpressionPtr = wmtk::components::simwild::expression_parser::ExpressionPtr;
8
9namespace wmtk::components::topological_offset {
10
24{
25 ExpressionPtr offset_selection;
26 std::set<std::string> offset_output_tag;
27 std::set<std::string> protected_tags;
28 bool offset_in;
29 bool offset_out;
30 double target_distance;
31 double target_distance_rel;
32 // Turn a non-converged run into a hard error instead of a warning. Off by default: a run that
33 // misses the target is still a usable offset, and the warnings name the criterion that failed.
34 // Integration tests set it true so a convergence regression fails rather than warns.
35 bool throw_on_nonconvergence;
39 // Half-width of the envelope that holds the held surfaces (3D: every region boundary, the
40 // input complex's included, and the domain wall; see the spec doc).
41 // Absolute; if <= 0, computed from envelope_size_rel (relative to the bbox diagonal) by
42 // init(). The spec's defaults, so a Parameters built without init() (the unit tests) holds
43 // defined values rather than uninitialized ones; its envelope_size is then -1, which the
44 // envelope build refuses, so such a test sets envelope_size itself.
45 double envelope_size = -1.0;
46 double envelope_size_rel = 1e-3;
47
48 // ---- the smooth offset potential ----
49 // Support radius of the potential, as a multiple of target_distance. Must be > 1: the offset
50 // level set has to lie strictly inside the support, or the vertices on it get no gradient. A
51 // band vertex that travels past the support is a hard error, not a silently frozen vertex.
52 double offset_dhat_factor;
53 std::string offset_field;
54 // ---- the convergence epsilon ----
55 // front_conv is an ABSOLUTE LENGTH, resolved in init(); if <= 0 it is computed from
56 // front_conv_rel, which since 2026-10-21 is a fraction of TARGET_DISTANCE -- the config's,
57 // fixed for the run, every growth stage included (from 2026-09-23 to 2026-10-20 it was a
58 // fraction of the bounding box diagonal). init() refuses a front_conv above target_distance:
59 // an epsilon coarser than the offset it measures cannot decide anything.
60 //
61 // THE ONE BAR, in 2D and in 3D. One quantity is measured everywhere -- over a simplex's
62 // stencil (a face's in 3D, a front chord's in 2D), the RMS of the distance to the level set
63 // along the field (OffsetPotential::relative_residual(), which for the euclidean field is the
64 // relative error (Phi - c)/c) -- and compared against this. A vertex is placed when that same
65 // measure at the vertex alone is within it, which is the order-0 stencil, so the vertex
66 // measure and the face (chord) measure are one measure at two sample counts. Replaces
67 // vertex_conv / sag_conv, which split the two apart 2026-09-23. What the loop exits on is
68 // front_measure's choice; the vertex measure is a diagnostic since 2026-09-25.
69 double front_conv;
70 double front_conv_rel;
71
78 double front_conv_frac() const { return front_conv / std::max(target_distance, 1e-16); }
79 // 2D only: read by the 2D pipeline, which the app refuses until it is brought into lock-step
80 // with 3D. No JSON key; each keeps the default its key had. The 2D energy rule of collapses
81 // and swaps, the 2D front vertices' smoothing veto, and the 2D collapse rule over the changed
82 // cells (TopoOffsetTriMesh). The 2D non-front veto reads the engine's smooth_quality_veto,
83 // which keeps the engine's default, true, the default of the key offset_smooth_veto that set
84 // it. 3D's rules are always on and read none of these (TopoOffsetTetMesh::tet_energy()).
85 bool offset_collapse_veto = true, offset_swap_veto = true;
86 bool offset_front_smooth_veto = false;
87 bool offset_collapse_changed_cells = true;
93 bool debug_crossings = false;
96 bool front_pocket_split = true;
102 bool exit_no_folds = true;
110 std::string coarsen_guard = "bar";
116 std::string label_cleanup = "one_sided_at_target";
124 bool use_rest_pose = false;
127 int max_rounds = 30;
128 // Sampling density of THE measure -- both the criterion's and the energy's, in 2D and 3D --
129 // and of the residual diagnostics that share the lattice. Order k puts these points on a
130 // face (3D):
131 // 0 -> 3, the CORNERS alone, so the measure is exactly the three vertices' placement error;
132 // k >= 1 -> the vertices of the triangle subdivided k-1 times by 4-way midpoint refinement
133 // plus the centroid of each of its 4^(k-1) sub-triangles, i.e. 4, 10, 31, 109, ...
134 // and on a front chord (2D), the same rule one dimension down:
135 // 0 -> 2, the ENDS alone;
136 // k >= 1 -> the vertices of the chord cut into 2^(k-1) pieces plus the midpoint of each
137 // piece, i.e. 3, 5, 9, 17, ...
138 // The corners are IN the stencil, unlike the strictly interior lattice this replaces, because
139 // the quantity measured is a distance to the level set rather than an interpolation error and
140 // so is not identically zero there. Raising it costs a Phi value per sample in
141 // energy_criterion() and the frames, and in 2D also in the operations' energy rules
142 // (TopoOffsetTriMesh::tri_energy()) and every front solve; the 3D energy has no face term.
143 // See TopoOffsetTetMesh::for_each_face_sample, TopoOffsetTriMesh::for_each_edge_sample.
144 // That is EXPERIMENTAL_stencil_type "subdivision", the default; the other types (3D) have
145 // their own lattices, below.
146 int stencil_order = 1; // the spec's default
154 std::string stencil_type = "subdivision";
163 std::string front_measure;
164 bool sorted_marching;
172 std::string construction_mode = "max_marchable_fallback";
177 bool init_optimize = false;
178 // 2D only: read by the 2D pipeline, which the app refuses until it is brought into lock-step
179 // with 3D. No JSON key; the default its key had. The stencil_order of the 2D init_optimize
180 // loop alone; -1 uses stencil_order there too.
181 int init_optimize_stencil_order = -1;
193 bool split_energy_gate = false;
213 std::string band_volume_rule = "exact";
218 double settle_stall_eps = 0.1;
223 std::string settle_test = "stall";
233 bool settle_refinement = true;
239 bool gap_refinement = false;
245 bool damping = false;
248 double damping_factor = 0.5;
265 std::string growth_mode = "none";
279 double foldover_angle = 30.;
287 bool debug_stencil = false;
290 bool allow_mult_comp = false;
308 std::string settle_mark = "vertex_edge";
309 std::string output_path; // no extension
310 bool save_vtu;
311
312 // Samples per side of the grid the smooth offset potential is written on, beside the result,
313 // for the viewer. 0 disables it. The whole domain in 2D, one plane through the box in 3D.
314 int phi_grid_resolution;
315
316 int num_threads; // number of threads for parallel execution (smoothing, collapse). 0 = serial
320 // NO KEY since 2026-10-20 (offset_envelope / offset_envelope_rel removed): the offset
321 // surface's envelope in a TetWild pass has envelope_size's eps, like every other held surface
322 // (optimize_construction's pass alone widens it). init() sets these two to
323 // envelope_size / envelope_size_rel; only the 2D code still reads them.
324 double offset_envelope = -1.;
325 double offset_envelope_rel = -1.;
326
327 // l_min from the paper: the shortest edge the sizing field may ask for, given as a multiple of
328 // target_distance rather than of the bounding box because that is the scale the offset has;
329 // min_edge_length is derived from min_edge_length_rel in init() when negative. A floor on
330 // refinement, so raising it makes the result coarser. When not given, it falls back to the
331 // offset envelope eps, following TetWild: a surface pinned only to within eps cannot buy
332 // fidelity from shorter edges, so this is a runaway rail, not a resolution setting.
333 double min_edge_length;
334 double min_edge_length_rel;
335
336 // ---- sizing field ----
337 // bounds for VertexAttributes::m_sizing_scalar
338 double min_sizing_scalar;
339 double max_sizing_scalar;
340 // gradation cap: neighboring vertices' sizing scalars may differ by at most this factor,
341 // enforced by propagating the refinement outward (monotone, only ever lowers a
342 // neighbor's scalar). <= 1 disables gradation entirely. Only used by "ring" mode.
343 double sizing_gradation;
352
353 VectorXd box_min;
354 VectorXd box_max;
355
356 Parameters() = default;
357
358 Parameters(const nlohmann::json& json_params)
359 {
360 for (const std::string& tag : json_params["offset_output_tags"]) {
361 if (tag == "ambient") {
362 logger().warn(
363 "'ambient' tag cannot be given explicitly to offset_output_tags, ignoring. To "
364 "set offset to 'ambient', pass offset_output_tags=[].");
365 continue;
366 }
367 offset_output_tag.insert(tag);
368 }
369 for (const std::string& tag : json_params["protected_tags"]) {
370 if (tag == "ambient") {
371 logger().warn("'ambient' tag cannot be protected, ignoring.");
372 continue;
373 }
374 protected_tags.insert(tag);
375 }
376 offset_in = json_params["offset_in"];
377 offset_out = json_params["offset_out"];
378 target_distance = json_params["target_distance"];
379 target_distance_rel = json_params["target_distance_rel"];
380 throw_on_nonconvergence = json_params["throw_on_nonconvergence"];
381 optimize_offset = json_params["optimize_offset"];
382 envelope_size = json_params["envelope_size"];
383 envelope_size_rel = json_params["envelope_size_rel"];
384 offset_dhat_factor = json_params["offset_dhat_factor"];
385 offset_field = json_params["offset_field"];
386 front_conv = json_params["front_conv"];
387 front_conv_rel = json_params["front_conv_rel"];
388 stencil_order = json_params["stencil_order"];
389 stencil_type = json_params["EXPERIMENTAL_stencil_type"];
390 if (stencil_type != "subdivision" && stencil_type != "interior" && stencil_type != "fem") {
391 log_and_throw_error(
392 "EXPERIMENTAL_stencil_type is \"subdivision\", \"interior\" or \"fem\", not "
393 "\"{}\"",
395 }
396 if (stencil_type != "subdivision" && stencil_order < 1) {
397 log_and_throw_error(
398 "EXPERIMENTAL_stencil_type \"{}\" needs stencil_order >= 1, not {}",
400 stencil_order);
401 }
402 front_measure = json_params["front_measure"];
403
404 sorted_marching = json_params["sorted_marching"];
405 save_offset_correspondence = json_params["save_offset_correspondence"];
406 construction_mode = json_params["construction_mode"];
407 sphere_trace_target_rel_tol = json_params["sphere_trace_target_rel_tol"];
408 init_optimize = json_params["init_optimize"];
409 repulsion_smoothing_passes = json_params["repulsion_smoothing_passes"];
410 repulsion_rounds = json_params["repulsion_rounds"];
411 split_energy_gate = json_params["EXPERIMENTAL_split_energy_gate"];
412 band_volume_rule = json_params["EXPERIMENTAL_band_volume_rule"];
413 if (band_volume_rule != "corner_bound" && band_volume_rule != "exact" &&
414 band_volume_rule != "front_flux" && band_volume_rule != "exact_swept" &&
415 band_volume_rule != "surface") {
416 log_and_throw_error(
417 "EXPERIMENTAL_band_volume_rule is \"corner_bound\", \"exact\", \"front_flux\", "
418 "\"exact_swept\" or \"surface\", not \"{}\"",
420 }
421 // The gate sums the cells' tet_energy(); under "surface" the offset term lives on the
422 // front's faces, which it would not see.
423 if (split_energy_gate && band_volume_rule == "surface") {
424 log_and_throw_error(
425 "EXPERIMENTAL_split_energy_gate compares the cells' energy, which under "
426 "EXPERIMENTAL_band_volume_rule \"surface\" has no offset term: leave it out");
427 }
428 settle_stall_eps = json_params["settle_stall_eps"];
429 settle_test = json_params["EXPERIMENTAL_settle_test"];
430 if (settle_test != "stall" && settle_test != "bar") {
431 log_and_throw_error(
432 "EXPERIMENTAL_settle_test is \"stall\" or \"bar\", not \"{}\"",
434 }
435 if (band_volume_rule != "surface" && settle_test != "stall") {
436 log_and_throw_error(
437 "EXPERIMENTAL_settle_test \"{}\" is read only under "
438 "EXPERIMENTAL_band_volume_rule \"surface\" (this config has \"{}\"): leave it "
439 "out",
442 }
443 if (settle_test == "bar" && settle_stall_eps != 0.1) {
444 log_and_throw_error(
445 "settle_stall_eps ({}) is read only by EXPERIMENTAL_settle_test \"stall\": leave "
446 "it out under \"bar\"",
448 }
449 if (band_volume_rule != "surface" && settle_stall_eps != 0.1) {
450 log_and_throw_error(
451 "settle_stall_eps ({}) is read only under EXPERIMENTAL_band_volume_rule "
452 "\"surface\" (this config has \"{}\"): leave it out",
455 }
456 settle_refinement = json_params["EXPERIMENTAL_settle_refinement"];
457 gap_refinement = json_params["EXPERIMENTAL_gap_refinement"];
459 log_and_throw_error("EXPERIMENTAL_gap_refinement needs EXPERIMENTAL_settle_refinement");
460 }
461 damping = json_params["EXPERIMENTAL_damping"];
462 damping_factor = json_params["EXPERIMENTAL_damping_factor"];
463 if (!(damping_factor > 0.)) {
464 log_and_throw_error("EXPERIMENTAL_damping_factor must be > 0, not {}", damping_factor);
465 }
466 // 0.5 is the spec's default: a factor set to anything else means damping was intended.
467 if (!damping && damping_factor != 0.5) {
468 log_and_throw_error(
469 "EXPERIMENTAL_damping_factor {} needs EXPERIMENTAL_damping true",
471 }
472 optimize_construction = json_params["optimize_construction"];
473 growth_mode = json_params["growth_mode"];
474 linear_growth_steps = json_params["linear_growth_steps"];
475 smart_growth_attempts = json_params["smart_growth_attempts"];
476 smart_growth_max_fails = json_params["smart_growth_max_fails"];
477 if (growth_mode != "none" && growth_mode != "linear" && growth_mode != "smart" &&
478 growth_mode != "individual_op_reject") {
479 log_and_throw_error(
480 "growth_mode is \"none\", \"linear\", \"smart\" or \"individual_op_reject\", "
481 "not \"{}\"",
483 }
484 // The spec's defaults: a value set to anything else means that mode was intended.
485 if (growth_mode != "linear" && linear_growth_steps != 4) {
486 log_and_throw_error(
487 "linear_growth_steps {} needs growth_mode \"linear\", not \"{}\"",
490 }
491 if (growth_mode != "smart" &&
493 log_and_throw_error(
494 "smart_growth_attempts {} / smart_growth_max_fails {} need growth_mode \"smart\", "
495 "not \"{}\"",
499 }
500 // Only max_marchable_fallback traces the front below the target, where growth starts;
501 // midpoint_fallback never does.
502 if (growth_mode != "none" && construction_mode != "max_marchable_fallback") {
503 log_and_throw_error(
504 "growth_mode \"{}\" needs construction_mode \"max_marchable_fallback\", not "
505 "\"{}\": growth starts at the distance the march traced the front to, and only "
506 "that mode traces below target_distance",
509 }
510 if (growth_mode != "none" && init_optimize) {
511 log_and_throw_error(
512 "growth_mode \"{}\" and init_optimize both decide the loops that follow a march "
513 "below target_distance: set only one of them",
515 }
516 foldover_angle = json_params["foldover_angle"];
517 allow_mult_comp = json_params["TESTING_allow_mult_comp"];
518 settle_mark = json_params["EXPERIMENTAL_settle_mark"];
519 if (settle_mark != "max" && settle_mark != "rms" && settle_mark != "vertex" &&
520 settle_mark != "vertex_edge" && settle_mark != "vertex_split" &&
521 settle_mark != "stencil") {
522 log_and_throw_error(
523 "EXPERIMENTAL_settle_mark is \"max\", \"rms\", \"vertex\", \"vertex_edge\", "
524 "\"vertex_split\" or \"stencil\", not \"{}\"",
526 }
527 output_path = json_params["output"];
528 save_vtu = json_params["save_vtu"];
529 phi_grid_resolution = json_params["phi_grid_resolution"];
530
531 num_threads = json_params["num_threads"];
532 max_iterations = json_params["max_iterations"];
533
534 min_edge_length = json_params["min_edge_length"];
535 min_edge_length_rel = json_params["min_edge_length_rel"];
536
537 min_sizing_scalar = json_params["min_sizing_scalar"];
538 max_sizing_scalar = json_params["max_sizing_scalar"];
539 sizing_gradation = json_params["sizing_gradation"];
540 sizing_gradation_mode = json_params["sizing_gradation_mode"];
541 pre_smooth = json_params["pre_smooth"];
542
543 // ---- inherited from wmtk::OptimizerParameters ----
544 debug_output = json_params["DEBUG_output"];
545 debug_remote_output = json_params["DEBUG_remote_output"];
546 // DEBUG_remote_output overrides DEBUG_output: everything debug output computes is on, and
547 // the frames follow the remote schedule (TopoOffsetTetMesh::remote_frames()).
548 if (debug_remote_output) debug_output = true;
549 debug_stencil = json_params["DEBUG_stencil"];
550 if (debug_stencil && !debug_output) {
551 log_and_throw_error(
552 "DEBUG_stencil writes a file beside every debug frame: it needs DEBUG_output (or "
553 "DEBUG_remote_output)");
554 }
555 lr = json_params["length_rel"];
556 l = json_params["length"];
557 stop_energy = json_params["stop_energy"];
558 num_smoothing_passes = json_params["num_smoothing_passes"];
559 interleaved_smoothing = json_params["interleaved_smoothing"];
560 interleaved_smoothing_passes = json_params["interleaved_smoothing_passes"];
561 colored_smoothing = json_params["colored_smoothing"];
562 split_high_valence_threshold = json_params["split_high_valence_threshold"];
563 // skip_good_regions is deliberately not exposed: it would restrict a smoothing pass to
564 // cells still far from stop_energy, but the smoother is what places the offset boundary,
565 // so a well-shaped yet badly-placed patch is exactly what must not be skipped.
566 // Every key of the coarsening group is copied here, not just the on/off switch: declaring
567 // a key in the spec only makes jse inject its default into the json, so a key nothing
568 // copies into this struct silently keeps whatever OptimizerParameters holds.
569 coarsen_pass = json_params["coarsen_pass"];
570 coarsen_unbounded = json_params["coarsen_unbounded"];
571 coarsen_local_smoothing_passes = json_params["coarsen_local_smoothing_passes"];
572 coarsen_smooth_ring = json_params["coarsen_smooth_ring"];
573 coarsen_global_smoothing_passes = json_params["coarsen_global_smoothing_passes"];
574 coarsen_max_rounds = json_params["coarsen_max_rounds"];
575 coarsen_guard = json_params["EXPERIMENTAL_coarsen_guard"];
576 if (coarsen_guard != "bar" && coarsen_guard != "no_rise") {
577 log_and_throw_error(
578 "EXPERIMENTAL_coarsen_guard is \"bar\" or \"no_rise\", not \"{}\"",
580 }
581 if (coarsen_guard != "bar" && !coarsen_pass) {
582 log_and_throw_error(
583 "EXPERIMENTAL_coarsen_guard \"{}\" is read only with coarsen_pass true: set it, or "
584 "leave the guard out",
586 }
587 stuck_refine_stall_eps = json_params["stuck_refine_stall_eps"];
588 stuck_refine_cooldown = json_params["stuck_refine_cooldown"];
589 stuck_refine_num_worst = json_params["stuck_refine_num_worst"];
590 stuck_refine_rings = json_params["stuck_refine_rings"];
591 stuck_refine_factor = json_params["stuck_refine_factor"];
592 stuck_refine_min_scalar = json_params["stuck_refine_min_scalar"];
593 stuck_refine_gradation = json_params["stuck_refine_gradation"];
594 stuck_refine_force_split = json_params["stuck_refine_force_split"];
595 offset_amips_weight = json_params["offset_amips_weight"];
596 debug_crossings = json_params["DEBUG_crossings"];
597 front_pocket_split = json_params["EXPERIMENTAL_front_pocket_split"];
598 front_spanned_split = json_params["EXPERIMENTAL_front_spanned_split"];
599 exit_no_folds = json_params["EXPERIMENTAL_exit_no_folds"];
600 label_cleanup = json_params["EXPERIMENTAL_label_cleanup"];
601 if (label_cleanup != "off" && label_cleanup != "one_sided" &&
602 label_cleanup != "one_sided_tips" && label_cleanup != "one_sided_at_target" &&
603 label_cleanup != "energy_decrease") {
604 log_and_throw_error(
605 "EXPERIMENTAL_label_cleanup is \"off\", \"one_sided\", \"one_sided_tips\", "
606 "\"one_sided_at_target\" or \"energy_decrease\", not \"{}\"",
608 }
609 // "energy_decrease" reads the face term of EXPERIMENTAL_band_volume_rule "surface".
610 if (label_cleanup == "energy_decrease" && band_volume_rule != "surface") {
611 log_and_throw_error(
612 "EXPERIMENTAL_label_cleanup \"energy_decrease\" needs "
613 "EXPERIMENTAL_band_volume_rule \"surface\" (this config has \"{}\")",
615 }
616 // The "one_sided" modes read d through the exact band integral, which
617 // init_offset_potential() builds under the "exact", "front_flux" and "surface" rules,
618 // EXPERIMENTAL_settle_mark "max" or DEBUG_output: refused here rather than at the first
619 // clean-up.
620 if (label_cleanup != "off" && label_cleanup != "energy_decrease" &&
621 band_volume_rule == "corner_bound" && !(settle_refinement && settle_mark == "max") &&
622 !debug_output) {
623 log_and_throw_error(
624 "EXPERIMENTAL_label_cleanup \"{}\" reads the distance through the exact band "
625 "integral, which EXPERIMENTAL_band_volume_rule \"corner_bound\" does not build: "
626 "set "
627 "the clean-up \"off\", or another rule",
629 }
630 // The clean-up runs in loop_group()'s EXPERIMENTAL_front_pocket_split path, between the
631 // operation passes and the condition-1 splits before the first smoothing pass; the other
632 // path has no such point.
633 if (label_cleanup != "off" && !front_pocket_split) {
634 log_and_throw_error(
635 "EXPERIMENTAL_label_cleanup \"{}\" needs EXPERIMENTAL_front_pocket_split true: it "
636 "runs right before the condition-1 splits that precede the first smoothing pass "
637 "of each operation group",
639 }
640 // "vertex_split" splits its edges in loop_group()'s EXPERIMENTAL_front_pocket_split path,
641 // before the first smoothing pass of the next turn; the other path has no such point.
642 if (settle_mark == "vertex_split" && !front_pocket_split) {
643 log_and_throw_error(
644 "EXPERIMENTAL_settle_mark \"vertex_split\" needs EXPERIMENTAL_front_pocket_split "
645 "true: its splits are made right before the first smoothing pass of the next turn");
646 }
647 sizing_collapse_min = json_params["sizing_collapse_min"];
648 use_rest_pose = json_params["use_rest_pose"];
649 max_rounds = json_params["max_rounds"];
650 w_amips = json_params["w_amips"];
651 smoothing_mode = json_params["smoothing_mode"];
652 project_line_search_steps = json_params["project_line_search_steps"];
653 project_line_search_nested_steps = json_params["project_line_search_nested_steps"];
654 w_envelope = 1. - w_amips;
655 perform_sanity_checks = json_params["perform_sanity_checks"];
656 }
657
658 void init(const VectorXd& min_, const VectorXd& max_)
659 {
660 box_min = min_;
661 box_max = max_;
662
663 // Not a user knob: a topological offset preserves the topology of the region it wraps, so
664 // the shared collapse must always apply the substructure link condition, or a collapse
665 // across a thin band pinches the two sides together and the region stops being manifold.
666 // Set here because tetwild and simwild leave the flag off.
667 preserve_topology = true;
668
669 // Fills diag_l, l/lr and splitting_l2 / collapsing_l2. It also derives eps from epsr,
670 // which the offset never reads: its envelope tolerance is m_envelope_eps, set on the mesh.
671 init_lengths_from_diagonal((max_ - min_).norm());
672
673 if (target_distance > 0) {
674 target_distance_rel = target_distance / diag_l;
675 } else {
676 target_distance = target_distance_rel * diag_l;
677 }
678
679 // An ordinary relative length: it bounds how far a region boundary may drift in space, so
680 // the bounding box is the right reference.
681 if (envelope_size > 0) {
682 envelope_size_rel = envelope_size / diag_l;
683 } else {
684 envelope_size = envelope_size_rel * diag_l;
685 }
686
687 // The convergence epsilon, absolute or relative; relative to target_distance (resolved
688 // above), the offset it measures. A growth stage changes target_distance later, never
689 // this.
690 if (front_conv > 0) {
691 front_conv_rel = front_conv / target_distance;
692 } else {
693 front_conv = front_conv_rel * target_distance;
694 }
695
696 // An epsilon coarser than the offset it measures decides nothing: every front vertex is
697 // "placed" and every face "resolved" from the first turn, whatever the offset looks like.
698 // Checked on the resolved ABSOLUTE values, so it catches the mistake whichever of the two
699 // forms the config used to state it.
700 if (front_conv > target_distance) {
701 log_and_throw_error(
702 "front_conv {} must be <= target_distance {}: the convergence epsilon cannot be "
703 "coarser than the offset distance it measures, or every front face reads as "
704 "resolved from the first turn",
705 front_conv,
706 target_distance);
707 }
708
709 // The offset surface's envelope is envelope_size's (no key of its own since 2026-10-20);
710 // the two fields only for the 2D code, which still reads them.
711 offset_envelope = envelope_size;
712 offset_envelope_rel = envelope_size_rel;
713
714 // l_min is relative to the offset distance rather than the bounding box: it is the offset
715 // that has to be resolved. See the declaration.
716 if (min_edge_length_rel < 0) {
717 // The envelope eps expressed as a multiple of target_distance, which is what this
718 // wants: envelope_size, the offset surface's envelope too since offset_envelope went
719 // (2026-10-20), resolved to an absolute length above.
720 min_edge_length_rel = std::max(envelope_size / std::max(target_distance, 1e-16), 1e-12);
721 }
722 if (min_edge_length < 0) {
723 min_edge_length = min_edge_length_rel * target_distance;
724 } else {
725 min_edge_length_rel = min_edge_length / std::max(target_distance, 1e-16);
726 }
727 }
728};
729} // namespace wmtk::components::topological_offset
The parameters tetwild, triwild and simwild all share.
Definition OptimizerParameters.h:29
int project_line_search_nested_steps
Partial-projection bisections tried after it gives up; 0 disables that pass.
Definition OptimizerParameters.h:178
double w_amips
Definition OptimizerParameters.h:172
std::string smoothing_mode
"projected" or "exact"; see SmoothVertexOptions::SmoothingMode.
Definition OptimizerParameters.h:174
bool coarsen_pass
Definition OptimizerParameters.h:238
int coarsen_global_smoothing_passes
Definition OptimizerParameters.h:303
int coarsen_max_rounds
Definition OptimizerParameters.h:327
void init_lengths_from_diagonal(const double diag)
Derive the edge-length and envelope quantities from the bounding-box diagonal.
Definition OptimizerParameters.h:357
bool coarsen_unbounded
Definition OptimizerParameters.h:262
int split_high_valence_threshold
Definition OptimizerParameters.h:72
int coarsen_local_smoothing_passes
Definition OptimizerParameters.h:280
int project_line_search_steps
Bisections tried before the projected search gives up. See SmoothVertexOptions.
Definition OptimizerParameters.h:176
bool colored_smoothing
Definition OptimizerParameters.h:201
int coarsen_smooth_ring
Definition OptimizerParameters.h:296
int num_smoothing_passes
Number and placement of smoothing passes in the shared Wild optimization driver.
Definition OptimizerParameters.h:188
What the offset needs on top of the parameters every wmtk optimizer shares.
Definition Parameters.h:24
std::string label_cleanup
Definition Parameters.h:116
bool gap_refinement
Definition Parameters.h:239
bool allow_mult_comp
Definition Parameters.h:290
bool split_energy_gate
Definition Parameters.h:193
bool damping
Definition Parameters.h:245
bool use_rest_pose
Definition Parameters.h:124
bool pre_smooth
Definition Parameters.h:351
double foldover_angle
Definition Parameters.h:279
int repulsion_rounds
Definition Parameters.h:190
bool optimize_construction
Definition Parameters.h:253
int smart_growth_attempts
growth_mode "smart": the turns an attempt gets to converge. At least 1 (the spec's min).
Definition Parameters.h:270
double offset_amips_weight
Definition Parameters.h:91
std::string offset_field
"smooth" (Phi level set) or "euclidean" (exact distance)
Definition Parameters.h:53
double front_conv_frac() const
Definition Parameters.h:78
int max_iterations
Definition Parameters.h:319
bool sizing_collapse_min
Definition Parameters.h:120
int max_rounds
Definition Parameters.h:127
int smart_growth_max_fails
Definition Parameters.h:273
std::string growth_mode
Definition Parameters.h:265
bool optimize_offset
Definition Parameters.h:38
bool debug_crossings
Log-only: per pass, the front vertices whose ring measure crosses the bar (see the spec).
Definition Parameters.h:93
std::string settle_mark
Definition Parameters.h:308
bool debug_stencil
Definition Parameters.h:287
bool init_optimize
Definition Parameters.h:177
std::string stencil_type
Definition Parameters.h:154
int linear_growth_steps
Definition Parameters.h:268
double damping_factor
Definition Parameters.h:248
bool settle_refinement
Definition Parameters.h:233
std::string front_measure
Definition Parameters.h:163
bool front_spanned_split
Definition Parameters.h:99
double settle_stall_eps
Definition Parameters.h:218
std::string sizing_gradation_mode
Definition Parameters.h:348
bool debug_remote_output
Definition Parameters.h:284
int repulsion_smoothing_passes
Definition Parameters.h:186
bool front_pocket_split
Definition Parameters.h:96
std::string coarsen_guard
Definition Parameters.h:110
std::string construction_mode
Definition Parameters.h:172
double sphere_trace_target_rel_tol
Definition Parameters.h:173
bool exit_no_folds
Definition Parameters.h:102
std::string settle_test
Definition Parameters.h:223
std::string band_volume_rule
Definition Parameters.h:213
bool save_offset_correspondence
Definition Parameters.h:167