use crate::inference::layer_transport::{
ChartTopology, DEFAULT_COMPOSITION_GRID, FittedTransport, fit_transport_map,
};
use crate::inference::transport_class::{
CircleTransportClass, CircleTransportReport, classify_circle_transport_fit,
};
use ndarray::{Array1, ArrayView2};
use std::f64::consts::TAU;
#[derive(Debug, Clone)]
pub struct ModelCoordinate {
pub model: String,
pub coordinate: Array1<f64>,
pub topology: ChartTopology,
}
impl ModelCoordinate {
pub fn new(model: impl Into<String>, coordinate: Array1<f64>, topology: ChartTopology) -> Self {
Self {
model: model.into(),
coordinate,
topology,
}
}
pub fn from_plane_scores(
model: impl Into<String>,
scores: ArrayView2<'_, f64>,
col_a: usize,
col_b: usize,
) -> Result<Self, String> {
let p = scores.ncols();
if col_a >= p || col_b >= p || col_a == col_b {
return Err(format!(
"from_plane_scores: need two distinct columns inside {p}; got {col_a}, {col_b}"
));
}
let mut coord = Array1::<f64>::zeros(scores.nrows());
for row in 0..scores.nrows() {
let angle = scores[[row, col_b]].atan2(scores[[row, col_a]]);
coord[row] = wrap_tau(angle);
}
Ok(Self::new(model, coord, ChartTopology::Circle))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UniversalityVerdict {
ConsistentWithSharedFeatureWithinNoise,
SharedFeatureWithMeasuredReparameterization,
NotSharedFeature,
}
impl UniversalityVerdict {
pub fn label(self) -> &'static str {
match self {
UniversalityVerdict::ConsistentWithSharedFeatureWithinNoise => {
"consistent with a shared feature within noise/gauge"
}
UniversalityVerdict::SharedFeatureWithMeasuredReparameterization => {
"shared feature with measured reparameterization"
}
UniversalityVerdict::NotSharedFeature => "not a shared feature by this coordinate",
}
}
}
#[derive(Debug, Clone)]
pub struct CrossModelTransportReport {
pub model_from: String,
pub model_to: String,
pub n_obs: usize,
pub fit: FittedTransport,
pub circle: Option<CircleTransportReport>,
pub verdict: UniversalityVerdict,
pub gauge_defect_scale: f64,
}
impl CrossModelTransportReport {
pub fn isometry_defect(&self) -> f64 {
self.fit.isometry_defect
}
pub fn winding(&self) -> Option<i8> {
self.circle.as_ref().map(|r| r.winding)
}
pub fn phase(&self) -> Option<f64> {
self.circle.as_ref().map(|r| r.phase)
}
pub fn o2_defect(&self) -> Option<f64> {
self.circle.as_ref().map(|r| r.defect)
}
}
pub fn fit_cross_model_transport(
from: &ModelCoordinate,
to: &ModelCoordinate,
) -> Result<CrossModelTransportReport, String> {
validate_coordinate(from)?;
validate_coordinate(to)?;
if from.coordinate.len() != to.coordinate.len() {
return Err(format!(
"cross-model transport needs paired coordinates with equal length: {} vs {}",
from.coordinate.len(),
to.coordinate.len()
));
}
let fit = fit_transport_map(
from.coordinate.view(),
to.coordinate.view(),
from.topology,
to.topology,
)
.map_err(|e| {
format!(
"cross-model transport {}→{} failed: {e}",
from.model, to.model
)
})?;
let circle = classify_circle_transport_fit(
&fit,
from.topology,
to.topology,
0,
1,
DEFAULT_COMPOSITION_GRID,
);
let gauge_defect_scale = circle
.as_ref()
.map(|r| 2.0 / (r.n_samples as f64).sqrt())
.unwrap_or(0.0);
let verdict = universality_verdict(&fit, circle.as_ref(), gauge_defect_scale);
Ok(CrossModelTransportReport {
model_from: from.model.clone(),
model_to: to.model.clone(),
n_obs: from.coordinate.len(),
fit,
circle,
verdict,
gauge_defect_scale,
})
}
fn validate_coordinate(coord: &ModelCoordinate) -> Result<(), String> {
if coord.coordinate.is_empty() {
return Err(format!("{} coordinate is empty", coord.model));
}
if coord.coordinate.iter().any(|v| !v.is_finite()) {
return Err(format!(
"{} coordinate contains non-finite values",
coord.model
));
}
Ok(())
}
fn universality_verdict(
fit: &FittedTransport,
circle: Option<&CircleTransportReport>,
gauge_defect_scale: f64,
) -> UniversalityVerdict {
if !fit.topology_preserved {
return UniversalityVerdict::NotSharedFeature;
}
if let Some(report) = circle {
if report.class == CircleTransportClass::Mixing || !matches!(report.winding, -1 | 1) {
return UniversalityVerdict::NotSharedFeature;
}
}
let roundoff_scale = f64::EPSILON.sqrt();
let isometry_noise_scale = fit.isometry_defect_se.max(roundoff_scale);
let isometry_distinguished = fit.isometry_defect > isometry_noise_scale;
let gauge_distinguished = circle
.map(|report| report.defect > gauge_defect_scale)
.unwrap_or(false);
if isometry_distinguished || gauge_distinguished {
UniversalityVerdict::SharedFeatureWithMeasuredReparameterization
} else {
UniversalityVerdict::ConsistentWithSharedFeatureWithinNoise
}
}
fn wrap_tau(x: f64) -> f64 {
x.rem_euclid(TAU)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
fn circle_coord(name: &str, values: Vec<f64>) -> ModelCoordinate {
ModelCoordinate::new(name, Array1::from_vec(values), ChartTopology::Circle)
}
fn wrap_pi(x: f64) -> f64 {
let w = (x + PI).rem_euclid(TAU) - PI;
if w <= -PI { w + TAU } else { w }
}
#[test]
fn recovers_cross_model_rotation_reflection() {
let n = 256;
let phi = 0.73_f64;
let theta_a: Vec<f64> = (0..n).map(|i| TAU * i as f64 / n as f64).collect();
let theta_b: Vec<f64> = theta_a.iter().map(|&t| wrap_tau(-t + phi)).collect();
let a = circle_coord("model-a", theta_a);
let b = circle_coord("model-b", theta_b);
let report = fit_cross_model_transport(&a, &b).expect("cross-model fit");
let circle = report.circle.as_ref().expect("circle classification");
assert_eq!(circle.class, CircleTransportClass::Reflect);
assert_eq!(circle.winding, -1);
assert_eq!(report.o2_defect(), Some(circle.defect));
assert!(wrap_pi(circle.phase - phi).abs() < 1e-6);
assert!(report.fit.isometry_defect < 1e-10);
assert!(report.fit.residual_rms < 1e-6);
assert_eq!(
report.verdict,
UniversalityVerdict::ConsistentWithSharedFeatureWithinNoise
);
}
#[test]
fn different_manifold_reports_non_shared_feature() {
let n = 256;
let theta_a: Vec<f64> = (0..n).map(|i| TAU * i as f64 / n as f64).collect();
let theta_b: Vec<f64> = theta_a.iter().map(|&t| wrap_tau(2.0 * t + 0.2)).collect();
let a = circle_coord("model-a", theta_a);
let b = circle_coord("model-b", theta_b);
let report = fit_cross_model_transport(&a, &b).expect("cross-model fit");
assert_eq!(report.fit.degree, Some(2));
assert!(!report.fit.topology_preserved);
assert_eq!(report.verdict, UniversalityVerdict::NotSharedFeature);
assert!(report.fit.isometry_defect > 0.5);
}
#[test]
fn plane_scores_become_dimension_free_angle_coordinate() {
let n = 64;
let mut scores = ndarray::Array2::<f64>::zeros((n, 3));
for row in 0..n {
let t = TAU * row as f64 / n as f64;
scores[[row, 1]] = t.cos();
scores[[row, 2]] = t.sin();
}
let coord = ModelCoordinate::from_plane_scores("wide-model", scores.view(), 1, 2)
.expect("angle coordinate");
assert_eq!(coord.topology, ChartTopology::Circle);
assert_eq!(coord.coordinate.len(), n);
for row in 0..n {
let expected = TAU * row as f64 / n as f64;
assert!(wrap_pi(coord.coordinate[row] - expected).abs() < 1e-12);
}
}
}