use crate::detection::normal_cdf;
pub type Mat = Vec<Vec<f64>>;
#[derive(Clone, Debug)]
pub struct AmbiguityFix {
pub fixed: Vec<i64>,
pub residual: f64,
pub ratio: f64,
pub success_rate: f64,
}
pub fn ldlt(q: &Mat) -> Option<(Mat, Vec<f64>)> {
let n = q.len();
let mut l = vec![vec![0.0; n]; n];
let mut d = vec![0.0; n];
for j in 0..n {
let mut dj = q[j][j];
for k in 0..j {
dj -= l[j][k] * l[j][k] * d[k];
}
if dj <= 0.0 {
return None;
}
d[j] = dj;
l[j][j] = 1.0;
for i in (j + 1)..n {
let mut s = q[i][j];
for k in 0..j {
s -= l[i][k] * l[j][k] * d[k];
}
l[i][j] = s / dj;
}
}
Some((l, d))
}
#[allow(clippy::needless_range_loop)]
fn congruence(z: &[Vec<i64>], q: &Mat) -> Mat {
let n = q.len();
let mut qz = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
let mut s = 0.0;
for (k, qik) in q[i].iter().enumerate() {
s += qik * z[k][j] as f64;
}
qz[i][j] = s;
}
}
let mut r = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
let mut s = 0.0;
for k in 0..n {
s += z[k][i] as f64 * qz[k][j];
}
r[i][j] = s;
}
}
r
}
#[allow(clippy::needless_range_loop)]
pub fn decorrelate(q: &Mat) -> Option<(Vec<Vec<i64>>, Mat)> {
let n = q.len();
let (mut l, _d) = ldlt(q)?;
let mut z = vec![vec![0i64; n]; n];
for (i, row) in z.iter_mut().enumerate() {
row[i] = 1;
}
for i in (1..n).rev() {
for j in (0..i).rev() {
let mu = l[i][j].round();
if mu != 0.0 {
let m = mu as i64;
for k in 0..=j {
l[i][k] -= mu * l[j][k];
}
for k in 0..n {
z[k][i] -= m * z[k][j];
}
}
}
}
let qz = congruence(&z, q);
Some((z, qz))
}
pub fn transform_float(z: &[Vec<i64>], a_hat: &[f64]) -> Vec<f64> {
let n = a_hat.len();
let mut out = vec![0.0; n];
for j in 0..n {
let mut s = 0.0;
for (i, &ai) in a_hat.iter().enumerate() {
s += z[i][j] as f64 * ai;
}
out[j] = s;
}
out
}
#[allow(clippy::needless_range_loop)]
pub fn back_transform(z: &[Vec<i64>], z_fixed: &[i64]) -> Vec<i64> {
let n = z.len();
let mut a: Mat = (0..n)
.map(|i| (0..n).map(|j| z[j][i] as f64).collect())
.collect();
let mut b: Vec<f64> = z_fixed.iter().map(|&v| v as f64).collect();
for i in 0..n {
let mut p = i;
for r in (i + 1)..n {
if a[r][i].abs() > a[p][i].abs() {
p = r;
}
}
a.swap(i, p);
b.swap(i, p);
let piv = a[i][i];
for r in (i + 1)..n {
let f = a[r][i] / piv;
for c in i..n {
a[r][c] -= f * a[i][c];
}
b[r] -= f * b[i];
}
}
let mut x = vec![0.0; n];
for i in (0..n).rev() {
let mut s = b[i];
for c in (i + 1)..n {
s -= a[i][c] * x[c];
}
x[i] = s / a[i][i];
}
x.iter().map(|v| v.round() as i64).collect()
}
fn ils_two_best(q: &Mat, a_hat: &[f64]) -> Option<(Vec<i64>, f64, f64)> {
let n = a_hat.len();
let (l, d) = ldlt(q)?;
let mut best: Option<Vec<i64>> = None;
let mut best_cost = f64::INFINITY;
let mut second_cost = f64::INFINITY;
let mut z = vec![0i64; n];
let mut u = vec![0.0f64; n]; let mut nodes: u64 = 0;
let budget: u64 = 2_000_000;
#[allow(clippy::too_many_arguments)]
fn dfs(
i: usize,
n: usize,
l: &Mat,
d: &[f64],
a_hat: &[f64],
z: &mut Vec<i64>,
u: &mut Vec<f64>,
cost: f64,
best: &mut Option<Vec<i64>>,
best_cost: &mut f64,
second_cost: &mut f64,
nodes: &mut u64,
budget: u64,
) {
if *nodes > budget {
return;
}
if i == n {
if cost < *best_cost {
*second_cost = *best_cost;
*best_cost = cost;
*best = Some(z.clone());
} else if cost < *second_cost {
*second_cost = cost;
}
return;
}
let mut zc = a_hat[i];
for j in 0..i {
zc += l[i][j] * u[j];
}
let center = zc.round();
let mut visited_center = false;
let mut up = center + 1.0;
let mut down = center - 1.0;
loop {
*nodes += 1;
if *nodes > budget {
return;
}
let cand = if !visited_center {
visited_center = true;
center
} else if (up - zc).abs() <= (zc - down).abs() {
let c = up;
up += 1.0;
c
} else {
let c = down;
down -= 1.0;
c
};
let ui = cand - zc;
let add = ui * ui / d[i];
if cost + add >= *second_cost {
break;
}
z[i] = cand.round() as i64;
u[i] = ui;
dfs(
i + 1,
n,
l,
d,
a_hat,
z,
u,
cost + add,
best,
best_cost,
second_cost,
nodes,
budget,
);
if (cand - center).abs() > 1_000.0 {
break;
}
}
}
dfs(
0,
n,
&l,
&d,
a_hat,
&mut z,
&mut u,
0.0,
&mut best,
&mut best_cost,
&mut second_cost,
&mut nodes,
budget,
);
best.map(|b| (b, best_cost, second_cost))
}
pub fn ils(q: &Mat, a_hat: &[f64]) -> Option<Vec<i64>> {
ils_two_best(q, a_hat).map(|(b, _, _)| b)
}
pub fn bootstrap_success_rate(q: &Mat) -> Option<f64> {
let (_l, d) = ldlt(q)?;
let mut p = 1.0;
for &di in &d {
let sigma = di.sqrt();
p *= 2.0 * normal_cdf(1.0 / (2.0 * sigma)) - 1.0;
}
Some(p)
}
pub fn resolve(q: &Mat, a_hat: &[f64]) -> Option<AmbiguityFix> {
let (z, qz) = decorrelate(q)?;
let z_float = transform_float(&z, a_hat);
let (z_fixed, best_cost, second_cost) = ils_two_best(&qz, &z_float)?;
let fixed = back_transform(&z, &z_fixed);
let ratio = if second_cost.is_finite() && best_cost > 0.0 {
second_cost / best_cost
} else {
f64::INFINITY
};
let success_rate = bootstrap_success_rate(&qz)?;
Some(AmbiguityFix {
fixed,
residual: best_cost,
ratio,
success_rate,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn ldlt_reconstruct(l: &Mat, d: &[f64]) -> Mat {
let n = d.len();
let mut q = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
let mut s = 0.0;
for k in 0..n {
s += l[i][k] * d[k] * l[j][k];
}
q[i][j] = s;
}
}
q
}
#[test]
fn ldlt_reconstructs_the_matrix() {
let q = vec![
vec![6.0, 5.0, 2.0],
vec![5.0, 6.0, 3.0],
vec![2.0, 3.0, 5.0],
];
let (l, d) = ldlt(&q).expect("spd");
let r = ldlt_reconstruct(&l, &d);
for i in 0..3 {
for j in 0..3 {
assert!((r[i][j] - q[i][j]).abs() < 1e-10, "[{i}][{j}]");
}
}
}
}