Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ cheatsheet/output/

# Curated and preliminary assessment outputs are local generated artifacts.
analysis/comp_assessments/results/

# Manually refreshed SAIO source snapshots; the selected revision is tracked.
analysis/comp_assessments/cache/saio/
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ transparent, and consistent with existing package structure.
- Run targeted tests first, then the full test suite.
- Run `R CMD check` for substantial changes when practical.
- Do not weaken existing tests merely to make new code pass.
- Share fitted fixtures across tests; refit or render HTML only when that
operation protects behavior not already covered. Judge coverage by meaningful
contracts rather than the number of assertions.
- Retired interfaces and old saved-object migrations need no compatibility
tests. Preserve checks of current scientific conventions, including M blocks.

## RTMB / model conventions

Expand Down
2 changes: 0 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export(ar1)
export(as_tam_data)
export(bh)
export(cAIC)
export(check_obs)
export(check_tam)
export(check_tam_data)
export(compute_hindcast_mase)
Expand Down Expand Up @@ -74,7 +73,6 @@ export(stack_nested)
export(tam_cols)
export(tam_pal)
export(tidy_array)
export(tidy_mat)
export(tidy_obs_pred)
export(tidy_par)
export(tidy_pop)
Expand Down
26 changes: 18 additions & 8 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# tinyAM 0.1.1.9001

* Organize plots by purpose and colocate population getters. Share fitted test
fixtures, remove redundant renders and unused implementations, and retire the
`check_obs()` and `tidy_mat()` aliases in favor of `check_tam_data()` and
`tidy_array()`. Old saved-fit migrations are no longer maintained.

* Open dashboards on Model structure, with Diagram and Function calls tabs.
Use responsive, palette-colored boxes with normal dashboard typography and
SVG connections, without adding a diagramming dependency.

* Restore catch-only preparation, projections, simulation and tidy outputs,
retaining empty survey tables without unused catchability or survey-SD
parameters. Render reporting references with missing yield, observations or
Expand All @@ -10,10 +19,11 @@
Compare stored specifications with an independent model selector; link
components to maintained equations without calculating or changing fits.

* Add Auto / Standardized / OSA controls to public residual plots and dashboards.
Auto selects retained OSAs separately for each model and panel, preserving
partial gaps and models without OSA calculations. Labels identify the method;
changing plot controls never calculates residuals.
* Offer Standardized / OSA residual controls without bubble outlines. Start with
retained OSAs when available; overlays use a common residual type and model
frames choose separately. Preserve partial gaps and identify the OSA method;
plotting never calculates residuals. Explicit row-wise hover values stay
aligned through model frames and missing residuals.

* Select any supported OSA algorithm through `method` and retain diagnostics
with `add_osa_residuals()`, without refitting. Explicit computation requests
Expand All @@ -32,10 +42,10 @@
rotations, while rejecting changes to predictor grids or Gaussian penalties.
This fixes platform-dependent retrospective warm-start checks.

* Read assessment examples from SAIO's latest committed main by default, recording
the exact source revision and separating caches by database version. Explicit
revisions remain available for reproducing historical results. Keep curated
and preliminary translation membership and outputs separate.
* Run comparative assessments from an explicitly refreshed local SAIO snapshot,
recording its commit in a tracked manifest. Separate fitting, dashboard
rendering and overview figures; honor the caller's future plan. Keep curated
and preliminary membership and revision-specific result caches separate.

