Repository navigation
Fixes #28, fixes #33, fixes #41 - #91
Merged
Merged
Conversation
An int64/uint64 bin's representative was binning_min + (a + b) / 2, rounded twice, and its bound was (widest bin - 1) / 2 whatever float64 made of it, so [2**53 + 1] was reported exact with error 0 although its representative is 2**53. The representative is now (first + last) / 2 divided once as Python integers, the float64 nearest the midpoint. For a plane with a value of magnitude 2**51 or more the bound is the exact largest distance between each bin's (and tail's) integers and the float64 standing in for them, rounded up, so a histogram is exact only when every representative equals its value. Below 2**51 both are exact float64 arithmetic and unchanged. Bin counts and limits never change. Outputs that move: int64/uint64 lines of tools/dump_outputs.py (186 lines) and --corpus (90 lines), all checked against an exact Fraction oracle; the 400 golden fits are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
PooledHistogram.std and WeightedHistogram.std summed squared deviations in float64, which overflow beyond about 1e154: [-1e200, 1e200] gave inf (so zscore was refused) although its standard deviation is 1e200, and a tail could raise a bare OverflowError from float ** 2. Both now go through _std_with_tails. The plain sum is tried first, so every finite result is bit-identical; when it is not finite but the mean and every value are, it is redone with all values divided by a power of two near the largest (exact), and multiplied back. A tail whose stored sum of squares is infinite (its pixels' squares overflowed when the file was written) is refused by name, since its spread is not known; mean, percentiles, median and MAD remain available. Tests compare with an exact Fraction/Decimal oracle: +-1e200, 1e200 +- 1e185, +-1e308, constant 1e200, weighted parts and robust tails near 1e153, plus a CSV round trip of an infinite tail. tools/dump_outputs.py and --corpus are unchanged by this commit; the 400 golden fits are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
With scope="biological_repeat" and per_time_point=True the fitted groups are (label, t), but repeat_missing_conditions gathered the conditions of each label over all time points. Two rows (control, r1, t=0) and (treated, r1, t=1) gave the groups r1::t=0 and r1::t=1, one condition each, and no warning. The check now runs on the groups scope_groups returns, so it uses the same keys as the fit, and names them with group_name (r1::t=0). The expected set stays every condition of the statistics (contract 4.2). Without per_time_point the keys, items and message are unchanged. Controls: complete same-time groups, a condition missing an entire time point in every repeat, renamed pairs, pooled time and the image scope. tools/dump_outputs.py and --corpus are unchanged by this commit; the 400 golden fits are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Say who is affected (int64/uint64 images with a value of magnitude 2**51 or more), by how much representatives, bounds, exactness flags, pooled statistics and fitted parameters move, and that statistics files need not be recomputed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
pushed a commit
that referenced
this pull request
Nov 11, 2026
The #91 tests for 64-bit integers and overflowing tail squares and the #92 test of exact percentile names build PlaneRecords and Parameters; they now pass channel IDs (PlaneRecord channels=, write_plane_histograms channel_ids=, Parameters channels by ID). The code-rules baseline drops the two entries the merge left behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
pushed a commit
that referenced
this pull request
Nov 11, 2026
…draft evidence tool Contract: header IDs are the declared channels, never used_ids (§6.1); code point order and the place of the channels column (§6.1, §6.2); a failed measurement removes an earlier table (§6.2); normalization_parameters_version and the one channel-order rule, RGB included (§8.1, §8.2); partial_channels (§1.5, §2, §8.3); the new refusals (§9.1) and the opt-in-only channel_partially_present warning (§9.2). Plan §12 lists the implementation choices for the owner's review. CHANGELOG Unreleased describes them and the evidence; the #91 entries name Histogram_Exact_<ID>. tools/evidence_v4_parity.py was draft-only (it compares two installed releases, so it is not a test); it is removed after its last run (3600 fitted channels, identical to main 0b35530 and to v0.4.0). The test helper takes the declared channels, with a test that a retired ID stays out of the header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet
pushed a commit
that referenced
this pull request
Nov 11, 2026
Owner decision of 11 November 2026 (plan N65; #91's question). The underflow and overflow of a robust histogram are stored as Histogram_{Underflow,Overflow}Count, ...Mean and ...Std per channel ID, replacing the raw float64 Sum and SumOfSquares, which cancel for tails of large values with a small spread and overflow beyond about 1.3e154. HistogramTail is now (count, mean, std). The new pure module tail_moments.py takes every tail pixel at its exact value (a float as its 53-bit significand times a power of two, an integer as a Python int), keeps the sum and sum of squares as exact rationals over blocks, and rounds the mean and the population standard deviation (integer square root with a sticky bit) once each to the nearest float64. Both are finite for finite pixels (the std is at most half the tail's range), so no scale column is needed, and a plane gives the same bits whole or in blocks of any order. Pooling adds count * std**2 and count * (mean - pooled mean)**2 per tail (centred, no cancellation) with the existing power-of-two rescaling on overflow; the pooled mean of tails whose products overflow is rescaled the same way. This supersedes #91's named refusals of infinite tail sums and sums of squares: those tails now give their finite statistics. integrity.check_tail checks a finite mean in the tail's range and a std of at most half of it. Tests: tests/test_tail_moments.py against an exact Fraction oracle (large values with a small spread where the raw formula is wrong by more than the spread, squares beyond float64, subnormals, both float64 limits, 64-bit integers beyond 2**53, blocks in any order, pooling exact given the stored moments). Tests of the old sums were rewritten for the moments; blocks now equal whole planes bit for bit. Numbers without tails do not change: the 400 golden fits and the fully-present-plane comparison are identical. tools/dump_outputs.py dumps (count, mean, std), or the old fields on older commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Numerical-correctness batch against
mainc952263 (v0.4.0). There is one commit per issue. Each issue was reproduced onmainwith a failing test before it was fixed.Fixes #28, fixes #33, fixes #41.
#27: not reproduced, no change
Float histogram binning of near-zero and subnormal ranges is already correct on
main, through #84/#85 (float_bin_positionuses a bounded fraction where halving would underflow orn / half_rangewould overflow) and #86. Evidence on c952263:[0, 1e-308]with 100 bins puts the values in bins 0 and 99.[0, 5e-324]with 2 and 100 bins puts them in the first and last bin.[-1e-300, 1e-300]with 64 bins puts them in bins 0 and 63.1e-300plane puts everything in bin 0.Fractionoracle (floor((v - lo) / (hi - lo) * n)) bin for bin, with 0 differences.test_float_range_of_the_smallest_subnormal_keeps_distinct_bins,test_float_narrow_range_does_not_overflow_the_bin_scaleandtest_float_range_between_subnormals_keeps_every_pixel_in_its_bincover the issue's cases, including_bin_index.#27 can be closed as fixed by #85/#87.
#28: 64-bit integers that float64 cannot hold (5551e2e)
Problem.
[2**53 + 1](uint64) was reportedis_exact=Truewithmax_representative_error == 0, although its representative is2**53. A second problem: representatives were rounded twice (binning_min, then the sum). For example, the bin holding only2**53 + 2, which float64 can represent, got the representative2**53.Fix. The fix is in the new pure module
integer_representatives.py. It lives there becausehistogram.pyis at its shrink-only line baseline, which is now 861.(first + last) / 2, divided once as Python integers, so it is the float64 nearest the midpoint. Below2**51this gives exactly the old value.2**51or more,max_representative_erroris the exact largest distance between each bin's (and tail's) integers and the float64 that stands in for them, rounded up. A histogram is therefore exact only when every representative equals its value.normalization_exactandHistogram_Exact_C<i>become false for such planes.Outputs that move (intended). Only int64 and uint64 lines with values of magnitude
2**51or more. Every moved histogram line was checked against an independent oracle. It recomputes the bin limits with its ownceilformula, takes the nearest float to each exactFractionmidpoint, and computes the exact bound rounded up. All lines match it, and counts, extrema and tails are unchanged on every line.tools/dump_outputs.py(8c2d3895… → 1723cafb…), 186 of 1788 lines:Histogram_Exact_C0goes from True to False for the constant images. Fitted int64 offsets and scales move by about 1e-13 relative. Example: the zscore image offset goes from 9618469625711740 to 9618469625711942.Is the new mean more accurate? The exact model mean (every pixel at its exact bin midpoint, computed with
Fraction) is 9618469625711938. The pooled mean is now 4.7 away from it; before it was 197.3 away. uint64 means are unchanged.--corpus(a4c37fdf… → 8bb7c3c1…), 90 of 4764 lines:[-2**63, -2**63+1][2**63-2, 2**63-1]and uint64[2**64-2, 2**64-1]2**63 - 2and2**64 - 2are stored as2**63and2**64.The 400 golden fits are bit-identical: they contain no 64-bit values of magnitude
2**51or more.#33: standard deviations of large finite values (47e5339)
Problem.
PooledHistogram.stdandWeightedHistogram.stdreturnedinffor[-1e200, 1e200],1e200 ± 1e185and±1e308, sozscorewas refused even though the true spread is finite.OverflowErrorfromfloat ** 2.Fix.
_std_with_tails. The plain sum is tried first, so every finite result is bit-identical to before.Histogram_*SumOfSquaresis infinite is refused by name, e.g. "tail 2 of 2 has sum of squares inf … use percentile or robust z-score, or the min-max range", because its spread cannot be known. Mean, percentiles, median and MAD stay available.Tests. They compare against an exact
Fraction/Decimaloracle for ±1e200, 1e200 ± 1e185, ±1e308, constant 1e200, weighted parts and robust tails near 1e153. They also cover a CSV round trip of an infinite tail through to thefitrefusal.Outputs that move: none. Dump and corpus digests are identical to the #28 commit, and the goldens are bit-identical.
Not fixed here (needs an owner decision and a new schema version). Schema v4 stores raw tail sums of squares, so:
s2 - s * mean.A centred or scaled moment representation is a file-format change (AGENTS §2.6). It may belong with the v5 schema work in #90.
Also seen, not in scope (follow-up). The pooled mean can still overflow when
count * valueexceeds the float64 maximum, e.g. two pixels of1e308with weights. A non-finite mean leaves the std non-finite andzscorerefused with its existing message.#41: missing conditions per time point (c084a35)
Problem. With
biological_repeatandper_time_point=True, the rows(control, r1, t=0)and(treated, r1, t=1)produced the fitted groupsr1::t=0andr1::t=1, each with one condition, and norepeat_missing_conditionswarning.Fix.
scope_groupsreturns, i.e. the same keys as the fit, and names them withgroup_name(e.g.r1::t=0).per_time_pointthe items and the message are unchanged.Tests. The controls are: complete same-time groups, a condition missing a whole time point in every repeat, renamed pairs, pooled time, and the image scope.
Outputs that move: none.
Gate
python tools/check.py(Python 3.13, Linux) runs ruff format, pytest and the code rules. Pytest: 1579 passed, 2 skipped. Both skips aretests/test_rgb.py:80(CSBDeep not installed locally; the CSBDeep CI job runs them).histogram.pylines: 862 → 861_incomplete_repeat_groupsdocstring entry was removedOverlap with #90 (
claude/stage3-v5-histograms)#90 does not touch
histogram.pyorscopes.py. Both PRs edit these files:pooling.py: here onlystdand_std_with_tailspyproject.toml: one added layer linetools/code_rules_baseline.json: two linesCHANGELOG.md: a new "Unreleased" sectiondocs/contract.md: §1.2.2, §1.2.3, §1.4 and §4.2AGENTS.md: the module maptests/test_scopes.pyandtests/test_pooling.py: tests appended at the endSmall textual conflicts are possible in the docs, CHANGELOG and baseline.
Generated by Claude Code