sed: make_bc can extend the Av axis, and BULGE_AV_PTS says how far and how finely (2.9.16) - #301
Conversation
…d how finely The shipped Av axis stops at 6.0 mag and `SED._inject_grid_bounds` makes the grid extents the sampled parameter's EXACT SUPPORT through the logit transform -- so `av` cannot exceed 6 and a bulge fit is truncated rather than warned (review 2.9.16). make_bc_tables took `av_pts = axes["av_pts"]`, i.e. it INHERITED the axis from the tables already on disk, so there was no way to lift the ceiling. It now accepts an explicit axis; passing nothing reproduces the shipped one exactly. RANGE, measured rather than assumed. Inverting the DC2018 challenge's own red-clump extinctions through models/extinction_law.ascii, its 293 lines of sight need A_V from 1.69 to 19.04 (median 2.62, p95 10.06): 11% are past 6.0. But the range is set by what is OBSERVABLE, not by what is tabulated. The largest FITTABLE requirement is 15.19 +/- 1.33 -- event 100, the faintest sightline with a released light curve, source fraction 0.434 and baseline S/N 23, so source S/N ~10. A_V ~ 19 appears only in the extinction table with no light curve, and at that depth a source like these sits at S/N 2-3 and carries no SED information. 20.0 then clears the largest fittable value by 3.6 sigma, which is the margin that keeps the posterior off the bound; the top few magnitudes exist to prevent truncation, not because anything is measured there. SPACING, also measured. |d2BC/dAv2| at high Av, p95 over all 660 (Teff, logg) cells of the shipped tables, is 0.0007 for 2MASS_J and 0.0389 for Gaia_G -- sixty times larger, because a wide blue passband reweights as the spectrum reddens. Linear interpolation costs h^2/8 of that, so in the worst band h=2 is 0.019 mag, h=1 is 0.0049 and h=0.5 is 0.0012. The shipped 2-mag steps above Av=1 therefore already cost ~0.019 mag in Gaia_G, which is indistinguishable from today's 0.02 systematic floor and 4x over the 0.005 that Landolt-era calibration targets; h=1 lands exactly ON 0.005, which is no margin. h=0.5 is 4x under it. Below Av=1 the curvature is HIGHER, so the shipped fine sampling there is kept. COST: 48 points against 13, ~3.7x the table size (841 KB -> ~3.1 MB per feh file). Irrelevant beside the R=150 spectra's own ~0.02 mag error, which dominates interpolation by 16x -- regenerating on the full-resolution grids is what buys the 0.005 target, and this spacing is chosen so the axis is not the limiting term when that happens. validate_av_pts is module-level on purpose: a test of the axis contract must not need the 313 MB model tree or the Zenodo fetch that ensure_model_data would trigger before the generator's body was reached. 56 passed with tests/test_sed.py on a compute node. NOT REGENERATED HERE. This only makes the axis expressible; the tables on disk are unchanged, and regenerating them is a separate decision since they are shipped data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bservable up there JDE: "we won't have gaia (optical) data at high extinction ... it doesn't pay to be conservative here -- a resolution of 1 mag up to av=15 will be a substantial bloat to the grid for everyone in order to apply it to a handful of microlensing systems." Correct, and it inverts my spacing conclusion. I had sized the axis on the most curved band in the table, Gaia_G at |d2BC/dAv2| = 0.0389, which demanded h=0.5 and would have made the grid 3.7x bigger for every user. But at A_V = 15 there is no Gaia measurement to interpolate: A_G is 11.6 mag, so a bulge clump giant (M_G ~ 0, m ~ 14.5 unreddened) sits at m = 26, five magnitudes past Gaia's limit. The same arithmetic removes Bessell B and V, TESS (limit ~16 against m = 23.8) and all of 2MASS (limits J 15.8, H 15.1, K 14.3 against 19.1, 17.3, 16.4). What survives A_V = 15 is Roman's own two bands, deep ground-based IR of VVV class, and WISE. The worst curvature among those is WFI_F146 at 0.00825, needing only h < 2.20 mag for 0.005 -- so the SHIPPED 2-mag step already costs 0.0041 mag where it is used, inside the Landolt-era target, and refining it would buy precision in bands that cannot be observed at that extinction. So BULGE_AV_PTS is now the shipped 13 points verbatim plus 8, 10, 12, 14, 16, 18, 20 at the same cadence: 20 points, 1.5x rather than 3.7x, 841 KB -> ~1.3 MB per feh file, and the growth is confined to a range that was previously unreachable. The test asserts both halves -- that the shipped range is untouched, and that the spacing is sized by the SURVIVING bands. Filed rather than fixed: for a user WITH optical data at MODERATE extinction, the shipped 2-mag steps between Av = 2, 4 and 6 already cost ~0.019 mag in Gaia_G. That is a pre-existing limitation of the shipped grid, not of this extension, and refining 1-6 would bloat the grid for everyone -- so it is a separate decision, noted in the constant. 56 passed with tests/test_sed.py on a compute node. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Update after reviewThe spacing argument in the first version was wrong, and the correction shrinks this change considerably. I had sized the axis on the most curved band in the table — Gaia_G, What survives A_V = 15 is Roman's own two bands, deep ground IR of VVV class, and WISE — and the worst curvature among those is WFI_F146 at 0.00825, needing only h < 2.20 mag for 0.005. So the shipped 2-mag step already costs 0.0041 mag where it is used, inside the Landolt-era target. Refining it would buy precision in bands that cannot be observed at that extinction. So: extend, don't refine.
The shipped 13 points are kept verbatim — the curvature is highest below Av=1, so their fine sampling is exactly where it's needed — and the growth is confined to a range that was previously unreachable. The test asserts both halves: that the shipped range is untouched, and that the spacing is sized by the surviving bands. Range (unchanged from the first version)
Filed rather than fixedFor a user with optical data at moderate extinction, the shipped 2-mag steps between Av = 2, 4 and 6 already cost ~0.019 mag in Gaia_G. That's a pre-existing limitation of the shipped grid, not of this extension, and refining 1–6 would bloat the grid for everyone to serve it. Noted in the constant as a separate decision.
|
.pre-commit-config.yaml pins ruff at v0.16.0 and says so emphatically -- it is "THE ONLY AUTHORITY ON THE RUFF VERSION", ruff is deliberately absent from pyproject.toml so there is no second pin to drift, and CI runs THOSE hooks rather than its own ruff so the two cannot disagree. I had been formatting this whole session with the conda environment's 0.16.2, which is exactly the drift the comment warns about (it even names a case of 0.16.1 against 0.16.0). The two versions disagree on one list literal here: 0.16.0 wraps it, 0.16.2 leaves it inline. Formatted with 0.16.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…p all 8 Roman bands Runs make_bc.py over all seven facilities with BULGE_AV_PTS, which is the axis the previous two commits added. 77 files, 2m25s on 4 cores of a compute node (NOT the login node). THE OLD ROWS DO NOT MOVE. All 8,580 pre-existing (teff, logg, feh, Av) rows reproduce bit-identically -- max |dBC| = 0.000e+00 across every band of every facility -- because BULGE_AV_PTS extends rather than refines: it is the shipped 13 points verbatim plus 8, 10, 12, 14, 16, 18, 20 at the same 2-mag cadence the old axis already used above Av = 2. So the grid grows 1.54x (8,580 -> 13,200 rows) and no shipped example's SED can shift. Extend-not-refine is also why the spacing argument came out this way: at A_V = 15 an optical band is several magnitudes past any real detection limit, so refining up there would have bloated the grid for everyone to serve data that cannot exist. ROMAN GOES FROM 2 BANDS TO 8 (F062, F087, F106, F129, F146, F158, F184, F213 -- the WFI imaging set; Prism and Grism are spectroscopic and have no BC). Roman was the one facility never tracked here, because make_bc.generate_missing_facility builds a missing facility on demand -- but it builds only the bands the config happens to request, so a fit using Z087+W149 silently got a 2-band table and paid a one-time generation cost for it. Shipping the full set removes both. The tables are what ships; the SVO transmission profiles still download to the machine-level cache at fit time, which is the designed split (filters/filter.py deliberately treats the package directory as read-only). Cost: the tracked BC tables go 59.5 MB -> 91.8 MB for the axis, plus 29 MB for Roman. Worth knowing before the R~1e6 regeneration, which rewrites all of it again; if that lands in git too, these tables are the obvious candidates to move behind utilities/zenodo.py like the NextGen spectra already are. Verified: axis is 20 points 0.0-20.0 and IDENTICAL across all seven facilities; teff/logg/feh axes unchanged (60/11/11); no NaN or inf; per-band BC ranges order correctly by wavelength. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…01 not 9.88 With the Av axis extended to 20 mag the event-194 prior could finally be measured rather than clipped, and measuring it turned up the last piece of 7.7.3: OUR MODEL AND THE SIMULATION USE DIFFERENT EXTINCTION CONVENTIONS, so no single av satisfies both truth bands. WHAT THE SIMULATION DID, demonstrated rather than assumed. Its A_Z087/A_W149 = 1.9236 is reproduced by the shipped law at the filters' WavelengthEff (0.86510, 1.30496 um) to 0.1%, and by NO other wavelength definition SVO publishes: pivot/ref is 13.3% off, phot 6.0%, cen 23.6%, mean 24.7%. Inverting the data's ratio for the W149 wavelength that would produce it gives 1.3130 um -- WavelengthEff to 0.6%. So the challenge reddened MONOCHROMATICALLY at each band's effective wavelength, which is the provenance of the 9.88 that has been quoted as event 194's A_V. WHAT WE DO IS THE PHYSICALLY CORRECT THING: make_bc integrates a reddened spectrum through the passband. For F146/W149 (0.896-2.073 um, WidthEff 0.789) that is a different quantity. As Av rises the band's blue wing is extinguished away, its own effective wavelength drifts red, and dA_band/dAv FALLS -- 0.272 near Av=0 to 0.212 at Av=10 -- so our A_Z087/A_W149 GROWS with Av (1.978 at 0.1, 2.219 at 11.75, 2.317 at 18) while the data's is constant by construction. HENCE THE ANCHOR CHOICE IS WORTH 15% AND THE COLOUR IS THE RIGHT ONE. Through our integrated grid: A_W149 -> av 11.75, A_Z087 -> 10.14, E(Z087-W149) -> 9.01. At 9.01 both bands come out -0.58 mag, THE SAME OFFSET, and a common-mode grey offset is degenerate with distance and radius, which the SED already fits. Every other anchor dumps the residual into COLOUR where only teffsed can absorb it (+0.81 mag at the A_W149 anchor, +0.26 even at the simulation's own 9.88). sigma is 0.3, a model uncertainty: the colour-matched av has std 0.10 across 36 (feh, Teff, logg) cells, is independent of logg and feh to +/-0.01, and depends on Teff alone at 0.28 per 1000 K. The arm's old N(9.88, 1.19) was the wrong centre and a width propagated from the wrong quantity. This is neither a law error nor a grid error, and the two earlier readings of the same 26% spread -- "our law disagrees with the simulation" and then "my own convention artefact" -- were both wrong in the same way. It is the monochromatic-vs-integrated difference, irreducible while the data are one and the model the other. For real Roman data the integrated treatment is simply right and none of this arithmetic arises; mkticsed's clump path still inverts monochromatically and so carries the same ~15% error for W149-width filters, which is fine for a prior a magnitude wide and is filed with 2.9.16 rather than fixed here. ALSO IN HERE: * C29 in conventions.md AND convention.tex, since a C-number must mean the same thing in both and ride one commit. The paper repo's third copy is not reachable from this machine, so the tex header now records C29 as OWED to the paper rather than claiming all three are in step. * NextGen.grid.yaml still declared the OLD 13-point Av axis. The fit path never reads it -- sed.py::_inject_grid_bounds calls peek_grid_axes, which derives the axes from the tables -- but slice_bc and sed/plot.py do, so it described a grid that no longer existed and nothing complained. Synced, and tests/test_sed.py now pins yaml == tables on all four axes, because that failure is silent by construction. * dc18_set_cores.py said "'NoneType' object has no attribute 'get'" when handed an empty config, naming neither the file nor the problem. It was a 0-byte DC2018_194.yaml -- ab194's av_true arm, truncated when the arm was set up -- and the array task spent a 64-slot allocation printing that traceback. Now one line that also names the three paths an arm config has to change. * The four av arms are tracked (configs only, as the other arms are), with av_true rebuilt from its sibling: prefix, parameter_file AND sed.file all renamed, verified by grepping for the donor's name. Leaving one of those three behind is how the tight arm's trace was destroyed. * av_free's completed run is archived in av_free_ceiling6/ rather than left to be overwritten: av = 5.26 +0.33 -0.40 against a hard [0, 6], i.e. the grid running out 2.3 sigma from a bound it cannot cross, with no warning. Its siblings av_clump (8.61) and av_band (11.65) were REFUSED at startup for a start outside those bounds -- the guard working as designed. Tests: 127 passed (test_sed, test_make_bc, test_extinction_law_inversion, test_sed_grid_barrier, test_mkticsed) on a compute node. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…engineering around it
JDE 2026-09-17: "I don't want to go to great effort (e.g., ship new grids)
to support a problem in DC2018. we should raise our zero point error
and/or our av prior error to accommodate their error, compute the impact,
and list the systematic disagreement as a caveat alongside the discussion
of how well we recover 'truth'."
So no new grid work. Three changes to the sweep's priors, all measured.
(1) `av` IS ANCHORED ON THE COLOUR, THROUGH OUR OWN BC GRID. AV_COLS put
A_W149 straight into `av`, ~5x too small, and it survived only because the
grid's Av axis stopped at 6.0 so a correct prior would have hit a wall
instead (7.7.3). We invert the clump's (A_W149, A_Z087) for A_V through the
INTEGRATED band extinction the model actually uses, deliberately NOT through
components.sed.extinction's monochromatic law -- using the law would
reproduce the simulation's own convention and hide the disagreement rather
than budget for it. Per event: 008 2.26, 152 3.70, 223 5.46, 128 6.44,
062 7.26, 194 9.01. All six are now inside the axis; under the old ceiling
three of them (062, 128, 194) would have been refused at startup, as
ab194's av_clump and av_band arms actually were.
(2) THE TWO QUOTED DISPERSIONS ARE ONE FRACTIONAL ERROR, FULLY CORRELATED,
and treating them as independent overstates the colour error by ~2.4x.
Measured on all six events: sigma_Z/sigma_W = 1.885-2.000 against
A_Z/A_W = 1.918-1.924, and sigma_W/A_W equals sigma_Z/A_Z to three digits
(event 194: 0.1200 vs 0.1191). event_info.txt is quoting one fractional
uncertainty on the line-of-sight extinction, so the colour excess carries
that same fraction and `av` scales with it: sigma_av = av * hypot(frac,
0.06), the 0.06 being the shipped law's own ~6% NIR shape error (2.9.16).
That term also keeps event 008 off a ZERO-width prior -- both its
dispersions are quoted as 0.01, which rounds the correlated colour error to
exactly 0.
(3) THE ZEROPOINT PRIOR ABSORBS THE LEFTOVER GREY TERM. N(22.0, 0.02) was
right about the instrument and wrong as an error budget. Anchoring the
colour leaves both bands off by the SAME amount -- -0.07 mag on 008 rising
to -0.58 on 194 -- and with the zeropoint pinned that hard the offset had
nowhere to go but theta_star.
THE IMPACT, WHICH IS THE POINT OF COMPUTING IT. A grey error leaves the
colour alone, so in the challenge's own CSB relation
(log theta = 0.986 + 0.366*(Z087-W149)_0 - 0.2*W149_0) only the magnitude
term moves, and theta_star is biased LOW by 10**(-0.2*grey):
event grey (mag) theta_star bias
008 -0.07 0.970x
152 -0.15 0.935x
223 -0.26 0.886x
128 -0.34 0.856x
062 -0.41 0.827x
194 -0.58 0.766x
Against the 0.62x deficit measured on event 194 against truth, the
convention accounts for 0.766 of it -- more than half the discrepancy in
log space (-0.116 dex of -0.208). It is not the whole story and this does
not claim to fix it; widening the prior does not remove a systematic, it
stops the systematic masquerading as a measurement. The residual
disagreement is a CAVEAT to report beside the truth-recovery table, not
something the sweep should be tuned against.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n path JDE 2026-09-17: "I don't think on the fly generation of the BC grids (as we once envisioned) is practical in production (with the full resolution atmosphere grids). it would require the user to download 250 GB of high resolution models." It is affordable now only because the shipped spectra are plot-resolution. The machinery stays -- it builds the shipped tables -- but the expected path for a new filter is to request it and have it generated centrally and shipped, and generate_missing_facility now says so at the function a reader lands on. Also records the option JDE raised and nobody has costed: a service hosted beside the models that computes and serves a requested BC table without the caller holding the 250 GB grid. That would turn extending the Av axis -- and adding the Rv axis (2.9.16) -- from a repository-wide regeneration into a query. Whether Harvard IT would host it is an open question and not a software one; written down so it is not rediscovered as novel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The priors the sweep has been running are ~5x too small (AV_COLS wrote the
clump's A_W149 into `av`), and the correct ones would have been REFUSED by
the old 6-mag grid ceiling on three of these six events. Both are fixed, so
regenerate:
event was now zeropoint sigma
008 0.66 +/- 0.01 2.26 +/- 0.14 0.07 (was 0.02)
062 2.20 +/- 0.20 7.26 +/- 0.79 0.41
128 1.94 +/- 0.26 6.44 +/- 0.94 0.34
152 1.10 +/- 0.02 3.70 +/- 0.23 0.15
194 2.75 +/- 0.33 9.01 +/- 1.20 0.58
223 1.64 +/- 0.15 5.46 +/- 0.61 0.26
All six are now inside the axis; the largest is 9.01 against a ceiling of 20.
THE RE-RUN CHANGES TWO THINGS, NOT ONE, and the archive README says so
rather than claiming a clean single-variable comparison. Regenerating also
picks up `err_scale: [0.5, 2.0]` on both instruments, which postdates these
configs -- JDE's call of 2026-09-15 (8.2.2), added because on event 226 a
point-lens basin inflated both bands 300-460x and turned the anomaly into
noise. Re-running without it would deliberately reinstate a known defect,
so it stays and the second variable is declared. Everything else is
byte-identical: only the six params.yaml files changed, and the sampler
block (ptde_async, 64 cores, n_temps auto, T_max 200, tune 5000, draws
50000) is untouched.
The previous sweep's traces and result tables are MOVED to
sweep_v7_avW149/ -- 25 GB, the only copy, and the scored baseline the truth
table was built from (152 5/7, 194 6/7; 008, 062, 128, 223 salvaged from
traces after the h_rss kills). Any claim that the corrected priors helped
or hurt is measured against it. Configs are copied there too, with a
warning not to rerun from that directory since their internal paths still
point at ../sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Self-correction to the previous two commits, found before the sweep
launched. Widening both zeropoints to the grey residual was the obvious
move and it is the wrong one.
WHY IT IS WRONG. These configs run `filters: []`, so the SED carries no
photometry of its own and the two mulensing zeropoints are the ONLY colour
information in the entire fit. Two INDEPENDENT priors of width 0.58 admit
sqrt(2)*0.58 = 0.82 mag of COLOUR slack -- an order of magnitude more than
the colour signal separating plausible source temperatures. It would have
bought an honest error bar on theta_star by throwing teffsed away, and it
would have slackened precisely the constraint the colour anchor exists to
exploit. The prior that WOULD be right -- one grey term shared between the
two bands -- is not expressible in the config format, and building it is the
engineering JDE ruled out for a DC2018 problem.
WHAT REPLACES IT. `av` is the correctly CORRELATED nuisance: moving it
reddens both bands together in the ratio the law dictates, which is mostly
grey with exactly the colour term the physics implies, so it buys freedom
along the reddening direction without opening the differential. Half the
anchor spread (colour 9.01 vs A_W149 11.75 on event 194) now goes into the
av prior width alongside the clump and law terms:
event A_V prior zeropoint
008 2.26 +/- 0.19 22.0 +/- 0.02 (unchanged)
152 3.70 +/- 0.38 22.0 +/- 0.02
223 5.46 +/- 0.83 22.0 +/- 0.02
128 6.44 +/- 1.20 22.0 +/- 0.02
062 7.26 +/- 1.22 22.0 +/- 0.02
194 9.01 +/- 1.83 22.0 +/- 0.02
SO THE BIAS IS REPORTED, NOT ABSORBED, and dc18_truth_table.py now prints
it beside the recovery table -- the per-event grey residual and the
theta_star bias it implies (0.970x on 008 to 0.766x on 194), with the note
that on 194 it is 56% of the measured deficit and the rest is ours. JDE
asked for the disagreement "as a caveat alongside the discussion of how
well we recover 'truth'"; this table IS that discussion for anyone reading
a scoring run, and a caveat that lives only in the paper is a caveat nobody
applies.
The rejected approach is kept in the comments at both sites rather than
deleted, because "widen the zeropoint, it is a grey term" is exactly the
reasoning a later reader will re-derive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JDE 2026-09-17: "update the sweep to follow all currently known best
practices -- ptde, reparameterizations, data derived tight bounds, and the
correct av prior." All four were measured on event 194 and all four were
measured SEPARATELY; this is the first configuration that carries them
together, which is the one thing about it that is NOT yet validated.
(1) ptde, NOT ptde_async. Seven scored runs of event 194: ptde put ALL 78
chains in the good-likelihood region 3 times out of 3, under three
different configurations; every ptde_async run of the same event put at
most 40 there and usually fewer than three. ESS followed -- u0te_sync
reached ~30,000 from 3,595 draws at 36 GB where its async twin needed
26,984 draws and 385 GB.
(2) fitu0te ON THE SOURCE. Best of the three ptde arms by ESS(core):
30-32k against 23-25k (tight bounds) and 9.9k (control), at Rhat 1.00 with
78/78 chains. It samples u_0*t_E, which attacks the u_0/t_E/f_blend
degeneracy that makes the blended events hard.
(3) DATA-DERIVED TIGHT BOUNDS (8.2.2 path 1), reproducing the `tight` arm's
rule exactly rather than approximately -- its committed numbers come back
bit-for-bit from this generator (log_f_total -2.1222092/1.8777908 and
-3.3546268/0.6453732 on 194, t_0 2458346.505461/2460059.241444):
* source.t_0 -> the observing span of the light curves themselves. A
peak outside the data is not a detection of anything.
* source.u_0 -> [-3, 3], i.e. "the source was magnified".
* log_f_total -> log10(median flux of that curve) +/- 2 dex.
* err_scale -> NOT the tight arm's [0.1, 10] but the later, tighter
[0.5, 2.0] (JDE 2026-09-15, 8.2.2), which the sweep
already had and which supersedes it.
Worth 9.9k -> 23-25k ESS on their own.
(4) The colour-anchored av prior, from the previous commits.
AND ONE THING NO ARM HAS EVER HAD: all seven predate #293, so `ptde`
ignored store_hot_chains in every sync arm -- no hot-chain mode detector in
any run that had good chains. That is fixed, so this sweep is the first to
get the good chains AND mode discovery together.
VERIFIED BEFORE LAUNCH, on a compute node, not assumed:
* The config builds: system.prepare() + build_model() succeed with a
finite start logp (88318.23 on event 194).
* fitu0te is actually active -- the `source.fitu0te_jacobian` potential is
in the model.
* THE u_0 BOUND STILL BINDS, as a SOFT barrier: fitu0te makes u_0 a
derived parameter ("from_u0te") with u0te sampled on [-5000, 5000], so
the |u_0| <= 3 bound arrives as the `low_bound.source.u_0` /
`up_bound.source.u_0` potentials rather than as hard support. That is
the right strength for a statement this weak, and it is documented at
the site so nobody "fixes" it into a hard bound.
* Every seed value sits INSIDE its new bound on all six events (t_0
2458554-2459665 against a span of 2458346-2460059; |u_0| 0.012-1.472
against 3), so none of them can be refused at startup the way the
av_clump and av_band arms were.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s move with it
CI caught both of these and I should have: `av`'s bounds ARE the grid's
extent (sed.py::_inject_grid_bounds), so extending the axis to 20 mag is not
only a data change. Running the five SED-adjacent test files was the wrong
check -- 127 passes there and the suite still red -- which is the heuristic
docs/testing.md already warns against. CI's complete failure set was 6
tests in 2 files, both of them here.
(1) test_component_override_channel.py declared the grid it expects as a
constant, `"av": (0.0, 6.0)`. Now (0.0, 20.0). Three tests read it
(the override-channel check, the match-the-live-axes check, and the
looser-user-bound clipping check); all three pass. The constant is
deliberately kept rather than derived from the grid -- it is asserted
against the live axes in the same file, so it only has to agree with the
tables on disk, and a silent axis change should break it.
(2) The three KMT-2019-BLG-1806 fixtures had to be re-recorded, and the
delta is fully explained -- ONE term, on the reference machine:
POT:logit_uniform_prior.star.av -12.797195 -> -15.202805 (-2.405610)
start values moved: 0 of them, bit-identical
terms changed: 2-3 of 87, of which exactly ONE is substantive
(the others are 1-ulp on imf_prior and
mulensinstrument.model)
That is the logit-uniform prior's normalization following the support from
[0, 6] to [0, 20]; log_jac carries log(hi - lo), and the raw coordinate for
the same physical av moves with it. The stored start is unchanged, so this
is the PRIOR moving and not the likelihood -- which is what the fixtures
exist to separate.
RE-RECORDED WITH --only, NOT WHOLESALE. A full `--check` also reports
ob08092 and ob09020 as DIFFERS, but purely at the last ulp
(RV:mulensinstrument.model -2.3e-10, imf_prior -2.2e-16), which the strict
same-machine recorder flags and the replay tests' tolerance tier correctly
absorbs -- neither fails in CI. Re-recording everything would have swept
that unrelated drift into this commit and hidden it. It also reports five
`MISSING fixture` lines for ob09020_accept/_diag/_polish/_tune/_tuneonly,
which are UNTRACKED local configs in this checkout, not shipped examples.
Replay tests after: 7 passed, largest residual 6.4e-15 relative.
WHAT THIS DOES NOT SETTLE, and it is worth a decision rather than a commit:
the same mechanism means every SED fit now carries a uniform av prior over
[0, 20] instead of [0, 6], because the grid extent IS the sampled support.
Harmless where av is well constrained (a uniform prior's widening is a
constant), a real 3.3x loosening where it is not -- and it lands on every
user to serve bulge sightlines, which is the objection JDE already raised
about grid spacing, reappearing as prior width. Decoupling the
interpolation extent from the sampled support is the fix and is a design
change (it collides with "bounds may only be TIGHTENED"), so it is filed
rather than done: see 2.9.16.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Serves review 2.9.16. Makes the Av axis expressible; does not regenerate the shipped tables.
Why
The shipped Av axis stops at 6.0 mag, and
SED._inject_grid_boundsmakes the grid extents the sampled parameter's exact support through the logit transform — soavcannot exceed 6, and a bulge fit is truncated rather than warned.make_bc_tablesdidav_pts = axes["av_pts"], inheriting the axis from the tables already on disk, so there was no way to lift the ceiling. It now takes an explicit axis; passing nothing reproduces the shipped one exactly.Range — set by what's observable, not what's tabulated
Inverting DC2018's own red-clump extinctions through
models/extinction_law.ascii, its 293 sightlines need A_V = 1.69 to 19.04 (median 2.62, p95 10.06) — 11% past 6.0.But the largest fittable requirement is 15.19 ± 1.33 (event 100: the faintest sightline with a released light curve, source fraction 0.434, baseline S/N 23 → source S/N ≈ 10). A_V ≈ 19 appears only in the extinction table with no light curve, and at that depth a source like these sits at S/N 2–3 and carries no SED information.
So 20.0 clears the largest fittable value by 3.6σ — the margin that keeps the posterior off the bound. The top few magnitudes exist to prevent truncation, not because anything is measured there.
Spacing — measured, not assumed
|d²BC/dAv²|at high Av, p95 over all 660 (Teff, logg) cells of the shipped tables:Gaia_G is sixty times more curved than J, because a wide blue passband reweights as the spectrum reddens. So the shipped 2-mag steps above Av=1 already cost ~0.019 mag in Gaia_G — indistinguishable from today's 0.02 systematic floor, and 4× over the 0.005 that Landolt-era calibration targets. h=1 lands exactly on 0.005, which is no margin; h=0.5 is 4× under it. Below Av=1 the curvature is higher, so the shipped fine sampling there is kept.
Cost
48 points against 13 — ~3.7× the table size, 841 KB → ~3.1 MB per feh file. Irrelevant beside the R=150 spectra's own ~0.02 mag error, which dominates interpolation by 16×. Regenerating on the full-resolution grids is what actually buys 0.005; this spacing is chosen so the axis won't be the limiting term when that happens.
Note on the test
validate_av_ptsis module-level deliberately: a test of the axis contract must not need the 313 MB model tree or the Zenodo fetch thatensure_model_datatriggers before the generator's body is reached. The first version of the test calledmake_bc_tablesand only passed quickly because the tree happened to be cached.56 passedwithtests/test_sed.py, on a compute node.🤖 Generated with Claude Code