Skip to content

Fix validSim() passing the resource spectrum as the other components - #601

Merged
gustavdelius merged 1 commit into
masterfrom
claude/heuristic-elbakyan-4fc131
Aug 29, 2026
Merged

Fix validSim() passing the resource spectrum as the other components#601
gustavdelius merged 1 commit into
masterfrom
claude/heuristic-elbakyan-4fc131

Conversation

@gustavdelius

Copy link
Copy Markdown
Member

What changed

validSim.MizerSim() diagnoses a simulation that went non-finite by recomputing the rates at the last good time step. The getRates() call passed sim@n_pp[max_t_idx, ] for the n_other argument — a copy-paste slip that handed the resource number density to the rate functions where they expect the list of other components.

Any rate function reading n_other$<component> therefore failed with:

Error in `n_other$gonads`: $ operator is invalid for atomic vectors

Rather than just swapping the slot, the call now uses the existing internal helper get_sim_rate_slice() (R/sim_rates.R), which already builds exactly this slice. That matters because a plain sim@n_other[max_t_idx, ] is not enough on its own: on a list-matrix with a single component, [ drops to an unnamed length-1 list, so n_other$<component> would come back NULL for a one-component model. The helper restores the component names from dimnames(sim@n_other)$component. It also supplies the dimnames on n that the hand-rolled dim(n) <- dim(sim@n)[2:3] left off.

Why it was invisible to the test suite

The shared fixtures have no other components, so nothing in the suite ever read n_other on this path. It surfaced when the suite was run with NS_params_small replaced by a mizerSeasonal object, which adds a gonads component — test-MizerSim-class.R ("validSim works") then errored as above.

Notes for the reviewer

  • There is only one occurrence of the slip. validSim.MizerSim() has a single if (!all(is.finite(sim@n))) block; is.finite() is already false for NaN as well as Inf/-Inf, and the existing test's second half exercises the NaN case through that same branch. There is no separate NaN or negative-value branch in the file — grep for is.finite|is.nan|negative|< 0 in R/MizerSim-class.R returns only the three lines of this block.
  • The two other places that slice a row out of @n_other (get_sim_rate_slice() itself and finalNOther()) already restore the names correctly, so nothing else needed fixing.
  • No upgrade-mizer-code entry: this turns a crash into working diagnostics and moves no results for code that was already working.

Tests

A regression test goes in tests/testthat/test-MizerSim-class.R, the file named after R/MizerSim-class.R which defines validSim (per .claude/skills/test-organisation.md). It builds on the shared NS_params_small fixture plus a one-component setComponent() model whose mort_fun reads n_other$gonads and records what it was handed, then asserts the truncation happened, that names(n_other) is "gonads", and that the value matches the one stored at that time step — so it catches both the wrong-slot slip and a names-dropping regression.

Verified the test reproduces the reported error against the unfixed code, and passes with the fix.

  • devtools::test(filter = "MizerSim-class") — 61 pass, 0 fail
  • devtools::test(filter = "extension|sim_rates|project|summary_methods") — 515 pass, 0 fail

NEWS.md gets an entry under Extensions.

🤖 Generated with Claude Code

`validSim.MizerSim()` diagnoses a simulation that went non-finite by
recomputing the rates at the last good time step. It passed
`sim@n_pp[max_t_idx, ]` for `n_other`, so any rate function reading
`n_other$<component>` was handed the resource number density instead of
the list of other components and failed with

    $ operator is invalid for atomic vectors

Use `get_sim_rate_slice()`, which already builds exactly this slice and
restores the component names that `[` drops when a model has only one
component. It also supplies the dimnames on `n` that the hand-rolled
`dim(n) <- ...` left off.

The bug was invisible to the test suite because the shared fixtures have
no components, so nothing read `n_other`. The regression test therefore
builds a one-component model with `setComponent()` and checks both that
the diagnosis path completes and that the rate function received a named
list holding the stored component value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gustavdelius
gustavdelius merged commit b6f52eb into master Aug 29, 2026
@gustavdelius
gustavdelius deleted the claude/heuristic-elbakyan-4fc131 branch August 29, 2026 08:24
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