* Group fishery diagnostics under Fisheries: one complete page per fleet,
followed by Aggregate fishery. Unlabeled catches remain stock-wide; aggregate
Expand Down
4 changes: 2 additions & 2 deletions R/check.R
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
#' @param model_list Optional named list of fitted `tam_fit` objects. Supply
#' this or individual models, not both.
#' @param grad_tol Positive gradient tolerance. `NULL` uses the tolerance stored
#' in the fit, or `1e-2` if none is stored.
#' in the fit.
#' @param detailed Examine covariance eigenvalues and weak parameter directions?
#' @return For one individual model, a `tam_check` list with `is_converged`, `numerical`, `structural`,
#' `structural_status`, `advisories`, `residuals`, `data`, and `curvature`.
Expand Down Expand Up @@ -99,7 +99,7 @@ check_tam <- function(fit, ..., grad_tol = NULL, detailed = FALSE, model_list =

.check_tam_fit <- function(fit, grad_tol = NULL, detailed = FALSE) {
fit <- .require_tam_fit(fit, arg = "fit")
if (is.null(grad_tol)) grad_tol <- if (is.null(fit$grad_tol)) 1e-2 else fit$grad_tol
if (is.null(grad_tol)) grad_tol <- fit$grad_tol
if (!is.numeric(grad_tol) || length(grad_tol) != 1L ||
!is.finite(grad_tol) || grad_tol <= 0) {
cli::cli_abort("{.arg grad_tol} must be one positive finite number.")
Expand Down
12 changes: 0 additions & 12 deletions R/data.R
Original file line number Diff line number Diff line change
Expand Up @@ -526,15 +526,3 @@ check_tam_data <- function(data, quiet = FALSE) {
invisible(NULL)
}

#' Check the structure of a TAM obs list
#'
#' A convenience wrapper for [check_tam_data()].
#' @param obs A named list of tinyAM input tables.
#' @inheritParams check_tam_data
#' @inherit check_tam_data details
#' @return `TRUE` (invisibly) if validation passes; otherwise aborts.
#' @examples
#' check_tam_data(cod_obs)
#' @export
check_obs <- function(obs, quiet = FALSE) check_tam_data(obs, quiet = quiet)

43 changes: 12 additions & 31 deletions R/fit.R
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,10 @@
.start_parameters <- function(start, dat) {
if (inherits(start, "tam_fit")) {
source <- start
if (!is.null(source$parameter_values) && !is.null(source$obj)) {
start <- source$obj$env$parList(par = source$parameter_values)
} else if (!is.null(source$sdrep)) {
start <- as.list(source$sdrep, "Estimate")
} else {
if (is.null(source$parameter_values) || is.null(source$obj)) {
cli::cli_abort("Starting fit has no stored parameter estimates.")
}
start <- source$obj$env$parList(par = source$parameter_values)
if (identical(source$dat$rec$type, "off") && !is.null(source$rep$recruitment)) {
years <- source$dat$rec$eligible
start$log_r <- setNames(log(source$rep$recruitment[years]), source$dat$years[years])
Expand All @@ -98,7 +95,6 @@
start$log_f <- start$log_f[, keep, drop = FALSE]
} else {
old <- source$dat$F_settings$age_blocks
if (is.null(old)) old <- stats::setNames(as.character(source$dat$ages), source$dat$ages)
new <- dat$F_settings$age_blocks
groups <- intersect(colnames(start$log_f), levels(new))
keep <- vapply(groups, function(g) identical(names(old)[as.character(old) == g], names(new)[as.character(new) == g]), logical(1))
Expand Down Expand Up @@ -165,35 +161,18 @@
#' optimizes the remaining fixed effects. [mono()] increments `dq` and `dmu_f` have a zero
#' lower bound, as do initial-abundance decrements `d_n0`. Their SEs remain on the increment scale, but symmetric Wald
#' intervals are only local approximations at a boundary.
#' Set `index_settings$q_link = "logit"` to restrict the full catchability
#' prediction to between zero and one; the default `"log"` allows q above one.
#' Formula coefficients are then named `logit_q`, and [mono()] increments
#' act on logit-q. Starting q coefficients must use the selected link scale:
#' old `log_q` starts are not reused as `logit_q`. See [prepare_tam()] and [tidy_par()].
#'
#' Random-effect blocks are chosen automatically from the model settings:
#'
#' - Includes `log_f` when the F process is active, and eligible `log_r` states when recruitment is stochastic. Early recruitment
#' anchors are fixed; see [recruitment_formulas].
#' - Includes `log_n0` only if `N_settings$init == "latent"`.
#' - Includes `log_n` if `N_settings$process != "off"`.
#' - Includes `log_m` if `M_settings$process != "off"`.
#' - Includes the unique `eta_q_*` states of Gaussian catchability formula terms.
#' - Includes Gaussian F/M mean and catch/index log-SD formula states.
#' Logistic midpoint/slope and process SD/correlation are fixed effects.
#' Random-effect blocks are chosen from the active processes and Gaussian
#' formula effects. Initial abundance and recruitment boundaries are integrated
#' only when their respective `init` setting is `"latent"`; see [tinyAM-model]
#' and [recruitment_formulas]. [make_par()] describes the parameter layout.
#'
#' A warning is issued if the number of random effects exceeds 1.5 times the
#' number of observed data points (rough identifiability check).
#'
#' For F random walks, [prepare_tam()] removes time-invariant mean columns
#' because they cancel from all increments. For M random walks, mean coefficients
#' with no effect on either increments or mortality outside the fitted process
#' states are held at their starting values.
#' An RW SD is also held fixed if the process has only one row (no increments).
#' These parameters cannot be estimated from the likelihood; no constraint is
#' imposed on the latent states themselves.
#' For AR1, the correlation for an axis containing a single row or column is
#' fixed at zero; otherwise it would be confounded with the SD.
#' Parameters without likelihood support are removed or fixed: examples include
#' constant mean directions canceled by RW increments, RW SDs with no increments,
#' and AR1 correlations on singleton axes. The latent states themselves retain
#' the conventions in [tinyAM-model].
#'
#' `is_converged` summarizes optimizer, gradient and uncertainty checks. Also inspect
#' `opt$convergence` and `opt$message`; a passing numerical check does not
Expand Down Expand Up @@ -223,6 +202,8 @@
#' structure returned by [make_par()]. Useful for warm starts or retrospective
#' runs where parameter estimates from a previous fit are reused to improve
#' convergence and speed. Non-matching or missing entries are ignored.
#' Fitted-object starts require the current stored parameter modes; regenerate
#' older saved fits. Catchability coefficients are omitted if `q_link` changes.
#' @param grad_tol Numeric tolerance passed to [check_tam()] when
#' evaluating the fitted object's gradients. Defaults to `1e-2`.
#'
Expand Down
60 changes: 60 additions & 0 deletions R/getters.R
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,63 @@ get_m <- function(..., model_list = NULL, interval = 0.95) {
.unit_label(tab, "")
tab
}

# Population-only collection shared by tidy_tam() and the quantity getters.
# Keep comparison conversions here so lightweight consumers use the same units
# and reported-versus-log SE conventions as dashboards.
.population_tables <- function(model_list, interval) {
pop_list <- lapply(model_list, function(fit) {
if (inherits(fit, "tam_ref")) return(fit$pop)
stored <- attr(fit$pop, "interval", exact = TRUE)
if (!is.null(fit$pop) && !is.null(stored) &&
isTRUE(all.equal(stored, interval))) {
fit$pop
} else {
tidy_pop(fit, interval = interval)
}
})
references <- Filter(function(fit) inherits(fit, "tam_ref"), model_list)
comparison_scales <- lapply(references, `[[`, "comparison_scales")
comparison_scales <- Filter(function(x) length(x) > 0L, comparison_scales)
if (length(comparison_scales)) {
comparison_scales <- lapply(comparison_scales, function(x) {
if (is.list(x)) x <- unlist(x, use.names = TRUE)
if (!is.numeric(x) || is.null(names(x)) || any(!nzchar(names(x))) ||
anyDuplicated(names(x)) || any(!is.finite(x)) || any(x <= 0)) {
cli::cli_abort("A {.cls tam_ref} {.field comparison_scales} value must be a named vector of positive finite numbers.")
}
x
})
if (length(comparison_scales) > 1L &&
!all(vapply(comparison_scales[-1L], identical, logical(1), comparison_scales[[1L]]))) {
cli::cli_abort("Assessment references use different {.field comparison_scales}; the fitted outputs cannot be shown on one scale.")
}
comparison_scales <- comparison_scales[[1L]]
reference_pop <- references[[1L]]$pop
for (i in seq_along(model_list)) {
if (inherits(model_list[[i]], "tam_ref")) next
for (metric in intersect(names(comparison_scales), names(pop_list[[i]]))) {
tab <- pop_list[[i]][[metric]]
scale <- comparison_scales[[metric]]
reference <- reference_pop[[metric]]
from <- unique(tab$unit[!is.na(tab$unit)])
to <- unique(reference$unit[!is.na(reference$unit)])
if (length(from) == 1L && length(to) == 1L) .check_unit_conversion(from, to, scale)
for (field in intersect(c("est", "lwr", "upr"), names(tab))) {
tab[[field]] <- tab[[field]] * scale
}
if ("se" %in% names(tab)) {
natural <- if ("se_scale" %in% names(tab)) tab$se_scale == "reported" else rep(TRUE, nrow(tab))
tab$se[which(natural)] <- tab$se[which(natural)] * scale
}
if (is.data.frame(reference) && "unit" %in% names(reference)) {
units <- unique(as.character(reference$unit[!is.na(reference$unit) &
nzchar(as.character(reference$unit))]))
if (length(units) == 1L) tab$unit <- units[[1L]]
}
pop_list[[i]][[metric]] <- tab
}
}
}
pop_list
}
Loading
Loading