Skip to content

Fix PhotulatorModelStack.fluxes; guard against legacy n_layers-1 artifacts#3

Open
ixkael wants to merge 2 commits into
Cosmo-Pop:torchfrom
ixkael:fix/stack-fluxes-and-legacy-artifact-guard
Open

Fix PhotulatorModelStack.fluxes; guard against legacy n_layers-1 artifacts#3
ixkael wants to merge 2 commits into
Cosmo-Pop:torchfrom
ixkael:fix/stack-fluxes-and-legacy-artifact-guard

Conversation

@ixkael

@ixkael ixkael commented Jul 11, 2026

Copy link
Copy Markdown

What this fixes

1. PhotulatorModelStack.fluxes() called a nonexistent per-emulator method.

PhotulatorModelStack.fluxes() called self.emulators[i].fluxes(theta, N),
but neither Photulator nor PhotulatorBasic define a fluxes() method —
only the singular flux(theta, N) (returns flux in maggies). Any caller
using the stack's fluxes() path hit:

AttributeError: 'Photulator' object has no attribute 'fluxes'

One-line fix: call .flux() instead of .fluxes(). No tests/ layout
exists on this branch, so the commit message includes a doctest-style usage
note instead of a unit test.

2. Guard against loading legacy n_layers-1 Photulator artifacts.

Two incompatible generations of trained Photulator artifact exist:

  • Current-generation artifacts were trained with the parametrized
    activation applied to every layer, including the output projection, and
    carry n_layers alpha/beta pairs — matching Photulator.forward()'s
    zip(self.W, self.b, self.alphas, self.betas) loop.
  • Older, TF-converted artifacts trained a linear output layer and
    carry only n_layers - 1 alpha/beta pairs — no activation was ever
    trained (or saved) for the output layer.

Loading the latter through PhotulatorModelStack previously produced no
error. forward()'s zip loop silently truncates to the shortest input:
depending on how the artifact was constructed, this either drops the final
linear projection from the computation entirely, or pairs it with
uninitialized, randomly-drawn activation parameters that are never
overwritten. Either way, output magnitudes are wrong with no warning. On a
real (non-mock) artifact this produced ~48.6 mag of disagreement against
the correct value, with 2.3e-5 agreement recovered once the forward pass
for that artifact was redone with a linear (unactivated) final layer — the
artifact itself was fine, only the forward convention applied to it was
wrong.

This PR adds _check_photulator_layer_parameters(), called from
PhotulatorModelStack.__init__() right after each torch.load(). It
compares the alpha/beta arrays an artifact actually supplies (as loaded)
against its own n_layers, and raises a ValueError naming the file and
pointing to a linear-output forward pass (out = out @ W[-1] + b[-1], no
activation on the final layer) as the correct evaluation path for such
artifacts, instead of silently loading. PhotulatorBasic (no
alphas/betas; fixed, untrainable activation) is left untouched by the
check.

Testing

No tests/ directory exists on this branch. Manually verified:

  • A toy Photulator, saved/loaded through PhotulatorModelStack, has
    stack.fluxes() succeed and match torch.cat([e.flux(theta, N) for e in stack.emulators]).
  • A toy Photulator with alphas/betas truncated to n_layers - 1
    (simulating a legacy artifact) now raises ValueError at
    PhotulatorModelStack load time instead of silently loading.
  • A normal n_layers-pair artifact is unaffected by the new check.

🤖 Generated with Claude Code

ixkael added 2 commits July 11, 2026 11:37
…thod

PhotulatorModelStack.fluxes() called self.emulators[i].fluxes(theta, N),
but neither Photulator nor PhotulatorBasic define a fluxes() method — only
the singular flux(theta, N), which returns flux in maggies. Any caller
using the stack's fluxes() path hit an AttributeError.

Usage (doctest-style, no test/ layout exists on this branch to add a unit
test to):

    >>> stack = PhotulatorModelStack(root_dir="trained_models/", filenames=[...])
    >>> stack.fluxes(theta, N).shape  # (n_objects, n_filters), maggies
    torch.Size([n_objects, n_filters])

Before this fix, the above raised:
    AttributeError: 'Photulator' object has no attribute 'fluxes'
Two incompatible generations of trained Photulator artifact exist. Current
artifacts were trained with the parametrized activation applied to every
layer including the output projection, and carry n_layers alpha/beta
pairs, matching Photulator.forward()'s zip(self.W, self.b, self.alphas,
self.betas) loop. Older, TF-converted artifacts trained a linear output
layer and carry only n_layers-1 alpha/beta pairs -- no activation was ever
trained or saved for the output layer.

Loading such a legacy artifact through PhotulatorModelStack previously
produced no error: forward()'s zip loop silently truncates to the
shortest input, either dropping the final linear projection from the
computation, or (depending on how the artifact was constructed) pairing
it with uninitialized, randomly-drawn activation parameters that are
never overwritten. Either way, output magnitudes are wrong with no
warning; on a real (non-mock) artifact this has produced ~48.6 mag of
disagreement against the correct value, with 2.3e-5 agreement recovered
once forward-pass on that artifact is redone with a linear (unactivated)
final layer.

Add _check_photulator_layer_parameters(), called from
PhotulatorModelStack.__init__() right after each torch.load(), which
compares the alpha/beta arrays an artifact actually supplies (as loaded)
against its own n_layers and raises a ValueError naming the file and
pointing to a linear-output forward pass as the correct evaluation path
for such artifacts. PhotulatorBasic (no alphas/betas; fixed activation)
is left untouched by the check.

Manually verified: a toy Photulator with alphas/betas truncated to
n_layers-1 (simulating a legacy artifact) now raises ValueError at
PhotulatorModelStack load time instead of silently loading; a normal
n_layers-pair artifact is unaffected and stack.fluxes() still matches
per-emulator .flux().
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