class: center, middle, inverse, title-slide .title[ # Random Effects in
Fisheries Integrated Modeling System (FIMS):
Architecture, implementation, and
future development ] .subtitle[ ## FIMS CIE Review ] .author[ ### Andrea Havron, NOAA Fisheries OST
andrea.havron@noaa.gov
] .date[ ### 2026/04/23 ] --- layout: true .footnote[U.S. Department of Commerce | National Oceanic and Atmospheric Administration | National Marine Fisheries Service] <style type="text/css"> code.cpp{ font-size: 20px; } code.r{ font-size: 20px; } code.Rcpp{ font-size: 20px; } </style> <!-- Start of slides --> --- # Outline - Hierarchical Modeling in Stock Assessments - Guiding Principles in Design - C++ architecture - User Interface - Informing Decisions via Sparsity Analysis - Future Development Plans --- # Hierarchical Modeling in Stock Assessments <br> <br> .center[ .large[ `\(Index \sim LogNormal(\hat{\mathbf{I}}, \sigma_{I})\)` <br> `\(\hat{\mathbf{I}} = f(Growth, Maturity, Mortality, \mathbf{Recruitment}, Selectivity)\)` <br> `\(\log(\mathbf{Recruitment}) \sim Normal(\log(\hat{R}), \sigma_{R})\)` ]] --- # Fitting Hierarchical Models: Speed vs. Accuracy .pull-left[ **Penalized likelihood** - Fixed effects estimation (fastest) - Uses a Constrained optimization approach **Laplace approximation** - Random effects estimation (moderate) - Uses 2nd-order Taylor series approximation **MCMC** - Full posterior sampling (slowest) - Asymptotically exact posterior ] .pull-right[ <!-- --> ] --- # FIMS Random Effects Requirements <br><br> - Include random effects option for fitting state-space models - Temporal and spatial varying random effects - Capture process variability and measurement error - Shared parameterizations across fleets and populations - Multivariate random effects - Hierarchical estimation across processes, species, areas, etc. --- # Guiding Principles in Design <br><br> - Generic and Flexible - Extensible - Separate the biological math from the statistics - Dependency on TMB and access to the Laplace Approximation --- # Choosing the default <br><br> .center[ .quote-card[ Unbiased estimation of parameter deviations (such as recruitment or selectivity) <br>in models without random effects requires iterative bias adjustment algorithms <br>which are inefficient and prone to error. ] ] <br>       ~ FIMS Requirements Documents --- .large[**Decoupling Biological Processes from Statistical Likelihood**]
--- # Globally Available Variable Map <br><br> ```cpp std::unordered_map<uint32_t, fims::Vector<Type>*> variable_map; ``` uint32_t: Parameter's unique ID<br> fims::Vector<Type>*: pointer to a FIMS vector <br><br> C++ map that stores pointers to all model parameters at the global scope --- # Globally Available Variable Map ```cpp std::unordered_map<uint32_t, fims::Vector<Type>*> variable_map; ``` .midi[ uint32_t: Parameter's unique ID<br> fims::Vector<Type>*: pointer to a FIMS vector <br> .pull-left[ **Rcpp Interface** <br>Populates the variable map with pointers to all model parameters, including random effects.<br><br> **Information** <br>Stores the variable map, which is accessible across all C++ files. <br><br> **Distributions** <br>Accesses the variable map to compute likelihood contributions. ]] --- # Globally Available Variable Map ```cpp std::unordered_map<uint32_t, fims::Vector<Type>*> variable_map; ``` .midi[ uint32_t: Parameter's unique ID<br> fims::Vector<Type>*: pointer to a FIMS vector <br> .pull-left[ **Rcpp Interface** <br>Populates the variable map with pointers to all model parameters, including random effects.<br><br> **Information** <br>Stores the variable map, which is accessible across all C++ files. <br><br> **Distributions** <br>Accesses the variable map to compute likelihood contributions. ] .pull-right[ **Prior Likelihood**<br> observed_value -> parameter <br><br> **Random Effects Likelihood**<br> observed_value -> random effect parameter<br> expected_value -> derived quantity (or vector of 0s) <br><br> **Data Likelihood**<br> observed_value -> data vector<br> expected_value -> derived quantity ]] --- # Connecting Random Effects to TMB ``` r recruitment$log_devs[i]$set_estimation_type("random_effects") ``` In the Rcpp Interface, if a parameter's estimation type is set to "random_effects": - The parameter gets added to the global `random_effects_parameters` vector. - An R function returns the list of random effects parameters for a model. - The random effects list is passed into FIMS.cpp through MakeADFun and is linked to the global `random_effects_parameters` vector. ``` r parameters <- list( p = get_fixed(), re = get_random() ) obj <- TMB::MakeADFun( data = list(), parameters, DLL = "FIMS", silent = TRUE, map = map, random = "re" ) ``` --- # Default FIMS Model ``` r library(FIMS) # Prepare the package data for being used in a FIMS model data("data_big") data_4_model <- FIMSFrame(data_big) # Set up the default configurations and parameters for the model default_configurations <- create_default_configurations(data = data_4_model) default_parameters <- create_default_parameters( configurations = default_configurations, data = data_4_model ) # run the model fit <- default_parameters |> initialize_fims(data = data_4_model) |> fit_fims(optimize = TRUE) ``` --- # Recruitment Deviations are random effects by default ``` r default_parameters |> tidyr::unnest(cols = data) |> dplyr::filter( label == "log_devs" ) ``` ``` ## # A tibble: 29 × 12 ## model_family module_name fleet_name module_type label age length time value estimation_type distribution_type distribution ## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr> ## 1 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 2 0 random_effects process Dnorm ## 2 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 3 0 random_effects process Dnorm ## 3 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 4 0 random_effects process Dnorm ## 4 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 5 0 random_effects process Dnorm ## 5 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 6 0 random_effects process Dnorm ## 6 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 7 0 random_effects process Dnorm ## 7 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 8 0 random_effects process Dnorm ## 8 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 9 0 random_effects process Dnorm ## 9 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 10 0 random_effects process Dnorm ## 10 catch_at_age Recruitment <NA> BevertonHolt log_devs NA NA 11 0 random_effects process Dnorm ## # ℹ 19 more rows ``` --- # Change to fixed effect Estimate with constrained optimization (penalized likelihood) ``` r # set estimation_type of log_devs to fixed_effects default_parameters <- default_parameters |> tidyr::unnest(cols = data) |> dplyr::mutate( estimation_type = ifelse(label == "log_devs", "fixed_effects", estimation_type) ) # fix Recruitment Distribution log_sd to "constant" default_parameters <- default_parameters |> dplyr::mutate( estimation_type = ifelse(label == "log_sd" & module_name == "Recruitment", "constant", estimation_type) ) ``` --- # Upcoming User Interface Enhancements New helper functions to set random effects for any process (GitHub Issue [#1235](https://github.com/NOAA-FIMS/FIMS/issues/1235)) <br> ``` r add_process( module_name = “selectivity”, fleet_name = “fleet1”, specification_type = “semi_parametric”, strucutre = “AR1”, base_function = “logistic”, logit_rho = 0) add_priors( fleet = c(fleet1, fleet2), module = selectivity, slope ~ normal(1.5, 10), inflection_point ~ normal(2, 10) ) ``` --- # Advanced User Implementation ``` r # Create a recruitment module recruitment <- new(BevertonHoltRecruitment) # Set the log_devs parameter to be estimated as random effects recruitment$log_devs$resize(n_years - 1) recruitment$log_devs$set_all_random(TRUE) # Set up a recruitment distribution module recruitment_distribution <- methods::new(DnormDistribution) recruitment_distribution$expected_values$resize(n_years - 1) recruitment_distribution$log_sd[1]$set_estimation_type("fixed_effects") # Link the observed values of the recruitment_distribution # to the log_devs parameter vector recruitment_distribution$set_distribution_links("process", recruitment$log_devs$get_id()) ``` 🚧 : The `set_distribution_links` function is being refactored in GitHub Issue [#1194](https://github.com/NOAA-FIMS/FIMS/issues/1194) --- # Sparse parameterization of recruitment $$ `\begin{align} log\_r &\sim Normal(\widehat{log\_r}, \sigma_{r})\\ \widehat{log\_r} &= log(BevertonHolt(SSB, parameters)) \end{align}` $$ ``` r # Create a recruitment module recruitment <- new(BevertonHoltRecruitment) # Set the log_devs parameter to be estimated as random effects recruitment$log_r$resize(n_years - 1) recruitment$log_r$set_all_random(TRUE) # Set up a recruitment distribution module recruitment_distribution <- methods::new(DnormDistribution) recruitment_distribution$log_sd[1]$set_estimation_type("fixed_effects") # Link the observed values of the recruitment_distribution # to the log_r parameter vector and log_expected_recruitment derived quantity recruitment_distribution$set_distribution_links("process", c(recruitment$log_r$get_id(), recruitment$log_expected_recruitment$get_id())) ``` --- # Hessian Inversion with the Cholesky Factorization To avoid the computational burden of a full matrix inversion, we use the Cholesky Factorization $$ H = LL^T\mathrm{~where~} L \mathrm{~is~a~lower~triangular~matrix} $$ .pull-left[.large[**Penalized Likelihood**] <br><br> .left[ - Uses the Cholesky factor, `\(L\)`, to calculate the Variance-Covariance matrix **once** at the MLE for uncertainty estimation ] ] .pull-right[.large[**Laplace Approximation**] <br> .left[ - Uses the Cholesky factor, `\(L\)`, to evaluate the Marginal Likelihood during **every iteration** of the optimizer ]] <br><br><br> **This factorization is efficient but its performance depends entirely on matrix structure** --- # The Scalability Bottleneck <br> .large[The ] `\(\Large{O(n^3)}\)` .large[ wall] <br> .large[ - Dense Cholesky factorization scales at `\(O(n^3)\)` <br><br> - In the Laplace Approximation this cost is paid at **every iteration** <br><br> - To move beyond moderate-sized models, we must exploit **Sparsity**] --- # Sparsity and TMB - Laplace Approximation requires the **Cholesky factorization** of the Hessian at **every iteration** <br><br> - Template Model Builder (TMB) makes this fast by taking advantage of **sparsity** <br><br> - After building the **static** computational graph, TMB **automatically detects** sparseness and **optimizes** the tape by removing operations involving constants of `\(0\)` <br><br> - When evaluating the Laplace Approximation, TMB uses the **sparse Cholesky factorization**, which eliminates all `\(0\)` calculations <br><br><br> `\(O(n^3)\)`: Dense Hessian <br> `\(O(n^{3/2})\)`: Sparse Spatial Hessian (GMRF / 2D Grid) `\(O(n)\)`: Sparse Time Series Hessian (AR1, RW / 1D) --- # Parameterization affects sparsity of Hessian .mylarge[ **AR1 model:** `\(x_t = \phi x_{t-1} + w_t\)`] .pull-left[ **Process Parameterization** `\(x_t \sim N(\phi x_{t-1}, \sigma^{2}_{x})\)` <br> <img src="static/ar1hess_process.jpg" alt="Hessian under the Process Parameterization" width="40%" /> Sparse Hessian, Cholesky factorization: `\(O(n)\)` ] .pull-right[ ** Deviations Parameterization** .narrowtopbottommargin[ `\(x_{t} = \phi x_{t-1} + \sigma_x w_{t}\)`<br> `\(w_{t} \sim N(0,1)\)`] <img src="static/ar1hess_deviations.jpg" alt="Hessian under the Deviations Parameterization" width="39%" /> .normal[ Dense Hessian, Cholesky factorization: `\(O(n^3)\)`] ] `\(w_t\)` is white noise and `\(\phi \neq 0\)` for an order-_1_ process --- # Benchmarking AR1 .pull-left[ Speed<br> <img src="static/ar1_benchmark.png" alt="Benchmarking the speed of AR1 Parameterizations" width="100%" /> ] .pull-right[ Memory<br> <img src="static/ar1_memory.png" alt="Benchmarking the memory of AR1 Parameterizations" width="100%" /> ] --- # Applications in Stock Assessments .pull-left[ **Case Study: Catch-at-age Model** - Models fit using a modified **babySAM** in RTMB <br><br> - Recruitment, Numbers at Age, and Fishing Mortality are AR1 processes <br><br> - **Focus:** Comparing parameterizations in Recruitment ] .pull-right[ **Parameterizing Recruitment**<br> **Process:** `\begin{aligned} log(\widehat{N_{y,1}}) &= log(N_{y-1,1})\\ log(N_{y,1}) &\sim Normal(\bar{r} + \phi(log(\widehat{N_{y,1}}) - \bar{r}), \sigma^{2}_{r}) \end{aligned}` **Deviations:** .narrowtopmargin[ `\begin{aligned} z_{1} &\sim Normal\big(0, \sqrt{\sigma_{r}^{2}/(1 - \phi^2)}\big)\\ z_{2:n} &\sim Normal(0, \sigma_{r})\\ dev_{1} &= z_{1}\\ dev_{y} &= \phi dev_{y-1} + z_{y}\\ log(N_{y,1}) &= \bar{r} + dev_{y} \end{aligned}` ]] --- # Speed and Memory Test Results - Relative results from 100 interactions using `bench::mark` - Model fit `\(n=45\)` years of recruitment <br> |Intercept|Model |Relative time difference |Relative memory allocation | |---------|------------|-------------------------|----------------------------| |Y |**process** |**1** |**1** | |N |**process** |**1** |**1** | |Y |deviations |1.4 |1.4 | |N |deviations |1.6 |1.4 | <br> At these sample sizes, the ~**1.5x speedup** reflects the reduction in the recruitment block's computational cost. The true `\(O(n^3)\)` "wall" becomes more visible as model scales up in complexity --- # Moving average (MA) models **MA1 model:** `\(x_t = w_t + \theta w_{t-1}\)` .pull-left[ **Process Parameterization** `\(w_{t} = x_{t} - \theta w_{t-1}\)`<br> `\(w_t \sim N(0, \sigma_{x})\)`<br> `\(y_{t} \sim N(x_{t}, \sigma_{y})\)`<br> where `\(w\)` and `\(x\)` are treated as random <img src="static/ma1hess_process.jpeg" alt="Hessian under the Process Parameterization" width="40%" /> ] .pull-right[ ** Deviations Parameterization** `\(w_t \sim N(0, \sigma_{w})\)`<br> `\(x_{t} = w_{t} + \theta w_{t-1}\)`<br> `\(y_{t} \sim N(x_{t}, \sigma_{y})\)`<br> where `\(w\)` is treated as random <img src="static/ma1hess_deviations.jpeg" alt="Hessian under the Deviations Parameterization" width="40%" /> ] --- # Next Generation Models <br> .large[ - **Recommendation**: - **AR1**: process approach - **MA1**: deviations approach <br><br> - No universal best parameterization but **optimal sparsity** depends on model structure <br><br> - For next gen TMB models, **good practices in efficiency and inference** should be evaluated through the lens of the Laplace Approximation ] --- # Future FIMS RE Development Goals <br> .large[ - Add a generic **ARn** distribution (Issue [#582](https://github.com/NOAA-FIMS/FIMS/issues/582)) <br><br> - Add multivariate functionality - **GMRF** distribtion (Issue [#582](https://github.com/NOAA-FIMS/FIMS/issues/582)) - Ability to set **multivariate pointers** <br><br> - Fully develop **non-parametric** and **semi-parametric** functionality for all time-varying parameters - Age-based selectivity Issue [#1206](https://github.com/NOAA-FIMS/FIMS/issues/1206) <br><br> - Refactor **Numbers At Age** with random effect capability <br><br> - Add OSA residuals (Issue [#546](https://github.com/NOAA-FIMS/FIMS/issues/546)) ] --- # Summary <br> .large[ - FIMS Random Effects are designed to be **generic**, **flexible**, and **extensible** <br><br> - FIMS keep the biological math **separate** from the statistical likelihood <br><br> - The global **variable map** allows any **parameter** or **derived quantity** to be linked to random effect distributions <br><br> - The current **default model** fits recruitment deviations as **random effects** with a Normal iid distribution ] --- # 👋 Thank you! <div style="text-align: center;"> <img src="static/FIMS_hexlogo.png" style="width: 100%; max-width: 300px;" /> </div> 📩 andrea.havron@noaa.gov