use super::state::EditorState;
use crate::{DeepSolveTierRow, OptimizeChangeRow, gui::editor::deep_solve::TierMastDelta};
use indicatrix::geometry::meet_solver::{MeetConstraint, SolvedTier, VerifiedSolveReport};
use indicatrix_cut_core::{Design, OptimizeOutcome, free_tier_indices};
pub(super) fn deep_solve_hint(state: &EditorState) -> (bool, String) {
if state.printed_proportions.is_none() {
return (
false,
"Deep Solve needs this design's printed proportions (Vol/W^3, L/W, C/W, P/W, \
H/W) from the catalogue to verify against -- unavailable for a new or \
placeholder-reconstructed design."
.to_string(),
);
}
let has_repairable = state
.design
.tiers
.iter()
.any(|t| !matches!(t.constraint, MeetConstraint::ScaleReference(_)));
if has_repairable {
(
true,
"Slow (a mean of ~68 solves per design on the corpus -- minutes on a large \
design); runs off the UI thread and can be cancelled."
.to_string(),
)
} else {
(
false,
"Every tier is currently pinned to its recorded mast, exactly as imported -- \
Deep Solve has nothing to repair until you convert a tier to a meet \
constraint (the tier list's Adopt action)."
.to_string(),
)
}
}
pub(in crate::gui::editor) fn format_deep_solve_report(
report: &VerifiedSolveReport,
stale: bool,
) -> String {
let verdict = if report.accepted {
"ACCEPTED -- reproduces the printed figures to verification accuracy (not a \
correctness proof, only a strong external signal)"
} else {
"not accepted -- still deviates from the printed figures"
};
let scores = if report.initial_score.is_finite() {
format!(
"combined deviation {:.4} -> {:.4} ({} vertex-level repair(s), {} anchor \
calibration move(s), {} pipeline run(s))",
report.initial_score,
report.final_score,
report.overrides_applied,
report.anchor_moves_applied,
report.pipeline_runs
)
} else {
"none of this design's printed figures overlapped what could be measured -- \
unverifiable"
.to_string()
};
let stale_note = if stale {
" NOTE: the design changed while this ran -- re-run Deep Solve for a result that \
reflects the current schedule."
} else {
""
};
format!("Deep Solve: {verdict}. {scores}.{stale_note}")
}
fn tier_name_for_row(design: &Design, tier_index: usize) -> String {
design
.tiers
.get(tier_index)
.map(|tier| tier.name.clone())
.unwrap_or_default()
}
#[must_use]
pub(in crate::gui::editor) fn deep_solve_tier_rows(
deltas: &[TierMastDelta],
design: &Design,
) -> Vec<DeepSolveTierRow> {
deltas
.iter()
.map(|d| DeepSolveTierRow {
tier_number: format!("#{}", d.tier_index + 1).into(),
name: tier_name_for_row(design, d.tier_index).into(),
before_mast: format!("{:.4}", d.before_mast).into(),
after_mast: format!("{:.4}", d.after_mast).into(),
delta: format!("{:+.4}", d.delta()).into(),
})
.collect()
}
#[must_use]
pub(in crate::gui::editor) fn optimize_change_rows(
outcome: &OptimizeOutcome,
design: &Design,
) -> Vec<OptimizeChangeRow> {
outcome
.changes
.iter()
.map(|change| OptimizeChangeRow {
tier_number: format!("#{}", change.index + 1).into(),
name: tier_name_for_row(design, change.index).into(),
from_angle: format!("{:.2}\u{b0}", change.from_deg).into(),
to_angle: format!("{:.2}\u{b0}", change.to_deg).into(),
delta: format!("{:+.2}\u{b0}", change.to_deg - change.from_deg).into(),
})
.collect()
}
#[must_use]
pub(in crate::gui::editor) fn facet_count_from_solved(
design: &Design,
solved: &[SolvedTier],
) -> usize {
design.planes_from_solved(solved).len()
}
pub(super) fn optimize_hint(state: &EditorState, max_evaluations: usize) -> (bool, String) {
let free = free_tier_indices(&state.design);
if free.is_empty() {
(
false,
"Every tier is currently pinned as a scale reference -- a freshly \
imported design starts this way, and that is correct, not broken. \
Optimize has nothing free to move until you adopt at least one tier's \
real meet constraint (the tier list's Adopt action) or author one by \
hand."
.to_string(),
)
} else {
(
true,
format!(
"Coordinate search over {} free tier angle(s), scored on windowing, \
extinction, and tilt brilliance under a FIXED canonical light pose \
(not necessarily the light you see in the viewport right now -- drag \
the light and these figures can disagree with the trace/HUD until you \
re-run Optimize). Roughly 7 ms per evaluation on a small design, but \
up to several seconds each on a large, heavily meet-derived one (a \
{max_evaluations}-evaluation budget can then take minutes) -- plus two \
fixed full-fidelity scorings (one before, one after the search) that \
can each take over a second on their own, so even a fast run has some \
up-front and trailing wait beyond the quoted per-evaluation cost. Runs \
off the UI thread and can be cancelled.",
free.len()
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use indicatrix_cut_core::{ConstraintTier, ObjectiveComponents};
fn some_proportions() -> indicatrix::geometry::stone_metrics::ExternalProportions {
indicatrix::geometry::stone_metrics::ExternalProportions {
vol_w3: Some(1.2),
lw: Some(1.0),
cw: Some(0.2),
pw: Some(0.4),
hw: Some(0.6),
}
}
fn scale_reference_tier(value: f64) -> ConstraintTier {
ConstraintTier {
angle_deg: 0.0,
name: "T".to_string(),
indices: vec![],
constraint: MeetConstraint::ScaleReference(value),
imported_meet: None,
original_notes: None,
detached: Vec::new(),
}
}
#[test]
fn deep_solve_hint_is_unavailable_for_a_fresh_design_with_no_printed_proportions() {
let state = EditorState::fresh();
let (available, hint) = deep_solve_hint(&state);
assert!(!available);
assert!(hint.contains("printed proportions"));
}
#[test]
fn deep_solve_hint_is_unavailable_when_every_tier_is_pinned_with_nothing_to_repair() {
let mut state = EditorState::fresh();
state.printed_proportions = Some(some_proportions());
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(scale_reference_tier(0.8));
let (available, hint) = deep_solve_hint(&state);
assert!(!available);
assert!(hint.contains("pinned"));
assert!(hint.contains("Adopt"));
}
#[test]
fn deep_solve_hint_is_available_with_the_cost_caveat_when_a_tier_is_meet_derived() {
let mut state = EditorState::fresh();
state.printed_proportions = Some(some_proportions());
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: "P1".to_string(),
indices: vec![0.0, 24.0],
constraint: MeetConstraint::MeetExisting,
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
let (available, hint) = deep_solve_hint(&state);
assert!(available);
assert!(!hint.contains("pinned"));
assert!(hint.to_lowercase().contains("cancel"));
}
#[test]
fn optimize_hint_is_unavailable_when_every_tier_is_pinned() {
let mut state = EditorState::fresh();
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(scale_reference_tier(0.8));
let (available, hint) = optimize_hint(&state, 200);
assert!(!available);
assert!(hint.contains("pinned"));
assert!(hint.contains("Adopt"));
}
#[test]
fn optimize_hint_is_available_once_a_tier_is_free_to_move() {
let mut state = EditorState::fresh();
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: "P1".to_string(),
indices: vec![0.0, 24.0],
constraint: MeetConstraint::MeetExisting,
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
let (available, hint) = optimize_hint(&state, 200);
assert!(available);
assert!(
hint.contains('1'),
"expected the free-tier count in: {hint}"
);
assert!(hint.to_lowercase().contains("cancel"));
}
#[test]
fn optimize_hint_quotes_the_configured_budget_not_a_hardcoded_number() {
let mut state = EditorState::fresh();
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: "P1".to_string(),
indices: vec![0.0, 24.0],
constraint: MeetConstraint::MeetExisting,
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
let (_, hint) = optimize_hint(&state, 750);
assert!(hint.contains("750"), "expected the budget in: {hint}");
assert!(!hint.contains("200-evaluation"));
}
#[test]
fn optimize_hint_names_the_canonical_light_pose() {
let mut state = EditorState::fresh();
state.design.tiers.push(scale_reference_tier(0.5));
state.design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: "P1".to_string(),
indices: vec![0.0, 24.0],
constraint: MeetConstraint::MeetExisting,
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
let (_, hint) = optimize_hint(&state, 200);
assert!(hint.to_lowercase().contains("canonical light pose"));
}
fn design_with_named_tiers(names: &[&str]) -> Design {
let mut state = EditorState::fresh();
for &name in names {
state.design.tiers.push(ConstraintTier {
angle_deg: -40.0,
name: name.to_string(),
indices: vec![0.0],
constraint: MeetConstraint::MeetExisting,
imported_meet: None,
original_notes: None,
detached: Vec::new(),
});
}
state.design
}
#[test]
fn deep_solve_tier_rows_formats_one_row_per_delta_with_the_tiers_own_name() {
let design = design_with_named_tiers(&["G1", "P1", "P2"]);
let deltas = [
TierMastDelta {
tier_index: 1,
before_mast: 1.0,
after_mast: 1.25,
},
TierMastDelta {
tier_index: 2,
before_mast: 2.0,
after_mast: 1.9,
},
];
let rows = deep_solve_tier_rows(&deltas, &design);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].tier_number.as_str(), "#2");
assert_eq!(rows[0].name.as_str(), "P1");
assert_eq!(rows[0].before_mast.as_str(), "1.0000");
assert_eq!(rows[0].after_mast.as_str(), "1.2500");
assert_eq!(rows[0].delta.as_str(), "+0.2500");
assert_eq!(rows[1].delta.as_str(), "-0.1000");
}
#[test]
fn deep_solve_tier_rows_is_empty_for_no_deltas() {
let design = design_with_named_tiers(&["G1"]);
assert_eq!(deep_solve_tier_rows(&[], &design).len(), 0);
}
#[test]
fn deep_solve_tier_rows_names_an_out_of_range_tier_blank_rather_than_panicking() {
let design = design_with_named_tiers(&["G1"]);
let deltas = [TierMastDelta {
tier_index: 5,
before_mast: 1.0,
after_mast: 1.1,
}];
let rows = deep_solve_tier_rows(&deltas, &design);
assert_eq!(rows[0].name.as_str(), "");
}
#[test]
fn optimize_change_rows_formats_one_row_per_angle_change_with_the_tiers_own_name() {
let design = design_with_named_tiers(&["G1", "P1"]);
let outcome = OptimizeOutcome {
before: ObjectiveComponents {
windowing_pct: 0.0,
extinction_pct: 0.0,
tilt_brilliance_pct: 0.0,
},
before_score: 0.0,
before_yield_loss_pct: 0.0,
after: ObjectiveComponents {
windowing_pct: 0.0,
extinction_pct: 0.0,
tilt_brilliance_pct: 0.0,
},
after_score: 0.0,
after_yield_loss_pct: 0.0,
evaluations: 1,
changes: vec![indicatrix_cut_core::AngleChange {
index: 1,
from_deg: -40.0,
to_deg: -41.5,
}],
cancelled: false,
polish_evaluations: 0,
polish_improvement: 0.0,
};
let rows = optimize_change_rows(&outcome, &design);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].tier_number.as_str(), "#2");
assert_eq!(rows[0].name.as_str(), "P1");
assert_eq!(rows[0].from_angle.as_str(), "-40.00\u{b0}");
assert_eq!(rows[0].to_angle.as_str(), "-41.50\u{b0}");
assert_eq!(rows[0].delta.as_str(), "-1.50\u{b0}");
}
#[test]
fn facet_count_from_solved_is_the_length_of_the_designs_expanded_planes() {
let mut design = design_with_named_tiers(&["G1"]);
design.tiers[0].constraint = MeetConstraint::ScaleReference(1.0);
let solved = design
.solve()
.expect("a single scale-reference tier always solves");
assert_eq!(
facet_count_from_solved(&design, &solved),
design.planes_from_solved(&solved).len()
);
assert!(
facet_count_from_solved(&design, &solved) > 0,
"a solved design always has at least one facet plane"
);
}
}