use super::{
MAX_PLANES, MeetConstraint, MeetTierInput, SolveControl, SolveError, SolvedTier,
first_non_finite_tier,
solve::{PipelineResult, SolveContext},
};
use crate::geometry::stone_metrics::ExternalProportions;
use std::collections::BTreeMap;
pub const VERIFY_ACCEPT_TOL: f64 = 0.01;
const VERIFY_MAX_ROUNDS: usize = 4;
const VERIFY_ALT_LEVELS: [usize; 3] = [0, 2, 3];
const VERIFY_MIN_GAIN: f64 = 0.002;
const VERIFY_RUN_BUDGET: f64 = 1.0e10;
const VERIFY_MAX_RUNS: usize = 120;
const VERIFY_MAX_RUNS_CALIBRATED: usize = 170;
const ANCHOR_GRID: [f64; 4] = [0.7, 0.85, 1.2, 1.4];
const ANCHOR_FINE_STEPS: [f64; 4] = [0.93, 0.965, 1.035, 1.075];
const VERIFY_MAX_ROUNDS_CALIBRATED: usize = 8;
enum RepairMove {
Level(usize, usize),
Anchor(usize, f64),
}
#[derive(Debug, Clone, Copy)]
pub struct VerifiedSolveReport {
pub initial_score: f64,
pub score_after_calibration: f64,
pub final_score: f64,
pub accepted: bool,
pub overrides_applied: usize,
pub anchor_moves_applied: usize,
pub pipeline_runs: usize,
}
#[must_use]
pub fn solve_meet_points_verified(
gear_teeth_abs: u32,
tiers: &[MeetTierInput],
targets: &ExternalProportions,
adjustable_anchors: &[usize],
) -> (Vec<SolvedTier>, VerifiedSolveReport) {
match solve_meet_points_verified_with(
gear_teeth_abs,
tiers,
targets,
adjustable_anchors,
&SolveControl::default(),
) {
Ok(result) => result,
Err(SolveError::TooManyPlanes { .. } | SolveError::NonFiniteInput { .. }) => {
let ctx = SolveContext::new(gear_teeth_abs, tiers);
let report = VerifiedSolveReport {
initial_score: f64::INFINITY,
score_after_calibration: f64::INFINITY,
final_score: f64::INFINITY,
accepted: false,
overrides_applied: 0,
anchor_moves_applied: 0,
pipeline_runs: 0,
};
(ctx.failed_solved(), report)
}
Err(SolveError::Cancelled) => {
unreachable!("SolveControl::default() never sets cancel")
}
}
}
pub fn solve_meet_points_verified_with(
gear_teeth_abs: u32,
tiers: &[MeetTierInput],
targets: &ExternalProportions,
adjustable_anchors: &[usize],
control: &SolveControl<'_>,
) -> Result<(Vec<SolvedTier>, VerifiedSolveReport), SolveError> {
if let Some(err) = first_non_finite_tier(tiers) {
return Err(err);
}
let ctx = SolveContext::new(gear_teeth_abs, tiers);
if ctx.total_planes > MAX_PLANES {
return Err(SolveError::TooManyPlanes {
planes: ctx.total_planes,
max: MAX_PLANES,
});
}
let adjustable: Vec<usize> = {
let mut v: Vec<usize> = adjustable_anchors
.iter()
.copied()
.filter(|&i| i < tiers.len() && ctx.is_anchor[i])
.collect();
v.sort_unstable();
v.dedup();
v
};
let planes = ctx.total_planes as f64;
let run_cap = if adjustable.is_empty() {
VERIFY_MAX_RUNS
} else {
VERIFY_MAX_RUNS_CALIBRATED
};
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "a positive budget ratio; `as` saturates (NaN gives 0) and the clamp bounds the run count"
)]
let max_runs =
((VERIFY_RUN_BUDGET / (planes * planes * planes * planes)) as usize).clamp(1, run_cap);
let max_rounds = if adjustable.is_empty() {
VERIFY_MAX_ROUNDS
} else {
VERIFY_MAX_ROUNDS_CALIBRATED
};
let anchor_values: BTreeMap<usize, f64> = adjustable
.iter()
.map(|&i| {
let v = match &tiers[i].constraint {
MeetConstraint::ScaleReference(v) => v.abs(),
_ => 0.0,
};
(i, v)
})
.collect();
let level_overrides: BTreeMap<usize, usize> = BTreeMap::new();
let best = ctx.run_pipeline(&level_overrides, &anchor_values, control)?;
let best_score = ctx.config_score(&best.mast, targets);
let initial_score = best_score;
let mut search = VerifiedSearch {
ctx: &ctx,
targets,
adjustable: &adjustable,
max_runs,
level_overrides,
anchor_values,
best,
best_score,
runs: 1,
anchor_moves: 0,
};
search.coarse_anchor_grid(control)?;
let score_after_calibration = search.best_score;
for _ in 0..max_rounds {
if !search.greedy_round(control)? {
break;
}
}
let report = VerifiedSolveReport {
initial_score,
score_after_calibration,
final_score: search.best_score,
accepted: search.best_score <= VERIFY_ACCEPT_TOL,
overrides_applied: search.level_overrides.len(),
anchor_moves_applied: search.anchor_moves,
pipeline_runs: search.runs,
};
Ok((ctx.to_solved(&search.best), report))
}
struct VerifiedSearch<'a, 'c> {
ctx: &'c SolveContext<'a>,
targets: &'c ExternalProportions,
adjustable: &'c [usize],
max_runs: usize,
level_overrides: BTreeMap<usize, usize>,
anchor_values: BTreeMap<usize, f64>,
best: PipelineResult,
best_score: f64,
runs: usize,
anchor_moves: usize,
}
impl VerifiedSearch<'_, '_> {
fn done(&self) -> bool {
!self.best_score.is_finite()
|| self.best_score <= VERIFY_ACCEPT_TOL
|| self.runs >= self.max_runs
}
fn coarse_anchor_grid(&mut self, control: &SolveControl<'_>) -> Result<(), SolveError> {
for ai in self.adjustable {
let base = self.anchor_values[ai];
if base <= 1e-9 {
continue;
}
for &mult in &ANCHOR_GRID {
if self.done() {
return Ok(());
}
let mut trial_anchors = self.anchor_values.clone();
trial_anchors.insert(*ai, base * mult);
let trial =
self.ctx
.run_pipeline(&self.level_overrides, &trial_anchors, control)?;
self.runs += 1;
let score = self.ctx.config_score(&trial.mast, self.targets);
if score < self.best_score {
self.anchor_values = trial_anchors;
self.best = trial;
self.best_score = score;
self.anchor_moves += 1;
}
}
}
Ok(())
}
fn candidate_moves(&self) -> Vec<RepairMove> {
let mut moves: Vec<RepairMove> = Vec::new();
for i in 0..self.ctx.tiers.len() {
if self.ctx.is_anchor[i] || self.level_overrides.contains_key(&i) {
continue;
}
let Some((current_li, n_levels)) = self.best.last_pick[i] else {
continue;
};
for &alt in &VERIFY_ALT_LEVELS {
if alt != current_li && alt < n_levels {
moves.push(RepairMove::Level(i, alt));
}
}
}
for &ai in self.adjustable {
let base = self.anchor_values[&ai];
for &step in &ANCHOR_FINE_STEPS {
moves.push(RepairMove::Anchor(ai, base * step));
}
}
moves
}
fn commit(&mut self, mv: &RepairMove) {
match *mv {
RepairMove::Level(i, alt) => {
self.level_overrides.insert(i, alt);
}
RepairMove::Anchor(ai, val) => {
self.anchor_values.insert(ai, val);
self.anchor_moves += 1;
}
}
}
fn greedy_round(&mut self, control: &SolveControl<'_>) -> Result<bool, SolveError> {
if self.done() {
return Ok(false);
}
let moves = self.candidate_moves();
let mut round_best: Option<(usize, PipelineResult, f64)> = None;
for (mi, mv) in moves.iter().enumerate() {
if self.runs >= self.max_runs {
break;
}
let (trial_levels, trial_anchors) = match *mv {
RepairMove::Level(i, alt) => {
let mut l = self.level_overrides.clone();
l.insert(i, alt);
(l, self.anchor_values.clone())
}
RepairMove::Anchor(ai, val) => {
let mut a = self.anchor_values.clone();
a.insert(ai, val);
(self.level_overrides.clone(), a)
}
};
let trial = self
.ctx
.run_pipeline(&trial_levels, &trial_anchors, control)?;
self.runs += 1;
let score = self.ctx.config_score(&trial.mast, self.targets);
if score <= VERIFY_ACCEPT_TOL {
self.commit(mv);
self.best = trial;
self.best_score = score;
return Ok(false);
}
if score < self.best_score && round_best.as_ref().is_none_or(|r| score < r.2) {
round_best = Some((mi, trial, score));
}
}
Ok(match round_best {
Some((mi, result, score)) if score < self.best_score - VERIFY_MIN_GAIN => {
self.commit(&moves[mi]);
self.best = result;
self.best_score = score;
self.runs < self.max_runs
}
_ => false,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verified_search_calibrates_an_estimated_anchor() {
let s = std::f64::consts::FRAC_1_SQRT_2;
let tiers = vec![
MeetTierInput {
angle_deg: 90.0,
indices: vec![0.0, 1.0, 2.0, 3.0],
constraint: MeetConstraint::ScaleReference(1.0),
names: vec![],
},
MeetTierInput {
angle_deg: -0.0,
indices: vec![],
constraint: MeetConstraint::ScaleReference(0.5),
names: vec![],
},
MeetTierInput {
angle_deg: 45.0,
indices: vec![0.0, 1.0, 2.0, 3.0],
constraint: MeetConstraint::ScaleReference(0.5),
names: vec![],
},
];
let targets = ExternalProportions {
hw: Some(0.75),
..Default::default()
};
let (solved, report) = solve_meet_points_verified(4, &tiers, &targets, &[2]);
assert!(report.accepted, "report: {report:?}");
assert!(report.anchor_moves_applied >= 1, "report: {report:?}");
assert!(
report.score_after_calibration < report.initial_score,
"calibration must have improved the score: {report:?}"
);
assert!((solved[0].mast - 1.0).abs() < 1e-12);
assert!((solved[1].mast - 0.5).abs() < 1e-12);
let rel = (solved[2].mast - s).abs() / s;
assert!(
rel < 0.02,
"calibrated roof mast {} vs true {s} (rel {rel})",
solved[2].mast
);
}
#[test]
fn verified_search_accepts_a_matching_config_without_searching() {
let s = std::f64::consts::FRAC_1_SQRT_2;
let tiers = vec![
MeetTierInput {
angle_deg: 90.0,
indices: vec![0.0, 1.0, 2.0, 3.0],
constraint: MeetConstraint::ScaleReference(1.0),
names: vec![],
},
MeetTierInput {
angle_deg: -0.0,
indices: vec![],
constraint: MeetConstraint::ScaleReference(0.5),
names: vec![],
},
MeetTierInput {
angle_deg: 45.0,
indices: vec![0.0, 1.0, 2.0, 3.0],
constraint: MeetConstraint::ScaleReference(s),
names: vec![],
},
];
let targets = ExternalProportions {
hw: Some(0.75),
..Default::default()
};
let (solved, report) = solve_meet_points_verified(4, &tiers, &targets, &[]);
assert!(report.accepted);
assert_eq!(report.pipeline_runs, 1);
assert_eq!(report.overrides_applied, 0);
assert_eq!(report.anchor_moves_applied, 0);
let plain = super::super::solve_meet_points(4, &tiers);
for (a, b) in solved.iter().zip(&plain) {
assert_eq!(a.mast.to_bits(), b.mast.to_bits());
}
}
}