Task
pysim/genome_compare.py:106 accumulates the Jaro distance in float64; Miller's genome-compare.cpp:83 accumulates it in float32 (float dw, and a 3.0f literal — every term, every addition and the final divide are single-precision). Change the final line to reproduce Miller's float32 accumulation, and tighten the oracle to assert bit-exactness in float32 rather than agreement to 1e-6. After this, jaro_winkler_distance should match the captured C++ output on 228/228 golden pairs bit-for-bit.
Context
This is small in magnitude — a 1-ULP effect that will not move the search landscape — but it is worth fixing because it is the one place the port's own precision thesis is not applied, in the one compare function whose C++ ends in 3.0f.
The gap is not an oversight: pysim/tests/test_genome_compare_oracle.py:36-37 says outright "Float32 vs float64 rounding is the only expected difference; the values are ratios of small integers, so equality to 1e-6 proves algorithmic identity." That reasoning is sound for proving algorithmic identity, which is what D0 was about. But the port's stated standard is stronger — pysim/indiv.py:23-30 claims the float/double boundary is modelled explicitly at every float store, and pysim/cints.py exists precisely so type width is never left implicit. The comment directly above the offending line (genome_compare.py:105) even reads "Float divisions (the C++ casts to float)" — the intent was understood; the code just doesn't do it.
Measured against the golden corpus (pysim/tests/golden/genome_golden.json, key jaro, 228 pairs), comparing bit-exactly after narrowing to float32:
current float64 accumulation : 81/228 differ from golden
Miller's float32 accumulation: 0/228 differ from golden
Max absolute deviation is 7.285e-08 — comfortably under TOL = 1e-6, which is why the suite passes green today. But pysim/indiv.py:397 narrows this value to float32 for the GENETIC_SIM_FWD sensor, and double rounding does not always recover the correct float32: 81 of 228 pairs (~36%) produce a sensor value one ULP away from what Miller's binary produces. So the README's headline "0/228 divergence" is true at 1e-6, not bit-exact, and the claim should be either made true or restated.
GENETIC_SIM_FWD is a gate sensor and was the subject of D0 (the critical defect that returned garbage for the project's entire life with zero tests) and D11. It is the most scrutinised function in the port; it should be the one that matches exactly.
Reproduction
- Load
pysim/tests/golden/genome_golden.json → ["jaro"] (228 cases, each {g1, g2, distance}).
- For each case, build genes with the test's
_gene helper and call pysim.genome_compare.jaro_winkler_distance.
- Compare
np.float32(got) != np.float32(expected).
Expected: 0/228 differ.
Actual: 81/228 differ, each by exactly 1 float32 ULP.
Environment: Python 3.13.7, numpy 2.1.3 (the harness/requirements.txt pin). Verified 2026-07-18.
A one-line replacement reproducing Miller's accumulation order gives 0/228 — confirmed locally. Note the order matters as well as the width: Miller adds the three terms left-to-right with each intermediate landing in a float32 register, then divides by 3.0f.
Pointers
Constraints
- Match Miller's accumulation order, not just the width. Narrowing only the final result is not equivalent and will not reach 0/228.
- Do not change the matching logic (the
sflags/transposition/match_range code). D0's three fixes are correct and golden-pinned; only the final arithmetic line is in scope.
- Keep a teeth test. If
TOL is tightened to bit-exact, TestJaroOracleHasTeeth must still demonstrate the oracle rejects a wrong implementation.
- No new dependencies. numpy is already a dependency;
np.float32 is sufficient.
- Preserve the
path:line C++ citation style used throughout pysim/.
Acceptance criteria
Out of scope
- The float64-vs-float32 chance draws in the mutation layer (
genome.py:410, 437, 453, 454, 474) — same class, different layer, and that layer has no golden test at all. Tracked separately.
hamming_distance_bits/bytes (genome_compare.py:120, raises) — unported, and genome_similarity method 0 is the only method slsim uses.
- Re-running any published M0 result. A 1-ULP sensor change does not invalidate prior numbers; note it, don't re-run.
Reasoning guidance
Prioritize responding quickly rather than thinking deeply — the diagnosis is complete and the fix is one line plus a test tightening. The only subtlety is accumulation order; get that right and the goldens confirm the rest.
Cross-references
Task
pysim/genome_compare.py:106accumulates the Jaro distance in float64; Miller'sgenome-compare.cpp:83accumulates it in float32 (float dw, and a3.0fliteral — every term, every addition and the final divide are single-precision). Change the final line to reproduce Miller's float32 accumulation, and tighten the oracle to assert bit-exactness in float32 rather than agreement to1e-6. After this,jaro_winkler_distanceshould match the captured C++ output on 228/228 golden pairs bit-for-bit.Context
This is small in magnitude — a 1-ULP effect that will not move the search landscape — but it is worth fixing because it is the one place the port's own precision thesis is not applied, in the one compare function whose C++ ends in
3.0f.The gap is not an oversight:
pysim/tests/test_genome_compare_oracle.py:36-37says outright "Float32 vs float64 rounding is the only expected difference; the values are ratios of small integers, so equality to 1e-6 proves algorithmic identity." That reasoning is sound for proving algorithmic identity, which is what D0 was about. But the port's stated standard is stronger —pysim/indiv.py:23-30claims the float/double boundary is modelled explicitly at everyfloatstore, andpysim/cints.pyexists precisely so type width is never left implicit. The comment directly above the offending line (genome_compare.py:105) even reads "Float divisions (the C++ casts to float)" — the intent was understood; the code just doesn't do it.Measured against the golden corpus (
pysim/tests/golden/genome_golden.json, keyjaro, 228 pairs), comparing bit-exactly after narrowing to float32:Max absolute deviation is 7.285e-08 — comfortably under
TOL = 1e-6, which is why the suite passes green today. Butpysim/indiv.py:397narrows this value to float32 for theGENETIC_SIM_FWDsensor, and double rounding does not always recover the correct float32: 81 of 228 pairs (~36%) produce a sensor value one ULP away from what Miller's binary produces. So the README's headline "0/228 divergence" is true at 1e-6, not bit-exact, and the claim should be either made true or restated.GENETIC_SIM_FWDis a gate sensor and was the subject of D0 (the critical defect that returned garbage for the project's entire life with zero tests) and D11. It is the most scrutinised function in the port; it should be the one that matches exactly.Reproduction
pysim/tests/golden/genome_golden.json→["jaro"](228 cases, each{g1, g2, distance})._genehelper and callpysim.genome_compare.jaro_winkler_distance.np.float32(got) != np.float32(expected).Expected: 0/228 differ.
Actual: 81/228 differ, each by exactly 1 float32 ULP.
Environment: Python 3.13.7, numpy 2.1.3 (the
harness/requirements.txtpin). Verified 2026-07-18.A one-line replacement reproducing Miller's accumulation order gives 0/228 — confirmed locally. Note the order matters as well as the width: Miller adds the three terms left-to-right with each intermediate landing in a float32 register, then divides by
3.0f.Pointers
pysim/genome_compare.py:106— the line to change;:105is the comment that already describes the correct behaviour.genome-compare.cpp:83(vendored C++ @45e808c) —dw = (((float)m / sl) + ((float)m / al) + ((float)(m - t) / m)) / 3.0f;, withfloat dwdeclared at:28.pysim/tests/test_genome_compare_oracle.py:36-37—TOL = 1e-6and the rationale to revise.pysim/tests/test_genome_compare_oracle.py—TestJaroOracleHasTeethis the pattern to preserve: any tightening must keep a test proving the oracle can still fail.pysim/indiv.py:397— thef32()narrowing that makes this sensor-visible.pysim/cints.py— where a float32 helper belongs if one is added, alongside the existing integer-width helpers.pysim/README.md:111— the D0 row claiming "0% divergence"; update to say bit-exact once true.Constraints
sflags/transposition/match_rangecode). D0's three fixes are correct and golden-pinned; only the final arithmetic line is in scope.TOLis tightened to bit-exact,TestJaroOracleHasTeethmust still demonstrate the oracle rejects a wrong implementation.np.float32is sufficient.path:lineC++ citation style used throughoutpysim/.Acceptance criteria
jaro_winkler_distancematchesgenome_golden.json["jaro"]on 228/228 pairs, bit-exactly in float32.TOL = 1e-6, and its comment reflects the new standard.pysim/README.md's D0 row states bit-exact parity.npx tsc --noEmit,npm test,python3 harness/test_m0_gate.py,python3 -m unittest discover -s pysim/tests -t .(expect 184+/184+).Out of scope
genome.py:410, 437, 453, 454, 474) — same class, different layer, and that layer has no golden test at all. Tracked separately.hamming_distance_bits/bytes(genome_compare.py:120, raises) — unported, andgenome_similaritymethod 0 is the only method slsim uses.Reasoning guidance
Prioritize responding quickly rather than thinking deeply — the diagnosis is complete and the fix is one line plus a test tightening. The only subtlety is accumulation order; get that right and the goldens confirm the rest.
Cross-references