Clip raw predicted log_mass in decode_secondaries (gitea #54)
CI / Lint (ruff check) (push) Successful in 30s
CI / Format (ruff format) (push) Successful in 30s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 33s
CI / Type check (ty) (push) Successful in 36s
CI / Format (ruff format) (pull_request) Successful in 39s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 39s
CI / Tests (pull_request) Successful in 3m36s
CI / Tests (push) Successful in 3m51s
CI / Lint (ruff check) (push) Successful in 30s
CI / Format (ruff format) (push) Successful in 30s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 33s
CI / Type check (ty) (push) Successful in 36s
CI / Format (ruff format) (pull_request) Successful in 39s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 39s
CI / Tests (pull_request) Successful in 3m36s
CI / Tests (push) Successful in 3m51s
decode_secondaries inverted a secondary's raw predicted log_mass with inv_log_transform (exp(y) - eps) unclipped. log_mass is a raw regression output, not itself the result of log_transform, so it isn't guaranteed to land in the range that round-trips cleanly: too negative and exp(y) undershoots eps, making the result go slightly negative; too positive and exp(y) overflows float32 to inf. Either one crashes the next rollout step, since a track descended from that secondary feeds its mass back in as conditioning, and log_transform raises on a non-finite input. Clip log_mass to [log(_EPS), _LOG_MASS_MAX] before inverting, guaranteeing a finite, non-negative mass. _LOG_MASS_MAX=80.0 matches the value from the stale fix/rollout-negative-secondary-mass branch (comfortably below float32's ~88.7 overflow point, far beyond any physical particle mass a converged model would predict) — that branch had already implemented this fix but forked before gitea #35/#36 and couldn't be merged as-is, so this reimplements it fresh against current master and leaves the stale branch untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,14 @@ _EPS = 1e-8
|
||||
# the conservation it slightly softens is physically negligible (~0.001%).
|
||||
_SIMPLEX_FLOOR = 1e-5
|
||||
|
||||
# Upper clip for a raw predicted log_mass before inv_log_transform: exp(y)
|
||||
# must stay well inside float32 range (~3.4e38, i.e. y < ~88.7) or it
|
||||
# overflows to inf, which — like the negative-mass case below — blows up the
|
||||
# next log_transform call once that mass is fed back in as conditioning.
|
||||
# 80.0 leaves comfortable headroom while still being far beyond any physical
|
||||
# particle mass a converged model would ever predict.
|
||||
_LOG_MASS_MAX = 80.0
|
||||
|
||||
|
||||
def log_transform(x: np.ndarray, eps: float = _EPS) -> np.ndarray:
|
||||
x = np.asarray(x, dtype=np.float32)
|
||||
@@ -685,9 +693,18 @@ def decode_secondaries(
|
||||
log_mass = sec_cont[:, :, 4] # (N, K)
|
||||
charge = sec_cont[:, :, 5] # (N, K)
|
||||
|
||||
# mass is non-negative by construction (inv_log_transform of a real
|
||||
# number is always > 0); clip to 0 for padded/invalid slots rather than
|
||||
# leaving a spurious small positive floor from the log inverse.
|
||||
# log_mass is a raw model prediction, not itself the output of
|
||||
# log_transform, so it can land far outside the range that round-trips
|
||||
# cleanly through inv_log_transform: too negative and exp(log_mass)
|
||||
# undershoots _EPS, making inv_log_transform go slightly negative; too
|
||||
# positive and exp(log_mass) overflows float32 to inf. Either one then
|
||||
# blows up the next log_transform call on this track's mass once it's
|
||||
# fed back in as conditioning for a further rollout step
|
||||
# (giant/rollout.py -> build_cond_features -> _physical_cond_columns).
|
||||
# Clip to a range whose inverse is guaranteed finite and >= 0 before
|
||||
# that can happen; clip to 0 separately for padded/invalid slots rather
|
||||
# than leaving a spurious small positive floor.
|
||||
log_mass = np.clip(log_mass, np.log(_EPS), _LOG_MASS_MAX)
|
||||
sec_mass = np.where(sec_valid, inv_log_transform(log_mass), 0.0).astype(np.float32)
|
||||
sec_charge = np.where(sec_valid, charge, 0.0).astype(np.float32)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user