Repository navigation
Releases: afsc-assessments/Rceattle
Release list
Rceattle 5.64.0
197 commits since 5.49.1. Read the first table before refitting anything: seven
configurations change the advice a stored fit gives, and three of them change it with no console
output at all.
The input API is unchanged. One export is added (plot_index_residual), none removed, no schema
column added, renamed or removed, all 20 switch maps byte-identical, and the live REPORT (121),
ADREPORT (15) and DATA_* (182) name sets identical to 5.49.1 — so a 5.49.1 script reads the
same fit$quantities.
Does your fit move?
| If your model is | What changed | What to do |
|---|---|---|
M1_re > 0 with M1_model = 0 |
It gave constant M and now estimates the deviations. On BS2017SS the objective goes 1,537,143.80 → 995,573.78, and log_M1_dev from 0 to 117 free parameters. It gave constant-M advice and will now give time-varying-M advice. |
Refit deliberately. |
| A mirrored selectivity or catchability block | A shared Selectivity_index/Catchability_index group now estimates one coefficient, not one per fleet. This was the default path (by defaults to ~ fleet), and mirrored fleets previously fitted different curves with no warning. GOA2018SS fleets 9/10 differed by 0.123 at the worst age; objective +0.1222 nats. |
Saved fits still refit. The answer changes. |
Selectivity = "DoubleNormal" from default starts |
It fitted flat at ~1 at every age and now fits a dome. GOA pollock fishery: 3085.98 → 914.10. Age-based fleets only; a length-based one is unchanged. | Refit. No bundled dataset uses the form. |
M1_model >= 1 on a ragged-nsex multispecies model |
A padding sex cell no longer dilutes the estimated M1 start. GOA2018SS: −10.52 nats, M1 species 1 −26%, terminal SSB 362,066 → 330,402 mt (−8.75%). |
The live GOA multispecies assessment is in this configuration and has not been refitted. Whether its numbers move is unmeasured. |
| Species n's male growth parameters | They were species n+1's female growth parameters — a genuine parameter collision. | Refit any model estimating growth on more than one species. |
run_mse() with estDynamics > 0 |
NByageFixed is no longer extended into the projection; a missing row reads as zero, which is its intended meaning. Pacific hake/04-mse.R is affected: two predator species contribute no biomass and no predation after 2019 and 2024, and both prey on hake, so its projected predation mortality on hake moves. |
Supply rows through projyr, or accept the zeros. |
A fit whose numbers-at-age are input (estDynamics > 0) |
It scored an M random-effect density it should not have. | Refit. |
Every multispecies fit in the Pacific hake MSE also moves +3.4e-09 to −4.3e-08 nats from the
other-food floor — numerically trivial, but stored references need re-recording at 16 digits.
Breaking changes
Each refuses a configuration that never fitted the model it described, so each is a minor bump
rather than a major one.
M1_re > 0where no map arm serves it — 16 of the 42(M1_model, M1_re)pairs through
fit_mod(). A stored fit of that shape stops refitting throughretrospective(),self_test(),
jitter(),sim_mod(),project_no_f()andrun_mse()'s EM refits. UseM1_re = 0,
M1_model0/1/2, orM1_model = 3withiid_year/ar1_year.Time_varying_sel = "RandomWalkAscending"on any form butDoubleLogistic,2DAR1and
3DAR1. Elsewhere the deviates stayed mapped out and a static curve was fitted. Use a
random-effect linkage on the ascending parameters.- Diet diagnostics on a fit that never predicted a diet —
residuals(source = "diet")was
returning zeros as fitted values, every observation its own residual, on 2025 of 2025 rows of
BS2017MS.plot_diet_comp(),plot_diet_comp1()andplot_diet_comp2()inherit the refusal. jitter()on an argument it does not take —...was read only on the numeric
base::jitter()path, sojitter(mod, njiter = 10)silently ran 50 jitters instead of 10.- Four mirrored-block linkage configurations, a
Time_varying_qcolumn that is required and
absent, a missingM1_baserow, a non-finitelog_M1start, and a degenerate CAAL range.
New and improved
- Selectivity: two integrable non-parametric forms, an apical sex offset, SS3 pattern-24
(DoubleNormalSS3) reproducing SS3 line for line, and selectivity penalties reconciled with AMAK. - Linkages: random-effect densities (IID, RW, AR1), time blocks via
~ cut(Year, ...)now
documented invignette("environmental-linkages-and-priors"), and Dirichlet-multinomial comp
weight priors. - Diagnostics: conditional-CDF OSA residuals,
report_tables()/quantity_dictionary()for
SAFE reporting, and three convergence-reporting defects fixed —fit$convergencenamed the last
k parameters rather than the flagged ones, andfit$.conv_hindcast's bound vectors are now in
obj$parorder. - Deprecations, warning only:
plot_depletionSSB()→plot_ssb_depletion()(18 sibling call
sites),plot_indexresidual()→plot_index_residual()(18). Both old names keep working.
Verification at this tag
/golden-check 21 assertions across six blocks, none skipped, 0 failures — the four reference
models reproduce their pinned objectives. Full suite 10,820 pass, 0 failures, 4 skips, 271 files.
Pacific hake MSE end to end including run_mse(nsim = 2, cores = 2), 0 errors, max absolute
deviation 4.34e-07 from the recorded references. rcmdcheck --as-cran: 0 errors, 1 warning (three
-Wbitwise-instead-of-logical sites that exist verbatim on 5.49.1), 1 note (CRAN-submission
feasibility only). Installed into an explicit library and the version read back out of it.
Full detail, with the measurement behind every figure above, is in
NEWS.md.
Rceattle 5.49.1
Covers 5.46.0 through 5.49.1. main was at 5.45.3. NEWS.md carries the full entry for every version; this is the short answer.
remotes::install_github("afsc-assessments/Rceattle@5.49.1")What forces a refit
Two changes move spawning-stock biomass on models that already fit. Neither is an optimizer fix; each is a case where the old number answered a different question.
-
A length-selective fleet weighs its catch by the mean weight of the fish it SELECTS,
sum_l P(l|a) s(l) w(l) / sum_l P(l|a) s(l), rather than by the age class's mean weight at the fleet's month. A length-selective fleet takes the larger fish of a young age class, so the age-class mean understated its catch weight; this is also how Stock Synthesis weighs catch. Reaches a fleet with length-based selectivity and estimated growth. Age-selective fleets and empirical-weight models are unchanged.On the 2024 GOA Pacific cod approximation (224 parameters) the objective moves 6415.8172 → 6416.2875 and terminal SSB 1,004,925 → 935,106 mt (−6.9%), the series mean −7.1% and the largest single year −12.9%. SSB sets B40% and depletion. Convergence goes OK → WARN at max gradient 0.0013 on R0, worth at most 0.00085 standard errors.
-
Length compositions no longer carry ageing error. The age-length key is P(length | true age) and a measured length involves no otolith reading, so the ageing-error matrix has no part in predicting a length composition — SS3 applies its
age_agematrix only to age and CAAL data, WHAM only to its age and CAAL predictions, and SPoRC passesAgeingError = NA. Moves any fit with a non-identityage_errorand length comps on the same species.objective terminal SSB GOAatf394.1074 → 393.1657 (−0.9416) +1953 mt (+0.223%) NorthernRockfish20222297.1645 → 2294.2623 (−2.9022) +14 mt (+0.019%) GOA2018SS12867.9903 → 12866.8457 (−1.1445) sp 2 +1678 mt (+0.161%) Every objective falls. On the live assessments: GOA arrowtooth 2026 moves +0.113% with M fixed and −0.238% with M estimated — the sign is not universal, so do not generalise a direction from one model. EBS pollock 2024 and the Pacific hake MSE are not reached (identity matrix, or no length comps). GOA pollock 2025 IS reached — 74 length-comp rows,
max|A−I|0.355 — and its move is unmeasured, because its entry point cannot run as committed:Data/2024pollock_mfix_estSigR.Rdatawas deleted fromRceattle-modelsin9d0d5c1. Restore that file or repoint the script before using it for advice.The two GOA golden references were re-pinned:
goa_ss12867.9902664788 → 12866.8457276232,goa_ms12932.7902167145 → 12931.8602763619.BS2017SS/BS2017MSare unchanged, their matrices being the identity.
A third change moves a number only if you warm-start across a change of linkage formula. A pinned (Intercept) linkage coefficient is re-zeroed over supplied inits. It is 0 by construction — the base parameter carries the level and the row is mapped out — but fit_mod() did not re-impose that, and a slope-only spec yields the same beta_linkage length as an intercept-only one, so the length guard passed. Refitting M1 = ~ 1 from the inits of an M1 = ~ 0 + temp fit held natural mortality at 6.69×, unreported. A refit of an unchanged model already carries 0 there, so retrospective(), jitter(), self_test(), model_average() and run_mse() are unaffected.
Refit and re-record before using any of these for advice.
What breaks
Each refuses a configuration that never fitted the model it described, so a stored fit using one stops and needs rebuilding. NEWS.md has the migration for each.
- Catchability: an
exponentiallink on a non-qprocess; anexponentialrow and a random-effect row on the same spec or fleet; a masked base parameter; a shared row reaching every fleet. - Selectivity: a
DoubleNormalSS3fleet with its own end parameter estimated and reaching nothing; a sharedSelectivity_indexgroup given different curves; P1 "peak" mismatches in both directions; a length-based fleet indexed by bin; anAgeing_error_indexnaming no matrix. - Recruitment: an
R_initlinkage on a non-loglink, with more than one coefficient per species, with a non-positiveinitor a negative bound, or underinitMode = "FreeParams"/"OffsetEquilibrium". - New in 5.49.0: an initial equilibrium catch on a species with
estDynamics > 0(the equilibrium age structure is only built where the numbers-at-age are estimated, so the prediction was 0 and the lognormal tooklog(0)— a non-finite objective that read as a failed fit); and alognormal,gammaorbetaprior on aDoubleNormalSS3slot other thandn_peak, where the slot holds a logit or a log and a positive-support family was evaluated on the transformed value.
jnll_comp gained a row. Initial equilibrium catch is appended, so the enum goes 21 → 22 rows and the last row is no longer the linkage random effects. If you index a jnll_comp row by position, look it up by name through .JNLL_ROW_AXIS instead. The fleet axis is no longer contiguous: rows 1–8 and row 22 count fleets, 9–20 species, 21 neither.
Two configurations that were refused and should not have been now fit again: a length-based parametric selectivity on a finer population grid (its time-varying deviates are in cm, not bins), and any selectivity linkage on an integer-coded Selectivity column.
Nothing here bites a consumer workbook. main 5.45.3 against this release over every .xlsx in the four consumer repositories: 0 newly broken, 0 newly passing, and not one changed verdict on 375 files, of which 183 reach the validator. NAMESPACE is byte-identical, 90 exports either side, and the only exported signature change is build_growth() gaining four arguments, all appended with defaults.
What is new
initMode = "FishedNonEquilibriumSelected"(6) — SS3's InitF convention, where the initial F is spread over ages by the fishery's selectivity before it accumulates, plus an initial equilibrium catch observation atstyr - 1soFinithas something to fit.- Stock Synthesis growth, maturity and length-bin options —
sd_form,plus_group_length,plus_group_decay,pop_lengths, maturity-at-length,Selectivity = "DoubleNormalSS3"(code 15), length-based selectivity on the population length bins, and a per-fleet ageing-error matrix. link = "exponential"— SS3 environmental link type 1 on catchability,q^exp(beta*x), which multiplieslog qwhere"log"shifts it.R_init— a fourth recruitment linkage parameter and an unpenalised log-scale multiplier on the initial age structure, for a stock whose series begins after a regime shift. Without it the level had to go intoinit_dev, which is penalised per age, and the optimiser retires that penalty by loweringR_0: on GOA Pacific cod 0.37 log units, leavingR_031% below what the same data support while terminal SSB moved only −1.35%.SB_0,B_40%and depletion all scale withR_0, so a penalty on the initial state was displacing the quantities that set the catch limit.
Bound R_init on a live assessment. The level is informed only by the first year's observations, so it is weakly identified at the low end: a few percent of simulated fits reach an effectively annihilated initial state with a clean gradient, which no convergence filter catches and a multi-start does not escape, because the data genuinely prefer that state. The bound is the natural-scale multiplier, so bounds = list(intercept = c(0.05, 20)) keeps a fit out of the tail; a lower bound of exactly 0 does not, because it logs to −Inf. This is documented, not enforced — a deliberate open decision.
Verification
Full suite 254 files and 10,138 assertions, 0 failures, 4 pre-existing skips. Golden regression 21 assertions with 0 skips, verified on its own because skip_on_cran() plus skip_on_covr() let that file report green while measuring nothing. All 13 vignettes execute with their chunks evaluated. The Pacific hake MSE reproduces its four stages to 16 digits with run_mse() end to end. No bounds violation in 8 configurations under a -DTMB_SAFEBOUNDS build. R-CMD-check green on macOS, Windows and Ubuntu (release, devel, oldrel-1).
One known R CMD check --as-cran WARNING carries over from 5.45.3: -Wbitwise-instead-of-logical, bitwise |/& on boolean operands in src/TMB/ceattle.cpp. The same lines are on 5.45.3, so this release does not regress it; it is the first follow-up in inst/dev/SESSION_HANDOFF.md.
Rceattle 5.45.3
Two composition-figure fixes, both from an external report. No fit changes and no API changes — the only package code touched is R/7-plot_comp.R and R/7-plot_osa.R, both post-fit display.
remotes::install_github("afsc-assessments/Rceattle@5.45.3")An aggregated composition no longer pools two observation structures
A fleet can carry more than one composition structure — sexes combined in the early years of a series and disaggregated later — and the likelihood scores one density per structure. The aggregated figure pooled every row for a fleet and comp type into one panel and renormalized across the whole of it, so neither the combined series nor the sex-specific ones summed to 1, and the two were drawn on axes that are not comparable.
The panel now splits on the structure, only where a fleet mixes them. No bundled data set does, so every existing figure is unchanged: verified on a BS2017SS fit, which keeps its 7 panels and the title Pollock - age comp. The effective-sample-size annotation on the same figure already grouped on the key the likelihood scores a density over; the aggregate now follows it.
A joint-sex OSA series says it is half of one unit
No residual values change. A Sex = 3 composition was always appended as one decomposition unit over the stacked female-then-male vector, with a single cell dropped for the sum-to-one constraint rather than one per sex. The figure re-bases the male bins onto a common axis so both halves can be read, and labelled them - male and - female, which reads as two separate distributions — an assessment author asked why the residual structure did not match the likelihood structure. It does. The labels are now - joint, male and - joint, female, and a single-sex row, which genuinely is its own density, stays unlabelled so the distinction is visible.
If a fleet's comps are supplied as separate Sex = 1 and Sex = 2 rows, the OSAs are separate, because the likelihood is too — and those rows carry no sex-ratio information at all. That is a data-structure question rather than a residuals one.
Verification
Full serial suite at the tagged commit: 9,781 assertions, 0 failures, 0 errors, 245 files. All 13 test-plot-*.R files: 726 assertions, including three new tests. A before/after diff on the OSA labelling confirms source is the only column that differs — bins, residuals and single-sex rows untouched. GOA2018SS and GOAatf carry the Sex = 3 comps, so their OSA figures gain the clearer labels; that is the only visible change anywhere.
Full detail in NEWS.md.
Rceattle 5.45.2
Documentation only. No package code changed, nothing to refit, nothing breaks.
remotes::install_github("afsc-assessments/Rceattle@5.45.2")The two-sex selectivity vignettes now say what an apical linkage adds to
Both described the multiplier correctly, and both likened it to Stock Synthesis's male-offset option — without saying that the form's own parameters are estimated separately for each sex, and stay that way. A reader who knows SS takes that comparison to mean the shape is shared and one parameter carries the sex difference. It is not.
A two-sex Logistic fleet with an apical linkage estimates five selectivity parameters: a slope and an inflection for each sex, plus one multiplier on the sex named. The named sex's curve is free in slope, inflection and height; the reference sex's has two parameters. An SS male offset is usually an offset from the female shape, so there the shape is shared; here both shapes stay free and the offset carries only the height.
That distinction decides what the parameterization is worth on thin sex-specific compositions, which is the case it was built for. The two halves are informed by different data: each shape by every row for that sex, the height only by the joint-sex rows (comp_data$Sex = 3). Read the standard error on the offset as covering the height alone, not the whole sex difference.
Sharing a shape across the sexes is not a switch today — it needs a hand-edited map — and the vignettes now say so rather than leaving it to be inferred from the SS comparison.
Full detail in NEWS.md.
Rceattle 5.45.1
A patch release, and a short one. No package code changed: the diff against 5.45.0 touches tests, tools/verify/, CI configuration, the README and the developer notes. Nothing to refit, nothing breaks, no API moves.
remotes::install_github("afsc-assessments/Rceattle@5.45.1")Why it exists
deep-checks golden is the committed backstop for "a numeric change needs golden equivalence", and it is the only job that runs it. It had been red on main for weeks, so the guard a release leans on was failing before the 5.45.0 release began. This makes it mean something again.
The golden check no longer asks which local minimum the machine found
It re-fit four reference models and compared objectives, which answered two questions at once: whether the likelihood had changed — its purpose — and whether a cold phased optimization reaches the lower of two minima on this machine, which has a platform-dependent answer. goa_ss has a second local minimum 52.9 units above the reference, a one-ULP change in a single log_F gradient element is enough for nlminb to reach it, and goa_ms inherits the basin through its warm start.
The likelihood is now evaluated at committed reference parameters, so a change to the model moves the objective while a change to the optimizer's path cannot. All four references reproduce to 7e-12 — the rounding of the 15-digit pinned literal, not a model difference — at gradients of 1.3e-11 to 5.0e-11 against a 1e-8 threshold.
What it gives up: an edit confined to the optimization path — a phase order, a starting value, a bound inactive at the reference — no longer fails the gate. tools/verify/verify-golden-cold-start.R covers that, asserting the one direction that cannot flake: a cold fit must not land below the reference.
The MVN configuration pin no longer asserts where the optimizer stopped
The red seen at the 5.45.0 release was not the 52.9 basin. It was the MVN/Normal index-family pin failing its gradient assertion — 3.5e-04 on ubuntu-latest against a 1e-4 gate, where local macOS/arm64 gives 2.5e-05 — while its objective reproduced and every other assertion passed. That configuration sits on the q-versus-population-scale ridge, softened 11.5x by compound symmetry in Sigma, so its gradient records where the optimizer stopped rather than what the model is. The gate is 1e-3 with both measurements recorded.
The README's install pin is checked against DESCRIPTION
The pinning example names a version and went stale five times across 5.34.0–5.45.1, each time because a version landed after the release paperwork was written. Three were caught in review; one reached main, telling assessors to install a tag that does not exist. A test now fails when the pin and DESCRIPTION disagree.
Verification
Full serial suite at the tagged commit: 9,768 assertions, 0 failures, 0 errors, 244 files. The golden file: 21 assertions, 0 failures, 0 skips. The gate was checked by mutating jnll in ceattle.cpp by 0.01 (9.8e-7 relative), confirming it fails, and reverting.
What is not yet proven. Every measurement above is macOS/arm64. The gate is deterministic given a platform, and the cross-platform question is narrower than before — a fixed-parameter evaluation rather than a re-optimization — but the first deep-checks run on this tag is the real verdict on whether golden is green on Ubuntu, and it has not been taken.
Full detail is in NEWS.md.
Rceattle 5.45.0
Covers 5.34.0 through 5.45.0. main was at 5.33.0. NEWS.md carries the full entry for every version; this is the short answer.
The package has moved to afsc-assessments/Rceattle, and the last installable tag was 5.28.0. Versions 5.29.0 through 5.33.0 reached main untagged, so a consumer pinning tags moves 5.28.0 to 5.45.0, seventeen minor versions. Change the install line and the pin:
remotes::install_github("afsc-assessments/Rceattle@5.45.0")GitHub redirects the old path, so existing clones and install_github("grantdadams/Rceattle") keep working — but a redirect is not a permanent address, and it dies the moment anything is created at the old name. The documented website did not redirect: the site is now https://afsc-assessments.github.io/Rceattle/.
What forces a refit
Six changes move numbers on models that already fit. None is a bug fix to an optimizer; each is a case where the old number answered a different question.
- The plus group's mean length decays at the species' own natural mortality, not a hardcoded 0.2. Every parametric-growth fit whose M is not 0.2 moves; empirical weight-at-age fits do not.
- Multispecies
ConstantFprojects at the input F, and a species with input numbers-at-age is projected at F = 0 under every rule. remove_F()zeroes F from the year afterendyr, not after the latestsuit_endyr. The old behaviour removed fishing inside the hindcast, so dynamic-B0 quantities in an MSE move.mse_summary()takes multispecies dynamic depletion from the operating model.- A non-parametric fleet's selectivity coefficients below
Bin_first_selectedare held at 0. They were mapped off but still read by the curve's centring, so a value there shifted the whole curve unscored: 0.9 in those cells movedAtka2022's fishery objective by 704 nats. AffectsNonParametric(2) wheneverinitscome from a fit with a lowerBin_first_selected. - A factor-valued
Time_varying_qis read by its label, not its level index.read.csv(stringsAsFactors = TRUE)factors every column, and this was the one switch columnswitch_check()did not already resolve, sofactor("Off")reached the template asIIDand estimated catchability deviations nobody asked for, withdata_check()clean. Refit anything whosefleet_controlwas built that way.
Three more move a reported number rather than a fitted one. Projected F_flt / F_flt_age are indexed by fleet and fisheries only. A standard error is now NA where its variance came back negative — summary() and report_tables() took sqrt(abs(variance)), and an indefinite Hessian inverts without being positive definite, so a meaningless number printed as an uncertainty in the table a SAFE executive summary is built from. And a parameter pushed past a bound reports parameters_outside_bounds at FAIL rather than parameters_on_bounds at WARN, which reaches report_tables()$model$converged; newtonsteps defaults to 0, so no default fit is affected.
Refit and re-record before using any of these for advice.
What breaks
Each refuses or re-shapes a configuration that never fitted the model it appeared to describe, so a stored fit or a script using one stops and needs correcting. NEWS.md has the migration for each.
estDynamics = 3is retired — it always fitted as code 2.- The dead QAR1 catchability path is removed. Use a
qlinkage withar1(1 | Year). pop_scalaris a vector, not a matrix.- A species with input numbers-at-age reports
NAfor every stock-recruit quantity, where abuild_params()placeholder used to stand; single-speciesSB0,B0and depletion were built on it. - Stock-recruit alpha and beta gained bounds of ±30 on the log scale, so a fit saved on the old unbounded ridge no longer refits — and that takes
retrospective(),profile()andrun_mse()with it. random_sel = TRUEis refused for non-parametric selectivity where the deviations could not be integrated;NonParametricIntegrable(13) is the form for that case.fit_mod(config = )no longer replaces the data'smodel_configwholesale, which used to drop every linkage silently.- A stored
mapis checked rather than trusted, and a factor-valued switch no longer fits as its level index. - Selectivity penalties the template never read the way the column claimed are refused: a negative
Sel_curve_pen,Sel_shape_diroutsideNonParametricPM,Sel_devmag_sdon a form that ignores it, and an apical linkage or selectivity prior on anOfffleet. - A blank
Fleet_typeis refused, and one outside0, 1, 2, "Fishery", "Survey", "Off"is refused byrearrange_data()— closing3,-1and2.7, each of which reached the template and meant something other than what the column said.
None of these bites any bundled data set or any workbook in the four consumer repositories: 375 workbooks, 183 with a fleet_control sheet, 15 bundled data sets.
What is new
osa_residuals(method = "cdf"), one-step-ahead residuals from the conditional CDF, and everyoneStepPredict()call now conditions on the preceding observations — stored OSA diagnostics change.NonParametricIntegrable(Selectivity = 13), a non-parametric selectivity whose deviations carry a proper density, sorandom_sel = TRUEis available.- A per-sex apical selectivity offset, the equivalent of Stock Synthesis's male-offset option.
convergence_diagnostics()names the quantity and the coordinate —log_M1 (M1): Pollock, age 1rather than a block of 392 elements — and reports how far the estimates are from the optimum in standard errors.- A shared catchability warns when it starts where no fleet asked. Fleets sharing a
Catchability_indexshare oneindex_log_q, started at the geometric mean of theirCatchability_init; found on an SS3 bridge where that put a survey's predicted index 18% low. - A degenerate stock-recruit curve under predation is reported rather than silently converged, and OSA outliers are flagged per panel at a Bonferroni threshold.
Verification
Full test suite at the tagged commit, serial with every Suggests installed: 9,765 assertions, 0 failures, 0 errors, 244 files. Ecosystem sweep across the four consumer repositories: 375 workbooks, 183 with a fleet_control sheet, plus 15 bundled data sets — nothing the new refusals stop. Three adversarial review rounds ran over this release; what they found is in inst/dev/SESSION_HANDOFF.md.
Known red, and not introduced here — all three reproduce on main at 5.33.0. deep-checks golden fails because goa_ss reaches a second local minimum 52.9 units up and goa_ms inherits it through its warm start; a goa_ss delta of 52.9 with the other three bit-identical is that, not a regression. Making it robust is the first job after this release and ships as 5.45.1. R-CMD-check fails intermittently on Windows when a parallel test worker dies — read the log for a Failure ( line before treating it as real, because the same commit has passed and failed the same leg. deep-checks suite does not finish inside its 5-hour ceiling.
Full detail for every version is in NEWS.md.
Rceattle 5.28.0
Follows 5.27.0 and carries that version alone (merged in #140). Full detail in
NEWS.md.
Two changes to how fleet_control and env_data are read. No fitted model moves, and no
workbook in the assessment repositories is affected.
Results change — refit or re-check
| Change | What it does to your numbers |
|---|---|
An env_data year outside the model years is dropped on the linkage path |
Covariate rows are matched to model years by position, so a row before styr shifted every later row — the wrong covariate into consumption and, under multispecies, into predation mortality. A workbook with such a row either fitted with the row already filtered (no linkage) or did not fit at all (linkage), so there is no fitted result to change. The 15 workbooks carrying pre-styr rows were already flagged for refitting by 5.25.0. |
Rows after projyr are dropped too |
They shift nothing, but the fixed part of a linkage formula goes to model.matrix(), so cut(), poly() and scale() were computed partly over years the model never fits. This moves such a fit: +434.9 on ~ cut(Year, 3) and +823.8 on ~ scale(temp) at identical parameters, with ~ (1 | Year) unchanged. Of the 171 sibling workbooks carrying env_data, none has a row after projyr. |
A Sel_norm_bin past the fleet's last selected bin is now an error |
It was silently overwritten: anything above nages was clamped to nages — a bin count used as a ceiling for an absolute age, so on a minage = 3 stock ages 11 and 12 were valid and became 10. The clamp also ran before data_check(), so nothing downstream saw the original. Swept 51 workbooks carrying the column; values in use are 1, 3, 5, 7, 10, so none is affected. |
Breaking changes
No exported argument is removed or renamed, and every existing Sel_norm_bin value keeps its
meaning — blank, 0, -1, -999 and a positive bin reach the model as exactly the code they
always did.
- A
Sel_norm_binoutside the fleet's selected range is refused, where it was previously
clamped and fitted (above), or silently reinterpreted (below). This includes anOfffleet:
selectivity.hppskips normalization onSelectivity = "Fixed", not onFleet_type, so an
Offfleet's bin still indexes the selectivity array. - An unreadable value is refused rather than read as "do not normalize". A typo such as
Maxxpreviously resolved to blank and turned normalization off. - An
env_datatable lying entirely outside the model years, or carrying anNAyear, is an
error rather than being read into the wrong years.
New
-
Sel_norm_bintakes a word. It says where selectivity is normalized to 1, and accepted
only a number that encoded three intents with nothing to say which. Now, in any case:Value Meaning MaxNormalize by the largest value Off/None/ blankDo not normalize AllThe whole selected range — LogisticPMonly, where the column is a penalty age-range rather than a normalization referencea number An absolute age, or a 1-based length bin on a length-based fleet switch_check()writes the word back, so a saved workbook says what it does.
Sel_norm_bin_uppertakesOffthe same way, and the deprecated spellings
(Age_max_selected,Sel_norm_bin1, and the_upperpair) behave identically. -
The bounds are the fleet's own selected range. The floor is
Bin_first_selected— below it
the curve is zeroed, so a reference taken there divides by nothing — and a value below the
floor is read asMax, which is what a negative has always meant. That reinterpretation is now
reported rather than silent. NoteBin_first_selectedis a 1-based bin ordinal while
Sel_norm_binis an absolute age; the conversion is handled, and the schema states both
conventions.On a stock recruiting at age 0,
Sel_norm_bin = 0is the first age and is kept as a reference
bin rather than read as a flag.
Upgrading
../Rceattle-models, ../GOA-ATF-ESP and ../GOA-multispecies-assessment consume this API
directly. Pin to this tag in renv.lock or a Remotes: field rather than tracking main.
Rceattle 5.27.0
Fourth release of the 5.x line, covering 5.24.0 through 5.27.0 (merged in #139). The six
versions between were never tagged, so this release carries all seven. Full detail in
NEWS.md.
Highlights
- Two-sex stocks get their spawner-per-recruit back. SPR applied the female fraction twice
and returnedNaNwheresex_ratiostopped short of the plus group — so a two-sex stock could
not be given a Tier 3 reference point at all. Refit two-sex stocks; one-sex stocks are
unchanged. - Three input settings were being read wrongly, or not at all: an environmental covariate
year beforestyr, composition tail accumulation under its pre-4.4 column names, and an
integerTime_varying_selon a non-parametric fleet. Between them they touch most workbooks in
the sibling repositories. See the table. - An assessment reports itself in one call.
report_tables()builds the tables a SAFE
chapter needs andstandard_output()emits the NOAA standardized format; a reference point the
model never estimated now comes backNAwith a stated basis, not the 1.0/yr sitting in the
array. - Diagnostics name the fleet, bin and year they flag, rather than the parameter block, and
selectivity can be drawn with a confidence interval.
Both defects in the first two rows below are present in 5.23.0 and every earlier 5.x release.
Results change — refit or re-check
| Change | What it does to your numbers |
|---|---|
| Spawning biomass per recruit was wrong for a two-sex stock (5.24.1) | The female fraction was applied twice, to a schedule that is already female, and returned NaN wherever sex_ratio stopped short of the plus group. One-sex stocks are unchanged. Under a Beverton-Holt or Ricker curve it moves the fit at any HCR, through steepness and initial recruitment: on bundled GOAatf, steepness 0.4655 → 0.5516 and R_init 0.9505 → 1.0623. Refit a two-sex stock whose sex_ratio varies over the mature ages or stops short of the plus group. Where the ratio is flat over the mature ages, F40% and F35% do not move but the reported SPR quantities do — ×1.412 on the GOA arrowtooth ESP. Recheck any quoted SPR; no refit. |
An environmental covariate year before styr shifted the whole series (5.25.0) |
Covariate rows are matched to model years by position, so one early observation fed every later year the wrong value — into consumption, and under multispecies into predation mortality. 15 workbooks in the sibling repositories need refitting: GOA CEATTLE 2018–2023, AI cod, a single-species hake input, and three GOA circulation-study runs. The hake operating model behind 04-mse.R is not among them, so the MSE and predation references stand. No bundled dataset is affected. |
| Composition tail accumulation was silently ignored under its pre-4.4 column names (5.25.0) | Accumatation_age_lower / _upper were missing from the alias list, so those fleets were fit with unaccumulated tails. 147 of the 183 fleet_control sheets in the sibling repositories carry them; 79 fold bins. On the 2023 GOA pollock workbook the composition negative log-likelihood moves 16333.05 → 16039.59 at identical parameters. No bundled dataset is affected. |
Time_varying_sel = 4 turned the random walk off and overwrote the selectivity penalties on a non-parametric fleet (5.25.0) |
The pre-4.4 format upgrade could not tell a shape penalty weight of 4 from the RandomWalk code 4. On GOAcod, weights of 20 and 12.5 became 4. The string "RandomWalk" always worked. |
Non-parametric selectivity returned NaN once a coefficient grew large (5.25.0) |
Fits that previously died mid-optimization now run. No converged fit moves — the four reference models reproduce to 1e-11. |
report_tables() described the projection, not the hindcast (5.24.2) |
max_gradient and pdHess came from the HCR projection. An HCR held as an integer also matched no rule, so an estimated F40% was reported as NA with a basis saying it had never been estimated. |
condition_number changed meaning (5.26.0) |
Read on the correlation matrix rather than the covariance, so it measures how nearly confounded the estimates are instead of the units they are held in. It falls 0.4–1.1 orders on the models measured, so at unchanged thresholds the check fires less readily. Not comparable with earlier releases — covariance_condition_number keeps the old value. |
Nothing else moves a fitted hindcast. The four golden reference objectives are reproduced
throughout.
Breaking changes
No exported argument is removed or renamed. Three configurations that previously ran are now
refused, and none of them produced a number worth using:
random_sel = TRUEwith non-parametric selectivity (5.25.0), for two reasons. Under
"RandomWalk"the walk is scored on the renormalized curve, which never sees the level of each
year's coefficients, so the standard deviation of the selectivity deviations collapses —
2.7e-8 onAtka2022, a time-invariant selectivity reported as time-varying. Under"IID"the
shape penalty leaves the integrated likelihood only piecewise smooth and the optimizer stops at
a kink: maximum gradient 6.8, and a deviation standard deviation 27% from what the same
model reaches withSel_curve_pen1 = 0. Fit these withrandom_sel = FALSE, the penalized
form AMAK intends, which is unaffected.Ceqoutside 1–4 (5.25.0). An unrecognized value left the temperature function at zero,
and with it consumption — and under multispecies, predation mortality.- A workbook carrying the same column under two spellings (5.25.0). It previously read,
silently keeping one of them. One file in the sibling repositories does.
New
report_tables()(5.24.0) — what a SAFE chapter reports, as one set of tables: model,
parameters, likelihood, time series, reference points and fits, plus retrospective, jitter and
OSA residuals when supplied. Never refits.standard_output()(5.24.0) — the NOAA standardized assessment format, forstockplotr
andasar.quantity_dictionary()(5.24.0) — what each of the 99 reported quantities means, with
units and dimensions.parameter_index()(5.26.0) — where each estimated parameter sits in
the model, so a convergence warning names the fleet, bin and year rather than the parameter
block.- A standard error for the selectivity curve (5.27.0) —
fit_control(selectivity_se = TRUE)
reports it andplot_selectivity(add_ci = TRUE)draws it. Off by default. Selectivity is
identified only up to a scalar, so the interval is relative to theSel_norm_binanchor, which
is pinned rather than estimated: quote the anchor with the interval. plot_catchability()(5.25.0) — fitted survey catchability by year, faceted by fleet.
Upgrading
../Rceattle-models, ../GOA-ATF-ESP and ../GOA-multispecies-assessment consume this API
directly. Pin to this tag in renv.lock or a Remotes: field rather than tracking main.
Rceattle 5.23.0
Third release of the 5.x line, covering 5.22.0 and 5.23.0 (merged in #128). 5.22.0 was
never tagged, so this release carries both.
Full detail in NEWS.md.
Results change — refit or re-run before carrying a model forward
| Change | Effect |
|---|---|
| A retrospective peel reported a standard error of zero for every hindcast year (5.22.0) | Peels plotted with no confidence band while the unpeeled model had one. Bands now appear. Mohn's rho, every peel's SSB and every peel's objective are unchanged — regenerate retrospective figures, do not refit |
fit_control(projection_uncertainty) was reset to its default by any refit (5.22.0) |
Under an HCR this decided whether a refit reported 6 free parameters and no hindcast uncertainty, or 316 and real errors. Affects retrospective(), jitter() and MSE refits |
run_mse() expanded a time-invariant (Year = 0) weight or ration_data series into one naming only projection years, and the expansion added duplicate rows that silently replaced observations (5.22.0) |
Re-run any MSE whose workbook carries a time-invariant weight or ration series |
run_mse() took a weight group's last row by table position, not by year (5.22.0) |
Rows ordered 2019, 2017, 2018 carried 2018's weight-at-age through the whole projection. Nothing sorts weight on read. Re-run any MSE whose weight table is not in ascending year order |
run_mse() skipped every assessment whose year already carried catch, rebuilding the operating model instead of advancing it (5.22.0) |
Re-run any MSE whose workbook carries data past endyr |
No change to any fitted hindcast. Nothing in this release moves a reference fit's objective.
Breaking changes
run_mse()refuses an operating and estimation model whose horizons disagree (5.22.0).
The assessment loop indexes one model's table by the other's row positions, so a mismatch was
silently wrong rather than an error. A live assessment MSE with matchingendyr/projyris
unaffected.profile(param = "q")refusesAnalyticalandAnalyticalArith(5.22.0). Those solve q
from the fleet's own index and overwriteindex_q, so the grid moved nothing and every point
returned the same fit — a flat curve that read as "the data do not inform q" when the profile
had measured nothing.
New
parameter_dictionary()(5.23.0) — whatlog_M1,R_log_sd,index_log_qand the other
45 internal parameter names actually are, with their natural-scale name, process, meaning and
dimensions. Filter by name or by process. The table existed but was internal and generated no
help page.profile(joint =)(5.22.0) —"multiply","add"or"value"move a whole set of cells
on one grid. Slots otherwise cross, so a ten-age M schedule over 13 values was13^10fits.
This is what makes an empirical age-based M profilable the way it is normally reported.profile(param = "q")(5.22.0) — profile a fleet's base catchability; a shared
Catchability_indexgroup moves together.plot_profile(relative = "scaled")(5.22.0) — one component routinely dwarfs the rest, so
the others flatten onto the axis and where they prefer the parameter cannot be read. Every
curve goes on 0 to 1.minfractionand the legend order are computed on the raw change,
before rescaling.retrospective()warns when a peel is dropped, carries$peels_requested, andprint()
reads "3 of 5 peel(s)" (5.22.0). Rho is averaged over the survivors, so a drop changes a
reported number; it was amessage(), whichsuppressMessages()hides.- The introduction vignette covers the fitted object (5.23.0) — the S3 methods an
Rceattle
fit responds to, the classes the diagnostics return, and where numbers live in$quantities
versus$estimated_params. It also stops telling users to readfit$opt$AIC, which is not
always there;AIC(fit)is.
Two traps worth knowing
retrospective(getsd = TRUE)turns on the non-positive-definite Hessian check, so it can
drop peels thatgetsd = FALSEkeeps — and rho is averaged over survivors, so the same model
can report a different rho depending only ongetsd.- A build flag set in a job's environment is not a build flag in force. The
deep-checks
safeboundsjob wascontinue-on-error, so the workflow reported success while the job
failed — and it had never run:NAMESPACEloads the package SHLIB as well as the TMB model
and the harness built only the second. "Five configurations clean on macOS" in the 5.20.0
notes was not the clearance it read as. Fixed, andcontinue-on-erroris off.
Rceattle 5.21.0
Point release on the 5.x line (merged in #126).
Full detail in NEWS.md.
Results change
None. No fit moves, and no stored result changes. The one template change
relocates a term between rows of jnll_comp; the objective is
jnll_comp.sum(), so it is bit-identical by construction. The four golden
reference objectives are unmoved.
Breaking changes
None. profile() gains one element on its returned list ($alias);
Rceattle_list, grid, nll, param and slots all index as before.
New
-
plot_profile()draws the likelihood components of a profile, with the
total overlaid. The total NLL says how well the data determine a parameter.
It does not say whether the data sources agree about it — a smooth
quadratic total can be two surveys pulling in opposite directions, and only
the components show that.The layout follows
r4ss::SSplotProfile():
every series re-zeroed at its own minimum with a point marking it, so the
spread of those points along the x axis is the disagreement. The total is
black and heavier, and kept out of the colour legend so the palette separates
only what is being compared. Components moving less thanminfraction(1% by
default) of the total's change are dropped.plot()on a profile is the same
thing; a list of profiles facets two model configurations side by side. -
profile_components()returns the same numbers as a data frame — one row
per grid point per component, each cell labelled with the fleet or species it
belongs to.jnll_comp's columns count fleets on the data, selectivity
and catchability rows and species on the priors, penalties and predation
rows, so this is the piece that stops a survey's likelihood being reported
against a species. -
A profile is reported in the units you profiled it in.
profile(param = "M1")resolvesparamto"log_M1", soprint()reported
a minimum of 0.28 next to the namelog_M1when 0.28 is an M, not a log M.
The alias is kept on the returned object as$aliasand used byprint()
and the figure's axis.
Fixed
- The QAR1 process error is reported as a deviate, not as a prior. The AR1
density onindex_q_dev(Rogers et al. 2024) scored into the "Catchability
prior" row ofjnll_comp, where it reads as a prior on log q, and assigned
rather than accumulated. Unreachable in practice —data_check()refuses
Catchability = 6and the live QAR1 form is a q linkage — so no fit is
affected; fixed because a component profile reads these row labels to name
the term in conflict.
Under the hood
tests/testthat/test-schema-jnll-rows.R pins the three-way hand-synced
registry the jnll_comp rows depend on — the JnllRow enum, the display names
in R/6-rename_output.R, and the new column-axis registry. It parses every
jnll_comp(JNLL_*, col) write in the template (124 of them, covering all 21
rows) and asserts each row's declared axis matches the column it is actually
indexed by. Nothing linked the three at build time before.
Known limitation
Carried forward from 5.20.0 and not cured: R CMD check on
windows-latest died once with 0xC0000005, a Windows access violation,
inside test-selectivity-catchability.R. That is memory corruption, not a bad
optimum. What ships is the scaffolding to catch it — RCEATTLE_SAFEBOUNDS=true
plus tools/verify/verify-safebounds.R, and a nightly deep-checks workflow.
Every check on this release passed, Windows included, but a green Windows run
is not evidence: the guard usually skips the file that crashes.