Skip to content

background: the light-curve tie subtracts every star the event puts in the blend (8.10.3) - #503

Merged
jdeast merged 1 commit into
masterfrom
fix/background-lens-tie
Oct 27, 2026
Merged

jdeast merged 1 commit into
masterfrom
fix/background-lens-tie

Conversation

@jdeast

@jdeast jdeast commented Oct 27, 2026

Copy link
Copy Markdown
Owner

What

Follow-up to #497 (review 8.10.3). In a band a microlensing light curve covers, background.<name>.fluxratio_<k> is DERIVED as f_blend/f_source - sum_{j in psf_stars} F_j/F_S, but only the stars the user listed in the background's psf_stars: were subtracted. A lens left off that list was silently counted as "unresolved background", at the background's position and proper motion instead of its own.

This PR makes the identity

F_bg/F_S = f_blend/f_source - F_lens/F_S - sum_{modeled non-source stars in the PSF} F_j/F_S

hold by construction (JDE 2026-10-27: "f_neighbor = f_blend - f_lens - sum(f_modeled stars)").

  • The event's own blend members are required in psf_stars. Background.event_blend_stars lists the stars the event itself puts in every light curve's blend:

    • every lens body that is a star: the primary always, plus a luminous stellar companion (2L1S, 3L1S);
    • every star on the far side of a source's xallarap orbit (an unlensed source companion).

    Sources are excluded. In a tied band, _check_tie_membership (stage 1) RAISES, naming the star, when psf_stars leaves one of these out, and also when psf_stars names a source. It also refuses a star listed twice.

  • The "empty psf_stars skips the SED check" path is gone. The lens is always a member, so every tied band needs:

    • an sed: block,
    • a band: block naming the band,
    • a filter the BC grid has.

    Each one missing RAISES, naming the fix: leave that light curve out of light_curves: so the band is sampled. A member the SED severed also raises, at stage 6, the same way astrometry's _flux_ratios does.

  • sed_constrains_blend: true on a tied light curve RAISES (see the finding below).

  • SED readership. SED.photometric_readers now counts the tie through Background.sed_read_stars: the primary source plus the tied psf_stars. Before this, a lens that only the tie read was "unseen": its av/teffsed/radiussed were pinned and its radius/teff/feh were inactive, yet the derived flux read its SED magnitude anyway.

  • The derived role, the member_flux context node and the [0, 1000] soft barrier are unchanged. There is no pt.where.

