use ndarray::{Array1, Array2, ArrayView2};
use statrs::distribution::{ContinuousCDF, StudentsT};
use super::isa_seed::IsaPlaneCandidate;
use crate::null_battery::phase_randomized_surrogate;
use gam_solve::structure_search::StructureMove;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PhaseChannel {
Difference1,
Difference2,
Sum1,
}
impl PhaseChannel {
pub fn as_str(self) -> &'static str {
match self {
PhaseChannel::Difference1 => "difference_h1",
PhaseChannel::Difference2 => "difference_h2",
PhaseChannel::Sum1 => "sum_h1",
}
}
pub fn all() -> [PhaseChannel; 3] {
[
PhaseChannel::Difference1,
PhaseChannel::Difference2,
PhaseChannel::Sum1,
]
}
fn row_angle(self, theta_a: f64, theta_b: f64) -> f64 {
match self {
PhaseChannel::Difference1 => theta_a - theta_b,
PhaseChannel::Difference2 => 2.0 * (theta_a - theta_b),
PhaseChannel::Sum1 => theta_a + theta_b,
}
}
}
struct PlanePhases {
theta: Vec<f64>,
r2: Vec<f64>,
active: Vec<bool>,
}
fn plane_phases(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
cand: &IsaPlaneCandidate,
) -> PlanePhases {
let (n, p) = data.dim();
let mut theta = vec![0.0_f64; n];
let mut r2 = vec![0.0_f64; n];
let mut active = vec![false; n];
for i in 0..n {
let (mut p1, mut p2) = (0.0_f64, 0.0_f64);
for j in 0..p {
let ri = data[[i, j]] - mean[j];
p1 += ri * cand.basis[[j, 0]];
p2 += ri * cand.basis[[j, 1]];
}
theta[i] = p2.atan2(p1);
r2[i] = p1 * p1 + p2 * p2;
active[i] = cand.gate_logits[i].is_finite();
}
PlanePhases { theta, r2, active }
}
fn resultant(channel: PhaseChannel, theta_a: &[f64], theta_b: &[f64], w: &[f64]) -> (f64, f64) {
let (mut cx, mut sx, mut wsum, mut wsq) = (0.0_f64, 0.0_f64, 0.0_f64, 0.0_f64);
for i in 0..theta_a.len() {
let wi = w[i];
if wi <= 0.0 {
continue;
}
let ang = channel.row_angle(theta_a[i], theta_b[i]);
cx += wi * ang.cos();
sx += wi * ang.sin();
wsum += wi;
wsq += wi * wi;
}
if wsum <= 0.0 {
return (0.0, 0.0);
}
let t = ((cx * cx + sx * sx).sqrt() / wsum).min(1.0);
let n_eff = wsum * wsum / wsq;
(t, n_eff)
}
#[derive(Clone, Debug)]
pub struct ChannelVerdict {
pub channel: PhaseChannel,
pub resultant: f64,
pub n_eff: f64,
pub null_mean: f64,
pub null_sd: f64,
pub z: f64,
pub p_value: f64,
pub e_value: f64,
}
#[derive(Clone, Debug)]
pub struct PhaseVerdict {
pub atom_a: usize,
pub atom_b: usize,
pub n_co_active: usize,
pub channels: Vec<ChannelVerdict>,
pub best_channel: PhaseChannel,
pub best_e_value: f64,
pub best_p_value: f64,
pub energy_rho: f64,
pub total_energy_cv: f64,
pub torus_proposed: bool,
pub fuse_race_proposed: bool,
}
pub const PHASE_NULL_REPLICATES: usize = 200;
const PHASE_SCREEN_ALPHA: f64 = 0.05;
fn permutation_e_value(exceed: usize, b: usize) -> f64 {
if exceed == 0 { (b + 1) as f64 } else { 0.0 }
}
fn plane_support_columns(cand: &IsaPlaneCandidate) -> Option<[usize; 2]> {
let p = cand.basis.nrows();
let mut cols = [usize::MAX; 2];
for c in 0..2 {
let mut hit = None;
for j in 0..p {
if cand.basis[[j, c]].abs() > 1e-9 {
if hit.is_some() {
return None;
}
hit = Some(j);
}
}
cols[c] = hit?;
}
Some(cols)
}
fn reduced_null_inputs(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
cand_a: &IsaPlaneCandidate,
cand_b: &IsaPlaneCandidate,
) -> Option<(
Array2<f64>,
Array1<f64>,
IsaPlaneCandidate,
IsaPlaneCandidate,
)> {
let sa = plane_support_columns(cand_a)?;
let sb = plane_support_columns(cand_b)?;
let mut cols: Vec<usize> = Vec::new();
for &c in sa.iter().chain(sb.iter()) {
if !cols.contains(&c) {
cols.push(c);
}
}
let n = data.nrows();
let pr = cols.len();
let mut sub = Array2::<f64>::zeros((n, pr));
for (jr, &jc) in cols.iter().enumerate() {
for i in 0..n {
sub[[i, jr]] = data[[i, jc]];
}
}
let mut sub_mean = Array1::<f64>::zeros(pr);
for (jr, &jc) in cols.iter().enumerate() {
sub_mean[jr] = mean[jc];
}
let remap = |cand: &IsaPlaneCandidate, supp: [usize; 2]| -> IsaPlaneCandidate {
let mut basis = Array2::<f64>::zeros((pr, 2));
for c in 0..2 {
let jr = cols.iter().position(|&x| x == supp[c]).unwrap_or(0);
basis[[jr, c]] = cand.basis[[supp[c], c]];
}
IsaPlaneCandidate {
basis,
amplitudes: cand.amplitudes,
phases_turns: cand.phases_turns.clone(),
gate_logits: cand.gate_logits.clone(),
kappa: cand.kappa,
q_hat: cand.q_hat,
}
};
Some((sub, sub_mean, remap(cand_a, sa), remap(cand_b, sb)))
}
pub fn screen_pair_phase(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
atom_a: usize,
atom_b: usize,
cand_a: &IsaPlaneCandidate,
cand_b: &IsaPlaneCandidate,
replicates: usize,
seed: u64,
) -> Result<PhaseVerdict, String> {
let pa = plane_phases(data, mean, cand_a);
let pb = plane_phases(data, mean, cand_b);
let n = pa.theta.len();
let w: Vec<f64> = (0..n)
.map(|i| {
if pa.active[i] && pb.active[i] {
1.0
} else {
0.0
}
})
.collect();
let n_co_active = w.iter().filter(|&&x| x > 0.0).count();
let channels_all = PhaseChannel::all();
let observed: Vec<(f64, f64)> = channels_all
.iter()
.map(|&ch| resultant(ch, &pa.theta, &pb.theta, &w))
.collect();
let (null_data, null_mean, ncand_a, ncand_b) = reduced_null_inputs(data, mean, cand_a, cand_b)
.unwrap_or_else(|| {
(
data.to_owned(),
mean.clone(),
copy_candidate(cand_a),
copy_candidate(cand_b),
)
});
let mut null_samples: Vec<Vec<f64>> = vec![Vec::with_capacity(replicates); channels_all.len()];
let mut null_energy_rho: Vec<f64> = Vec::with_capacity(replicates);
let mut null_energy_cv: Vec<f64> = Vec::with_capacity(replicates);
for rep in 0..replicates {
let rep_seed = mix_seed(seed, rep as u64);
let surrogate = phase_randomized_surrogate(null_data.view(), rep_seed)?;
let sa = plane_phases(surrogate.view(), &null_mean, &ncand_a);
let sb = plane_phases(surrogate.view(), &null_mean, &ncand_b);
for (ci, &ch) in channels_all.iter().enumerate() {
let (t, _) = resultant(ch, &sa.theta, &sb.theta, &w);
null_samples[ci].push(t);
}
let (s_rho, s_cv) = coactive_energy_stats(&sa.r2, &sb.r2, &w);
if s_rho.is_finite() {
null_energy_rho.push(s_rho);
}
if s_cv.is_finite() {
null_energy_cv.push(s_cv);
}
}
let mut channels = Vec::with_capacity(channels_all.len());
for (ci, &ch) in channels_all.iter().enumerate() {
let (t, n_eff) = observed[ci];
let samples = &null_samples[ci];
let b = samples.len();
let mean_null = samples.iter().sum::<f64>() / b.max(1) as f64;
let var = samples
.iter()
.map(|x| (x - mean_null) * (x - mean_null))
.sum::<f64>()
/ (b.saturating_sub(1)).max(1) as f64;
let sd = var.sqrt();
let exceed = samples.iter().filter(|&&x| x >= t).count();
let p_value = (1 + exceed) as f64 / (b + 1) as f64;
let z = if sd > 0.0 { (t - mean_null) / sd } else { 0.0 };
channels.push(ChannelVerdict {
channel: ch,
resultant: t,
n_eff,
null_mean: mean_null,
null_sd: sd,
z,
p_value,
e_value: permutation_e_value(exceed, b),
});
}
let (best_idx, best_e) = channels
.iter()
.enumerate()
.map(|(i, c)| (i, c.e_value))
.fold((0usize, f64::NEG_INFINITY), |acc, (i, e)| {
if e > acc.1 { (i, e) } else { acc }
});
let best_channel = channels[best_idx].channel;
let best_p_value = channels
.iter()
.map(|c| c.p_value)
.fold(f64::INFINITY, f64::min);
let (energy_rho, total_energy_cv) = coactive_energy_stats(&pa.r2, &pb.r2, &w);
let rho_p = {
let b = null_energy_rho.len();
if !energy_rho.is_finite() || b == 0 {
1.0
} else {
(1 + null_energy_rho.iter().filter(|&&s| s <= energy_rho).count()) as f64
/ (b + 1) as f64
}
};
let cv_p = {
let b = null_energy_cv.len();
if !total_energy_cv.is_finite() || b == 0 {
1.0
} else {
(1 + null_energy_cv
.iter()
.filter(|&&s| s <= total_energy_cv)
.count()) as f64
/ (b + 1) as f64
}
};
let fuse_race_proposed =
n_co_active >= 2 && rho_p <= PHASE_SCREEN_ALPHA && cv_p <= PHASE_SCREEN_ALPHA;
Ok(PhaseVerdict {
atom_a,
atom_b,
n_co_active,
channels,
best_channel,
best_e_value: best_e,
best_p_value,
energy_rho,
total_energy_cv,
torus_proposed: false,
fuse_race_proposed,
})
}
fn coactive_energy_stats(r2a: &[f64], r2b: &[f64], w: &[f64]) -> (f64, f64) {
let (mut ma, mut mb, mut cross, mut wsum) = (0.0_f64, 0.0_f64, 0.0_f64, 0.0_f64);
for i in 0..r2a.len() {
if w[i] <= 0.0 {
continue;
}
ma += r2a[i];
mb += r2b[i];
cross += r2a[i] * r2b[i];
wsum += 1.0;
}
if wsum < 2.0 || ma <= 0.0 || mb <= 0.0 {
return (f64::NAN, f64::NAN);
}
ma /= wsum;
mb /= wsum;
cross /= wsum;
let rho = cross / (ma * mb);
let mut mt = 0.0_f64;
for i in 0..r2a.len() {
if w[i] > 0.0 {
mt += r2a[i] + r2b[i];
}
}
mt /= wsum;
let mut vt = 0.0_f64;
for i in 0..r2a.len() {
if w[i] > 0.0 {
let d = (r2a[i] + r2b[i]) - mt;
vt += d * d;
}
}
vt /= wsum;
let cv = if mt > 0.0 { vt.sqrt() / mt } else { f64::NAN };
(rho, cv)
}
fn copy_candidate(c: &IsaPlaneCandidate) -> IsaPlaneCandidate {
IsaPlaneCandidate {
basis: c.basis.clone(),
amplitudes: c.amplitudes,
phases_turns: c.phases_turns.clone(),
gate_logits: c.gate_logits.clone(),
kappa: c.kappa,
q_hat: c.q_hat,
}
}
fn mix_seed(seed: u64, rep: u64) -> u64 {
let mut x = seed ^ rep.wrapping_mul(0x9E3779B97F4A7C15);
x ^= x >> 30;
x = x.wrapping_mul(0xBF58476D1CE4E5B9);
x ^= x >> 27;
x = x.wrapping_mul(0x94D049BB133111EB);
x ^= x >> 31;
x
}
pub fn ebh_reject(e_values: &[f64], alpha: f64) -> Vec<usize> {
let m = e_values.len();
if m == 0 || !(alpha > 0.0) {
return Vec::new();
}
let mut order: Vec<usize> = (0..m).collect();
order.sort_by(|&i, &j| {
e_values[j]
.partial_cmp(&e_values[i])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut k_star = 0usize;
for rank in 1..=m {
let e = e_values[order[rank - 1]];
if e >= (m as f64) / (alpha * rank as f64) {
k_star = rank;
}
}
order.into_iter().take(k_star).collect()
}
pub fn screen_all_pairs_phase(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
candidates: &[IsaPlaneCandidate],
replicates: usize,
seed: u64,
alpha: f64,
) -> Result<Vec<PhaseVerdict>, String> {
let mut verdicts = Vec::new();
let mut ledger_e: Vec<f64> = Vec::new();
let mut ledger_owner: Vec<usize> = Vec::new();
for a in 0..candidates.len() {
for b in (a + 1)..candidates.len() {
let pair_seed = mix_seed(seed, ((a as u64) << 20) ^ b as u64);
let v = screen_pair_phase(
data,
mean,
a,
b,
&candidates[a],
&candidates[b],
replicates,
pair_seed,
)?;
if v.n_co_active >= 2 {
for ch in &v.channels {
ledger_e.push(ch.e_value);
ledger_owner.push(verdicts.len());
}
}
verdicts.push(v);
}
}
for idx in ebh_reject(&ledger_e, alpha) {
verdicts[ledger_owner[idx]].torus_proposed = true;
}
Ok(verdicts)
}
#[derive(Clone, Copy, Debug)]
pub struct ResidualCoupling {
pub atom_a: usize,
pub atom_b: usize,
pub channel: PhaseChannel,
pub e_value: f64,
}
#[derive(Clone, Debug)]
pub struct PhaseCouplingScreen {
pub no_coupling_detected: bool,
pub residual_couplings: Vec<ResidualCoupling>,
pub verdicts: Vec<PhaseVerdict>,
}
pub fn screen_pairwise_phase_coupling(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
candidates: &[IsaPlaneCandidate],
replicates: usize,
seed: u64,
alpha: f64,
) -> Result<PhaseCouplingScreen, String> {
let verdicts = screen_all_pairs_phase(data, mean, candidates, replicates, seed, alpha)?;
let residual_couplings: Vec<ResidualCoupling> = verdicts
.iter()
.filter(|v| v.torus_proposed)
.map(|v| ResidualCoupling {
atom_a: v.atom_a,
atom_b: v.atom_b,
channel: v.best_channel,
e_value: v.best_e_value,
})
.collect();
Ok(PhaseCouplingScreen {
no_coupling_detected: residual_couplings.is_empty(),
residual_couplings,
verdicts,
})
}
pub fn phase_fusion_moves(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
candidates: &[IsaPlaneCandidate],
replicates: usize,
seed: u64,
alpha: f64,
) -> Result<Vec<StructureMove>, String> {
let verdicts = screen_all_pairs_phase(data, mean, candidates, replicates, seed, alpha)?;
let mut moves = Vec::new();
let mut seen: Vec<(usize, usize)> = Vec::new();
for v in verdicts.iter().filter(|v| v.torus_proposed) {
seen.push((v.atom_a, v.atom_b));
moves.push(StructureMove::Fusion {
a: v.atom_a,
b: v.atom_b,
});
}
for v in verdicts.iter().filter(|v| v.fuse_race_proposed) {
if !seen.contains(&(v.atom_a, v.atom_b)) {
moves.push(StructureMove::Fusion {
a: v.atom_a,
b: v.atom_b,
});
}
}
Ok(moves)
}
#[derive(Clone, Debug)]
pub struct FuseRaceCandidate {
pub support_columns: Vec<usize>,
pub basis: Array2<f64>,
pub captured_energy_fraction: f64,
pub atom_a: usize,
pub atom_b: usize,
}
pub fn fuse_race_candidate(
data: ArrayView2<'_, f64>,
mean: &Array1<f64>,
atom_a: usize,
atom_b: usize,
cand_a: &IsaPlaneCandidate,
cand_b: &IsaPlaneCandidate,
) -> Option<FuseRaceCandidate> {
let sa = plane_support_columns(cand_a)?;
let sb = plane_support_columns(cand_b)?;
let mut cols: Vec<usize> = Vec::new();
for &c in sa.iter().chain(sb.iter()) {
if !cols.contains(&c) {
cols.push(c);
}
}
let d = cols.len();
if d < 2 {
return None;
}
let pa = plane_phases(data, mean, cand_a);
let pb = plane_phases(data, mean, cand_b);
let n = data.nrows();
let active: Vec<bool> = (0..n).map(|i| pa.active[i] && pb.active[i]).collect();
let n_act = active.iter().filter(|&&x| x).count();
if n_act < 2 {
return None;
}
let mut cov = Array2::<f64>::zeros((d, d));
for i in 0..n {
if !active[i] {
continue;
}
let mut v = vec![0.0_f64; d];
for (r, &c) in cols.iter().enumerate() {
v[r] = data[[i, c]] - mean[c];
}
for r in 0..d {
for s in 0..d {
cov[[r, s]] += v[r] * v[s];
}
}
}
cov.mapv_inplace(|x| x / n_act as f64);
let total: f64 = (0..d).map(|r| cov[[r, r]]).sum();
let (evecs, evals) = symmetric_eig_jacobi(&cov);
let mut order: Vec<usize> = (0..d).collect();
order.sort_by(|&i, &j| {
evals[j]
.partial_cmp(&evals[i])
.unwrap_or(std::cmp::Ordering::Equal)
});
let captured = if total > 0.0 {
(evals[order[0]].max(0.0) + evals[order[1]].max(0.0)) / total
} else {
0.0
};
let p = data.ncols();
let mut basis = Array2::<f64>::zeros((p, 2));
for k in 0..2 {
let col = order[k];
for (r, &c) in cols.iter().enumerate() {
basis[[c, k]] = evecs[[r, col]];
}
}
Some(FuseRaceCandidate {
support_columns: cols,
basis,
captured_energy_fraction: captured,
atom_a,
atom_b,
})
}
fn symmetric_eig_jacobi(a: &Array2<f64>) -> (Array2<f64>, Vec<f64>) {
let d = a.nrows();
let mut m = a.clone();
let mut v = Array2::<f64>::eye(d);
for _ in 0..64 {
let mut off = 0.0_f64;
for p in 0..d {
for q in (p + 1)..d {
off += m[[p, q]] * m[[p, q]];
}
}
if off < 1e-24 {
break;
}
for p in 0..d {
for q in (p + 1)..d {
let apq = m[[p, q]];
if apq.abs() < 1e-300 {
continue;
}
let app = m[[p, p]];
let aqq = m[[q, q]];
let phi = 0.5 * (2.0 * apq).atan2(app - aqq);
let (c, s) = (phi.cos(), phi.sin());
for k in 0..d {
let mkp = m[[k, p]];
let mkq = m[[k, q]];
m[[k, p]] = c * mkp + s * mkq;
m[[k, q]] = -s * mkp + c * mkq;
}
for k in 0..d {
let mpk = m[[p, k]];
let mqk = m[[q, k]];
m[[p, k]] = c * mpk + s * mqk;
m[[q, k]] = -s * mpk + c * mqk;
}
for k in 0..d {
let vkp = v[[k, p]];
let vkq = v[[k, q]];
v[[k, p]] = c * vkp + s * vkq;
v[[k, q]] = -s * vkp + c * vkq;
}
}
}
}
let evals: Vec<f64> = (0..d).map(|i| m[[i, i]]).collect();
(v, evals)
}
use crate::chart_transfer::{certify_square_transfer, so2_polar_angle};
fn so2_generator() -> Array2<f64> {
let mut g = Array2::<f64>::zeros((2, 2));
g[[0, 1]] = -1.0;
g[[1, 0]] = 1.0;
g
}
pub fn phase_transfer_operator(
theta_a: &[f64],
theta_b: &[f64],
w: &[f64],
) -> Result<Array2<f64>, String> {
if theta_a.len() != theta_b.len() || theta_a.len() != w.len() {
return Err("phase_transfer_operator: length mismatch".to_string());
}
let mut cross = Array2::<f64>::zeros((2, 2)); let mut gram = Array2::<f64>::zeros((2, 2)); let mut wsum = 0.0_f64;
for i in 0..theta_a.len() {
let wi = w[i];
if wi <= 0.0 {
continue;
}
let ua = [theta_a[i].cos(), theta_a[i].sin()];
let ub = [theta_b[i].cos(), theta_b[i].sin()];
for r in 0..2 {
for c in 0..2 {
cross[[r, c]] += wi * ub[r] * ua[c];
gram[[r, c]] += wi * ua[r] * ua[c];
}
}
wsum += wi;
}
if wsum <= 0.0 {
return Err("phase_transfer_operator: no positive-weight rows".to_string());
}
let det = gram[[0, 0]] * gram[[1, 1]] - gram[[0, 1]] * gram[[1, 0]];
let scale = (gram[[0, 0]].abs() * gram[[1, 1]].abs()).max(1e-300);
if !det.is_finite() || det.abs() <= f64::EPSILON.sqrt() * scale {
return Err(
"phase_transfer_operator: singular input angle gram (θ_A not exciting)".to_string(),
);
}
let inv = {
let mut m = Array2::<f64>::zeros((2, 2));
m[[0, 0]] = gram[[1, 1]] / det;
m[[1, 1]] = gram[[0, 0]] / det;
m[[0, 1]] = -gram[[0, 1]] / det;
m[[1, 0]] = -gram[[1, 0]] / det;
m
};
Ok(cross.dot(&inv))
}
#[derive(Clone, Debug)]
pub struct PhaseCircuitCertificate {
pub transfer_angle: Option<f64>,
pub orientation: i8,
pub transport_defect: f64,
pub equivariance_defect: f64,
pub dose_slope: f64,
pub dose_r2: f64,
pub certified: bool,
}
pub fn predicted_theta_b(op: ArrayView2<'_, f64>, theta_a_steered: f64) -> f64 {
let ua = [theta_a_steered.cos(), theta_a_steered.sin()];
let vb0 = op[[0, 0]] * ua[0] + op[[0, 1]] * ua[1];
let vb1 = op[[1, 0]] * ua[0] + op[[1, 1]] * ua[1];
vb1.atan2(vb0)
}
fn wrap_pi(x: f64) -> f64 {
let tau = std::f64::consts::TAU;
let mut y = x % tau;
if y > std::f64::consts::PI {
y -= tau;
} else if y <= -std::f64::consts::PI {
y += tau;
}
y
}
const CIRCUIT_TRANSPORT_DEFECT_MAX: f64 = 0.35;
const CIRCUIT_DOSE_SLOPE_LO: f64 = 0.6;
const CIRCUIT_DOSE_SLOPE_HI: f64 = 1.4;
pub fn certify_phase_circuit(
theta_a: &[f64],
theta_b: &[f64],
w: &[f64],
predicted_dtheta_b: &[f64],
observed_dtheta_b: &[f64],
) -> Result<PhaseCircuitCertificate, String> {
let op = phase_transfer_operator(theta_a, theta_b, w)?;
let det = op[[0, 0]] * op[[1, 1]] - op[[0, 1]] * op[[1, 0]];
let orientation = if det > 1e-9 {
1
} else if det < -1e-9 {
-1
} else {
0
};
let transfer_angle = match so2_polar_angle(op.view()) {
Ok(angle) => Some(angle),
Err(err) => {
log::debug!("pair phase: transfer operator has no SO(2) polar angle: {err}");
None
}
};
let g = so2_generator();
let cert = certify_square_transfer(op.view(), g.view(), g.view())?;
if predicted_dtheta_b.len() != observed_dtheta_b.len() {
return Err("certify_phase_circuit: dose shard length mismatch".to_string());
}
let (mut sxx, mut sxy, mut syy) = (0.0_f64, 0.0_f64, 0.0_f64);
for i in 0..predicted_dtheta_b.len() {
let x = wrap_pi(predicted_dtheta_b[i]);
let y = wrap_pi(observed_dtheta_b[i]);
sxx += x * x;
sxy += x * y;
syy += y * y;
}
let dose_slope = if sxx > 0.0 { sxy / sxx } else { 0.0 };
let ss_res = syy - 2.0 * dose_slope * sxy + dose_slope * dose_slope * sxx;
let dose_r2 = if syy > 0.0 {
(1.0 - ss_res / syy).clamp(0.0, 1.0)
} else {
0.0
};
let n_dose = predicted_dtheta_b.len();
let dose_present = if n_dose >= 2 && sxx > 0.0 && ss_res.is_finite() && ss_res >= 0.0 {
let dof = n_dose as f64 - 1.0;
let sigma2 = ss_res / dof;
let se = (sigma2 / sxx).sqrt();
let t_crit = StudentsT::new(0.0, 1.0, dof)
.map(|dist| dist.inverse_cdf(1.0 - PHASE_SCREEN_ALPHA / 2.0))
.unwrap_or(f64::INFINITY);
dose_slope.abs() > t_crit * se
} else {
false
};
let certified = orientation == 1
&& cert.transport_defect <= CIRCUIT_TRANSPORT_DEFECT_MAX
&& dose_slope >= CIRCUIT_DOSE_SLOPE_LO
&& dose_slope <= CIRCUIT_DOSE_SLOPE_HI
&& dose_present;
Ok(PhaseCircuitCertificate {
transfer_angle,
orientation,
transport_defect: cert.transport_defect,
equivariance_defect: cert.equivariance_defect,
dose_slope,
dose_r2,
certified,
})
}
#[cfg(test)]
mod tests {
include!("pair_phase_tests.rs");
}