diff --git a/crates/geom/src/fill_iso.rs b/crates/geom/src/fill_iso.rs index 0ebcde9552..a324064737 100644 --- a/crates/geom/src/fill_iso.rs +++ b/crates/geom/src/fill_iso.rs @@ -2054,11 +2054,13 @@ impl Normal { /// form a banded block, eliminated exactly onto the shared unknowns (the spokes', the centre's, /// and any a row couples across patches) by their Schur complement, which is solved dense; the /// interiors then follow by back substitution. Entries are kept sparse until the blocks are -/// known (callers add both halves of the symmetric matrix). +/// known (callers add both halves of the symmetric matrix), in ordered maps: the eliminations +/// sum them in their order, so a hashed map's order (another each run) would make the fill +/// differ in its last bits from one run to the next, and a design read back another surface. struct BlockNormal { /// Each coordinate's patch when only one patch holds its point, else `None`. owner: Vec>, - entries: std::collections::HashMap<(usize, usize), f64>, + entries: std::collections::BTreeMap<(usize, usize), f64>, } impl BlockNormal { @@ -2077,7 +2079,7 @@ impl BlockNormal { let o = if let [p] = h.as_slice() { Some(*p) } else { None }; [o; 3] }); - BlockNormal { owner: owner.collect(), entries: std::collections::HashMap::new() } + BlockNormal { owner: owner.collect(), entries: std::collections::BTreeMap::new() } } fn add(&mut self, r: usize, s: usize, v: f64) { @@ -2128,8 +2130,8 @@ impl BlockNormal { let m = shared.len(); // The shared block, and per patch its block, its coupling to the shared unknowns. let mut s_mat = vec![0.0; m * m]; - let mut blocks: Vec> = vec![std::collections::HashMap::new(); patches]; - let mut couple: Vec> = vec![std::collections::HashMap::new(); patches]; + let mut blocks: Vec> = vec![std::collections::BTreeMap::new(); patches]; + let mut couple: Vec> = vec![std::collections::BTreeMap::new(); patches]; for ((r, c), v) in std::mem::take(&mut self.entries) { let (or, oc) = (self.owner.get(r).copied().flatten(), self.owner.get(c).copied().flatten()); let (pr, pc) = (place.get(r).copied().unwrap_or(0), place.get(c).copied().unwrap_or(0)); diff --git a/crates/geom/src/fill_iso_tests.rs b/crates/geom/src/fill_iso_tests.rs index f2e26a4426..8e72be3c76 100644 --- a/crates/geom/src/fill_iso_tests.rs +++ b/crates/geom/src/fill_iso_tests.rs @@ -404,3 +404,27 @@ fn a_crown_fills_out_to_the_side_the_neighbours_face() { eprintln!("the window's middle {plain:.4} from the centre, crowned {out:.4}, crowned to inward neighbours {sunk:.4}"); assert!(out > plain + 0.1 && sunk < plain - 0.1, "{plain} {out} {sunk}"); } + +/// The same loop fills to the same bits every time, about a centre and in a ring (a design is +/// rebuilt on reading it back, and must come back the same surface). +#[test] +fn a_fill_is_the_same_to_the_last_bit_every_time() { + let tube = circle_quarters(30.0, 10.0); + let radial = |p: Vec3| (Vec3::new(p.x, p.y, 0.0).normalized().unwrap(), Vec3::Z); + let s = IsoSpec { tol: 2e-5, ..spec() }; + let ring = |_: usize| iso_fill(&tube, &mut beside_by(&tube, radial), &s).unwrap(); + let corners: Vec = + (0..6).map(|k| Vec3::new((k as f64 * 1.047).cos(), (k as f64 * 1.047).sin(), 0.0) * 20.0 + Vec3::Z * (k % 2) as f64).collect(); + let hexagon: Vec = (0..6) + .map(|k| { + let (a, b) = (corners[k], corners[(k + 1) % 6]); + IsoEdge { degree: 2, knots: vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0], points: vec![a, (a + b) * 0.5 + Vec3::Z * 2.0, b], weights: None } + }) + .collect(); + let mut g0 = |_k: usize, ts: &[f64]| -> Result>, String> { Ok(vec![None; ts.len()]) }; + let centre = |g0: &mut dyn FnMut(usize, &[f64]) -> Result>, String>| iso_fill(&hexagon, g0, &s).unwrap(); + let (a, b) = (ring(0), ring(1)); + assert_eq!(a.patches, b.patches, "the ring"); + let (a, b) = (centre(&mut g0), centre(&mut g0)); + assert_eq!(a.patches, b.patches, "about a centre"); +}