diff --git a/DESCRIPTION b/DESCRIPTION index 762b889a..6cbf44ad 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,7 +19,7 @@ Authors@R: c(person("Gustav", "Delius", email="gustav.delius@york.ac.uk", comment = c(ORCID = "0000-0002-8478-3430")), person("Richard", "Southwell", email="richard.southwell@york.ac.uk", role=c("ctb", "cph"))) -Version: 2.5.3.9000 +Version: 2.5.3.9001 License: GPL-3 Imports: assertthat, diff --git a/NEWS.md b/NEWS.md index 4065e7bb..53c1b6a2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# Development version 2.5.3.9000 +# Development version 2.5.3.9001 - `species` and `gears` columns are now never factors, so no longer need to call `as.character()` so often. @@ -6,6 +6,10 @@ - `validParams()` checks that `w_min` is valid for all species and increases it if necessary. +## Bug fixes + +- `getReproductionProportion()` no longer returns incorrect proportions > 1 (#299) + # mizer 2.5.3 A patch update so that users who had changed `w_max` manually in their model diff --git a/R/setReproduction.R b/R/setReproduction.R index b3b76544..1cb430e3 100644 --- a/R/setReproduction.R +++ b/R/setReproduction.R @@ -1,68 +1,68 @@ #' Set reproduction parameters -#' +#' #' Sets the proportion of the total energy available for reproduction and growth #' that is invested into reproduction as a function of the size of the #' individual and sets additional density dependence. -#' +#' #' @section Setting reproduction: -#' -#' For each species and at each size, the proportion \eqn{\psi}{psi} of the -#' available energy +#' +#' For each species and at each size, the proportion \eqn{\psi}{psi} of the +#' available energy #' that is invested into reproduction is the product of two factors: the #' proportion `maturity` of individuals that are mature and the proportion -#' `repro_prop` of the energy available to a mature individual that is +#' `repro_prop` of the energy available to a mature individual that is #' invested into reproduction. There is a size `w_repro_max` at which all the #' energy is invested into reproduction and therefore all growth stops. There #' can be no fish larger than `w_repro_max`. If you have not specified the #' `w_repro_max` column in the species parameter data frame, then the maximum size #' `w_max` is used instead. -#' +#' #' \subsection{Maturity ogive}{ #' If the the proportion of individuals that are mature is not supplied via -#' the `maturity` argument, then it is set to a sigmoidal +#' the `maturity` argument, then it is set to a sigmoidal #' maturity ogive that changes from 0 to 1 at around the maturity size: #' \deqn{{\tt maturity}(w) = \left[1+\left(\frac{w}{w_{mat}}\right)^{-U}\right]^{-1}.}{ #' maturity(w) = [1+(w/w_mat)^(-U)]^(-1)} #' (To avoid clutter, we are not showing the species index in the equations, #' although each species has its own maturity ogive.) -#' The maturity weights are taken from the `w_mat` column of the +#' The maturity weights are taken from the `w_mat` column of the #' species_params data frame. Any missing maturity weights are set to 1/4 of the #' maximum weight in the `w_max` column. -#' +#' #' The exponent \eqn{U} determines the steepness of the maturity ogive. By #' default it is chosen as \eqn{U = 10}, however this can be overridden by #' including a column \code{w_mat25} in the species parameter dataframe that #' specifies the weight at which 25% of individuals are mature, which sets #' \eqn{U = \log(3) / \log(w_{mat} / w_{mat25}).}{U = log(3) / log(w_mat / #' w_mat25).} -#' +#' #' The sigmoidal function given above would strictly reach 1 only #' asymptotically. Mizer instead sets the function equal to 1 already at a size #' taken from the `w_repro_max` column in the species parameter data frame, if it #' exists, or otherwise from the `w_max` column. Also, for computational #' simplicity, any proportion smaller than `1e-8` is set to `0`. #' } -#' +#' #' \subsection{Investment into reproduction}{ -#' If the the energy available to a mature individual that is +#' If the the energy available to a mature individual that is #' invested into reproduction is not supplied via the `repro_prop` argument, #' it is set to the allometric form #' \deqn{{\tt repro\_prop}(w) = \left(\frac{w}{w_{\tt{repro\_max}}}\right)^{m-n}.}{ #' repro_prop(w) = (w/w_repro_max)^(m - n).} #' Here \eqn{n} is the scaling exponent of the energy income rate. Hence #' the exponent \eqn{m} determines the scaling of the investment into -#' reproduction for mature individuals. By default it is chosen to be -#' \eqn{m = 1} so that the rate at which energy is invested into reproduction -#' scales linearly with the size. This default can be overridden by including a +#' reproduction for mature individuals. By default it is chosen to be +#' \eqn{m = 1} so that the rate at which energy is invested into reproduction +#' scales linearly with the size. This default can be overridden by including a #' column `m` in the species parameter dataframe. The maximum sizes are taken #' from the `w_repro_max` column in the species parameter data frame, if it #' exists, or otherwise from the `w_max` column. -#' +#' #' The total proportion of energy invested into reproduction of an individual #' of size \eqn{w} is then #' \deqn{\psi(w) = {\tt maturity}(w){\tt repro\_prop}(w)}{psi(w) = maturity(w) * repro_prop(w)} #' } -#' +#' #' \subsection{Reproductive efficiency}{ #' The reproductive efficiency \eqn{\epsilon}, i.e., the proportion of energy allocated to #' reproduction that results in egg biomass, is set through the `erepro` @@ -72,7 +72,7 @@ #' [getRDI()]: #' \deqn{R_{di} = \frac{\epsilon}{2 w_{min}} \int N(w) E_r(w) \psi(w) \, dw}{R_di = (\epsilon/(2 w_min)) \int N(w) E_r(w) \psi(w) dw} #' } -#' +#' #' \subsection{Density dependence}{ #' The stock-recruitment relationship is an emergent phenomenon in mizer, with #' several sources of density dependence. Firstly, the amount of energy invested @@ -81,7 +81,7 @@ #' larvae that grow up to recruitment size depends on the larval mortality, #' which depends on the density of predators, and on larval growth rate, which #' depends on density of prey. -#' +#' #' Finally, to encode all the density dependence in the stock-recruitment #' relationship that is not already included in the other two sources of density #' dependence, mizer puts the the density-independent rate of egg production @@ -109,11 +109,11 @@ #' were previously overwritten with custom values. If set to FALSE (default) #' then a recalculation from the species parameters will take place only if no #' custom values have been set. -#' @param RDD The name of the function calculating the density-dependent -#' reproduction rate from the density-independent rate. Defaults to +#' @param RDD The name of the function calculating the density-dependent +#' reproduction rate from the density-independent rate. Defaults to #' "[BevertonHoltRDD()]". #' @param ... Unused -#' +#' #' @return `setReproduction()`: A MizerParams object with updated reproduction #' parameters. #' @export @@ -123,12 +123,12 @@ #' # Plot maturity and reproduction ogives for Cod in North Sea model #' maturity <- getMaturityProportion(NS_params)["Cod", ] #' repro_prop <- getReproductionProportion(NS_params)["Cod", ] -#' df <- data.frame(Size = w(NS_params), -#' Reproduction = repro_prop, -#' Maturity = maturity, +#' df <- data.frame(Size = w(NS_params), +#' Reproduction = repro_prop, +#' Maturity = maturity, #' Total = maturity * repro_prop) -#' dff <- melt(df, id.vars = "Size", -#' variable.name = "Type", +#' dff <- melt(df, id.vars = "Size", +#' variable.name = "Type", #' value.name = "Proportion") #' library(ggplot2) #' ggplot(dff) + geom_line(aes(x = Size, y = Proportion, colour = Type)) @@ -144,24 +144,24 @@ setReproduction <- function(params, maturity = NULL, exists(RDD), is.function(get(RDD))) species_params <- params@species_params - + if (reset) { if (!is.null(maturity)) { - warning("Because you set `reset = TRUE`, the value you provided ", + warning("Because you set `reset = TRUE`, the value you provided ", "for `maturity` will be ignored and a value will be ", "calculated from the species parameters.") maturity <- NULL } comment(params@maturity) <- NULL if (!is.null(repro_prop)) { - warning("Because you set `reset = TRUE`, the value you provided ", + warning("Because you set `reset = TRUE`, the value you provided ", "for `repro_prop` will be ignored and a value will be ", "calculated from the species parameters.") repro_prop <- NULL } comment(params@psi) <- NULL } - + # Check maximum sizes if (!("w_max" %in% colnames(species_params))) { stop("The maximum sizes of the species must be specified in the w_max ", @@ -180,12 +180,12 @@ setReproduction <- function(params, maturity = NULL, # idx <- which.min(abs(species_params$w_max[i] - params@w)) # params@species_params$w_max[i] < params@w[idx] # } - + # set maturity proportion ---- if (!is.null(maturity)) { assert_that(is.array(maturity), identical(dim(maturity), dim(params@psi))) - if (!is.null(dimnames(maturity)) && + if (!is.null(dimnames(maturity)) && !all(dimnames(maturity)[[1]] == species_params$species)) { stop("You need to use the same ordering of species as in the ", "params object: ", toString(species_params$species)) @@ -213,18 +213,18 @@ setReproduction <- function(params, maturity = NULL, assert_that(all(species_params$w_mat > species_params$w_min)) assert_that(all(species_params$w_mat < species_params$w_max)) params@species_params$w_mat <- species_params$w_mat - + # Set defaults for w_mat25 species_params <- set_species_param_default( - species_params, "w_mat25", + species_params, "w_mat25", species_params$w_mat / (3 ^ (1 / 10))) # Check w_mat25 assert_that(all(species_params$w_mat25 > species_params$w_min)) assert_that(all(species_params$w_mat25 < species_params$w_mat)) params@species_params$w_mat25 <- species_params$w_mat25 - + maturity <- params@maturity # To get the right dimensions - maturity[] <- + maturity[] <- unlist( tapply(params@w, seq_along(params@w), function(wx, w_mat, w_mat25) { @@ -235,10 +235,10 @@ setReproduction <- function(params, maturity = NULL, w_mat25 = species_params$w_mat25 ) ) - - # For reasons of efficiency we next set all very small values to 0 + + # For reasons of efficiency we next set all very small values to 0 maturity[maturity < 1e-8] <- 0 - + # If maturity is protected by a comment, keep the old value if (!is.null(comment(params@maturity))) { if (different(params@maturity, maturity)) { @@ -249,21 +249,21 @@ setReproduction <- function(params, maturity = NULL, } } assert_that(all(maturity >= 0 & maturity <= 1)) - + # Need to update psi because it contains maturity as a factor if (different(params@maturity, maturity)) { params@psi[] <- params@psi / params@maturity * maturity params@psi[is.nan(params@psi)] <- 0 } - + params@maturity[] <- maturity comment(params@maturity) <- comment(maturity) - + # set reproduction proportion ---- if (!is.null(repro_prop)) { assert_that(is.array(repro_prop), identical(dim(repro_prop), dim(params@psi))) - if (!is.null(dimnames(repro_prop)) && + if (!is.null(dimnames(repro_prop)) && !all(dimnames(repro_prop)[[1]] == species_params$species)) { stop("You need to use the same ordering of species as in the ", "params object: ", toString(species_params$species)) @@ -285,7 +285,7 @@ setReproduction <- function(params, maturity = NULL, # Set defaults for w_repro_max species_params <- set_species_param_default(species_params, "w_repro_max", params@species_params$w_max) - + repro_prop <- array( unlist( tapply(params@w, seq_along(params@w), @@ -296,17 +296,17 @@ setReproduction <- function(params, maturity = NULL, ), dim = c(nrow(species_params), length(params@w))) repro_prop[repro_prop > 1] <- 1 } - + psi <- params@maturity * repro_prop # psi should never be larger than 1 psi[psi > 1] <- 1 # Set psi for all w > w_repro_max to 1 psi[outer(species_params$w_repro_max, params@w, "<")] <- 1 assert_that(all(psi >= 0 & psi <= 1)) - + # if the slot is protected and the user did not supply a new repro_prop # then don't overwrite the slot with auto-generated values. We can - # detect whether repro_prop is user-supplied by checking whether it has + # detect whether repro_prop is user-supplied by checking whether it has # a comment. if (!is.null(comment(params@psi)) && is.null(comment(repro_prop))) { if (different(params@psi, psi)) { @@ -317,36 +317,36 @@ setReproduction <- function(params, maturity = NULL, params@psi[] <- psi comment(params@psi) <- comment(repro_prop) } - + # If no erepro (reproductive efficiency), then set to 1 params <- set_species_param_default(params, "erepro", 1) if (!all(params@species_params$erepro > 0)) { stop("Some species have negative reproductive efficiency.") } - + # RDD function is currently called only with three arguments if (!all(names(formals(RDD)) %in% c("rdi", "species_params", "params", "t", "..."))) { stop("Arguments of RDD function can only contain 'rdi', 'species_params' and `t`.") } params@rates_funcs$RDD <- RDD if (identical(params@rates_funcs$RDD, "BevertonHoltRDD")) { - + # for legacy reasons (R_max used to be called r_max): if ("r_max" %in% names(params@species_params)) { params@species_params$R_max <- params@species_params$r_max params@species_params$r_max <- NULL message("The 'r_max' column has been renamed to 'R_max'.") } - + params <- set_species_param_default(params, "R_max", Inf) } - + params@time_modified <- lubridate::now() return(params) } #' @rdname setReproduction -#' @return `getMaturityProportion()` or equivalently `maturity(): +#' @return `getMaturityProportion()` or equivalently `maturity(): #' An array (species x size) that holds the proportion #' of individuals of each species at size that are mature. #' @export @@ -379,11 +379,11 @@ getReproductionProportion <- function(params) { assert_that(is(params, "MizerParams")) repro_prop <- params@psi / params@maturity repro_prop[is.nan(repro_prop)] <- 0 + repro_prop[repro_prop > 1] <- 1 comment(repro_prop) <- comment(params@psi) repro_prop } - #' @rdname setReproduction #' @export repro_prop <- function(params) { diff --git a/tests/testthat/test-setReproduction.R b/tests/testthat/test-setReproduction.R index 734ebae1..628e2f32 100644 --- a/tests/testthat/test-setReproduction.R +++ b/tests/testthat/test-setReproduction.R @@ -52,17 +52,17 @@ test_that("Comment works on maturity", { maturity <- params@maturity params <- setReproduction(params, maturity = maturity) expect_identical(comment(params@maturity), "set manually") - + # comment is stored comment(maturity) <- "test" params <- setReproduction(params, maturity = maturity) expect_identical(comment(params@maturity), "test") - + # if no comment, previous comment is kept comment(maturity) <- NULL params <- setReproduction(params, maturity = maturity) expect_identical(comment(params@maturity), "test") - + # no message when nothing changes expect_message(setReproduction(params), NA) # but message when a change is not stored due to comment @@ -83,17 +83,17 @@ test_that("Comment works on psi", { repro_prop <- getReproductionProportion(params) params <- setReproduction(params, repro_prop = repro_prop) expect_identical(comment(getReproductionProportion(params)), "set manually") - + # comment is stored comment(repro_prop) <- "test" params <- setReproduction(params, repro_prop = repro_prop) expect_identical(comment(getReproductionProportion(params)), "test") - + # if no comment, previous comment is kept comment(repro_prop) <- NULL params <- setReproduction(params, repro_prop = repro_prop) expect_identical(comment(getReproductionProportion(params)), "test") - + # no message when nothing changes expect_message(setReproduction(params), NA) # but message when a change is not stored due to comment @@ -127,13 +127,23 @@ test_that("getReproductionProportion works", { expect_unchanged(params, params2) }) +test_that("getReproductionProportion returns a proportion",{ + params <- NS_params + # Make extremely wide maturity ogive + species_params(params)$w_mat25 <- 1 + repro_prop <- getReproductionProportion(params) + expect_true(all(repro_prop >= 0)) + expect_true(all(repro_prop <= 1)) +}) + test_that("Can get and set repro_prop", { params <- NS_params - new <- repro_prop(params) ^ 2 + new <- repro_prop(params) ^ 2 comment(new) <- "test" repro_prop(params) <- new expect_equal(repro_prop(params)[2, 50], new[2, 50]) }) + test_that("Can get and set maturity", { params <- NS_params new <- 1/2 * maturity(params)