use kshana::lunar_faultobs::{pernode_range_row, NetworkLayout};
use kshana::lunar_gauge::T_BASE_S;
use kshana::lunar_llr_geometry::Vec3;
use kshana::lunar_orientation::de440_moon_pa_body_to_inertial;
fn round6(v: f64) -> f64 {
(v * 1_000_000.0).round() / 1_000_000.0
}
const T0: f64 = (2_460_310.5 - 2_451_545.0) / 36_525.0;
const ELAPSED_S: [f64; 3] = [21_600.0, 43_200.0, 86_400.0];
const SIGMAS: [f64; 3] = [0.5, 1.0, 2.0];
fn body_nodes() -> Vec<Vec3> {
let mut nodes: Vec<Vec3> = kshana::lunar_llr_geometry::reflectors()
.iter()
.map(|r| r.pa_body_m)
.collect();
nodes.push([1_600_000.0, 700_000.0, 900_000.0]);
nodes.push([-1_200_000.0, 1_000_000.0, -800_000.0]);
nodes.push([300_000.0, -1_500_000.0, 1_100_000.0]);
nodes
}
fn main() {
let body = body_nodes();
let m = body.len();
let layout = NetworkLayout::new(m);
let state_dim = layout.state_dim;
let inertial: Vec<[f64; 3]> = body
.iter()
.map(|&b| de440_moon_pa_body_to_inertial(b, T0))
.collect();
let mut rows: Vec<Vec<f64>> = Vec::new();
let mut sigmas: Vec<f64> = Vec::new();
let mut idx = 0usize;
for a in 0..m {
for b in (a + 1)..m {
let d = [
inertial[a][0] - inertial[b][0],
inertial[a][1] - inertial[b][1],
inertial[a][2] - inertial[b][2],
];
let n = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
let u = [d[0] / n, d[1] / n, d[2] / n];
for &elapsed in &ELAPSED_S {
let row = pernode_range_row(&layout, a, b, u, elapsed);
rows.push(row.into_iter().map(round6).collect());
sigmas.push(SIGMAS[idx % SIGMAS.len()]);
idx += 1;
}
}
}
let n_rows = rows.len();
assert_eq!(n_rows, 84, "expected C(8,2)*3 = 84 rows, got {n_rows}");
let off1 = layout.off_idx(1);
let b_in_range: Vec<f64> = rows.iter().map(|r| round6(r[off1])).collect();
let mut b_e0 = vec![0.0_f64; n_rows];
b_e0[0] = 1.0;
let mut b_e37 = vec![0.0_f64; n_rows];
b_e37[37] = 1.0;
const NCP: f64 = 17.075;
let c_in_range_norm = {
let nrm = (b_in_range.iter().map(|&v| v * v).sum::<f64>()).sqrt();
b_in_range
.iter()
.map(|&v| round6(v / nrm))
.collect::<Vec<f64>>()
};
let node_incidence = |k: usize| -> Vec<usize> {
let mut inc = Vec::new();
let mut i = 0usize;
for a in 0..m {
for b in (a + 1)..m {
for _w in 0..ELAPSED_S.len() {
if a == k || b == k {
inc.push(i);
}
i += 1;
}
}
}
inc
};
let node2_inc = node_incidence(2);
let jrow = |r: &Vec<f64>| serde_json::json!(r);
let doc = serde_json::json!({
"description": concat!(
"Fault-observability fixtures for `tests/lunar_faultobs_reference.rs` and the ",
"independent numpy/scipy oracle `scripts/gen_faultobs_ref.py`. `rows` is the ",
"n x state_dim measurement Jacobian G of the real-DE440 per-node lunar network ",
"(each row a differential inter-node one-way range built via ",
"`lunar_faultobs::pernode_range_row` with a real DE440 Moon PA-frame line of ",
"sight). Node geometry is Modelled; the DE440 orientation in the line of sight ",
"is the external ingredient. All row/vector floats are rounded to 6 dp before ",
"writing for byte-consistency with the oracle. sigma is per-measurement (W != I)."
),
"R_MOON_M": kshana::lunar_time::RE_MOON_M,
"T_BASE_S": T_BASE_S,
"epoch_jc": T0,
"round_decimals": 6,
"rel_tol": 1e-9,
"n_nodes": m,
"state_dim": state_dim,
"n_rows": n_rows,
"elapsed_s": ELAPSED_S.to_vec(),
"nodes_pa_body_m": body.iter().map(|b| serde_json::json!(b.to_vec())).collect::<Vec<_>>(),
"nodes_inertial_m": inertial
.iter()
.map(|p| serde_json::json!(p.iter().map(|&v| round6(v)).collect::<Vec<f64>>()))
.collect::<Vec<_>>(),
"sigma": sigmas,
"rows": rows.iter().map(jrow).collect::<Vec<_>>(),
"fault_vectors": {
"in_range_off1": b_in_range,
"generic_e0": b_e0,
"generic_e37": b_e37
},
"mdb_directions": [
{ "name": "mdb_e0", "c": b_e0, "ncp": NCP, "detectable": true },
{ "name": "mdb_e37", "c": b_e37, "ncp": NCP, "detectable": true },
{ "name": "mdb_in_range", "c": c_in_range_norm, "ncp": NCP, "detectable": false }
],
"peer_coalitions": {
"single_meas": [[0]],
"uniform_bias_node2": [node2_inc],
"six_independent": [[0], [12], [24], [37], [49], [61]]
}
});
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/faultobs");
std::fs::create_dir_all(dir).unwrap();
let path = format!("{dir}/network.json");
std::fs::write(&path, serde_json::to_string_pretty(&doc).unwrap()).unwrap();
eprintln!(
"wrote {path} (n_rows={n_rows}, state_dim={state_dim}, rank(G)=32, parity_dim=52, W != I)"
);
}