Skip to content

fix: fingerprint quantization no longer splits a value on a boundary (#87) - #90

Merged
NiJingzhe merged 1 commit into
devfrom
fix/fingerprint-quantization-boundary
Nov 12, 2026
Merged

NiJingzhe merged 1 commit into
devfrom
fix/fingerprint-quantization-boundary

Conversation

@NiJingzhe

Copy link
Copy Markdown
Collaborator

Fixes #87.

A design value such as -3.34690815 mm lies exactly on a boundary of the round(value / 1e-7) quantizer behind geometry fingerprints; computation noise of one ulp (the readback of a BREP) sends it to the neighbouring integer, so the fingerprint changed and restore_topology_snapshot raised geometry_mismatch. _q now snaps the ratio to a magnitude-scaled binary grid before rounding.

Reproduced on the issue's attachment (without running its script): the readback fingerprint equals the issue's B and the two edge centers quantize to the integers it lists. The regression test is at the quantizer (the issue's value pairs + 20000 random boundary values with ulp noise: 6482 split before, 0 now). A whole-solid synthetic repro was not possible, see the commit message.

Compatibility: a stored fingerprint of a body with a value on a boundary changes once; the reporter's recorded snapshot has to be captured again. The commit message has the decision record.

🤖 Generated with Claude Code

…t sits on a boundary (#87)

DECISION: snap the ratio value/tolerance to a binary grid before rounding it to an integer. Do not pre-round to a fixed
number of decimals, do not make the snapshot validator tolerant of off-by-one integers.

PROBLEM
A body's geometry fingerprint is a hash of every measured value divided by 1e-7 mm and rounded to an integer
(`_q`, artifacts/geometry_interface.py). A design value such as -3.34690815 mm is exactly a boundary of that
rounding, (k + 0.5) * 1e-7. The value computed one way is a unit in the last place under the boundary, the same value
after a BREP round trip is one over it (-3.3469081499999995 against -3.34690815), and `int(round(...))` sends the two to
neighbouring integers. The reporter's body had two such edge centers: its fingerprint changed across the readback and
`restore_topology_snapshot` raised `geometry_mismatch`.

REPRODUCED
- On the attachment of the issue (R02 BREP + recorded snapshot, read in this checkout, without running the attachment's
  own script): the fingerprint after the readback is exactly the issue's B (sha256:d9620b...), and the two edge centers
  quantize to -33469082 and -66530918, the integers the issue lists after the readback.
- A whole solid that changes its fingerprint across a BREP round trip could not be built synthetically: the doubles
  survive the binary BREP unchanged, the one-ulp difference of the issue comes from how the readback shape is measured
  (centers are integrated). So the regression test is at the quantizer, with the issue's two value pairs and 20000
  random values on a boundary with up to three ulps of noise on each side: before this change 6482 of them split, and
  both issue pairs failed.

WHY THIS FIX
- Pre-rounding to a fixed number of decimals either leaves large coordinates (a 1 m part is 1e10 quanta, one ulp is
  2e-6 of a quantum) unprotected or moves 1% of all values to a different integer. The grid follows the ratio's
  magnitude (2**-46 of it, at least 2**-24): a half-integer is exactly on it, noise is well under it, and the window of
  values that change integer is about 1e-5 of a quantum (test: at most 5 of 50000 ordinary values move).
- A tolerant validator (accept an off-by-one integer) would have to be taught every place a label is compared (body,
  face, edge and vertex labels, the topology match keys, the connector hashes); one deterministic quantizer fixes them
  all at the source.

WHAT THIS DOES NOT FIX
- Fingerprints are stored in artifacts. A value that was on a boundary now quantizes (half to even) where it used to
  fall on either side, so such a stored fingerprint changes once; every other value keeps its integer. The reporter's
  recorded snapshot (made on the other side of the boundary) still does not restore: it has to be captured again. It
  could not have been restored before either.
- `exporter/_surface_mesh.py` and `inspect/brep/compare.py` round the same way for mesh welding and BREP comparison;
  they were not part of the report and are left alone.

Checked: test_artifact_*, test_product_*, test_brep_region_snapshot, test_topology_getter_contract, test_part_interface,
test_assembly_builder, test_native_canonical pass. test_scene_contract (generated types) and test_native_without_ocp
fail on the base commit as well (this machine lacks OCP and the pinned generator).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@NiJingzhe
NiJingzhe merged commit e86b9a3 into dev Nov 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant