///|
/// Typed errors distinguish exhausted grids from invalid input and failures.
pub(all) enum SearchError {
InvalidInput(String)
NumericalFailure(String)
IndeterminateGeometry(String)
NoCandidate(Int32)
}
///|
/// First accepted point on a finite grid, not a continuous optimum.
pub(all) struct SearchResult {
geometry : Geometry
analysis : Analysis
tested : Int32
grid_step : Double
verification_resolution : Int32
}
///| Reuse geometry-independent motion meshes; reject candidates by scalar metrics.
///|
/// The coarse pass allocates no coordinates; the fine pass returns the metrics and
/// the coordinates that topology verification consumes.
pub fn search_base(
p : Program,
roller_radius : Double,
offset : Double,
lower : Double,
upper : Double,
intervals : Int32,
resolution : Int32,
pressure_limit : Double,
omega : Double,
) -> Result[SearchResult, SearchError] {
if !finite(lower) ||
!finite(upper) ||
lower < Base_radius_min ||
upper > Geometry_length_max ||
upper < lower ||
intervals < 1 ||
intervals > Search_intervals_max ||
resolution < Resolution_min ||
resolution > Search_resolution_max {
return Err(
InvalidInput(
"search needs ordered radius bounds in [\{Base_radius_min}, \{Geometry_length_max}], 1..\{Search_intervals_max} intervals and \{Resolution_min}..\{Search_resolution_max} resolution",
),
)
}
match validate_analysis(pressure_limit, omega) {
Err(e) => return Err(InvalidInput(e))
Ok(_) => ()
}
match validate_geometry({ base_radius: upper, roller_radius, offset, }) {
Err(e) => return Err(InvalidInput(e))
Ok(_) => ()
}
let coarse = match motion_mesh(p, resolution) {
Ok(m) => m
Err(e) => return Err(NumericalFailure(e))
}
let fine = match motion_mesh(p, resolution * Search_refinement) {
Ok(m) => m
Err(e) => return Err(NumericalFailure(e))
}
let step = (upper - lower) / intervals.to_double()
for i = 0; i <= intervals; i = i + 1 {
let g : Geometry = {
base_radius: lower + i.to_double() * step,
roller_radius,
offset,
}
if validate_geometry(g) is Err(_) {
continue
}
let a = match scan_mesh(coarse, g, pressure_limit, omega, false) {
Ok(a) => a
Err(e) => return Err(NumericalFailure(e))
}
if !a.metrics.sampled_local_feasible {
continue
}
// A retained scan returns the same metrics as a metrics-only scan, so the
// fine mesh is scanned once. The cost is real: a candidate that passes the
// coarse filter but fails the fine one builds coordinates it discards.
let b = match scan_mesh(fine, g, pressure_limit, omega, true) {
Ok(a) => a
Err(e) => return Err(NumericalFailure(e))
}
if !b.metrics.sampled_local_feasible {
continue
}
let complete = finish_scan(b)
match complete.crossings {
Failed(e) => return Err(NumericalFailure(e))
Indeterminate(_, _) =>
return Err(IndeterminateGeometry("profile topology is indeterminate"))
_ => ()
}
if complete.sampled_feasible {
return Ok({
geometry: g,
analysis: complete,
tested: i + 1,
grid_step: step,
verification_resolution: resolution * Search_refinement,
})
}
}
Err(NoCandidate(intervals + 1))
}