From a74d1866e3810309321236d923eb212833a80d34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:48:08 +0000 Subject: [PATCH 1/7] Initial plan From e24c1170418f62d915c67c2c1c9fc7db4f067519 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:51:41 +0000 Subject: [PATCH 2/7] refactor: use plain scalars in remaining interfaces --- .../rcpp/rcpp_objects/rcpp_distribution.hpp | 26 +++++----- .../rcpp/rcpp_objects/rcpp_fleet.hpp | 50 +++++++++---------- .../rcpp/rcpp_objects/rcpp_growth.hpp | 12 ++--- .../rcpp/rcpp_objects/rcpp_recruitment.hpp | 12 ++--- .../helper-integration-tests-setup-function.R | 4 +- tests/testthat/test-get_fixed.R | 2 +- tests/testthat/test-get_random.R | 2 +- ...tegration-fims-bayesian-prior-predictive.R | 4 +- tests/testthat/test-projections-looped.R | 4 +- tests/testthat/test-rcpp-ewaa.R | 6 +-- tests/testthat/test-rcpp-fleet-interface.R | 10 ++++ .../test-rcpp-recruitment-interface.R | 3 ++ 12 files changed, 74 insertions(+), 61 deletions(-) diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp index 6822530f5..f88009ce5 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp @@ -33,7 +33,7 @@ class DistributionsInterfaceBase : public FIMSRcppInterfaceBase { /** * @brief The type of density input. The options are prior, re, or data. */ - SharedString input_type_m; + std::string input_type_m; /** * @brief Control flag indicating whether to use the expected mean in the * distribution calculations. @@ -57,7 +57,7 @@ class DistributionsInterfaceBase : public FIMSRcppInterfaceBase { * @see DensityComponentBase::get_expected() in density_components_base.hpp * for the implementation that checks this flag. */ - SharedString use_mean_m = fims::to_string("no"); + std::string use_mean_m = "no"; /** * @brief The map associating the ID of the DistributionsInterfaceBase to the DistributionsInterfaceBase objects. This is a live object, which is an @@ -68,7 +68,7 @@ class DistributionsInterfaceBase : public FIMSRcppInterfaceBase { /** * @brief The ID of the observed data object, which is set to -999. */ - SharedInt interface_observed_data_id_m = -999; + int interface_observed_data_id_m = -999; /** * @brief The log probability density function value. @@ -230,7 +230,7 @@ class DnormDistributionsInterface : public DistributionsInterfaceBase { * @param observed_data_id Unique ID for the observed data object. */ virtual bool set_observed_data(int observed_data_id) { - this->interface_observed_data_id_m.set(observed_data_id); + this->interface_observed_data_id_m = observed_data_id; return true; } @@ -240,7 +240,7 @@ class DnormDistributionsInterface : public DistributionsInterfaceBase { virtual bool set_distribution_mean(double input_value) { this->expected_mean[0].initial_value_m = input_value; this->expected_mean[0].estimation_type_m.set("fixed_effects"); - this->use_mean_m.set(fims::to_string("yes")); + this->use_mean_m = "yes"; return true; } @@ -249,7 +249,7 @@ class DnormDistributionsInterface : public DistributionsInterfaceBase { */ virtual bool set_distribution_links(std::string input_type, Rcpp::IntegerVector ids) { - this->input_type_m.set(input_type); + this->input_type_m = input_type; this->key_m->resize(ids.size()); for (R_xlen_t i = 0; i < ids.size(); i++) { this->key_m->at(i) = ids[i]; @@ -486,7 +486,7 @@ class DnormDistributionsInterface : public DistributionsInterfaceBase { } info->variable_map[this->log_sd.id_m] = &(distribution)->log_sd; - distribution->use_mean = this->use_mean_m.get(); + distribution->use_mean = this->use_mean_m; distribution->expected_mean.resize(this->expected_mean.size()); for (size_t i = 0; i < this->expected_mean.size(); i++) { distribution->expected_mean[i] = this->expected_mean[i].initial_value_m; @@ -590,7 +590,7 @@ class DlnormDistributionsInterface : public DistributionsInterfaceBase { * @param observed_data_id Unique ID for the observed data object. */ virtual bool set_observed_data(int observed_data_id) { - this->interface_observed_data_id_m.set(observed_data_id); + this->interface_observed_data_id_m = observed_data_id; return true; } @@ -604,7 +604,7 @@ class DlnormDistributionsInterface : public DistributionsInterfaceBase { */ virtual bool set_distribution_links(std::string input_type, Rcpp::IntegerVector ids) { - this->input_type_m.set(input_type); + this->input_type_m = input_type; this->key_m->resize(ids.size()); for (R_xlen_t i = 0; i < ids.size(); i++) { this->key_m->at(i) = ids[i]; @@ -875,7 +875,7 @@ class DmultinomDistributionsInterface : public DistributionsInterfaceBase { * @brief TODO: document this. * */ - SharedString notes; + std::string notes; /** * @brief The constructor. @@ -915,7 +915,7 @@ class DmultinomDistributionsInterface : public DistributionsInterfaceBase { * @param observed_data_id Unique ID for the observed data object. */ virtual bool set_observed_data(int observed_data_id) { - this->interface_observed_data_id_m.set(observed_data_id); + this->interface_observed_data_id_m = observed_data_id; return true; } @@ -929,7 +929,7 @@ class DmultinomDistributionsInterface : public DistributionsInterfaceBase { */ virtual bool set_distribution_links(std::string input_type, Rcpp::IntegerVector ids) { - this->input_type_m.set(input_type); + this->input_type_m = input_type; this->key_m->resize(ids.size()); for (R_xlen_t i = 0; i < ids.size(); i++) { this->key_m->at(i) = ids[i]; @@ -942,7 +942,7 @@ class DmultinomDistributionsInterface : public DistributionsInterfaceBase { * * @param note */ - void set_note(std::string note) { this->notes.set(note); } + void set_note(std::string note) { this->notes = note; } /** * @brief diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp index c52d09556..dae52d42c 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp @@ -69,29 +69,29 @@ class FleetInterface : public FleetInterfaceBase { /** * @brief The ID of the observed age-composition data object. */ - SharedInt interface_observed_agecomp_data_id_m = -999; + int interface_observed_agecomp_data_id_m = -999; /** * @brief The ID of the observed length-composition data object. */ - SharedInt interface_observed_lengthcomp_data_id_m = -999; + int interface_observed_lengthcomp_data_id_m = -999; /** * @brief The ID of the observed index data object. */ - SharedInt interface_observed_index_data_id_m = -999; + int interface_observed_index_data_id_m = -999; /** * @brief The ID of the observed landings data object. */ - SharedInt interface_observed_landings_data_id_m = -999; + int interface_observed_landings_data_id_m = -999; /** * @brief The ID of the selectivity object. */ - SharedInt interface_selectivity_id_m = -999; + int interface_selectivity_id_m = -999; public: /** * @brief The name of the fleet. */ - SharedString name = fims::to_string("NA"); + std::string name = "NA"; /** * @brief The number of age bins in the fleet data. */ @@ -108,12 +108,12 @@ class FleetInterface : public FleetInterfaceBase { * @brief What units are the observed landings for this fleet measured in. * Options are weight or numbers, default is weight. */ - SharedString observed_landings_units = fims::to_string("weight"); + std::string observed_landings_units = "weight"; /** * @brief What units is the observed index of abundance for this fleet * measured in. Options are weight or numbers, default is weight. */ - SharedString observed_index_units = fims::to_string("weight"); + std::string observed_index_units = "weight"; /** * @brief The natural log of the index of abundance scaling parameter * for this fleet. @@ -282,20 +282,20 @@ class FleetInterface : public FleetInterfaceBase { * @brief Sets the name of the fleet. * @param name The name to set. */ - void SetName(const std::string &name) { this->name.set(name); } + void SetName(const std::string &name) { this->name = name; } /** * @brief Gets the name of the fleet. * @return The name. */ - std::string GetName() const { return this->name.get(); } + std::string GetName() const { return this->name; } /** * @brief Set the unique ID for the observed age-composition data object. * @param observed_agecomp_data_id Unique ID for the observed data object. */ void SetObservedAgeCompDataID(int observed_agecomp_data_id) { - interface_observed_agecomp_data_id_m.set(observed_agecomp_data_id); + interface_observed_agecomp_data_id_m = observed_agecomp_data_id; } /** @@ -303,7 +303,7 @@ class FleetInterface : public FleetInterfaceBase { * @param observed_lengthcomp_data_id Unique ID for the observed data object. */ void SetObservedLengthCompDataID(int observed_lengthcomp_data_id) { - interface_observed_lengthcomp_data_id_m.set(observed_lengthcomp_data_id); + interface_observed_lengthcomp_data_id_m = observed_lengthcomp_data_id; } /** @@ -311,7 +311,7 @@ class FleetInterface : public FleetInterfaceBase { * @param observed_index_data_id Unique ID for the observed data object. */ void SetObservedIndexDataID(int observed_index_data_id) { - interface_observed_index_data_id_m.set(observed_index_data_id); + interface_observed_index_data_id_m = observed_index_data_id; } /** @@ -319,14 +319,14 @@ class FleetInterface : public FleetInterfaceBase { * @param observed_landings_data_id Unique ID for the observed data object. */ void SetObservedLandingsDataID(int observed_landings_data_id) { - interface_observed_landings_data_id_m.set(observed_landings_data_id); + interface_observed_landings_data_id_m = observed_landings_data_id; } /** * @brief Set the unique ID for the selectivity object. * @param selectivity_id Unique ID for the observed object. */ void SetSelectivityID(int selectivity_id) { - interface_selectivity_id_m.set(selectivity_id); + interface_selectivity_id_m = selectivity_id; } /** @@ -334,13 +334,13 @@ class FleetInterface : public FleetInterfaceBase { * * @return uint32_t */ - uint32_t GetSelectivityID() { return interface_selectivity_id_m.get(); } + int GetSelectivityID() { return interface_selectivity_id_m; } /** * @brief Get the unique ID for the observed age-composition data object. */ int GetObservedAgeCompDataID() { - return interface_observed_agecomp_data_id_m.get(); + return interface_observed_agecomp_data_id_m; } /** @@ -348,21 +348,21 @@ class FleetInterface : public FleetInterfaceBase { * object. */ int GetObservedLengthCompDataID() { - return interface_observed_lengthcomp_data_id_m.get(); + return interface_observed_lengthcomp_data_id_m; } /** * @brief Get the unique id for the observed index data object. */ int GetObservedIndexDataID() { - return interface_observed_index_data_id_m.get(); + return interface_observed_index_data_id_m; } /** * @brief Get the unique id for the observed landings data object. */ int GetObservedLandingsDataID() { - return interface_observed_landings_data_id_m.get(); + return interface_observed_landings_data_id_m; } /** * @brief Extracts the derived quantities from `Information` to the Rcpp @@ -442,17 +442,17 @@ class FleetInterface : public FleetInterfaceBase { fleet->observed_index_units = this->observed_index_units; fleet->fleet_observed_agecomp_data_id_m = - interface_observed_agecomp_data_id_m.get(); + interface_observed_agecomp_data_id_m; fleet->fleet_observed_lengthcomp_data_id_m = - interface_observed_lengthcomp_data_id_m.get(); + interface_observed_lengthcomp_data_id_m; fleet->fleet_observed_index_data_id_m = - interface_observed_index_data_id_m.get(); + interface_observed_index_data_id_m; fleet->fleet_observed_landings_data_id_m = - interface_observed_landings_data_id_m.get(); + interface_observed_landings_data_id_m; - fleet->fleet_selectivity_id_m = interface_selectivity_id_m.get(); + fleet->fleet_selectivity_id_m = interface_selectivity_id_m; fleet->log_q.resize(this->log_q.size()); for (size_t i = 0; i < this->log_q.size(); i++) { diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_growth.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_growth.hpp index 87b89018a..fe464d6fb 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_growth.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_growth.hpp @@ -87,7 +87,7 @@ class EWAAGrowthInterface : public GrowthInterfaceBase { * @brief An integer specifying the number of years. * */ - SharedInt n_years; + int n_years = 0; /** * @brief A map of empirical weight-at-age values allowing multiple modules to * access and modify the weights without copying values between modules. @@ -142,13 +142,13 @@ class EWAAGrowthInterface : public GrowthInterfaceBase { */ inline std::map> make_map(RealVector ages, RealVector weights, - SharedInt n_years) { + int n_years) { std::map> mymap; - const size_t n_years_plus_one = static_cast(n_years.get() + 1); + const size_t n_years_plus_one = static_cast(n_years + 1); // Reject invalid year counts because map keys are expected to include // at least one model year. - if (n_years.get() < 1) { + if (n_years < 1) { Rcpp::stop("EWAA Error:: n_years must be at least 1"); } @@ -169,7 +169,7 @@ class EWAAGrowthInterface : public GrowthInterfaceBase { "calculations. weights size: " + std::to_string(weights.size()) + " ages size: " + std::to_string(ages.size()) + - " n_years: " + std::to_string(n_years.get())); + " n_years: " + std::to_string(n_years)); } else if (weights.size() == ages.size()) { // One age-specific vector was provided, so replicate the same // weight-at-age values for every year key (0 through n_years). @@ -229,7 +229,7 @@ class EWAAGrowthInterface : public GrowthInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [\"n_years+1\",\"n_ages\"],\n"; - ss << " \"dimensions\": [" << this->n_years.get() + 1 << "," + ss << " \"dimensions\": [" << this->n_years + 1 << "," << this->ages.size() << "]\n},\n"; ss << " \"values\": [\n"; diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_recruitment.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_recruitment.hpp index 6f7da9ae5..4fb9bf0f8 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_recruitment.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_recruitment.hpp @@ -30,7 +30,7 @@ class RecruitmentInterfaceBase : public FIMSRcppInterfaceBase { /** * @brief The process id of the RecruitmentInterfaceBase object. */ - SharedInt process_id = -999; + int process_id = -999; /** * @brief The map associating the IDs of RecruitmentInterfaceBase to the * objects. This is a live object, which is an object that has been created @@ -87,7 +87,7 @@ class BevertonHoltRecruitmentInterface : public RecruitmentInterfaceBase { /** * @brief The number of years. */ - SharedInt n_years; + int n_years = 0; /** * @brief The logistic transformation of steepness (h; productivity of the * population), where the parameter is transformed to constrain it between @@ -167,7 +167,7 @@ class BevertonHoltRecruitmentInterface : public RecruitmentInterfaceBase { * @param process_id Unique ID for the recruitment process object. */ void SetRecruitmentProcessID(uint32_t process_id) { - this->process_id.set(process_id); + this->process_id = process_id; } /** @@ -319,7 +319,7 @@ class BevertonHoltRecruitmentInterface : public RecruitmentInterfaceBase { // set relative info recruitment->id = this->id; - recruitment->process_id = this->process_id.get(); + recruitment->process_id = this->process_id; // set logit_steep recruitment->logit_steep.resize(this->logit_steep.size()); for (size_t i = 0; i < this->logit_steep.size(); i++) { @@ -407,8 +407,8 @@ class BevertonHoltRecruitmentInterface : public RecruitmentInterfaceBase { info->variable_map[this->log_r.id_m] = &(recruitment)->log_r; // set log_expected_recruitment - recruitment->log_expected_recruitment.resize(this->n_years.get() - 1); - for (size_t i = 0; i < static_cast(this->n_years.get() - 1); i++) { + recruitment->log_expected_recruitment.resize(this->n_years - 1); + for (size_t i = 0; i < static_cast(this->n_years - 1); i++) { recruitment->log_expected_recruitment[i] = 0; } info->variable_map[this->log_expected_recruitment.id_m] = diff --git a/tests/testthat/helper-integration-tests-setup-function.R b/tests/testthat/helper-integration-tests-setup-function.R index 298f49e45..345030b8c 100644 --- a/tests/testthat/helper-integration-tests-setup-function.R +++ b/tests/testthat/helper-integration-tests-setup-function.R @@ -281,7 +281,7 @@ setup_and_run_FIMS_without_wrappers <- function(iter_id, # set up logit_steep recruitment$logit_steep[1]$value <- -log(1.0 - om_input[["h"]]) + log(om_input[["h"]] - 0.2) recruitment$logit_steep[1]$estimation_type$set("constant") - recruitment$n_years$set(om_input[["nyr"]]) + recruitment$n_years <- om_input[["nyr"]] # turn on estimation of deviations # recruit deviations should enter the model in normal space. @@ -367,7 +367,7 @@ setup_and_run_FIMS_without_wrappers <- function(iter_id, # Growth ewaa_growth <- methods::new(EWAAGrowth) - ewaa_growth$n_years$set(om_input[["nyr"]]) + ewaa_growth$n_years <- om_input[["nyr"]] ewaa_growth$ages[] <- om_input[["ages"]] ewaa_growth$weights[] <- c(t(om_input[["W.mt"]])) diff --git a/tests/testthat/test-get_fixed.R b/tests/testthat/test-get_fixed.R index 5ef947401..96793697d 100644 --- a/tests/testthat/test-get_fixed.R +++ b/tests/testthat/test-get_fixed.R @@ -77,7 +77,7 @@ test_that("`get_fixed()` works with correct inputs", { recruitment$log_rzero[1]$value <- log(r0) recruitment$log_rzero[1]$estimation_type$set("fixed_effects") # recruitment$n_years needs to be greater than 0 to avoid a CreateTMBModel() error - recruitment$n_years$set(1) + recruitment$n_years <- 1 rec_parm <- c(-log(1.0 - h) + log(h - 0.2), log(r0)) CreateTMBModel() diff --git a/tests/testthat/test-get_random.R b/tests/testthat/test-get_random.R index 5e68a0afe..e492450c4 100644 --- a/tests/testthat/test-get_random.R +++ b/tests/testthat/test-get_random.R @@ -77,7 +77,7 @@ test_that("`get_random()` works with correct inputs", { recruitment$log_rzero[1]$value <- log(r0) recruitment$log_rzero[1]$estimation_type$set("random_effects") # recruitment$n_years needs to be greater than 0 to avoid a CreateTMBModel() error - recruitment$n_years$set(1) + recruitment$n_years <- 1 rec_parm <- c(-log(1.0 - h) + log(h - 0.2), log(r0)) CreateTMBModel() diff --git a/tests/testthat/test-integration-fims-bayesian-prior-predictive.R b/tests/testthat/test-integration-fims-bayesian-prior-predictive.R index fd7d4e49f..2368c13f6 100644 --- a/tests/testthat/test-integration-fims-bayesian-prior-predictive.R +++ b/tests/testthat/test-integration-fims-bayesian-prior-predictive.R @@ -160,12 +160,12 @@ test_that("posterior equals prior with no data", { for (y in 1:(om_input[["nyr"]] - 1)) { recruitment$log_devs[y]$value <- om_input[["logR.resid"]][y + 1] } - recruitment$n_years$set(om_input[["nyr"]]) + recruitment$n_years <- om_input[["nyr"]] recruitment$SetRecruitmentProcessID(recruitment_process$get_id()) # Growth ewaa_growth <- methods::new(EWAAGrowth) - ewaa_growth$n_years$set(om_input[["nyr"]]) + ewaa_growth$n_years <- om_input[["nyr"]] ewaa_growth$ages[] <- om_input[["ages"]] ewaa_growth$weights[] <- c(t(om_input[["W.mt"]])) diff --git a/tests/testthat/test-projections-looped.R b/tests/testthat/test-projections-looped.R index 3210dc7f7..46f0bf3c7 100644 --- a/tests/testthat/test-projections-looped.R +++ b/tests/testthat/test-projections-looped.R @@ -259,7 +259,7 @@ run_FIMS_projection_scenario <- function(om_input, # set up logit_steep recruitment$logit_steep[1]$value <- (-log(1.0 - om_input[["h"]]) + log(om_input[["h"]] - 0.2)) recruitment$logit_steep[1]$estimation_type$set("constant") - recruitment$n_years$set(om_input[["nyr"]] + n_projection_years) + recruitment$n_years <- om_input[["nyr"]] + n_projection_years # turn on estimation of deviations # recruit deviations should enter the model in normal space. @@ -306,7 +306,7 @@ run_FIMS_projection_scenario <- function(om_input, # Growth ewaa_growth <- methods::new(EWAAGrowth) - ewaa_growth$n_years$set(om_input[["nyr"]] + n_projection_years) + ewaa_growth$n_years <- om_input[["nyr"]] + n_projection_years ewaa_growth$ages[] <- om_input[["ages"]] ewaa_growth$weights[] <- om_input[["W.mt"]] diff --git a/tests/testthat/test-rcpp-ewaa.R b/tests/testthat/test-rcpp-ewaa.R index 16c791dfd..8893c936a 100644 --- a/tests/testthat/test-rcpp-ewaa.R +++ b/tests/testthat/test-rcpp-ewaa.R @@ -17,7 +17,7 @@ on.exit(rm(fims_frame), add = TRUE) # Initialize an EWAAGrowth object clear() ewaa_growth <- methods::new(EWAAGrowth) -ewaa_growth$n_years$set(get_n_years(fims_frame)) +ewaa_growth$n_years <- get_n_years(fims_frame) # Assign age data to the EWAAGrowth object ages <- get_ages(fims_frame) ewaa_growth$ages[] <- ages @@ -52,7 +52,7 @@ clear() test_that("EWAAGrowth evaluate() doesn't work when missing weights", { # Initialize an EWAAGrowth object ewaa_growth <- methods::new(EWAAGrowth) - ewaa_growth$n_years$set(get_n_years(fims_frame)) + ewaa_growth$n_years <- get_n_years(fims_frame) # Assign age data to the EWAAGrowth object ewaa_growth$ages[] <- ages @@ -66,7 +66,7 @@ test_that("EWAAGrowth evaluate() doesn't work when missing weights", { test_that("EWAAGrowth evaluate() returns expected error for mismatched input lengths", { # Initialize an EWAAGrowth object ewaa_growth <- methods::new(EWAAGrowth) - ewaa_growth$n_years$set(get_n_years(fims_frame)) + ewaa_growth$n_years <- get_n_years(fims_frame) # Assign age data and intentionally mismatch the length of ages and weights age_vector_long <- c(get_ages(fims_frame), 13) ewaa_growth$ages$resize(length(age_vector_long)) diff --git a/tests/testthat/test-rcpp-fleet-interface.R b/tests/testthat/test-rcpp-fleet-interface.R index f7228df6f..e6d5a0907 100644 --- a/tests/testthat/test-rcpp-fleet-interface.R +++ b/tests/testthat/test-rcpp-fleet-interface.R @@ -36,6 +36,16 @@ test_that("rcpp fleet works with correct inputs", { #' @description Test that getting the age-composition ID works within the fleet module. expect_equal(fleet1$GetObservedAgeCompDataID(), 1) + #' @description Test that setting and getting the fleet name continue to work when the scalar field is stored directly. + expect_silent(fleet1$SetName("fleet one")) + expect_equal(fleet1$GetName(), "fleet one") + + #' @description Test that observed units can be assigned directly through the Rcpp field bindings. + fleet1$observed_landings_units <- "numbers" + fleet1$observed_index_units <- "numbers" + expect_equal(fleet1$observed_landings_units, "numbers") + expect_equal(fleet1$observed_index_units, "numbers") + #' @description Test that setting the index ID works within the fleet module. expect_silent(fleet1$SetObservedIndexDataID(1)) #' @description Test that getting the index ID works within the fleet module. diff --git a/tests/testthat/test-rcpp-recruitment-interface.R b/tests/testthat/test-rcpp-recruitment-interface.R index 76cb91610..cd4ebd102 100644 --- a/tests/testthat/test-rcpp-recruitment-interface.R +++ b/tests/testthat/test-rcpp-recruitment-interface.R @@ -30,6 +30,9 @@ test_that("rcpp recruitment interface works with correct inputs", { object = recruitment$get_id(), expected = 1 ) + #' @description Test that the scalar recruitment year count can be assigned directly through the Rcpp field binding. + recruitment$n_years <- 4 + expect_equal(recruitment$n_years, 4) #' @description Test that the logit_steep value is 0.78845736. expect_equal( object = recruitment$logit_steep[1]$value, From 77fed61db2f81012d13edd466c0f142aa66b84d0 Mon Sep 17 00:00:00 2001 From: Andrea-Havron-NOAA Date: Wed, 15 Jul 2026 17:02:43 -0700 Subject: [PATCH 3/7] add design doc and example implementation using rcpp_population --- inst/design_docs/Refactor_S4_2_XPtr.Rmd | 699 ++++++++++++++++++ .../rcpp/rcpp_objects/rcpp_population.hpp | 102 +-- src/rcpp_population.cpp | 192 +++-- 3 files changed, 859 insertions(+), 134 deletions(-) create mode 100644 inst/design_docs/Refactor_S4_2_XPtr.Rmd diff --git a/inst/design_docs/Refactor_S4_2_XPtr.Rmd b/inst/design_docs/Refactor_S4_2_XPtr.Rmd new file mode 100644 index 000000000..48e1d01fc --- /dev/null +++ b/inst/design_docs/Refactor_S4_2_XPtr.Rmd @@ -0,0 +1,699 @@ +--- +title: "Design Document: FIMS Rcpp Interface Refactor to Rcpp::XPtr" +author: "FIMS Development Team" +date: "`r Sys.Date()`" +output: + html_document: + toc: true + toc_depth: 3 + theme: united +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +``` + +## 1. Motivation + +The current FIMS architecture relies on S4 Rcpp::Module classes to expose C++ interface +objects (such as `AgeCompDataInterface` or `PopulationInterface`) directly to R. This +design introduces two structural problems that compound each other. + +### 1.1 The Copy Problem + +When `methods::new(Population)` is called in R, Rcpp owns the C++ object's memory through +its own internal mechanism. A `std::shared_ptr` cannot safely be created pointing at a +Rcpp-managed object, because Rcpp controls its lifetime. However, `CreateTMBModel()` needs +to iterate over all interface objects to call `add_to_fims_tmb()` via a registry it owns. +The solution in the current design is to make a heap copy of every interface object in the +constructor: + +```cpp +// PopulationInterface constructor — current approach +PopulationInterface() : PopulationInterfaceBase() { + // ... + std::shared_ptr population = + std::make_shared(*this); // heap copy + FIMSRcppInterfaceBase::fims_interface_objects.push_back(population); + PopulationInterfaceBase::live_objects[this->id] = population; +} +``` + +Because these global static collections (variables that are shared across the program and persist for the lifetime of the program) maintain strong `std::shared_ptr` references to +every object created, their reference counts never drop to zero. Even when an R variable is +overwritten (e.g., `my_fleet <- NULL`), the underlying C++ memory is never deallocated +until an explicit manual call to `clear()`. This prevents clean iterative modeling workflows +(bootstrapping, sensitivity runs, profile likelihood) within a single R session. + +### 1.2 The SharedPrimitive Overhead Problem + +When users initialize a module (e.g. pop <- methods::new(Population)), a copy gets stored in the registry. When the user later sets +`pop$n_years <- 30` on the R-side S4 object, this modifies the original Rcpp-managed object +but not the registry copy. `CreateTMBModel()` would see stale initial values. + +The `SharedInt`, `SharedBoolean`, `SharedReal`, and `SharedString` wrapper classes were +introduced to solve this synchronization problem: by wrapping scalar fields in `shared_ptr`, +both the original and the registry copy share the same underlying value. Modifying the +original is automatically seen by the copy. + +However, this comes at a significant memory cost. Each `SharedPrimitive` field requires one +heap allocation for a single scalar. On a 64-bit system, `make_shared(0)` allocates +approximately 28 bytes (16-byte control block + 4-byte int + alignment padding) for 4 bytes +of actual data — a 7:1 overhead ratio. `PopulationInterface` alone has 8 `SharedInt`, 2 +`SharedBoolean`, and 1 `SharedString` configuration fields: roughly 11 heap allocations +before any model data is set. Additionally, every `Variable` element in every +`VariableVector` carries a `SharedString estimation_type_m`, adding one heap allocation per +parameter element across the entire model. + +The SharedPrimitive types and the constructor copies are symptoms of the same root cause: +the S4 module system forces a two-object design (Rcpp-owned original + heap registry copy) +that requires shared ownership of individual fields to stay synchronized. + +### 1.3 The Rcpp::XPtr Solution + +By transitioning the R-to-C++ boundary to `Rcpp::XPtr>`, both problems +are eliminated together. + +**One object, no copies.** The creator function allocates the C++ object on the heap +directly and hands ownership to a `shared_ptr`. The XPtr wraps that `shared_ptr`. There is +no Rcpp-managed original and no registry copy — there is one object. User modifications via +standalone setter functions go directly to that object. + +**SharedPrimitives become unnecessary.** Without a copy to keep synchronized with the +original, scalar fields can revert to plain C++ types (`int`, `bool`, `double`, +`std::string`). The `Variable::estimation_type_m` field can become an enum. + +**Automated lifecycle management.** When an R variable holding an XPtr is overwritten or +goes out of scope, R's garbage collector deletes the external pointer wrapper, decrementing +the `shared_ptr` reference count. Memory is freed without requiring an explicit `clear()` +call. + +--- + +## 2. Refactoring Steps + +### Step 1: Replace `SharedPrimitive` scalar fields with plain C++ types + +In every interface class, replace `SharedInt`, `SharedBoolean`, `SharedReal`, and +`SharedString` fields that hold scalar configuration values with their plain C++ equivalents. +This is only safe under XPtr because there is no registry copy that needs to remain +synchronized with user modifications. + +```cpp +// inst/include/interface/rcpp/rcpp_objects/rcpp_population.hpp + +// BEFORE — SharedPrimitive fields (one heap allocation each) +SharedInt n_ages = 0; +SharedInt n_fleets; +SharedInt n_years; +SharedInt n_lengths; +SharedInt maturity_id; +SharedInt growth_id; +SharedInt recruitment_id; +SharedInt recruitment_err_id; +SharedString name = fims::to_string("NA"); + +// AFTER — plain C++ types (no heap allocation per scalar) +int n_ages = 0; +int n_fleets = 0; +int n_years = 0; +int n_lengths = 0; +int maturity_id = -1; +int growth_id = -1; +int recruitment_id = -1; +int recruitment_err_id = -1; +std::string name = "NA"; +``` + +`VariableVector` retains its `shared_ptr>` internal storage because R-side +access to individual elements relies on shared ownership within the vector. Only standalone +scalar configuration fields change. + +`SharedBoolean` fields in base classes (`initialize_catch_at_age`, +`initialize_surplus_production`) follow the same pattern — replace with plain `bool`. + +### Step 2: Replace `Variable::estimation_type_m` with an enum + +Every `Variable` element in every `VariableVector` currently carries a +`SharedString estimation_type_m`, which heap-allocates one `std::string` per element. The +field always holds exactly one of three values. Replace it with an enum stored as a single +byte: + +```cpp +// inst/include/interface/rcpp/rcpp_objects/rcpp_interface_base.hpp + +// BEFORE — one heap allocation per Variable element +class Variable { + public: + SharedString estimation_type_m = SharedString("constant"); + // ... +}; + +// AFTER — zero heap allocations, 1 byte per Variable element +enum class EstimationType : uint8_t { + constant = 0, + fixed_effects = 1, + random_effects = 2 +}; + +class Variable { + public: + EstimationType estimation_type_m = EstimationType::constant; + // ... +}; +``` + +Update all callers that previously compared strings (e.g., +`estimation_type_m.get() == "fixed_effects"`) to use the enum values: + +```cpp +// BEFORE +if (this->log_M[i].estimation_type_m.get() == "fixed_effects") { ... } + +// AFTER +if (this->log_M[i].estimation_type_m == EstimationType::fixed_effects) { ... } +``` + +Update `VariableVector::set_estimation_types()` to accept an +`Rcpp::CharacterVector` and convert string input to enum internally, preserving the +existing R-side API. + +### Step 3: Remove constructor copies and implement standalone lifecycle functions + +Strip `live_objects` map insertions and `fims_interface_objects` push_back calls from all +interface class constructors. The constructor should perform only pure initialization: + +```cpp +// inst/include/interface/rcpp/rcpp_objects/rcpp_data.hpp + +// BEFORE — constructor registers in two global registries +AgeCompDataInterface(int = 0, int n_ages = 0) : DataInterfaceBase() { + this->n_ages = n_ages; + this-> = ; + this->age_comp_data.resize(n_ages * ); + this->uncertainty.resize(n_ages * ); + DataInterfaceBase::live_objects[this->id] = + std::make_shared(*this); + FIMSRcppInterfaceBase::fims_interface_objects.push_back( + DataInterfaceBase::live_objects[this->id]); +} + +// AFTER — pure initialization only +AgeCompDataInterface(int = 0, int n_ages = 0) : DataInterfaceBase() { + this->n_ages = n_ages; + this-> = ; + this->age_comp_data.resize(n_ages * ); + this->uncertainty.resize(n_ages * ); +} +``` + +Implement standalone creator functions in the corresponding `.cpp` source file. Wrap the +`shared_ptr` (not a raw pointer) inside the XPtr so that the reference count is safely +decremented by R's garbage collector when the XPtr is collected: + +```cpp +// src/rcpp_data.cpp + +using SharedAgeComp = std::shared_ptr; + +Rcpp::XPtr create_age_comp(int , int n_ages) { + auto obj = std::make_shared(, n_ages); + // Heap-allocate the shared_ptr itself and hand it to the XPtr. + // 'true' binds the XPtr to R's garbage collector: when the R variable + // is collected, 'delete shared_ptr*' is called, decrementing the ref count. + return Rcpp::XPtr(new SharedAgeComp(obj), true); +} +``` + +Implement standalone setter functions for each field that the user needs to set from R. +Because there is one object and no copy to synchronize, plain C++ types suffice: + +```cpp +void set_age_comp_values(Rcpp::XPtr xp, + Rcpp::NumericVector values) { + auto& data = (*xp)->age_comp_data; + data.resize(values.size()); + for (int i = 0; i < values.size(); ++i) { + data[i] = values[i]; + } +} +``` + +```cpp +void set_population_constants(Rcpp::XPtr xp, + int n_ages, + int n_years, + int n_lengths) { + (*xp)->n_ages = n_ages; + (*xp)->n_years = n_years; + (*xp)->n_lengths = n_lengths; +} +``` +```cpp +void set_population_parameters(Rcpp::XPtr xp, + Rcpp::NumericVector log_M_values, + Rcpp::NumericVector log_init_naa_values) { + // xp returns the Xptr to the shared pointer + // *xp returns the shared pointer to the PopulationInterface + // **xp returns the PopulationInterface object + PopulationInterface& pop = **xp; + + pop.log_M.resize(log_M_values.size()); + for (int i = 0; i < log_M_values.size(); i++) { + + Variable& v = pop.log_M.storage_m->at(i); + v.initial_value_m = log_M_values[i]; + v.estimation_type_m = EstimationType::constant; + } + + pop.log_init_naa.resize(log_init_naa_values.size()); + for (int i = 0; i < log_init_naa_values.size(); i++) { + Variable& v = pop.log_init_naa.storage_m->at(i); + v.initial_value_m = log_init_naa_values[i]; + v.estimation_type_m = EstimationType::fixed_effects; + } +} +``` + +Register these functions inside the existing module registration blocks. Do **not** use +`// [[Rcpp::exports]]`, as this causes essential TMB callables to be overwritten by +`Rcpp::compileAttributes()`. Add a trailing underscore to every registered name to prevent +name collisions with the R wrapper functions defined in Section 4b. Remove the +`Rcpp::class_` registration for any interface class that has been fully migrated to XPtr: + +```cpp +// src/rcpp_data.cpp + +void register_data(Rcpp::Module& m) { + // Rcpp::class_ removed — users no longer call methods::new() + Rcpp::function("create_age_comp_", &create_age_comp); + Rcpp::function("set_age_comp_values_", &set_age_comp_values); + Rcpp::function("set_population_constants_", &set_population_constants); + Rcpp::function("set_population_parameters_",&set_population_parameters); + // ... repeat for LengthComp, Index, Landings ... +} +``` + +### Step 4: Implement dispatch through a shared base class for `CreateTMBModel()` + +`CreateTMBModel()` currently iterates `FIMSRcppInterfaceBase::fims_interface_objects` to +call `add_to_fims_tmb()` on every registered object. Under XPtr, the C++ global registry +is removed. This step replaces it with two pieces: a C++ mechanism for calling +`add_to_fims_tmb()` through a common base class pointer, and an R-side registry that +automatically tracks objects as they are created so the user does not need to build the list +by hand. + +#### 4a. Why two XPtr types are needed + +Each concrete creator function (e.g., `create_age_comp()`, `create_population()`) returns +an XPtr typed to its specific class — `XPtr`, `XPtr`, etc. +These types are all different, so they cannot be stored together in a single list and passed +to `CreateTMBModel()`. + +The solution is a second, less specific XPtr typed to the common base class +`FIMSRcppInterfaceBase`, which all interface classes inherit from. Because +`add_to_fims_tmb()` is declared as a `virtual` method on that base class, calling it through +a base class pointer is sufficient — C++ automatically invokes the correct derived-class +implementation at runtime. This is what "dispatch through a shared base class" means: +`CreateTMBModel()` does not need to know whether it is looking at an `AgeCompDataInterface` +or a `PopulationInterface`; it just calls `add_to_fims_tmb()` and C++ figures out the rest. + +The two XPtr types point to the same underlying C++ object in memory. The concrete XPtr is +what the user works with (setting values, reading results). The base XPtr is only needed +by `CreateTMBModel()` to make that call. Both XPtrs share ownership of the object via +`shared_ptr`, so the object stays alive as long as either pointer exists. + +**Define the base alias** in `rcpp_interface_base.hpp`: + +```cpp +// inst/include/interface/rcpp/rcpp_objects/rcpp_interface_base.hpp +using SharedBase = std::shared_ptr; +``` + +**Provide a conversion function for each concrete type.** This function takes the +concrete XPtr the user already has and returns a second XPtr pointing to the same object +but typed as the base class. C++ allows this conversion implicitly because +`AgeCompDataInterface` inherits from `FIMSRcppInterfaceBase` — no special cast syntax +is needed: + +```cpp +// src/rcpp_data.cpp + +// create_age_comp_ — allocates a NEW AgeCompDataInterface object +// age_comp_to_fims_xptr_ — does NOT create a new object; returns a second pointer +// to the SAME object typed as the base class +Rcpp::XPtr age_comp_to_fims_xptr(Rcpp::XPtr xp) { + SharedBase base = *xp; // implicit conversion: shared_ptr -> shared_ptr + return Rcpp::XPtr(new SharedBase(base), true); +} +``` + +Register both functions inside the existing module registration block, again with trailing +underscores so the clean names remain available for the R wrappers in Section 4b: + +```cpp +void register_data(Rcpp::Module& m) { + Rcpp::function("create_age_comp_", &create_age_comp); + Rcpp::function("set_age_comp_values_", &set_age_comp_values); + Rcpp::function("age_comp_to_fims_xptr_", &age_comp_to_fims_xptr); + // ... repeat for LengthComp, Index, Landings ... +} +``` + +Register `CreateTMBModel` and `clear` the same way in their respective registration blocks: + +```cpp +Rcpp::function("CreateTMBModel_", &CreateTMBModel); +Rcpp::function("clear_", &clear_impl); +``` +``` + +**Update `CreateTMBModel()` in C++ to accept a list of base XPtrs** and call +`add_to_fims_tmb()` on each: + +```cpp +// inst/include/interface/rcpp/rcpp_interface.hpp + +bool CreateTMBModel(Rcpp::List xptr_list) { + init_logging(); + + std::shared_ptr> info0 = + fims_info::Information::GetInstance(); + info0->Clear(); + + std::shared_ptr> info = + fims_info::Information::GetInstance(); + info->Clear(); + + for (int i = 0; i < xptr_list.size(); i++) { + Rcpp::XPtr xp = + Rcpp::as>(xptr_list[i]); + (*xp)->add_to_fims_tmb(); // C++ calls the correct derived-class method + } + + info0->CreateModel(); + info0->CheckModel(); + info->CreateModel(); + + std::shared_ptr> m0 = + fims_model::Model::GetInstance(); + + return true; +} +``` + +#### 4b. R-side automatic registry (intermediate step before R6) + +Rather than requiring the user to manually build the list passed to `CreateTMBModel()` — +which risks silently omitting a component — an R-side package-level registry can be +maintained automatically. The registry is a simple list stored inside a private package +environment (not visible to the user). Thin R wrapper functions around each C++ creator +call both `create_()` and `_to_fims_xptr()` and add the result to the registry. +The user-facing `CreateTMBModel()` and `clear()` functions are also R wrappers that operate +on this registry. + +This approach is more reliable than scanning the R environment for XPtr objects, because +registration happens at the moment of creation — it is not affected by variable names, +scoping, or objects stored inside lists. + +```r +# R/fims_interface.R + +# Private package-level registry — not exported, not user-visible +.fims_registry <- new.env(parent = emptyenv()) +.fims_registry$objects <- list() + +# R wrapper around C++ create_age_comp_: +# creates the concrete XPtr, converts to base XPtr, registers it, returns concrete XPtr. +# The trailing underscore on the C++ names (create_age_comp_, age_comp_to_fims_xptr_) +# avoids collision with these R wrapper functions, which own the clean names. +create_age_comp <- function(n_years, n_ages) { + xp <- create_age_comp_(n_years, n_ages) # concrete XPtr — user works with this + base_xp <- age_comp_to_fims_xptr_(xp) # base XPtr — for CreateTMBModel() + .fims_registry$objects <- c(.fims_registry$objects, + list(base_xp)) # auto-register + xp # return concrete XPtr to user +} + +# Repeat for every module type: +# create_population(), create_fleet(), create_index(), etc. + +# R wrapper around C++ CreateTMBModel_ — uses the registry automatically +CreateTMBModel <- function() { + CreateTMBModel_(.fims_registry$objects) +} + +# clear() resets both the C++ TMB singletons and the R registry +clear <- function() { + clear_() + .fims_registry$objects <- list() +} +``` + +With this in place, the user workflow is identical to the current S4 workflow — create +objects, set values, call `CreateTMBModel()` — with no manual list management required. +The `clear()` function simplifies on the C++ side to resetting the TMB `Information` +singletons and `id_g` counters, with the R registry reset handling the bookkeeping that +`live_objects` and `fims_interface_objects` previously provided. + +#### 4c. Model description from the registry + +Because the R wrapper functions know the type and configuration of each object at the +moment of creation, a parallel metadata list can be stored alongside the base XPtrs at no +additional C++ cost. This metadata enables a `describe_model()` function that prints a +human-readable summary of the model the user has assembled — useful for verifying that all +components are present and correctly configured before calling `CreateTMBModel()`. + +Extend the registry environment with a `metadata` list, and populate it inside each R +wrapper function: + +```r +# R/fims_interface.R + +.fims_registry <- new.env(parent = emptyenv()) +.fims_registry$objects <- list() +.fims_registry$metadata <- list() + +create_age_comp <- function(n_years, n_ages) { + xp <- create_age_comp_(n_years, n_ages) + base_xp <- age_comp_to_fims_xptr_(xp) + .fims_registry$objects <- c(.fims_registry$objects, list(base_xp)) + .fims_registry$metadata <- c(.fims_registry$metadata, + list(list(type = "AgeComp", + id = get_id_(xp), + n_years = n_years, + n_ages = n_ages))) + xp +} + +create_population <- function() { + xp <- create_population_() + base_xp <- population_to_fims_xptr_(xp) + .fims_registry$objects <- c(.fims_registry$objects, list(base_xp)) + .fims_registry$metadata <- c(.fims_registry$metadata, + list(list(type = "Population", + id = get_id_(xp)))) + xp +} + +# Repeat the same pattern for every module type. + +# clear() resets both the C++ TMB singletons and the full R registry +clear <- function() { + clear_() + .fims_registry$objects <- list() + .fims_registry$metadata <- list() +} +``` + +`describe_model()` iterates the metadata list and prints a summary. Because this function +operates entirely on the R-side metadata, it requires no C++ changes: + +```r +describe_model <- function() { + if (length(.fims_registry$metadata) == 0) { + message("No model components have been registered.") + return(invisible(NULL)) + } + cat("Model components registered:\n") + cat(strrep("-", 40), "\n") + for (m in .fims_registry$metadata) { + # Build a string of any extra fields beyond type and id + extras <- m[!names(m) %in% c("type", "id")] + extra_str <- if (length(extras) > 0) { + paste(names(extras), unlist(extras), sep = " = ", collapse = ", ") + } else "" + cat(sprintf(" %-20s id: %d %s\n", m$type, m$id, extra_str)) + } + cat(strrep("-", 40), "\n") + cat(sprintf("Total: %d component(s)\n", length(.fims_registry$metadata))) + invisible(.fims_registry$metadata) +} +``` + +Example output after setting up a simple model: + +``` +Model components registered: +---------------------------------------- + AgeComp id: 1 n_years = 10, n_ages = 5 + Index id: 2 n_years = 10 + BevertonHoltRecruit id: 3 + LogisticSelectivity id: 4 + Fleet id: 5 + Population id: 6 +---------------------------------------- +Total: 6 component(s) +``` + +`describe_model()` is intended to be called interactively before `CreateTMBModel()` as a +sanity check. Additional metadata fields — such as linked component IDs (e.g., +`recruitment_id`, `selectivity_id`) or parameter dimensions — can be added to each wrapper +function without modifying any C++ code. + +--- + +## 3. Usage Example + +### 3.1 R Workflow with Automatic Registry + +With the R-side registry in place (Step 4b), the user workflow closely mirrors the current +S4 workflow. `CreateTMBModel()` is called with no arguments — the registry built up during +object creation is used automatically. Note that `$method()` syntax does not work directly +on raw XPtr objects; all field access goes through standalone setter functions. See +Section 3.2 for R6 wrappers that restore familiar `$` ergonomics. + +```{r eval=FALSE} +library(FIMS) + +# ==================== SETUP ==================== + +# 1. Create model components. +# Each R wrapper calls the C++ creator AND registers the object automatically. +# The concrete XPtr is returned for use in setter calls below. +age_comp <- create_age_comp(n_years = 10, n_ages = 5) +index <- create_index(n_years = 10) +pop <- create_population() +growth <- create_empirical_growth() + +# 2. Set values via standalone setter functions +set_age_comp_values(age_comp, my_age_comp_data) +set_index_values(index, my_index_data) +set_population_constants(pop, n_years = 10L, n_ages = 5L, n_length = 0L) +set_population_growth_id(pop, get_id(growth)) + +# 3. Build the TMB model — registry is passed automatically, no list needed +CreateTMBModel() + +# fit model... + + +# ==================== ITERATIVE WORKFLOW ==================== + +# 4. For a second scenario (e.g., a sensitivity run with different data), +# update values directly on the existing concrete XPtrs. +# The registry already holds base XPtrs pointing to the same underlying C++ objects, +# so any change made here is automatically visible when CreateTMBModel() runs again — +# no re-registration or clear() needed between scenarios. +set_age_comp_values(age_comp, new_age_comp_data) # update age composition data +set_index_values(index, new_index_data) # update index data +set_population_parameters(pop, # update parameter starting values + log_M_values = new_log_M, + log_init_naa_values = new_log_init_naa) + +# 5. Rebuild — CreateTMBModel() internally calls fims::Information::Clear() on both +# TMB singletons (double and TMBAD types) before re-populating from the registry, +# so the previous model run's state is fully reset. The R registry itself is NOT +# cleared, which is what allows the same objects to be reused across scenarios. +CreateTMBModel() + +# fit model... + + +# ==================== CLEANUP ==================== + +# 6. clear() resets the TMB singletons AND the R registry. +# Setting R variables to NULL releases the concrete XPtrs; +# shared_ptr ref counts drop to 0 on next GC and memory is freed. +clear() +age_comp_xp <- NULL +index_xp <- NULL +pop_xp <- NULL +gc() +``` + +### 3.2 R6 Wrapper Workflow - Future Development, will not be implemented in this issue + +R6 classes restore `$` ergonomics by wrapping both the concrete XPtr (for typed field +access) and the base XPtr (for `CreateTMBModel()` dispatch) inside a single user-facing +object: + +```{r eval=FALSE} +AgeComp <- R6::R6Class("AgeComp", + private = list( + .xp = NULL, + .base_xp = NULL + ), + public = list( + initialize = function(n_years, n_ages) { + private$.xp <- create_age_comp_(n_years, n_ages) + private$.base_xp <- age_comp_to_fims_xptr_(private$.xp) + }, + set_values = function(values) { + set_age_comp_values_(private$.xp, values) + }, + fims_xptr = function() private$.base_xp + ) +) + +Population <- R6::R6Class("Population", + private = list( + .xp = NULL, + .base_xp = NULL + ), + public = list( + initialize = function() { + private$.xp <- create_population_() + private$.base_xp <- population_to_fims_xptr_(private$.xp) + }, + set_n_years = function(n) set_population_n_years_(private$.xp, n), + set_n_ages = function(n) set_population_n_ages_(private$.xp, n), + fims_xptr = function() private$.base_xp + ) +) + +# User workflow +age_comp <- AgeComp$new(n_years = 10, n_ages = 5) +age_comp$set_values(my_age_comp_data) + +pop <- Population$new() +pop$set_n_years(10L) +pop$set_n_ages(5L) + +# Under R6, CreateTMBModel() can receive explicit base XPtrs from each object, +# or the R-side registry (Section 4b/4c) can be used here too — both patterns work. +CreateTMBModel(list(age_comp$fims_xptr(), pop$fims_xptr())) +``` + +--- + +## 4. Memory Impact + +The following table summarizes heap allocations eliminated per `PopulationInterface` +instance by the full refactor. The XPtr approach only realizes these savings when all four +steps are applied together; a partial migration that retains SharedPrimitives or constructor +copies will not produce a meaningful reduction. + +| Source | Before | After | +|---|---|---| +| `SharedInt` configuration fields (8) | 8 heap allocs | 0 | +| `SharedBoolean` fields (2 in base) | 2 heap allocs | 0 | +| `SharedString` field (1) | 1 heap alloc | 0 | +| Constructor registry copy | 1 `make_shared` | 0 | +| `Variable::estimation_type_m` per element | 1 heap alloc per `Variable` | 0 (enum) | +| `live_objects` map per class | 1 `std::map` + N entries | removed | +| `fims_interface_objects` global vector | 1 entry per object | removed | + +The `clear()` function simplifies substantially: instead of manually resetting every +class's `live_objects` map and `id_g` counter, it only needs to reset the TMB `Information` +singletons and the `id_g` counters — no per-class map iteration required. diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_population.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_population.hpp index 255af7bab..838b13bd8 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_population.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_population.hpp @@ -38,12 +38,12 @@ class PopulationInterfaceBase : public FIMSRcppInterfaceBase { * @brief Initialize the catch at age model. * */ - SharedBoolean initialize_catch_at_age; + bool initialize_catch_at_age = false; /** * @brief Initialize the surplus production model. * */ - SharedBoolean initialize_surplus_production; + bool initialize_surplus_production = false; /** * @brief The constructor. */ @@ -82,11 +82,11 @@ class PopulationInterface : public PopulationInterfaceBase { /** * @brief The number of age bins. */ - SharedInt n_ages = 0; + int n_ages = 0; /** * @brief The number of fleets. */ - SharedInt n_fleets; + int n_fleets = 0; /** * list of fleets that operate on this population. */ @@ -98,27 +98,27 @@ class PopulationInterface : public PopulationInterfaceBase { /** * @brief The number of years. */ - SharedInt n_years; + int n_years = 0; /** * @brief The number of length bins. */ - SharedInt n_lengths; + int n_lengths = 0; /** * @brief The ID of the maturity module. */ - SharedInt maturity_id; + int maturity_id = -999; /** * @brief The ID of the growth module. */ - SharedInt growth_id; + int growth_id = -999; /** * @brief The ID of the recruitment module. */ - SharedInt recruitment_id; + int recruitment_id = -999; /** * @brief The ID of the recruitment process module. */ - SharedInt recruitment_err_id; + int recruitment_err_id = -999; /** * @brief The natural log of the natural mortality for each year. */ @@ -152,7 +152,7 @@ class PopulationInterface : public PopulationInterfaceBase { /** * @brief The name for the population. */ - SharedString name = fims::to_string("NA"); + std::string name = "NA"; // Population based derived quantities /** @@ -234,50 +234,8 @@ class PopulationInterface : public PopulationInterfaceBase { this->proportion_female[0].initial_value_m = static_cast(0.5); this->proportion_female[0].estimation_type_m.set("constant"); this->fleet_ids = std::make_shared>(); - std::shared_ptr population = - std::make_shared(*this); - FIMSRcppInterfaceBase::fims_interface_objects.push_back(population); - PopulationInterfaceBase::live_objects[this->id] = population; } - /** - * @brief Construct a new Population Interface object - * - * @param other - */ - PopulationInterface(const PopulationInterface &other) - : PopulationInterfaceBase(other), - n_ages(other.n_ages), - n_fleets(other.n_fleets), - fleet_ids(other.fleet_ids), - n_years(other.n_years), - n_lengths(other.n_lengths), - maturity_id(other.maturity_id), - growth_id(other.growth_id), - recruitment_id(other.recruitment_id), - recruitment_err_id(other.recruitment_id), - log_M(other.log_M), - spawning_biomass_ratio(other.spawning_biomass_ratio), - log_f_multiplier(other.log_f_multiplier), - log_init_naa(other.log_init_naa), - proportion_female(other.proportion_female), - ages(other.ages), - name(other.name), - total_landings_weight(other.total_landings_weight), - total_landings_numbers(other.total_landings_numbers), - mortality_F(other.mortality_F), - mortality_M(other.mortality_M), - mortality_Z(other.mortality_Z), - numbers_at_age(other.numbers_at_age), - unfished_numbers_at_age(other.unfished_numbers_at_age), - biomass(other.biomass), - spawning_biomass(other.spawning_biomass), - unfished_biomass(other.unfished_biomass), - unfished_spawning_biomass(other.unfished_spawning_biomass), - proportion_mature_at_age(other.proportion_mature_at_age), - expected_recruitment(other.expected_recruitment), - sum_selectivity(other.sum_selectivity) {} - /** * @brief The destructor. */ @@ -293,34 +251,34 @@ class PopulationInterface : public PopulationInterfaceBase { * @brief Sets the name of the population. * @param name The name to set. */ - void SetName(const std::string &name) { this->name.set(name); } + void SetName(const std::string &name) { this->name = name; } /** * @brief Gets the name of the population. * @return The name. */ - std::string GetName() const { return this->name.get(); } + std::string GetName() const { return this->name; } /** * @brief Sets the unique ID for the Maturity object. * @param maturity_id Unique ID for the Maturity object. */ void SetMaturityID(uint32_t maturity_id) { - this->maturity_id.set(maturity_id); + this->maturity_id = maturity_id; } /** * @brief Set the unique ID for the growth object. * @param growth_id Unique ID for the growth object. */ - void SetGrowthID(uint32_t growth_id) { this->growth_id.set(growth_id); } + void SetGrowthID(uint32_t growth_id) { this->growth_id = growth_id; } /** * @brief Set the unique ID for the recruitment object. * @param recruitment_id Unique ID for the recruitment object. */ void SetRecruitmentID(uint32_t recruitment_id) { - this->recruitment_id.set(recruitment_id); + this->recruitment_id = recruitment_id; } /** @@ -409,13 +367,13 @@ class PopulationInterface : public PopulationInterfaceBase { // set relative info population->id = this->id; - population->n_years = this->n_years.get(); - population->n_fleets = this->n_fleets.get(); + population->n_years = this->n_years; + population->n_fleets = this->n_fleets; // only define ages if n_ages greater than 0 - if (this->n_ages.get() > 0) { - population->n_ages = this->n_ages.get(); - if (static_cast(this->n_ages.get()) == this->ages.size()) { - population->ages.resize(this->n_ages.get()); + if (this->n_ages > 0) { + population->n_ages = this->n_ages; + if (static_cast(this->n_ages) == this->ages.size()) { + population->ages.resize(this->n_ages); } else { throw std::invalid_argument( "The size of the ages vector for population " + @@ -428,19 +386,19 @@ class PopulationInterface : public PopulationInterfaceBase { population->fleet_ids.insert(*it); } - population->growth_id = this->growth_id.get(); - population->recruitment_id = this->recruitment_id.get(); - population->maturity_id = this->maturity_id.get(); + population->growth_id = this->growth_id; + population->recruitment_id = this->recruitment_id; + population->maturity_id = this->maturity_id; population->log_M.resize(this->log_M.size()); if (this->log_f_multiplier.size() == - static_cast(this->n_years.get())) { + static_cast(this->n_years)) { population->log_f_multiplier.resize(this->log_f_multiplier.size()); } else { FIMS_WARNING_LOG( "The log_f_multiplier vector is not of size n_years. Filling with " "zeros."); - this->log_f_multiplier.resize((this->n_years.get())); + this->log_f_multiplier.resize(this->n_years); for (size_t i = 0; i < log_f_multiplier.size(); i++) { this->log_f_multiplier[i].initial_value_m = static_cast(0.0); this->log_f_multiplier[i].estimation_type_m.set("constant"); @@ -449,13 +407,13 @@ class PopulationInterface : public PopulationInterfaceBase { } if (this->spawning_biomass_ratio.size() == - static_cast(this->n_years.get() + 1)) { + static_cast(this->n_years + 1)) { population->spawning_biomass_ratio.resize( this->spawning_biomass_ratio.size()); } else { FIMS_WARNING_LOG( "Setting spawning_biomass_ratio vector to size n_years + 1."); - this->spawning_biomass_ratio.resize((this->n_years.get() + 1)); + this->spawning_biomass_ratio.resize(this->n_years + 1); population->spawning_biomass_ratio.resize( this->spawning_biomass_ratio.size()); } @@ -524,7 +482,7 @@ class PopulationInterface : public PopulationInterfaceBase { if (this->proportion_female.size() == 1 || this->proportion_female.size() == - static_cast(this->n_ages.get())) { + static_cast(this->n_ages)) { population->proportion_female.resize(this->proportion_female.size()); } else { FIMS_WARNING_LOG( diff --git a/src/rcpp_population.cpp b/src/rcpp_population.cpp index 4fc40c40b..c1b2d770e 100644 --- a/src/rcpp_population.cpp +++ b/src/rcpp_population.cpp @@ -12,67 +12,135 @@ std::map> PopulationInterfaceBase::live_objects; #include -/** - * Function to register population classes with the Rcpp module system. - * - */ + +// ── XPtr type alias ────────────────────────────────────────────────────────── +using SharedPopulation = std::shared_ptr; + +// ── Creator ────────────────────────────────────────────────────────────────── +Rcpp::XPtr create_population_() { + auto obj = std::make_shared(); + return Rcpp::XPtr(new SharedPopulation(obj), true); +} + +// ── Scalar setters ─────────────────────────────────────────────────────────── +void set_population_constants_(Rcpp::XPtr xp, + int n_years, int n_ages, int n_lengths) { + (*xp)->n_years = n_years; + (*xp)->n_ages = n_ages; + (*xp)->n_lengths = n_lengths; +} +void set_population_process_ids_(Rcpp::XPtr xp, + int maturity_id = -999, + int growth_id = -999, + int recruitment_id = -999, + int recruitment_err_id = -999) { + (*xp)->maturity_id = maturity_id; + (*xp)->growth_id = growth_id; + (*xp)->recruitment_id = recruitment_id; + (*xp)->recruitment_err_id = recruitment_err_id; +} +void set_population_name_(Rcpp::XPtr xp, + std::string name) { + (*xp)->name = name; +} +void add_fleet_to_population_(Rcpp::XPtr xp, + uint32_t fleet_id) { + (*xp)->fleet_ids->insert(fleet_id); +} + +// ── VariableVector setters ─────────────────────────────────────────────────── +// Each setter resizes the vector, assigns initial values, and sets estimation +// types from a character vector (length 1 is recycled across all elements). +void set_population_log_M_(Rcpp::XPtr xp, + Rcpp::NumericVector values, + Rcpp::CharacterVector estimation_types) { + PopulationInterface& pop = **xp; + pop.log_M.resize(values.size()); + for (int i = 0; i < values.size(); i++) { + Variable& v = pop.log_M.storage_m->at(i); + v.initial_value_m = values[i]; + v.estimation_type_m.set( + Rcpp::as(estimation_types[estimation_types.size() == 1 ? 0 : i])); + } +} + +void set_population_log_init_naa_(Rcpp::XPtr xp, + Rcpp::NumericVector values, + Rcpp::CharacterVector estimation_types) { + PopulationInterface& pop = **xp; + pop.log_init_naa.resize(values.size()); + for (int i = 0; i < values.size(); i++) { + Variable& v = pop.log_init_naa.storage_m->at(i); + v.initial_value_m = values[i]; + v.estimation_type_m.set( + Rcpp::as(estimation_types[estimation_types.size() == 1 ? 0 : i])); + } +} + +void set_population_log_f_multiplier_(Rcpp::XPtr xp, + Rcpp::NumericVector values, + Rcpp::CharacterVector estimation_types) { + PopulationInterface& pop = **xp; + pop.log_f_multiplier.resize(values.size()); + for (int i = 0; i < values.size(); i++) { + Variable& v = pop.log_f_multiplier.storage_m->at(i); + v.initial_value_m = values[i]; + v.estimation_type_m.set( + Rcpp::as(estimation_types[estimation_types.size() == 1 ? 0 : i])); + } +} + +void set_population_proportion_female_(Rcpp::XPtr xp, + Rcpp::NumericVector values, + Rcpp::CharacterVector estimation_types) { + PopulationInterface& pop = **xp; + pop.proportion_female.resize(values.size()); + for (int i = 0; i < values.size(); i++) { + Variable& v = pop.proportion_female.storage_m->at(i); + v.initial_value_m = values[i]; + v.estimation_type_m.set( + Rcpp::as(estimation_types[estimation_types.size() == 1 ? 0 : i])); + } +} + +void set_population_ages_(Rcpp::XPtr xp, + Rcpp::NumericVector values) { + (*xp)->ages.resize(values.size()); + for (int i = 0; i < values.size(); i++) { + (*xp)->ages[i] = values[i]; + } +} + +// ── ID getter ──────────────────────────────────────────────────────────────── +uint32_t get_population_id_(Rcpp::XPtr xp) { + return (*xp)->get_id(); +} + +// ── Base-class conversion for CreateTMBModel() ─────────────────────────────── +using SharedBase = std::shared_ptr; + +Rcpp::XPtr population_to_fims_xptr_( + Rcpp::XPtr xp) { + SharedBase base = *xp; // implicit upcast: shared_ptr -> shared_ptr + return Rcpp::XPtr(new SharedBase(base), true); +} + +// ── Module registration ─────────────────────────────────────────────────────── void register_population(Rcpp::Module& m) { - Rcpp::class_( - "Population", - "See " - "https://noaa-fims.github.io/FIMS/doxygen/classPopulationInterface.html.") - .constructor() - .method("get_id", &PopulationInterface::get_id) - .field("n_ages", &PopulationInterface::n_ages) - .field("n_fleets", &PopulationInterface::n_fleets) - .field("n_years", &PopulationInterface::n_years) - .field("n_lengths", &PopulationInterface::n_lengths) - .field("log_M", &PopulationInterface::log_M) - .field("log_f_multiplier", &PopulationInterface::log_f_multiplier) - .field("spawning_biomass_ratio", - &PopulationInterface::spawning_biomass_ratio) - .field("log_init_naa", &PopulationInterface::log_init_naa) - .field("proportion_female", &PopulationInterface::proportion_female) - .field("ages", &PopulationInterface::ages) - .field("total_landings_weight", - &PopulationInterface:: - total_landings_weight) - .field("total_landings_numbers", - &PopulationInterface:: - total_landings_numbers) - .field("mortality_F", - &PopulationInterface::mortality_F) - .field("mortality_M", - &PopulationInterface::mortality_M) - .field("mortality_Z", - &PopulationInterface::mortality_Z) - .field("numbers_at_age", - &PopulationInterface::numbers_at_age) - .field("unfished_numbers_at_age", - &PopulationInterface:: - unfished_numbers_at_age) - .field("biomass", - &PopulationInterface::biomass) - .field("spawning_biomass", - &PopulationInterface:: - spawning_biomass) - .field("unfished_biomass", - &PopulationInterface::unfished_biomass) - .field("unfished_spawning_biomass", - &PopulationInterface:: - unfished_spawning_biomass) - .field("proportion_mature_at_age", - &PopulationInterface:: - proportion_mature_at_age) - .field("expected_recruitment", - &PopulationInterface:: - expected_recruitment) - .field("sum_selectivity", - &PopulationInterface::sum_selectivity) - .method("SetMaturityID", &PopulationInterface::SetMaturityID) - .method("SetGrowthID", &PopulationInterface::SetGrowthID) - .method("SetRecruitmentID", &PopulationInterface::SetRecruitmentID) - .method("AddFleet", &PopulationInterface::AddFleet) - .method("SetName", &PopulationInterface::SetName) - .method("GetName", &PopulationInterface::GetName); + // XPtr creator, setters, and conversion — trailing underscores prevent name + // collision with the R wrapper functions that own the clean names (Section 4b + // of the design doc). + Rcpp::function("create_population_", &create_population_); + Rcpp::function("set_population_constants_", &set_population_constants_); + Rcpp::function("set_population_process_ids_", &set_population_process_ids_); + Rcpp::function("set_population_name_", &set_population_name_); + Rcpp::function("add_fleet_to_population_", &add_fleet_to_population_); + Rcpp::function("set_population_log_M_", &set_population_log_M_); + Rcpp::function("set_population_log_init_naa_", &set_population_log_init_naa_); + Rcpp::function("set_population_log_f_multiplier_",&set_population_log_f_multiplier_); + Rcpp::function("set_population_proportion_female_", + &set_population_proportion_female_); + Rcpp::function("set_population_ages_", &set_population_ages_); + Rcpp::function("get_population_id_", &get_population_id_); + Rcpp::function("population_to_fims_xptr_", &population_to_fims_xptr_); } \ No newline at end of file From f72c64617d1956963698d592a89e7b345f37dd17 Mon Sep 17 00:00:00 2001 From: Andrea-Havron-NOAA <85530309+Andrea-Havron-NOAA@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:40:43 +0000 Subject: [PATCH 4/7] remove get/set --- .../rcpp/rcpp_objects/rcpp_models.hpp | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp index 8feb6b0b8..64ec17dea 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp @@ -363,8 +363,8 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [" << "\"n_years\", \"n_ages\"" << "],\n"; - ss << " \"dimensions\": [" << population_interface->n_years.get() << ", " - << population_interface->n_ages.get() << "]\n},\n"; + ss << " \"dimensions\": [" << population_interface->n_years << ", " + << population_interface->n_ages << "]\n},\n"; ss << " \"values\": " << population_interface->log_M << "\n\n"; ss << "},\n"; @@ -378,7 +378,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [" << "\"n_years\"" << "],\n"; - ss << " \"dimensions\": [" << population_interface->n_years.get() + ss << " \"dimensions\": [" << population_interface->n_years << "]\n},\n"; ss << " \"values\": " << population_interface->log_f_multiplier << "\n\n"; ss << "},\n"; @@ -394,7 +394,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [" << "\"n_years\"" << "],\n"; - ss << " \"dimensions\": [" << (population_interface->n_years.get() + 1) + ss << " \"dimensions\": [" << (population_interface->n_years + 1) << "]\n},\n"; ss << " \"values\": " << population_interface->spawning_biomass_ratio << "\n\n"; @@ -409,7 +409,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [" << "\"n_ages\"" << "],\n"; - ss << " \"dimensions\": [" << population_interface->n_ages.get() + ss << " \"dimensions\": [" << population_interface->n_ages << "]\n},\n"; ss << " \"values\":" << population_interface->log_init_naa << "\n"; @@ -1001,152 +1001,152 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { std::stringstream ss; derived_quantities["total_landings_weight"] = - fims::Vector(population_interface->n_years.get()); + fims::Vector(population_interface->n_years); derived_quantities_dim_info["total_landings_weight"] = fims_popdy::DimensionInfo( "total_landings_weight", - fims::Vector{(int)population_interface->n_years.get()}, + fims::Vector{(int)population_interface->n_years}, fims::Vector{"n_years"}); info->variable_map[population_interface->total_landings_weight.id_m] = &derived_quantities["total_landings_weight"]; derived_quantities["total_landings_numbers"] = - fims::Vector(population_interface->n_years.get()); + fims::Vector(population_interface->n_years); derived_quantities_dim_info["total_landings_numbers"] = fims_popdy::DimensionInfo( "total_landings_numbers", - fims::Vector{population_interface->n_years.get()}, + fims::Vector{population_interface->n_years}, fims::Vector{"n_years"}); info->variable_map[population_interface->total_landings_numbers.id_m] = &derived_quantities["total_landings_numbers"]; derived_quantities["mortality_F"] = - fims::Vector(population_interface->n_years.get() * - population_interface->n_ages.get()); + fims::Vector(population_interface->n_years * + population_interface->n_ages); derived_quantities_dim_info["mortality_F"] = fims_popdy::DimensionInfo( "mortality_F", - fims::Vector{population_interface->n_years.get(), - population_interface->n_ages.get()}, + fims::Vector{population_interface->n_years, + population_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[population_interface->mortality_F.id_m] = &derived_quantities["mortality_F"]; derived_quantities["mortality_M"] = - fims::Vector(population_interface->n_years.get() * - population_interface->n_ages.get()); + fims::Vector(population_interface->n_years * + population_interface->n_ages); derived_quantities_dim_info["mortality_M"] = fims_popdy::DimensionInfo( "mortality_M", - fims::Vector{population_interface->n_years.get(), - population_interface->n_ages.get()}, + fims::Vector{population_interface->n_years, + population_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[population_interface->mortality_M.id_m] = &derived_quantities["mortality_M"]; derived_quantities["mortality_Z"] = - fims::Vector(population_interface->n_years.get() * - population_interface->n_ages.get()); + fims::Vector(population_interface->n_years * + population_interface->n_ages); derived_quantities_dim_info["mortality_Z"] = fims_popdy::DimensionInfo( "mortality_Z", - fims::Vector{population_interface->n_years.get(), - population_interface->n_ages.get()}, + fims::Vector{population_interface->n_years, + population_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[population_interface->mortality_Z.id_m] = &derived_quantities["mortality_Z"]; derived_quantities["numbers_at_age"] = - fims::Vector((population_interface->n_years.get() + 1) * - population_interface->n_ages.get()); + fims::Vector((population_interface->n_years + 1) * + population_interface->n_ages); derived_quantities_dim_info["numbers_at_age"] = fims_popdy::DimensionInfo( "numbers_at_age", - fims::Vector{(population_interface->n_years.get() + 1), - population_interface->n_ages.get()}, + fims::Vector{(population_interface->n_years + 1), + population_interface->n_ages}, fims::Vector{"n_years+1", "n_ages"}); info->variable_map[population_interface->numbers_at_age.id_m] = &derived_quantities["numbers_at_age"]; derived_quantities["unfished_numbers_at_age"] = - fims::Vector((population_interface->n_years.get() + 1) * - population_interface->n_ages.get()); + fims::Vector((population_interface->n_years + 1) * + population_interface->n_ages); derived_quantities_dim_info["unfished_numbers_at_age"] = fims_popdy::DimensionInfo( "unfished_numbers_at_age", - fims::Vector{(population_interface->n_years.get() + 1), - population_interface->n_ages.get()}, + fims::Vector{(population_interface->n_years + 1), + population_interface->n_ages}, fims::Vector{"n_years+1", "n_ages"}); info->variable_map[population_interface->unfished_numbers_at_age.id_m] = &derived_quantities["unfished_numbers_at_age"]; derived_quantities["biomass"] = - fims::Vector((population_interface->n_years.get() + 1)); + fims::Vector((population_interface->n_years + 1)); derived_quantities_dim_info["biomass"] = fims_popdy::DimensionInfo( "biomass", - fims::Vector{(population_interface->n_years.get() + 1)}, + fims::Vector{(population_interface->n_years + 1)}, fims::Vector{"n_years+1"}); info->variable_map[population_interface->biomass.id_m] = &derived_quantities["biomass"]; derived_quantities["spawning_biomass"] = - fims::Vector((population_interface->n_years.get() + 1)); + fims::Vector((population_interface->n_years + 1)); derived_quantities_dim_info["spawning_biomass"] = fims_popdy::DimensionInfo( "spawning_biomass", - fims::Vector{(population_interface->n_years.get() + 1)}, + fims::Vector{(population_interface->n_years + 1)}, fims::Vector{"n_years+1"}); info->variable_map[population_interface->spawning_biomass.id_m] = &derived_quantities["spawning_biomass"]; derived_quantities["unfished_biomass"] = - fims::Vector((population_interface->n_years.get() + 1)); + fims::Vector((population_interface->n_years + 1)); derived_quantities_dim_info["unfished_biomass"] = fims_popdy::DimensionInfo( "unfished_biomass", - fims::Vector{(population_interface->n_years.get() + 1)}, + fims::Vector{(population_interface->n_years + 1)}, fims::Vector{"n_years+1"}); info->variable_map[population_interface->unfished_biomass.id_m] = &derived_quantities["unfished_biomass"]; derived_quantities["unfished_spawning_biomass"] = - fims::Vector((population_interface->n_years.get() + 1)); + fims::Vector((population_interface->n_years + 1)); derived_quantities_dim_info["unfished_spawning_biomass"] = fims_popdy::DimensionInfo( "unfished_spawning_biomass", - fims::Vector{(population_interface->n_years.get() + 1)}, + fims::Vector{(population_interface->n_years + 1)}, fims::Vector{"n_years+1"}); info->variable_map[population_interface->unfished_spawning_biomass.id_m] = &derived_quantities["unfished_spawning_biomass"]; derived_quantities["proportion_mature_at_age"] = - fims::Vector((population_interface->n_years.get() + 1) * - population_interface->n_ages.get()); + fims::Vector((population_interface->n_years + 1) * + population_interface->n_ages); derived_quantities_dim_info["proportion_mature_at_age"] = fims_popdy::DimensionInfo( "proportion_mature_at_age", - fims::Vector{(population_interface->n_years.get() + 1), - population_interface->n_ages.get()}, + fims::Vector{(population_interface->n_years + 1), + population_interface->n_ages}, fims::Vector{"n_years+1", "n_ages"}); info->variable_map[population_interface->proportion_mature_at_age.id_m] = &derived_quantities["proportion_mature_at_age"]; derived_quantities["expected_recruitment"] = - fims::Vector((population_interface->n_years.get() + 1)); + fims::Vector((population_interface->n_years + 1)); derived_quantities_dim_info["expected_recruitment"] = fims_popdy::DimensionInfo( "expected_recruitment", - fims::Vector{(population_interface->n_years.get() + 1)}, + fims::Vector{(population_interface->n_years + 1)}, fims::Vector{"n_years+1"}); info->variable_map[population_interface->expected_recruitment.id_m] = &derived_quantities["expected_recruitment"]; derived_quantities["sum_selectivity"] = - fims::Vector(population_interface->n_years.get() * - population_interface->n_ages.get()); + fims::Vector(population_interface->n_years * + population_interface->n_ages); derived_quantities_dim_info["sum_selectivity"] = fims_popdy::DimensionInfo( "sum_selectivity", - fims::Vector{population_interface->n_years.get(), - population_interface->n_ages.get()}, + fims::Vector{population_interface->n_years, + population_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[population_interface->sum_selectivity.id_m] = &derived_quantities["sum_selectivity"]; From f5a8b6539c808958a765901907bfee93f7ddbaa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:08:39 +0000 Subject: [PATCH 5/7] fix: update model glue for plain population scalars --- inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp index 64ec17dea..27b3a1c34 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp @@ -243,7 +243,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { pit = PopulationInterfaceBase::live_objects.find(id); if (pit != PopulationInterfaceBase::live_objects.end()) { std::shared_ptr &pop = (*pit).second; - pop->initialize_catch_at_age.set(true); + pop->initialize_catch_at_age = true; } else { FIMS_ERROR_LOG("Population with id " + fims::to_string(id) + " not found."); @@ -692,9 +692,9 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { std::dynamic_pointer_cast( PopulationInterfaceBase::live_objects[*pit]); if (population_interface) { - recruitment_ids.insert(population_interface->recruitment_id.get()); - growth_ids.insert(population_interface->growth_id.get()); - maturity_ids.insert(population_interface->maturity_id.get()); + recruitment_ids.insert(population_interface->recruitment_id); + growth_ids.insert(population_interface->growth_id); + maturity_ids.insert(population_interface->maturity_id); for (fids = population_interface->fleet_ids->begin(); fids != population_interface->fleet_ids->end(); fids++) { From c37af43287c936543ce5e7bae92a6815107b5fc8 Mon Sep 17 00:00:00 2001 From: Andrea-Havron-NOAA <85530309+Andrea-Havron-NOAA@users.noreply.github.com> Date: Fri, 7 Aug 2026 22:24:07 +0000 Subject: [PATCH 6/7] convert additional types --- .../rcpp/rcpp_objects/rcpp_distribution.hpp | 2 +- .../rcpp/rcpp_objects/rcpp_fleet.hpp | 24 ++--- .../rcpp/rcpp_objects/rcpp_models.hpp | 100 +++++++++--------- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp index f88009ce5..fd8a037e5 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_distribution.hpp @@ -38,7 +38,7 @@ class DistributionsInterfaceBase : public FIMSRcppInterfaceBase { * @brief Control flag indicating whether to use the expected mean in the * distribution calculations. * - * This shared string member serves as a boolean flag (i.e., "yes" or "no") + * This string member serves as a boolean flag (i.e., "yes" or "no") * that determines whether the distribution should use the `expected_mean` * vector or other expected values (e.g., from data or random effects) when * computing the expected value in the likelihood calculations. diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp index dae52d42c..d55bf5613 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_fleet.hpp @@ -95,15 +95,15 @@ class FleetInterface : public FleetInterfaceBase { /** * @brief The number of age bins in the fleet data. */ - SharedInt n_ages = 0; + int n_ages = 0; /** * @brief The number of length bins in the fleet data. */ - SharedInt n_lengths = 0; + int n_lengths = 0; /** * @brief The number of years in the fleet data. */ - SharedInt n_years = 0; + int n_years = 0; /** * @brief What units are the observed landings for this fleet measured in. * Options are weight or numbers, default is weight. @@ -435,9 +435,9 @@ class FleetInterface : public FleetInterfaceBase { // set relative info fleet->id = this->id; - fleet->n_ages = this->n_ages.get(); - fleet->n_lengths = this->n_lengths.get(); - fleet->n_years = this->n_years.get(); + fleet->n_ages = this->n_ages; + fleet->n_lengths = this->n_lengths; + fleet->n_years = this->n_years; fleet->observed_landings_units = this->observed_landings_units; fleet->observed_index_units = this->observed_index_units; @@ -472,16 +472,16 @@ class FleetInterface : public FleetInterfaceBase { } } - if (this->log_Fmort.size() != static_cast(this->n_years.get())) { + if (this->log_Fmort.size() != static_cast(this->n_years)) { FIMS_ERROR_LOG("The size of `log_Fmort` does not match `n_years`: " + fims::to_string(this->log_Fmort.size()) + - " != " + fims::to_string(this->n_years.get())); + " != " + fims::to_string(this->n_years)); throw std::invalid_argument( "Fleet log_Fmort size mismatch." "Fleet log_Fmort is of size " + fims::to_string(this->log_Fmort.size()) + " and the number of years is " + - fims::to_string(this->n_years.get())); + fims::to_string(this->n_years)); } fleet->log_Fmort.resize(static_cast(this->log_Fmort.size())); for (size_t i = 0; i < log_Fmort.size(); i++) { @@ -503,16 +503,16 @@ class FleetInterface : public FleetInterfaceBase { // add to variable_map info->variable_map[this->log_Fmort.id_m] = &(fleet)->log_Fmort; - if (this->n_lengths.get() > 0) { + if (this->n_lengths > 0) { fleet->age_to_length_conversion.resize( this->age_to_length_conversion.size()); if (this->age_to_length_conversion.size() != - static_cast(this->n_ages.get() * this->n_lengths.get())) { + static_cast(this->n_ages * this->n_lengths)) { FIMS_ERROR_LOG( "age_to_length_conversion don't match, " + fims::to_string(this->age_to_length_conversion.size()) + " != " + - fims::to_string((this->n_ages.get() * this->n_lengths.get()))); + fims::to_string((this->n_ages * this->n_lengths))); } for (size_t i = 0; i < fleet->age_to_length_conversion.size(); i++) { diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp index 27b3a1c34..ed3a19757 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp @@ -614,9 +614,9 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"module_name\": \"Fleet\",\n"; ss << " \"fleet\": \"" << fleet_interface->name << "\",\n"; ss << " \"module_id\": " << fleet_interface->id << ",\n"; - ss << " \"n_ages\": " << fleet_interface->n_ages.get() << ",\n"; - ss << " \"n_years\": " << fleet_interface->n_years.get() << ",\n"; - ss << " \"n_lengths\": " << fleet_interface->n_lengths.get() << ",\n"; + ss << " \"n_ages\": " << fleet_interface->n_ages << ",\n"; + ss << " \"n_years\": " << fleet_interface->n_years << ",\n"; + ss << " \"n_lengths\": " << fleet_interface->n_lengths << ",\n"; ss << "\"data_ids\" : [\n"; ss << "{\"agecomp\": " << fleet_interface->GetObservedAgeCompDataID() << "},\n"; @@ -638,7 +638,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { ss << " \"type\": \"vector\",\n"; ss << " \"dimensionality\": {\n"; ss << " \"header\": [\"" << "n_years" << "\"],\n"; - ss << " \"dimensions\": [" << fleet_interface->n_years.get() + ss << " \"dimensions\": [" << fleet_interface->n_years << "]\n},\n"; ss << " \"values\": " << fleet_interface->log_Fmort << "},\n"; @@ -1186,186 +1186,186 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { // initialize derive quantities // landings derived_quantities["landings_numbers_at_age"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["landings_numbers_at_age"] = fims_popdy::DimensionInfo( "landings_numbers_at_age", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_ages.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->landings_numbers_at_age.id_m] = &derived_quantities["landings_numbers_at_age"]; derived_quantities["landings_weight_at_age"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["landings_weight_at_age"] = fims_popdy::DimensionInfo( "landings_weight_at_age", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_ages.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->landings_weight_at_age.id_m] = &derived_quantities["landings_weight_at_age"]; derived_quantities["landings_numbers_at_length"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_lengths.get()); + fleet_interface->n_years * fleet_interface->n_lengths); derived_quantities_dim_info["landings_numbers_at_length"] = fims_popdy::DimensionInfo( "landings_numbers_at_length", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_lengths.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_lengths}, fims::Vector{"n_years", "n_lengths"}); info->variable_map[fleet_interface->landings_numbers_at_length.id_m] = &derived_quantities["landings_numbers_at_length"]; derived_quantities["landings_weight"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["landings_weight"] = fims_popdy::DimensionInfo( "landings_weight", - fims::Vector{(fleet_interface->n_years.get())}, + fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->landings_weight.id_m] = &derived_quantities["landings_weight"]; derived_quantities["landings_numbers"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["landings_numbers"] = fims_popdy::DimensionInfo( "landings_numbers", - fims::Vector{(fleet_interface->n_years.get())}, + fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->landings_numbers.id_m] = &derived_quantities["landings_numbers"]; derived_quantities["landings_expected"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["landings_expected"] = fims_popdy::DimensionInfo( "landings_expected", - fims::Vector{(fleet_interface->n_years.get())}, + fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->landings_expected.id_m] = &derived_quantities["landings_expected"]; derived_quantities["log_landings_expected"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["log_landings_expected"] = fims_popdy::DimensionInfo( "log_landings_expected", - fims::Vector{(fleet_interface->n_years.get())}, + fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->log_landings_expected.id_m] = &derived_quantities["log_landings_expected"]; derived_quantities["agecomp_proportion"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["agecomp_proportion"] = fims_popdy::DimensionInfo( "agecomp_proportion", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_ages.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->agecomp_proportion.id_m] = &derived_quantities["agecomp_proportion"]; derived_quantities["lengthcomp_proportion"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_lengths.get()); + fleet_interface->n_years * fleet_interface->n_lengths); derived_quantities_dim_info["lengthcomp_proportion"] = fims_popdy::DimensionInfo( "lengthcomp_proportion", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_lengths.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_lengths}, fims::Vector{"n_years", "n_lengths"}); info->variable_map[fleet_interface->lengthcomp_proportion.id_m] = &derived_quantities["lengthcomp_proportion"]; // index derived_quantities["index_numbers_at_age"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["index_numbers_at_age"] = fims_popdy::DimensionInfo( "index_numbers_at_age", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_ages.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->index_numbers_at_age.id_m] = &derived_quantities["index_numbers_at_age"]; derived_quantities["index_weight_at_age"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["index_weight_at_age"] = fims_popdy::DimensionInfo( "index_weight_at_age", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_ages.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_ages}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->index_weight_at_age.id_m] = &derived_quantities["index_weight_at_age"]; derived_quantities["index_numbers_at_length"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_lengths.get()); + fleet_interface->n_years * fleet_interface->n_lengths); derived_quantities_dim_info["index_numbers_at_length"] = fims_popdy::DimensionInfo( "index_numbers_at_length", - fims::Vector{(fleet_interface->n_years.get()), - fleet_interface->n_lengths.get()}, + fims::Vector{(fleet_interface->n_years), + fleet_interface->n_lengths}, fims::Vector{"n_years", "n_lengths"}); info->variable_map[fleet_interface->index_numbers_at_length.id_m] = &derived_quantities["index_numbers_at_length"]; derived_quantities["index_weight"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years; derived_quantities_dim_info["index_weight"] = fims_popdy::DimensionInfo( - "index_weight", fims::Vector{(fleet_interface->n_years.get())}, + "index_weight", fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->index_weight.id_m] = &derived_quantities["index_weight"]; derived_quantities["index_numbers"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["index_numbers"] = fims_popdy::DimensionInfo( - "index_numbers", fims::Vector{(fleet_interface->n_years.get())}, + "index_numbers", fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->index_numbers.id_m] = &derived_quantities["index_numbers"]; derived_quantities["index_expected"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["index_expected"] = fims_popdy::DimensionInfo( - "index_expected", fims::Vector{(fleet_interface->n_years.get())}, + "index_expected", fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->index_expected.id_m] = &derived_quantities["index_expected"]; derived_quantities["log_index_expected"] = - fims::Vector(fleet_interface->n_years.get()); + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["log_index_expected"] = fims_popdy::DimensionInfo( "log_index_expected", - fims::Vector{(fleet_interface->n_years.get())}, + fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"}); info->variable_map[fleet_interface->log_index_expected.id_m] = &derived_quantities["log_index_expected"]; derived_quantities["agecomp_expected"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_ages.get()); + fleet_interface->n_years * fleet_interface->n_ages); derived_quantities_dim_info["agecomp_expected"] = fims_popdy::DimensionInfo( "agecomp_expected", - fims::Vector{(fleet_interface->n_years.get()), - (fleet_interface->n_ages.get())}, + fims::Vector{(fleet_interface->n_years), + (fleet_interface->n_ages)}, fims::Vector{"n_years", "n_ages"}); info->variable_map[fleet_interface->agecomp_expected.id_m] = &derived_quantities["agecomp_expected"]; derived_quantities["lengthcomp_expected"] = fims::Vector( - fleet_interface->n_years.get() * fleet_interface->n_lengths.get()); + fleet_interface->n_years * fleet_interface->n_lengths); derived_quantities_dim_info["lengthcomp_expected"] = fims_popdy::DimensionInfo( "lengthcomp_expected", - fims::Vector{(fleet_interface->n_years.get()), - (fleet_interface->n_lengths.get())}, + fims::Vector{(fleet_interface->n_years), + (fleet_interface->n_lengths)}, fims::Vector{"n_years", "n_lengths"}); info->variable_map[fleet_interface->lengthcomp_expected.id_m] = &derived_quantities["lengthcomp_expected"]; From a67365197af0ff1e7c0637d232de202bc2485661 Mon Sep 17 00:00:00 2001 From: Andrea-Havron-NOAA <85530309+Andrea-Havron-NOAA@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:17:50 +0000 Subject: [PATCH 7/7] fix bug --- inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp index ed3a19757..038006888 100644 --- a/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp +++ b/inst/include/interface/rcpp/rcpp_objects/rcpp_models.hpp @@ -1315,7 +1315,7 @@ class CatchAtAgeInterface : public FisheryModelInterfaceBase { &derived_quantities["index_numbers_at_length"]; derived_quantities["index_weight"] = - fims::Vector(fleet_interface->n_years; + fims::Vector(fleet_interface->n_years); derived_quantities_dim_info["index_weight"] = fims_popdy::DimensionInfo( "index_weight", fims::Vector{(fleet_interface->n_years)}, fims::Vector{"n_years"});