Decisions for review

  1. Spelling: explicit and required, not implicit or auto-added. psf_stars: keeps one meaning on both the background and the astrometry datasets: every modeled non-source star in this PSF, the lens included. A tied band whose list omits a required member raises; it never auto-adds.

    • Implicit (list only the OTHER stars) would make the background's list differ from the astrometry dataset's for the same PSF, because the dataset must name the lens to put it on its own track. The existing "must name the same psf_stars" check would then need a translation rule.
    • Implicit and auto-add would both make psf_stars: [Lens] and psf_stars: [] describe the same light: two spellings for one statement (config.md's one-spelling rule).

    With the explicit spelling the existing astrometry consistency check is unchanged and still guarantees no star is counted twice or missed. astrometryinstrument.py is not touched.

  2. Which stars are "necessarily" in the blend.

    • Required (raise if omitted):
      • stellar lens bodies, since their separation is ~theta_E;
      • stars on the far side of a source's xallarap orbit, since an orbit the light curve resolves in time is far below any PSF.
    • Not required (the user states them in psf_stars):
      • a resolved neighbor;
      • a star that is only bound to a lens or source body in a static or RV orbit group, because its separation is not set by the event and a ground PSF may or may not hold it.
    • Dark (not subtracted): planet companions, since the SED predicts no planet flux.
    • Raised: sources listed in psf_stars, because their light is f_source.
    • Raised already, unchanged: an astrometry dataset reading a tied band whose background lists a luminous lens COMPANION. Only the primary lens's track is derived (review 8.10.4), so that dataset raises NotImplementedError.
  3. A dark lens cannot be tied today. An FFP or remnant that no BC grid hosts has no SED prediction, so its band raises and must stay sampled. An explicit "dark" flag would be a follow-up.

  4. Left as a follow-up: an astrometry dataset reading the background in an UNTIED band is still not required to name the lens. There the background's sampled flux absorbs the lens at the background's photocenter. This is recorded in background.md "Known limits", and I kept it out of this PR to stay clear of the concurrent astrometryinstrument work.

The sed_constrains_blend finding

The two statements constrain THE SAME THING, so the combination raises instead of being merged.

  • sed_constrains_blend adds -0.5 * (2.5 log10((f_lens_inst + neighbor_flux)/f_blend) / sed_blend_sigma)^2.
    • f_lens_inst = 10^(-0.4 (m_nonsources - zp)).
    • The zeropoint is always m_S,pred + 2.5 log10 f_S, whether its prior is stated or it is only derived.
    • So f_lens_inst = f_S * sum_{non-sources} F_j/F_S, which is exactly the background tie's member term.
  • With the tie in place, the Gaussian only holds neighbor_flux (one extra sampled dimension) at f_S * fluxratio within 0.2 mag. That is two parameters for one light, which 8.10.3's "one object" ruling forbids.
  • Its "blend >= lens" one-sidedness (neighbor_flux >= 0) duplicates the background's fluxratio >= 0 barrier, so the potential would be double-counted.
  • Its member list is every non-source star in the system, not the PSF's, so a neighbor outside the PSF would sit in one sum and not the other.

The tie is the exact form, so the error says to drop the flag or untie that light curve.

Bit-identity (start logp, repr, origin/master 2d75d79 vs this branch)

All shipped example configs (the same sweep driver as #498/#501): 45/45 buildable configs bit-identical (diff of the two outputs is empty). theophylline.yaml fails identically on both sides because its data file is not shipped. No shipped config has a background: block.

Test configs with a background tie (tests/test_catalog_astrometry.py builders):

config master branch why
untied truth config -6687.534092786007 -6687.534092786007 identical
tied, no psf_stars, no SED -6687.534092786007 raises (Lens missing) the gap: master counted the lens as background
tied, no psf_stars, SED -6717.761685611055 raises (Lens missing) same
tied, psf_stars [Lens], SED -6717.761685611055 -6717.761685611055 identical (the astrometry band: already made every star SEEN)
tied, [Lens, Neighbor], SED -58599.87192978996 -58599.87192978996 identical
light-curve only, [Lens] 5128.036865209975 5107.806614636171 -20.230: readership fix, see below
light-curve only, [Lens, Neighbor] -46754.07337896916 -46784.300971793964 -30.228: same

The two light-curve-only movers, term by term:

  • On master, nothing but the tie read the members' SED flux, so the SED counted them as unseen. Their av/teffsed/radiussed were pinned and their radius/teff/feh inactive.
  • On the branch they are SEEN. The model gains 6 sampled vectors (6 -> 12 free RVs) and with them:
    • their logit-uniform priors and raw N(0,1) terms;
    • sed.fbolsed_floor_prior (+62.71 / +94.19);
    • sed.teffsed_floor_prior (-9.50 / -14.25);
    • zero-valued loggsed bound terms.
  • low_bound.background.fluxratio moves by 3e-11 relative (-51882.11024417914 -> -51882.1102441789). The members' magnitudes now pass through the sampled knobs' logit round trip, so this is a last-bits change.

(The large barrier in the [Lens, Neighbor] rows is the test vehicle's toy photometry: the modeled neighbor is far brighter than that light curve's blend.)

Tests

tests/test_catalog_astrometry.py, section 6:

  • Identity: holds to 1e-12 at the start and at 5 random raw points, with psf_stars [Lens, Neighbor]. The members' sum is an independent rebuild from the SED's per-star magnitudes, not _member_flux.
  • Luminous lens companion (2L1S): subtracted to 1e-12 and SEEN by the SED; the unlisted neighbor stays unseen. [Lens] alone raises naming Comp.
  • Xallarap source companion: omitting it raises naming SComp; listing it builds.
  • Raises:
    • the lens not listed (the original gap);
    • neighbor-only;
    • a source listed;
    • a duplicate;
    • no SED;
    • a filter the BC grid lacks;
    • two light curves in one band;
    • sed_constrains_blend on the tied light curve.
  • Gradients: C and JAX logp and gradients of the tied model agree (rtol 1e-7) at the start and at a displaced point.
  • The existing tie tests were ported to state psf_stars: [Lens] and an SED.

Full suite: 5381 passed, 2 skipped, 1 xfailed (47 min, box load ~30-40); no failures, no flakes.

Overlap

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5pG2s9aJEo6Xqk6hWvnkW

…n the blend

In a band a microlensing light curve covers, background.<name>.fluxratio_<k>
is derived as f_blend/f_source - sum_{psf_stars} F_j/F_S.  Only the
user-listed psf_stars were subtracted, so a lens left off the list was
silently counted as unresolved background (wrong position, wrong pm).

psf_stars stays the one complete, explicit list of modeled non-source stars
in the PSF (the same meaning it has on an astrometry dataset).  In a tied
band it must now hold every star the event itself puts in the blend --
every stellar lens body (the primary always, a luminous companion) and
every star on the far side of a source's xallarap orbit -- and may not
name a source; either raises at stage 1, naming the star
(Background.event_blend_stars / _check_tie_membership).  So the identity
F_bg/F_S = f_blend/f_source - F_lens/F_S - sum F_j/F_S holds in every model
that builds.

- The skip of the SED check for an empty psf_stars is gone: the lens is
  always a member, so a tied band always needs the SED, a band: block and
  a BC-grid filter; a member the SED severed raises at stage 6.
- sed_constrains_blend on a tied light curve raises: it states the same
  split a second time through its own neighbor_flux (an extra sampled
  dimension held at f_S * fluxratio by a 0.2-mag Gaussian), and its lower
  bound duplicates the background's [0, 1000] barrier.
- SED.photometric_readers counts the tie (Background.sed_read_stars), so a
  lens only the tie reads is SEEN rather than having its SED knobs pinned.
- The derived role and the soft barrier are unchanged.

Tests: identity to 1e-12 at random points with the lens and a neighbor
subtracted; a luminous lens companion subtracted and SEEN; lens /
source-orbit companion omitted, source listed, duplicate, no SED, filter
outside the BC grid and sed_constrains_blend all raise; C and JAX
gradients of the tied model agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5pG2s9aJEo6Xqk6hWvnkW
@jdeast
jdeast force-pushed the fix/background-lens-tie branch from 9cbaa61 to 23c0156 Compare October 27, 2026 16:40
@jdeast
jdeast merged commit bfa0ea1 into master Oct 27, 2026
20 checks passed
@jdeast
jdeast deleted the fix/background-lens-tie branch October 27, 2026 22:30
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