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
20{
21 ExpressionPtr offset_selection;
22 std::set<std::string> offset_output_tag;
23 std::set<std::string> protected_tags;
24 bool offset_in;
25 bool offset_out;
26 double target_distance;
27 double target_distance_rel;
28 // Turn a non-converged run into a hard error instead of a warning. Off by default: a run that
29 // misses the target is still a usable offset, and the warnings name the criterion that failed.
30 // Integration tests set it true so a convergence regression fails rather than warns.
31 bool throw_on_nonconvergence;
32 // Half-width of the envelope that contains every tag-region boundary during optimization.
33 // Absolute; if < 0, computed from envelope_size_rel (relative to the bbox diagonal).
34 double envelope_size;
35 double envelope_size_rel;
36
37 // ---- the smooth offset potential ----
38 // Support radius of the potential, as a multiple of target_distance. Must be > 1: the offset
39 // level set has to lie strictly inside the support, or the vertices on it get no gradient. A
40 // band vertex that travels past the support is a hard error, not a silently frozen vertex.
41 double offset_dhat_factor;
42 std::string offset_field;
46 // Convergence, 3D: the bound on the gradient of the offset energy E = (Phi(x) - c)^2 over
47 // reachable band vertices, as a fraction of target_distance. The gradient rather than the
48 // residual, because it is the stationarity condition of what Phase B minimises and so says the
49 // same thing for any Phi, with no length-scale conversion. It is the component's only residual
50 // scale: offset_residual_tolerance() is half of it times target_distance, and Phase A's offset
51 // envelope is offset_envelope_rel times that. It gates at band vertices and at interior samples
52 // of every band face alike -- a face whose corners sit on the level set can still cut across it
53 // -- and the two are reported separately: at-vertex wants smoothing, in-face wants refinement.
54 //
55 // Convergence, 2D -- same key, deliberately different meaning: this fraction of a measured
56 // reference, tested as the full gradient norm at vertices. The run criterion and every Phase B
57 // placement stop are that one identical test, so the run converges exactly when every visit
58 // stops immediately. Edge-interior samples are a reported diagnostic here and never gate.
59 double front_conv_rel;
60 // Which convergence test gates the run, used identically by the loop's vertex test and Phase
61 // B's pass stop. F is the vertex's Phase B objective, g its gradient, H its Gauss-Newton
62 // Hessian, n its move direction; all three compare against front_conv_rel. 2D only -- 3D never
63 // reads this key and always uses the gradient bound above. See front_vertex_conv_ratio().
64 // "step_size_rel" (the default): the remaining 1-D Newton step, |n.g| / (n^T H n), against
65 // rel x target_distance.
66 // "decrement": the Newton decrement, half of (n.g)^2 / (n^T H n), against rel x F.
67 // "gradient_norm_rel": |n.g| against rel x the reference gradient, measured once on the
68 // band as constructed.
70 // Phase B places a front vertex by a one-dimensional solve along its field normal
71 // n = grad Phi / |grad Phi| -- same objective, solver and accept test, restricted to the line
72 // x0 + s n -- instead of a free 2-D solve. Where a vertex sits along the front carries no
73 // offset information, and in the free solve that tangential motion made fronts slide and fold
74 // where two of them meet.
75 bool front_normal_projection = true;
80 bool sizing_collapse_min = false;
83 bool deform_others = true;
86 int max_rounds = 40;
87 // Points sampled in the interior of each band simplex when measuring the offset's residual;
88 // k = 1 is the midpoint, and 0 measures only at band vertices, which is blind to a band whose
89 // vertices sit on the level set while its simplices cut across it. 2D samples each band edge
90 // at i/(k+1); 3D samples each offset-surface face, k being the density (1, 3, 6, 10 points for
91 // k = 1..4). See TopoOffsetTriMesh::offset_edge_samples,
92 // TopoOffsetTetMesh::offset_face_samples.
93 int offset_residual_samples;
94 bool sorted_marching;
95 std::string output_path; // no extension
96 bool save_vtu;
97
98 // Samples per side of the grid the smooth offset potential is written on, beside the result,
99 // for the viewer. 0 disables it. 2D only.
100 int phi_grid_resolution;
101
102 int num_threads; // number of threads for parallel execution (smoothing, collapse). 0 = serial
107 double ab_smooth_tol;
124
125 // l_min from the paper: the shortest edge the sizing field may ask for, given as a multiple of
126 // target_distance rather than of the bounding box because that is the scale the offset has;
127 // min_edge_length is derived from min_edge_length_rel in init() when negative. A floor on
128 // refinement, so raising it makes the result coarser. When not given, it falls back to the
129 // Phase A envelope eps, following TetWild: a surface pinned only to within eps cannot buy
130 // fidelity from shorter edges, so this is a runaway rail, not a resolution setting.
131 double min_edge_length;
132 double min_edge_length_rel;
133
134 // ---- sizing field ----
135 // bounds for VertexAttributes::m_sizing_scalar
136 double min_sizing_scalar;
137 double max_sizing_scalar;
138 // gradation cap: neighboring vertices' sizing scalars may differ by at most this factor,
139 // enforced by propagating the refinement outward (monotone, only ever lowers a
140 // neighbor's scalar). <= 1 disables gradation entirely.
141 double sizing_gradation;
142
143 VectorXd box_min;
144 VectorXd box_max;
145
146 Parameters() = default;
147
148 Parameters(const nlohmann::json& json_params)
149 {
150 for (const std::string& tag : json_params["offset_output_tags"]) {
151 if (tag == "ambient") {
152 logger().warn(
153 "'ambient' tag cannot be given explicitly to offset_output_tags, ignoring. To "
154 "set offset to 'ambient', pass offset_output_tags=[].");
155 continue;
156 }
157 offset_output_tag.insert(tag);
158 }
159 for (const std::string& tag : json_params["protected_tags"]) {
160 if (tag == "ambient") {
161 logger().warn("'ambient' tag cannot be protected, ignoring.");
162 continue;
163 }
164 protected_tags.insert(tag);
165 }
166 offset_in = json_params["offset_in"];
167 offset_out = json_params["offset_out"];
168 target_distance = json_params["target_distance"];
169 target_distance_rel = json_params["target_distance_rel"];
170 throw_on_nonconvergence = json_params["throw_on_nonconvergence"];
171 envelope_size = json_params["envelope_size"];
172 envelope_size_rel = json_params["envelope_size_rel"];
173 offset_dhat_factor = json_params["offset_dhat_factor"];
174 offset_field = json_params["offset_field"];
175 front_conv_rel = json_params["front_conv_rel"];
176 front_conv_criterion = json_params["front_conv_criterion"];
177 offset_residual_samples = json_params["offset_residual_samples"];
178
179 sorted_marching = json_params["sorted_marching"];
180 output_path = json_params["output"];
181 save_vtu = json_params["save_vtu"];
182 phi_grid_resolution = json_params["phi_grid_resolution"];
183
184 num_threads = json_params["num_threads"];
185 max_iterations = json_params["max_iterations"];
186 ab_smooth_max_passes = json_params["ab_smooth_max_passes"];
187 ab_smooth_tol = json_params["ab_smooth_tol"];
188 vertex_grad_tol_rel = json_params["vertex_grad_tol_rel"];
189 offset_envelope_rel = json_params["offset_envelope_rel"];
190
191 min_edge_length = json_params["min_edge_length"];
192 min_edge_length_rel = json_params["min_edge_length_rel"];
193
194 min_sizing_scalar = json_params["min_sizing_scalar"];
195 max_sizing_scalar = json_params["max_sizing_scalar"];
196 sizing_gradation = json_params["sizing_gradation"];
197
198 // ---- inherited from wmtk::OptimizerParameters ----
199 debug_output = json_params["DEBUG_output"];
200 debug_output_per_pass = json_params["DEBUG_output_per_pass"];
201 lr = json_params["length_rel"];
202 l = json_params["length"];
203 stop_energy = json_params["stop_energy"];
204 num_smoothing_passes = json_params["num_smoothing_passes"];
205 interleaved_smoothing = json_params["interleaved_smoothing"];
206 interleaved_smoothing_passes = json_params["interleaved_smoothing_passes"];
207 split_high_valence_threshold = json_params["split_high_valence_threshold"];
208 // skip_good_regions is deliberately not exposed: it would restrict a smoothing pass to
209 // cells still far from stop_energy, but the smoother is what places the offset boundary,
210 // so a well-shaped yet badly-placed patch is exactly what must not be skipped.
211 // Every key of the coarsening group is copied here, not just the on/off switch: declaring
212 // a key in the spec only makes jse inject its default into the json, so a key nothing
213 // copies into this struct silently keeps whatever OptimizerParameters holds.
214 coarsen_pass = json_params["coarsen_pass"];
215 coarsen_unbounded = json_params["coarsen_unbounded"];
216 coarsen_local_smoothing_passes = json_params["coarsen_local_smoothing_passes"];
217 coarsen_smooth_ring = json_params["coarsen_smooth_ring"];
218 coarsen_global_smoothing_passes = json_params["coarsen_global_smoothing_passes"];
219 coarsen_max_rounds = json_params["coarsen_max_rounds"];
220 stuck_refine_stall_eps = json_params["stuck_refine_stall_eps"];
221 stuck_refine_cooldown = json_params["stuck_refine_cooldown"];
222 stuck_refine_num_worst = json_params["stuck_refine_num_worst"];
223 stuck_refine_rings = json_params["stuck_refine_rings"];
224 stuck_refine_factor = json_params["stuck_refine_factor"];
225 stuck_refine_min_scalar = json_params["stuck_refine_min_scalar"];
226 stuck_refine_gradation = json_params["stuck_refine_gradation"];
227 stuck_refine_force_split = json_params["stuck_refine_force_split"];
228 front_normal_projection = json_params["front_normal_projection"];
229 front_alignment_energy = json_params["front_alignment_energy"];
230 sizing_collapse_min = json_params["sizing_collapse_min"];
231 deform_others = json_params["deform_others"];
232 max_rounds = json_params["max_rounds"];
233 pre_optimize_input = json_params["pre_optimize_input"];
234 pre_optimize_sizing_from_edges = json_params["pre_optimize_sizing_from_edges"];
235 w_amips = json_params["w_amips"];
236 smoothing_mode = json_params["smoothing_mode"];
237 project_line_search_steps = json_params["project_line_search_steps"];
238 project_line_search_nested_steps = json_params["project_line_search_nested_steps"];
239 smooth_quality_veto = json_params["smooth_quality_veto"];
240 w_envelope = 1. - w_amips;
241 perform_sanity_checks = json_params["perform_sanity_checks"];
242 }
243
244 void init(const VectorXd& min_, const VectorXd& max_)
245 {
246 box_min = min_;
247 box_max = max_;
248
249 // Not a user knob: a topological offset preserves the topology of the region it wraps, so
250 // the shared collapse must always apply the substructure link condition, or a collapse
251 // across a thin band pinches the two sides together and the region stops being manifold.
252 // Set here because tetwild and simwild leave the flag off.
253 preserve_topology = true;
254
255 // Fills diag_l, l/lr and splitting_l2 / collapsing_l2. It also derives eps from epsr,
256 // which the offset never reads: its envelope tolerance is m_envelope_eps, set on the mesh.
257 init_lengths_from_diagonal((max_ - min_).norm());
258
259 if (target_distance > 0) {
260 target_distance_rel = target_distance / diag_l;
261 } else {
262 target_distance = target_distance_rel * diag_l;
263 }
264
265 // An ordinary relative length: it bounds how far a region boundary may drift in space, so
266 // the bounding box is the right reference.
267 if (envelope_size > 0) {
268 envelope_size_rel = envelope_size / diag_l;
269 } else {
270 envelope_size = envelope_size_rel * diag_l;
271 }
272
273 // l_min is relative to the offset distance rather than the bounding box: it is the offset
274 // that has to be resolved. See the declaration.
275 if (min_edge_length_rel < 0) {
276 // The envelope eps as a multiple of target_distance, which is what offset_envelope_rel
277 // already is, so there is no conversion left to do.
278 min_edge_length_rel = std::max(offset_envelope_rel, 1e-12);
279 }
280 if (min_edge_length < 0) {
281 min_edge_length = min_edge_length_rel * target_distance;
282 } else {
283 min_edge_length_rel = min_edge_length / std::max(target_distance, 1e-16);
284 }
285 }
286};
287} // 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:177
double w_amips
Definition OptimizerParameters.h:171
std::string smoothing_mode
"projected" or "exact"; see SmoothVertexOptions::SmoothingMode.
Definition OptimizerParameters.h:173
bool coarsen_pass
Definition OptimizerParameters.h:213
int coarsen_global_smoothing_passes
Definition OptimizerParameters.h:278
int coarsen_max_rounds
Definition OptimizerParameters.h:302
void init_lengths_from_diagonal(const double diag)
Derive the edge-length and envelope quantities from the bounding-box diagonal.
Definition OptimizerParameters.h:332
bool coarsen_unbounded
Definition OptimizerParameters.h:237
int split_high_valence_threshold
Definition OptimizerParameters.h:71
int coarsen_local_smoothing_passes
Definition OptimizerParameters.h:255
int project_line_search_steps
Bisections tried before the projected search gives up. See SmoothVertexOptions.
Definition OptimizerParameters.h:175
int coarsen_smooth_ring
Definition OptimizerParameters.h:271
bool smooth_quality_veto
Definition OptimizerParameters.h:183
int num_smoothing_passes
Number and placement of smoothing passes in the shared Wild optimization driver.
Definition OptimizerParameters.h:187
What the offset needs on top of the parameters every wmtk optimizer shares.
Definition Parameters.h:20
bool front_alignment_energy
Definition Parameters.h:76
double offset_envelope_rel
Definition Parameters.h:123
bool pre_optimize_sizing_from_edges
Definition Parameters.h:120
std::string front_conv_criterion
2D: gradient_norm_rel | step_size_rel | decrement
Definition Parameters.h:69
std::string offset_field
Definition Parameters.h:42
int max_iterations
Definition Parameters.h:105
bool pre_optimize_input
Definition Parameters.h:116
bool sizing_collapse_min
Definition Parameters.h:80
int ab_smooth_max_passes
cap on Phase B smoothing passes; negative = uncapped (default)
Definition Parameters.h:106
int max_rounds
Definition Parameters.h:86
double vertex_grad_tol_rel
Definition Parameters.h:113
bool deform_others
Definition Parameters.h:83
bool debug_output_per_pass
Definition Parameters.h:45