This guide is for people who want to modify Rceattle itself: add a parameter, a data input, a new selectivity or suitability form, or a likelihood component. It is not needed to fit models — for that, start with the user articles (Introduction and Model options and functionality).
It consolidates and updates the developer notes on the GitHub wiki.
Repository layout
| Path | Contents |
|---|---|
R/ |
The R side. Files are number-prefixed in rough pipeline order (see below). |
src/TMB/ |
The C++ TMB template ceattle_v01_11.cpp and its
included .hpp modules. |
tests/testthat/ |
Unit and regression tests, grouped by area. |
inst/extdata/ |
Excel templates, including meta_data_names.xlsx (the
authoritative column/switch reference). |
data/ |
Built-in example data lists (BS2017SS,
GOA2018SS, NorthernRockfish2022, …). |
vignettes/ |
User articles; vignettes/articles/ holds website-only
articles like this one. |
.github/workflows/ |
R-CMD-check.yaml and pkgdown.yaml CI. |
The whole model — observation model, population dynamics, and
likelihood — is a single C++ TMB script,
src/TMB/ceattle_v01_11.cpp, which #includes a
set of topical .hpp modules. fit_mod() is the
R entry point; it calls a chain of helpers that build the TMB inputs,
optimize, and label the output.
The fit pipeline
fit_mod() (R/6-fit_mod.R) orchestrates the
following stages. The number-prefixed file names mirror this order.
| Stage | File | Function | Role |
|---|---|---|---|
| 1 | R/0-clean_data.R |
clean_data() |
Coerce and clean the incoming data_list. |
| 2 | R/0-switches.R |
switch_check() |
Fill missing switches with defaults; normalize string/integer switches. |
| 3 | R/1-data_check.R |
data_check() |
Validate inputs; error early on unsupported options. |
| 4 | R/2-build_params.R |
build_params() |
Build the starting parameter list from the switches. |
| 5 | R/3-build_map.R |
build_map() |
Build the TMB map (which parameters are estimated
vs. fixed). |
| 6 | R/4-build_parameter_bounds.R |
build_bounds() |
Lower/upper parameter bounds. |
| 7 | R/5-rearrange_data.R |
rearrange_data() |
Reshape the data for TMB (and build the OSA observation vector when requested). |
| 8 | R/6-fit_mod.R |
fit_mod() |
TMB::MakeADFun() → nlminb() →
TMB::sdreport(). |
| 9 | R/6-rename_output.R |
rename_output() |
Label derived quantities on the returned object. |
Phasing (optional staged estimation) is handled by
set_phases() / TMBphase() in
R/OPT-phaser.R; TMBAIC()
(R/OPT-TMBAIC.R) computes AIC. Downstream wrappers —
retrospective(), jitter(),
run_mse(), sim_mod(),
self_test(), the plot_*() family — all take
the fitted Rceattle object returned by
fit_mod().
The switch system
Model options are supplied either as strings
("Logistic", "NPFMC") or as the integer codes
the TMB template consumes. R/0-switches.R is the single
source of truth for that correspondence, via four functions:
-
switch_check()— fill missing switches with defaults and normalize. -
validate_switches()— error if any switch is not a known code/string. -
convert_switches()— canonical string → integer code for TMB. -
revert_switches()— integer code → canonical string (backward compatibility for older integer-coded data files).
The mappings live in named vectors: sel_map,
tv_sel_map, q_map, tv_q_map,
comp_loglike_map, fleet_map,
initMode_map, suitMode_map, and
hcr_map. Adding a value to a map is what makes a new string
name legal; validate_switches() rejects anything not
listed.
The TMB template
ceattle_v01_11.cpp includes these modules (order matches
the top of the file):
| Module | Responsibility |
|---|---|
helper_functions.hpp |
Shared utilities. |
comp_osa.hpp |
Composition likelihoods and the one-step-ahead (OSA) decomposition. |
growth.hpp |
Growth (von Bertalanffy / Richards); weight- and length-at-age. |
selectivity.hpp |
Selectivity forms (cases 0–7, including the Hake/Taylor non-parametric case 5). |
recruitment.hpp |
Stock–recruitment functions. |
bioenergetics.hpp |
Temperature-dependent ration / consumption (Ceq). |
predation.hpp |
Prey suitability and predation mortality (MSVPA type-2, estimated iteratively). |
diet_data.hpp |
Predator diet (stomach-content) likelihood. |
linkage.hpp |
Environmental / covariate linkage machinery. |
The template opens with the DATA_* block (switches such
as estimateMode, msmMode,
suitMode, initMode, srr_fun,
HCR, plus the data objects), then the
PARAMETER_* block, then the population and observation
dynamics, and finally the joint negative log-likelihood. The likelihood
is accumulated in the jnll_comp object, organized by
species, fleet, and likelihood component.
Building. src/TMB/compile.R compiles
the template (framework = "TMBad"); TMB and
RcppEigen are LinkingTo dependencies. The
spurious Eigen -Warray-bounds warnings are silenced by a
source #pragma in the .cpp rather than a
compiler flag, since CRAN rejects warning-suppressing flags.
The linkage and priors system
Time-varying and covariate-driven parameters (M, growth, recruitment,
catchability, selectivity) share one formula-driven linkage system:
R/0-build_linkage.R, R/0-linkage_encode.R,
R/0-linkage_table.R, and R/0-priors.R.
linkage_spec(formula = ~ x, priors = list(...)) is the user
entry point.
Note that inside priors =, the bare constructors
normal(), lognormal(), gamma(),
beta() are not the exported
prior_*() functions. linkage_spec() evaluates
the priors quosure in a data mask
(rlang::eval_tidy(..., data = .prior_dispatch_mask())) that
binds normal → prior_normal, and so on. Both
spellings work inside linkage_spec(), and the bare form is
intentional — do not “fix” it to prior_normal().
Recipes
Add a new estimated parameter
- Declare the
PARAMETERinceattle_v01_11.cppand use it in the relevant.hppmodule. - Add a default starting value in
R/2-build_params.R. - Add mapping logic in
R/3-build_map.R(and the matchingbuild_map_*()helper) so it is turned on/off for the right configurations. - Add it to the phasing order in
R/OPT-phaser.R. - (Optional) expose an on/off or random-effect switch as a
fit_mod()argument. - Document it in
inst/extdata/meta_data_names.xlsxand/or thefit_mod()roxygen.
Add or change a data input
- Add the
DATA_*object inceattle_v01_11.cppand consume it in the module. - Update
R/0-read_write_excel_data.Rto read/write the new sheet or column. - Add validation in
R/1-data_check.R. - Add reshaping in
R/5-rearrange_data.Rif TMB needs a different layout. - Document it in
meta_data_names.xlsxand/or roxygen.
Add a new switch option (e.g. a selectivity form or
suitMode)
- Implement the case in the module
.hpp(e.g.selectivity.hppcase N,predation.hppsmodeN). - Register the string ↔︎ integer mapping in the relevant map in
R/0-switches.R(e.g.sel_map,suitMode_map); otherwisevalidate_switches()rejects the new name. - If the option is not yet ready for use, block it in
R/1-data_check.R— as the length-based suitability modes are (suitMode %in% c(1, 3, 5)). - Add bounds in
R/4-build_parameter_bounds.Rif the option introduces bounded parameters (see thesuitMode %in% c(1:2)branch). - Add a test under
tests/testthat/tests-<Area>/. - Document it.
Change the likelihood
The joint negative log-likelihood is assembled at the end of
ceattle_v01_11.cpp, in jnll_comp, indexed by
species, fleet, and component. Add or modify the relevant term there,
then recompile and re-run the regression tests (below) to confirm the
objective changes only as intended.
Testing
Tests use testthat and are grouped by area under
tests/testthat/: tests-Selectivity/,
tests-Dynamics/, tests-Likelihoods/,
tests-Data-processing/, tests-Growth/, and
tests-Mortality/. Notable fixtures and guards:
-
tests/testthat/fixtures/fit_baseline.rdsand the golden-jnllregression test pin the objective-function value so unintended numerical changes surface. -
test-tmb-makeadfun_smoke.Rchecks the template builds and evaluates. -
test-parameter-recovery.Rfits simulated data and checks estimates. -
helpers-make-msm-data.Rbuilds small multi-species fixtures.
Run the suite with devtools::test() (or
testthat::test_local()). After any C++ change, recompile
(src/TMB/compile.R or pkgbuild::compile_dll())
before testing, since the tests load the compiled
.so/.dll.
Branches and releases
origin/HEAD points at main. The general
model, per the Onboarding wiki page, is: cut feature branches from
dev, merge them back into dev, and merge
dev into main once a project’s developments
are complete.
| Branch | Role |
|---|---|
main |
Most stable / documented; CI and CRAN target. |
dev |
Active development branch; base for feature branches. |
dev-DSEM |
dev with DSEM-linked recruitment (experimental). |
dev-ebs-pk |
EBS pollock application / bridge. |
dev-RTMB |
RTMB port (remote). |
testing-suite-overhaul |
Test-suite expansion. |
depricated-ceattle_classic* |
Original Holsman et al. (2016) single-sex model
(ceattle_v01_02/_04.cpp); historical reference
only. |
Versioning (SemVer). MAJOR for breaking changes,
MINOR for new features/functionality, PATCH for bug fixes. Update
DESCRIPTION and add a NEWS.md entry with each
release.
Commit messages (Conventional Commits). Prefix with
one of feat, fix, docs,
style, refactor, perf,
test, chore, optionally scoped, e.g.
fix(selectivity): normalize Hake-type curve.
CI. R-CMD-check.yaml runs on macOS,
Windows, and Ubuntu (R release) on push/PR to
main/master and weekly;
pkgdown.yaml rebuilds this site.
Debugging tips
-
gradient is of length 1(or similar). The population is usually crashing — numbers-at-age going negative or a divide-by-zero. Check starting values and bounds for the offending process. -
Model will not converge. Inspect
mod$identified(when reported) to see which parameters are hard to estimate, and adjust their starting values. -
Reproducing an external model. Supply
numbers-at-age via the
NByageFixedsheet and selectivity viaemp_selwithSelectivity = "Fixed", setestDynamics = 1, and runfit_mod(estimateMode = 1)to evaluate without re-fitting. (See also the Stock Synthesis conversion article.)
See also
- GitHub wiki — the original developer notes this page consolidates.
- Model parameterizations — equation-level detail on selectivity, catchability, and predation.
- Adams et al. (2022), Fisheries Research 251:106303 — the TMB generalisation of CEATTLE. Holsman et al. (2016), Deep-Sea Research II 134:360–378 — the original model. Wassermann et al. (2024), ICES JMS — the hake/cannibalism extension.