mesh: strong relax objective never ranks an inverted element below a valid one

In the max-min phase a valid element's term q^-16 reached 1e96 as q -> 0+, far above
an invalid element's 2e8, so the descent kept elements inverted rather than pass
through flat: the distorted plate stayed tangled (4 invalid) once its mesh was the
same on every platform. The shape term now continues as its tangent line below
q = 0.3 (finite, steep) and an invalid element costs 1e11, above any valid one.

Adds a test that the plate's mesh, its perturbation and the strong relaxation are
the same bits with 1 and 4 threads and match a fingerprint taken on macOS (CI checks
it on Linux).

Part of #438.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-16 18:30:00 -04:00
1 parent 700a7e3d10
commit 2456912850
2 files changed
+65 -10

No files matched your search

+34
View File
@@ -357,3 +357,37 @@ fn strong_mode_parameters_are_checked() {
let r = s.execute("mesh.node.relax", &json!({"mode": "strong", "below": 0.01})).unwrap();
assert_eq!(r["moved"], 0, "{r}");
}
/// The mesh and the strong relaxation are the same bits on every platform and thread count:
/// the plate's hex mesh differed between macOS and Linux (the system maths library's `atan2`,
/// `sin` … are not correctly rounded), so the distorted-plate test passed on one and failed on
/// the other (#438). The fingerprint is of the nodes after mesh.hex.generate, after the
/// perturbation and after the strong relaxation; CI checks it on Linux, it was taken on macOS.
#[test]
fn mesh_and_strong_relaxation_are_the_same_bits_everywhere() {
let fingerprint = |s: &Session, h: &mut u64| {
// FNV-1a over every node's label and coordinate bits.
for (n, p) in s.model.nodes() {
for word in [u64::from(*n), p.xyz[0].to_bits(), p.xyz[1].to_bits(), p.xyz[2].to_bits()] {
for b in word.to_le_bytes() {
*h = (*h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3);
}
}
}
};
let run = || {
let mut h = 0xcbf2_9ce4_8422_2325;
let mut s = plate();
s.execute("mesh.hex.generate", &json!({"size": 2.0})).unwrap();
fingerprint(&s, &mut h);
perturb(&mut s, ON_HOLE, 6.0, 0.35);
fingerprint(&s, &mut h);
s.execute("mesh.node.relax", &json!({"mode": "strong", "below": 0.5, "reach": 4.0})).unwrap();
fingerprint(&s, &mut h);
h
};
let one = fieldcraft_parallel::with_threads(1, run);
let many = fieldcraft_parallel::with_threads(4, run);
assert_eq!(one, many, "1 and 4 threads differ");
assert_eq!(one, 0x7445_e04c_0d65_c1d0, "fingerprint {one:#018x}");
}