#![allow(clippy::needless_range_loop)]
use kshana::lambda::{resolve, Mat};
const REFERENCE: &str = include_str!("fixtures/lambda_rtklib_oracle/rtklib_lambda_reference.txt");
struct Problem {
q: Mat,
a: Vec<f64>,
f1: Vec<i64>,
f2: Vec<i64>,
s: [f64; 2],
}
fn ints(line: &str, tag: &str) -> Vec<i64> {
let rest = line.strip_prefix(tag).expect(tag);
rest.split_whitespace()
.map(|t| t.parse().expect("integer"))
.collect()
}
fn floats(line: &str, tag: &str) -> Vec<f64> {
let rest = line.strip_prefix(tag).expect(tag);
rest.split_whitespace()
.map(|t| t.parse().expect("float"))
.collect()
}
fn parse() -> (Vec<Problem>, usize) {
let mut lines = REFERENCE.lines().filter(|l| !l.starts_with('#')).peekable();
let mut out = Vec::new();
let mut rtklib_failures = 0;
while let Some(head) = lines.next() {
let h: Vec<i64> = ints(head, "P");
let (n, info) = (h[0] as usize, h[1]);
let a_int = ints(lines.next().unwrap(), "A");
let g_int = ints(lines.next().unwrap(), "G");
let a = floats(lines.next().unwrap(), "a");
assert_eq!((a_int.len(), g_int.len(), a.len()), (n * n, n * 3, n));
if info != 0 {
rtklib_failures += 1;
continue;
}
let f1 = ints(lines.next().unwrap(), "F1");
let f2 = ints(lines.next().unwrap(), "F2");
let s = floats(lines.next().unwrap(), "s");
let mut q = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
let mut acc: i64 = if i == j { 1 } else { 0 };
for l in 0..n {
acc += a_int[i * n + l] * a_int[j * n + l];
}
for l in 0..3 {
acc += 64 * g_int[i * 3 + l] * g_int[j * 3 + l];
}
q[i][j] = acc as f64 / 256.0;
}
}
out.push(Problem {
q,
a,
f1,
f2,
s: [s[0], s[1]],
});
}
(out, rtklib_failures)
}
#[test]
fn ils_solution_matches_rtklib_lambda_on_300_covariances() {
const REL_TOL: f64 = 1e-9;
let (problems, rtklib_failures) = parse();
assert_eq!(
rtklib_failures, 0,
"RTKLIB returned an error on {rtklib_failures} problems"
);
assert_eq!(problems.len(), 300, "the fixture holds 300 problems");
let mut worst_rel = [0.0_f64; 2];
let mut mismatches = Vec::new();
for (k, p) in problems.iter().enumerate() {
let fix = resolve(&p.q, &p.a).unwrap_or_else(|| panic!("problem {k}: no solution"));
if fix.fixed != p.f1 {
mismatches.push(format!(
"problem {k} (n = {}): kshana {:?}, RTKLIB {:?} (RTKLIB runner-up {:?})",
p.a.len(),
fix.fixed,
p.f1,
p.f2
));
continue;
}
let second = fix.residual * fix.ratio;
for (i, (&ours, &theirs)) in [fix.residual, second].iter().zip(&p.s).enumerate() {
let rel = (ours - theirs).abs() / theirs.abs();
worst_rel[i] = worst_rel[i].max(rel);
}
}
eprintln!(
"LAMBDA vs RTKLIB v2.4.2-p13: {} problems, {} integer mismatches, worst relative \
difference of the best norm {:.3e}, of the second-best norm {:.3e}",
problems.len(),
mismatches.len(),
worst_rel[0],
worst_rel[1]
);
assert!(
mismatches.is_empty(),
"integer solutions differ:\n{}",
mismatches.join("\n")
);
assert!(
worst_rel[0] <= REL_TOL && worst_rel[1] <= REL_TOL,
"squared norms differ from RTKLIB beyond {REL_TOL:e}: best {:.3e}, second {:.3e}",
worst_rel[0],
worst_rel[1]
);
}