use rigidity_cli::monte_carlo::{TrialConfig, run};
use rigidity_core::observability::Observability;
use rigidity_scenes::SceneKind;
fn config() -> TrialConfig {
TrialConfig {
trials: 200,
points_per_face: 250,
..TrialConfig::default()
}
}
#[test]
fn prediction_matches_reality_where_it_claims_confidence() {
let config = config();
let mut checked = 0usize;
for kind in SceneKind::ALL {
let outcome = run(kind, &config);
for direction in &outcome.directions {
if direction.observability == Observability::Low {
continue;
}
let ratio = direction.ratio();
assert!(
(0.75..1.3).contains(&ratio),
"{} direction {}: predicted {:.3e}, got {:.3e}, ratio \
{ratio:.3}",
kind.name(),
direction.index,
direction.predicted,
direction.empirical
);
checked += 1;
}
}
assert!(checked >= 25, "only {checked} directions were checked");
}
#[test]
fn nothing_marked_reliable_is_actually_broken() {
let config = config();
for kind in SceneKind::ALL {
let outcome = run(kind, &config);
for direction in &outcome.directions {
if direction.observability != Observability::High {
continue;
}
assert!(
direction.empirical < 3.0 * config.tolerance,
"{} direction {}: marked HIGH, yet the spread is {:.3e} m \
against a tolerance of {:.3e} m",
kind.name(),
direction.index,
direction.empirical,
config.tolerance
);
assert!(
direction.bias.abs() < direction.empirical,
"{} direction {}: the bias {:.3e} exceeds the spread {:.3e}",
kind.name(),
direction.index,
direction.bias,
direction.empirical
);
}
}
}
#[test]
fn unobservable_directions_keep_their_initial_error() {
let config = config();
let expected = config.initial_translation * config.scale / 3f64.sqrt();
for kind in [SceneKind::Plane, SceneKind::TwoPlanes, SceneKind::Corridor] {
let outcome = run(kind, &config);
let untouched: Vec<_> = outcome
.directions
.iter()
.filter(|d| !d.predicted.is_finite())
.collect();
assert!(
!untouched.is_empty(),
"{}: no directions with an infinite prediction were found",
kind.name()
);
for direction in untouched {
assert!(
direction.empirical > 0.2 * expected,
"{} direction {}: a spread of {:.3e} m is small — so ICP did \
move the pose along a supposedly unobservable direction",
kind.name(),
direction.index,
direction.empirical
);
}
}
}
#[test]
fn a_fully_observable_scene_tightens_as_data_accumulates() {
let sparse = run(
SceneKind::Corner,
&TrialConfig {
points_per_face: 250,
..config()
},
);
let dense = run(
SceneKind::Corner,
&TrialConfig {
points_per_face: 1_500,
..config()
},
);
for outcome in [&sparse, &dense] {
for direction in &outcome.directions {
assert_ne!(
direction.observability,
Observability::Low,
"the trihedral corner lost direction {}",
direction.index
);
}
assert!(
outcome.condition_number.is_finite(),
"the condition number {} is not finite",
outcome.condition_number
);
}
for direction in &dense.directions {
assert_eq!(
direction.observability,
Observability::High,
"dense scene: direction {} is marked {}",
direction.index,
direction.observability.label()
);
}
let expected = (1_500f64 / 250.0).sqrt();
for index in 0..6 {
let ratio = sparse.directions[index].empirical / dense.directions[index].empirical;
assert!(
(ratio / expected - 1.0).abs() < 0.25,
"direction {index}: the spread fell by {ratio:.2}×, while the \
1/√N law expects {expected:.2}"
);
}
assert!(
dense.converged * 10 >= dense.trials * 9,
"only {} of {} converged",
dense.converged,
dense.trials
);
}