convergence test: document the weight-order trap (documentation only; no functional change) - #57
Conversation
…did anything The third cached-column divergence flagged in the #55 audit, now identified. mcsampler.py:1138 and mcsamplerGPU.py:1559 carried the note "rvs['weights'] is *sorted* (side effect?), breaking test. Recalculated weights are not. Use explicitly calculated weights until sorting effect identified." The effect is real and its source is the save-P thresholding block (mcsampler.py:739-752): it reindexes EVERY _rvs column by cumulative-weight order, so surviving rows come out sorted by weight ascending. convergence_test_NormalSubIntegrals then splits them into `ncopies` CONTIGUOUS segments and assumes those are independent -- but sorted rows put the smallest weights in the first segment and the largest in the last. Measured on a reconstructed record, max/min segment mass is 1.15e3 weight-ordered versus 1.83 in draw order: the sub-integrals differ by construction and the normality check is meaningless. The workaround does NOT help, and this is the part worth recording: the components were permuted by the same reindexing, so recomputing from them reproduces the identical order. Measured, cached and recomputed weights are both 100% ascending. The comment's claim that recalculated weights are unsorted is simply false. What survives the permutation is `sample_n`, written immediately before that block precisely as an iteration number. Both copies of the test now reorder by it before segmenting. SEVERITY, stated honestly: every production caller of this test is commented out (util_ConstructIntrinsicPosterior_GenericCoordinates.py:693 and two siblings); the only live caller is test/test_like_and_samp.py. So this was a latent trap rather than a live defect -- but a misleading comment plus an ineffective workaround is exactly how one survives, and re-enabling the test would have silently produced nonsense. Adds test_convergence_sample_order.py (3 checks), including one that pins WHY the old workaround was ineffective so it is not reinstated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d it; deprecate the skymap branch Review of #57 was right, and measuring it turned up a second problem in the same place, so the source hunks are withdrawn. The diff against upstream in mcsampler.py / mcsamplerGPU.py is now COMMENT-ONLY. [P2, confirmed] A reused sampler appends rows to the weight columns while sample_n still has the previous length -- measured 3000 weights against 1500 ids -- and numpy fancy-indexing then silently returns the shorter array, dropping every new sample. [not raised, found while checking] Even after the threshold block re-runs and the lengths agree, reordering by sample_n does NOT restore draw order: it is (re)created as arange(len(...)) against an ALREADY-permuted array, so it encodes the order at the start of that call. Measured after two integrate() calls, 0.752 ascending (draw order is ~0.5) and a segment ratio of 373 rather than 1.83. So the proposed fix would have been partly ineffective even with a length guard. A real fix needs stable ids assigned where samples are APPENDED, in every sampler. This test has no live callers -- every production call site is commented out, only test/test_like_and_samp.py uses it -- so that plumbing is not justified. What ships instead: the false claim is removed (the old note said recomputing from components avoids the sorting; measured, cached and recomputed are BOTH 100% ascending), and the caveat now states what is actually true, including what anyone re-enabling the test must do first. Tests keep the evidence: 5 checks now, including both reuse failure modes. The draw-order test is written so it FAILS if the samplers ever grow stable append-time ids, which is the signal to revisit the caveat. Separately: flags the bayestar skymap branch as deprecated. It is unused in production for ~a decade and is the only place n-chunk drops to 500, making it the least-tested regime for anything scaling with chunk size -- notably --portfolio-weight-clip, whose tau = C*sqrt(n_chunk)*mean(w) is ~4.5x more aggressive there than at the n_chunk=1e4 every clip test used. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You were right, and checking it turned up a second problem in the same place -- so I have withdrawn the source hunks. The diff against [P2] confirmedMeasured the two- So in the window you identified, A second problem, which is what settled itEven once the block re-runs and the lengths agree, reordering by
So the recommended fix would have been partly ineffective even with a length guard. Getting it right needs stable ids assigned where samples are APPENDED, in every sampler -- and this test has no live callers (all production call sites commented out; only What ships instead
On "silly to keep a dead array": agreed, and that is roughly why this ended where it did. Also in this branch: the skymap branch flagged deprecatedUnrelated to the ordering issue, surfaced while auditing chunk-size-dependent behaviour. The bayestar-skymap path is unused in production for ~a decade and is the only place CI: agreed the two failing container canaries are unrelated upstream pygsl-lite/setuptools failures. |
…ests prove The module header still said sample_n 'is what survives' the permutation -- written before the reuse tests showed it is stale-and-short between calls and does not encode draw order after one. It now states both failed repairs, the measurements, and that this module is documentation only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documentation only. No functional change ships here: the two samplers differ from
rift_O4dby a corrected comment, and one deprecation note inhelper_LDG_Events.py. An earlier revision of this branch carried a draw-order reorder inconvergence_test_NormalSubIntegrals; it was withdrawn after review, for the reasons below.The trap
convergence_test_NormalSubIntegralssplits_rvsintoncopiesCONTIGUOUS segments and assumes they are independent. They are not: the save-P thresholding block (mcsampler.py:739-752) reindexes every_rvscolumn by cumulative-weight order, so surviving rows are sorted by weight ascending.Two candidate repairs, both of which fail
1. The in-tree workaround does nothing.
mcsampler.py:1138/mcsamplerGPU.py:1559said "rvs['weights'] is sorted ... Recalculated weights are not. Use explicitly calculated weights until sorting effect identified." Measured, cached and recomputed weights are both 100% ascending -- the components were permuted by the same reindexing. That claim was false and is removed.2. Reordering by
sample_nalso fails -- this is what the review caught, plus a second mode found while checking it:In that window, numpy fancy-indexing silently returns the shorter array and drops every new sample. And even once the lengths agree,
sample_nisarange(len(...))assigned to an already-permuted array, so it encodes the order at the start of that call:integrate()callssample_nSo the reorder was partly ineffective even with a length guard.
Why nothing is fixed here
A real repair needs stable ids assigned where samples are appended, in every sampler. This test has no live callers -- every production call site is commented out, only
test/test_like_and_samp.pyuses it -- so that plumbing is not justified. Since sorting here also drives output downselection, the conservative option was preferred.What ships
test_convergence_sample_order.py, 5 checks, holding the measurements.test_sample_n_does_not_encode_draw_order_after_reuseis written to FAIL if the samplers ever grow stable append-time ids -- the signal to revisit the note rather than let it rot.helper_LDG_Events.py: unused in production ~a decade, and the only place--n-chunkdrops to 500, which makes it the least-tested regime for anything chunk-scaled (--portfolio-weight-clipusestau = C*sqrt(n_chunk)*mean(w), ~4.5x more aggressive there than at then_chunk=1e4all clip testing used).CI: the two failing container canaries are unrelated upstream pygsl-lite/setuptools failures.