Skip to content

Commit

Permalink
Merge pull request #146 from gadget-framework/spawning-multiple-stocks
Browse files Browse the repository at this point in the history
Fix splitting g3a_spawn() offspring over multiple stocks
  • Loading branch information
lentinj committed Apr 19, 2024
2 parents 545f80b + 86fd865 commit 3cec204
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 14 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# gadget3 0.11-1-999:

## Bug fixes
* ``g3a_spawn()`` splits offspring into multiple stocks correctly

# gadget3 0.11-0:

## Bug fixes
Expand Down
6 changes: 5 additions & 1 deletion R/aab_env.R
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ g3_env$REprintf <- g3_native(r = function(...) {
cat(sprintf(...))
}, cpp = NULL)

# "Rcpp::warning" should be in scope, but on 2024-04-17 r86441 / gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 :-
# error: there are no arguments to ‘warning’ that depend on a template parameter, so a declaration of ‘warning’ must be available [-fpermissive]
g3_env$warning <- g3_native(r = 'warning', cpp = 'Rf_warning')

g3_env$print_array <- g3_native(r = function(ar_name, ar) {
writeLines(ar_name)
print(ar)
Expand All @@ -129,7 +133,7 @@ g3_env$assert_msg <- g3_native(r = function(expr, message) {
if (isFALSE(expr)) { warning(message) ; return(TRUE) }
return(FALSE)
}, cpp = '[](bool expr, std::string message) -> bool {
if (!expr) { warning(message.c_str()); return TRUE; }
if (!expr) { Rf_warning(message.c_str()); return TRUE; }
return FALSE;
}')

Expand Down
19 changes: 7 additions & 12 deletions R/action_spawn.R
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ g3a_spawn <- function(
stopifnot(is.list(output_stocks) && all(sapply(output_stocks, g3_is_stock)))
stopifnot(identical(names(recruitment_f), c('s', 'r')))
stopifnot(length(output_stocks) == length(output_ratios))
stopifnot(abs(sum(output_ratios) - 1) < 0.0001)
stopifnot(!is.numeric(output_ratios) || abs(sum(output_ratios) - 1) < 0.0001)
stock__num <- g3_stock_instance(stock, 0)
stock__wgt <- g3_stock_instance(stock, 1)
stock__spawnprop <- g3_stock_instance(stock, desc = "Proportion of parents that are spawning")
Expand Down Expand Up @@ -135,28 +135,24 @@ g3a_spawn <- function(

# Move spawned fish into their own stock
out_f <- ~{}
sum_all_outputs_f <- ~0
for (i in seq_along(output_stocks)) {
output_stock <- output_stocks[[i]]
output_ratio <- output_ratios[[i]]
output_stock__spawnednum <- g3_stock_instance(output_stock, desc = "Individuals spawned")

# Make a formula to sum all our outputs
sum_all_outputs_f <- g3_step(f_substitute(~stock_with(output_stock, sum(output_stock__spawnednum)) + sum_all_outputs_f, list(
sum_all_outputs_f = sum_all_outputs_f)), recursing = TRUE)
output_stock__spawnednum <- g3_stock_instance(output_stock, desc = "Individuals spawned by parent")

out_f <- g3_step(f_substitute(~{
debug_trace("Generate normal distribution for spawned ", output_stock)
# Equivalent to Spawner::Storage, pre-calcRecruitNumber()
stock_with(output_stock, output_stock__spawnednum[] <- 0)
# sum(*__spawnednum) is roughly equivalent to Spawner::Storage
# Spawner::Storage spawns into a union of output stocks first, we spawn directly to output stocks
stock_iterate(output_stock, if (run_f && renew_into_f && output_stock_cond) {
stock_ss(output_stock__spawnednum) <- exp(-(((output_stock__midlen - (mean_f)) * (1 / (stddev_f))) ** 2) * 0.5)
})
extension_point
debug_trace("Scale total spawned stock by total to spawn in cycle")
# sum_all_outputs_f equivalent to sum in Spawner::addSpawnStock()
# __offspringnum eqivalent to calcRecruitNumber()
stock_with(output_stock, stock_with(stock,
output_stock__spawnednum <- (output_stock__spawnednum / avoid_zero(sum_all_outputs_f)) * sum(stock__offspringnum) * output_ratio))
output_stock__spawnednum <- (output_stock__spawnednum / avoid_zero(sum(output_stock__spawnednum))) * sum(stock__offspringnum) * output_ratio))
stock_iterate(output_stock, if (run_f && renew_into_f && output_stock_cond) {
stock_ss(output_stock__wgt) <- ratio_add_vec(
stock_ss(output_stock__wgt),
Expand Down Expand Up @@ -189,8 +185,7 @@ g3a_spawn <- function(
out_f
}, list(out_f = out_f)), recursing = TRUE)
}
out[[step_id(recruit_at, stock, action_name)]] <- g3_step(f_substitute(out_f, list(
sum_all_outputs_f = f_optimize(sum_all_outputs_f))))
out[[step_id(recruit_at, stock, action_name)]] <- g3_step(out_f)

return(out)
}
2 changes: 1 addition & 1 deletion R/run_tmb.R
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ Type objective_function<Type>::operator() () {
try {
return *map_in.at(key_in);
} catch (const std::out_of_range&) {
warning(\"No value found in g3_param_table %s, ifmissing not specified\", err.c_str());
Rf_warning(\"No value found in g3_param_table %s, ifmissing not specified\", err.c_str());
return NAN;
}
}
Expand Down
92 changes: 92 additions & 0 deletions tests/test-action_spawn-multipleoutputs.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
library(unittest)
if (!interactive()) options(warn=2, error = function() { sink(stderr()) ; traceback(3) ; q(status = 1) })

library(gadget3)

actions <- list()
area_names <- g3_areas(c('AA', 'BB', 'CC'))

st_imm_f <- g3_stock(c(species = "fish", maturity = 'imm', sex = 'f'), seq(5L, 25L, 5)) |>
g3s_livesonareas(area_names["AA"]) |>
g3s_age(1L, 5L)
st_imm_m <- g3_stock(c(species = "fish", maturity = 'imm', sex = 'm'), seq(5L, 25L, 5)) |>
g3s_livesonareas(area_names["AA"]) |>
g3s_age(1L, 5L)
st_mat <- g3_stock(c(species = "fish", maturity = 'mat'), seq(5L, 25L, 5)) |>
g3s_livesonareas(area_names["AA"]) |>
g3s_age(3L, 10L)

actions <- list(
g3a_time(
1980, 1995,
step_lengths = c(6L, 6L)),
g3a_initialconditions(st_imm_f, quote( 0 * stock__midlen ), quote( 0 * stock__midlen )),
g3a_initialconditions(st_imm_m, quote( 0 * stock__midlen ), quote( 0 * stock__midlen )),
g3a_initialconditions_normalcv(st_mat),
g3a_spawn(
st_mat,
recruitment_f = g3a_spawn_recruitment_bevertonholt(
mu = g3_parameterized('spawn_mu', value = 5, by_year = TRUE),
lambda = g3_parameterized("spawn_lambda", value = 1, by_stock = TRUE) ),
proportion_f = g3_suitability_exponentiall50(),
output_stocks = list(st_imm_f, st_imm_m),
output_ratios = list(
st_imm_f = quote( g3_param('spawn_ratio', value = 0.5) ),
st_imm_m = quote( 1 - g3_param('spawn_ratio', value = 0.5) )),
run_f = quote( cur_step == 1 ) ),
g3a_age(st_imm_f),
g3a_age(st_imm_m),
g3a_age(st_mat) )

# Compile model
model_fn <- g3_to_r(c(actions, list(
g3a_report_history(actions, c(
'__offspringnum$',
'__num$',
'__wgt$' )))))
if (nzchar(Sys.getenv('G3_TEST_TMB'))) {
model_cpp <- g3_to_tmb(c(actions, list(
g3a_report_history(actions, c(
'__offspringnum$',
'__num$',
'__wgt$' )))))
# model_cpp <- edit(model_cpp)
model_tmb <- g3_tmb_adfun(model_cpp, compile_flags = c("-O0", "-g"))
} else {
writeLines("# skip: not compiling TMB model")
}

estimate_l50 <- g3_stock_def(st_mat, "midlen")[[length(g3_stock_def(st_mat, "midlen")) / 2]]
estimate_linf <- max(g3_stock_def(st_mat, "midlen"))

for (spawn_ratio in runif(5)) ok_group(paste0("spawn_ratio: ", spawn_ratio), {
attr(model_fn, 'parameter_template') |>
g3_init_val("*.Linf", estimate_linf, spread = 0.2) |>
g3_init_val("*.walpha", 0.01, optimise = FALSE) |>
g3_init_val("*.wbeta", 3, optimise = FALSE) |>

g3_init_val("*.*.l50", estimate_l50, spread = 0.25) |>
g3_init_val("spawn_ratio", spawn_ratio) |>

identity() -> params

r <- attributes(model_fn(params))
num_spawn <- colSums(r$hist_fish_mat__offspringnum, dims = 3)
num_m <- colSums(r$hist_fish_imm_m__num, dims = 3)
num_f <- colSums(r$hist_fish_imm_f__num, dims = 3)

ok(ut_cmp_equal(
cumsum(num_spawn * (1 - spawn_ratio)),
num_m,
tolerance = 1e-8), "hist_fish_imm_m__num: Cumulative proportion of __offspringnum")
ok(ut_cmp_equal(
cumsum(num_spawn * spawn_ratio),
num_f,
tolerance = 1e-8), "hist_fish_imm_f__num: Cumulatve proportion of __offspringnum")

if (nzchar(Sys.getenv('G3_TEST_TMB'))) {
param_template <- attr(model_cpp, "parameter_template")
param_template$value <- params[param_template$switch]
gadget3:::ut_tmb_r_compare(model_fn, model_tmb, param_template)
}
})

0 comments on commit 3cec204

Please sign in to comment.