R/rate_functions.R

Defines functions getFluxGradient.MizerSim getFluxGradient.MizerParams getFluxGradient getFlux.MizerSim flux_units flux_from_rates getFlux.MizerParams getFlux getRDD.MizerSim getRDD.MizerParams getRDD getRDI.MizerSim getRDI.MizerParams getRDI getEGrowth.MizerSim getEGrowth.MizerParams getEGrowth getERepro.MizerSim getERepro.MizerParams getERepro getMort.MizerSim getMort.MizerParams getMort getFMort.MizerSim getFMort.MizerParams getFMort getFMortGear.MizerSim getFMortGear.MizerParams getFMortGear getResourceMort.MizerParams getResourceMort getPredMort.MizerSim getPredMort.MizerParams getPredMort getPredRate.MizerSim getPredRate.MizerParams getPredRate getEReproAndGrowth.MizerSim getEReproAndGrowth.MizerParams getEReproAndGrowth getCriticalFeedingLevel.MizerParams getCriticalFeedingLevel getFeedingLevel.MizerSim getFeedingLevel.MizerParams getFeedingLevel getEncounter.MizerSim getEncounter.MizerParams getEncounter getRates.MizerParams getRates

Documented in flux_from_rates flux_units getCriticalFeedingLevel getEGrowth getEncounter getERepro getEReproAndGrowth getFeedingLevel getFlux getFluxGradient getFluxGradient.MizerParams getFluxGradient.MizerSim getFlux.MizerParams getFlux.MizerSim getFMort getFMortGear getMort getPredMort getPredRate getRates getRDD getRDI getResourceMort

# Copyright 2012 Finlay Scott and Julia Blanchard.
# Copyright 2018 Gustav Delius and Richard Southwell.
# Distributed under the GPL 3 or later
# Maintainer: Gustav Delius, University of York, <gustav.delius@york.ac.uk>

# Note on calling signatures of rate functions
#
# Most rate functions have the arguments as getRates():
#    params, n, n_pp, n_other, t, ...
# This is the case for getEncounter, getResourceMort, getPredRate,
# getEGrowth, getERepro, getEReproAndGrowth, getRDI
# Some have these arguments and in addition the effort argument:
#   getRates, getMort
# Some functions also accept a MizerSim and return the rate at the saved times:
# getEncounter(object, n, n_pp, n_other, time_range, drop, ...)
# getFeedingLevel(object, n, n_pp, n_other, time_range, drop, ...)
# getEReproAndGrowth(object, n, n_pp, n_other, time_range, drop, ...)
# getPredRate(object, n, n_pp, n_other, time_range, drop, ...)
# getPredMort(object, n, n_pp, n_other, time_range, drop, ...)
# getFMort(object, effort, time_range, drop, n, n_pp, n_other, t, ...)
# getFMortGear(object, effort, time_range, n, n_pp, n_other, t, ...)
# getMort(object, n, n_pp, n_other, effort, time_range, drop, ...)
# getERepro(object, n, n_pp, n_other, time_range, drop, ...)
# getEGrowth(object, n, n_pp, n_other, time_range, drop, ...)
# getDiffusion(object, n, n_pp, n_other, t, time_range, drop, ...)
# getRDI(object, n, n_pp, n_other, time_range, ...)
# getRDD(object, n, n_pp, n_other, time_range, ...)
# getFlux(object, n, n_pp, n_other, time_range, drop, ...)

#' Get all rates
#'
#' Calls other rate functions in sequence and collects the results in a list.
#' The rates returned are encounter, feeding level, energy for growth and
#' reproduction, predation rate, predation mortality, and resource mortality.
#' The purpose of this function is to provide a convenient way to get all the
#' rates at once, and to ensure that they are all calculated at the same time
#' step with the same inputs. The rates are returned in a list with the same
#' names as the rate functions that calculate them, so for example the encounter
#' rate is returned in the list element named "encounter" and is calculated with
#' the getEncounter() function.
#'
#' When mizer needs to calculate the rates during a simulation it does not use
#' this function but instead the faster `projectRates()`.
#'
#' @inheritParams mizerRates
#' @export
#' @family rate functions
#' @examples
#' rates <- getRates(NS_params)
#' names(rates)
#' identical(rates$encounter, getEncounter(NS_params))
getRates <- function(params, n = initialN(params),
                     n_pp = initialNResource(params),
                     n_other = initialNOther(params),
                     effort, t = 0, ...) {
    UseMethod("getRates")
}
#' @export
getRates.MizerParams <- function(params, n = initialN(params),
                     n_pp = initialNResource(params),
                     n_other = initialNOther(params),
                     effort, t = 0, ...) {
    params <- validParams(params)
    if (missing(effort)) {
        effort <- params@initial_effort
    }

    rates_fns <- projectRateFunctions(params)
    rates_fns$Rates(
        params, n = n, n_pp = n_pp, n_other = n_other,
        t = t, effort = effort,
        rates_fns = rates_fns, ...)
}

#' Get encounter rate
#'
#' Returns the rate at which a predator of species \eqn{i} and
#' weight \eqn{w} encounters food (grams/year).
#'
#' @inherit mizerEncounter details sections
#'
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (predator species x predator
#'   size) with the encounter rates.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x predator
#'   species x predator size) with the encounter rates at every time step.
#'   If `drop = TRUE` then dimensions of length 1 will be removed.
#' @export
#' @family rate functions
#' @examples
#' encounter <- getEncounter(NS_params)
#' str(encounter)
getEncounter <- function(object, ...) {
    UseMethod("getEncounter")
}
#' @export
getEncounter.MizerParams <- function(object, n = initialN(object),
                         n_pp = initialNResource(object),
                         n_other = initialNOther(object),
                         t = 0, ...) {
    params <- validParams(object)
    assert_that(is.array(n),
                is.numeric(n_pp),
                is.list(n_other),
                is.number(t),
                identical(dim(n), dim(params@initial_n)),
                identical(length(n_pp), length(params@initial_n_pp)),
                identical(length(n_other), length(params@initial_n_other))
    )
    if (params@rates_funcs$Encounter == "mizerEncounter") {
        encounter <- projectEncounter(params, n = n, n_pp = n_pp,
                                      n_other = n_other, t = t, ...)
    } else {
        encounter_fn <- get(params@rates_funcs$Encounter)
        encounter <- encounter_fn(params, n = n, n_pp = n_pp,
                                  n_other = n_other, t = t, ...)
    }
    ArraySpeciesBySize(encounter, value_name = "Encounter rate",
             units = "g/year", params = params)
}

#' @export
getEncounter.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                                  time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "Encounter",
                  slot = "encounter", value_name = "Encounter rate",
                  units = "g/year", ...)
}


#' Get feeding level
#'
#' Returns the feeding level.
#' By default this function uses [mizerFeedingLevel()] to calculate
#' the feeding level, but this can be overruled via [setRateFunction()].
#'
#' @inherit mizerFeedingLevel details sections
#'
#' @template param_object_dots
#'
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (predator species x predator
#'   size) with the feeding level.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x predator
#'   species x predator size) with the feeding level at every time step.
#'   If `drop = TRUE` then dimensions of length 1 will be removed.
#'
#' @export
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Get initial feeding level
#' fl <- getFeedingLevel(params)
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the feeding level at all saved time steps
#' fl <- getFeedingLevel(sim)
#' # Get the feeding level for years 15 - 20
#' fl <- getFeedingLevel(sim, time_range = c(15, 20))
#' }
getFeedingLevel <- function(object, ...) {
    UseMethod("getFeedingLevel")
}

#' @export
getFeedingLevel.MizerParams <- function(object, n, n_pp, n_other,
                            time_range, drop = FALSE, ...) {
    params <- validParams(object)
    if (missing(time_range)) time_range <- 0
    t <- min(time_range)
    if (missing(n)) n <- params@initial_n
    if (missing(n_pp)) n_pp <- params@initial_n_pp
    if (missing(n_other)) n_other <- params@initial_n_other
    # calculate feeding level
    encounter <- getEncounter(params, n, n_pp, n_other, t = t)
    if (params@rates_funcs$FeedingLevel == "mizerFeedingLevel") {
        feeding_level <- projectFeedingLevel(
            params, n = n, n_pp = n_pp, n_other = n_other,
            encounter = encounter, t = t)
    } else {
        f <- get(params@rates_funcs$FeedingLevel)
        feeding_level <- f(params, n = n, n_pp = n_pp, n_other = n_other,
                           encounter = encounter, t = t)
    }
    return(ArraySpeciesBySize(feeding_level, value_name = "Feeding level",
                              type = "proportion", params = params))
}

#' @export
getFeedingLevel.MizerSim <- function(object, n, n_pp, n_other,
                            time_range, drop = FALSE, ...) {
    sim <- object
    sim_size_rate(sim, time_range, drop, target = "FeedingLevel",
                  slot = "feeding_level", value_name = "Feeding level",
                  type = "proportion", ...)
}


#' Get critical feeding level
#'
#' The critical feeding level is the feeding level at which the food intake is
#' just high enough to cover the metabolic costs, with nothing left over for
#' growth or reproduction.
#'
#' @param params A MizerParams object
#' @return An `ArraySpeciesBySize` object (species x size) with the critical feeding level
#' @export
#' @examples
#' \donttest{
#' str(getFeedingLevel(NS_params))
#' }
getCriticalFeedingLevel <- function(params) {
    UseMethod("getCriticalFeedingLevel")
}
#' @export
getCriticalFeedingLevel.MizerParams <- function(params) {
    params <- validParams(params)
    result <- params@metab / params@intake_max / params@species_params$alpha
    ArraySpeciesBySize(result, value_name = "Critical feeding level",
                       type = "proportion", params = params)
}


#' Get energy rate available for reproduction and growth
#'
#' Calculates the energy rate \eqn{E_{r.i}(w)} (grams/year) available for
#' reproduction and growth after metabolism and movement have been accounted
#' for.
#'
#' @inherit mizerEReproAndGrowth details sections
#'
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) with the
#'   energy rate \eqn{E_{r.i}(w)} available for growth and reproduction
#'   (grams/year).
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the energy rate at every time step. If `drop = TRUE` then
#'   dimensions of length 1 will be removed.
#' @export
#' @seealso The part of this energy rate that is invested into growth is
#'   calculated with [getEGrowth()] and the part that is invested into
#'   reproduction is calculated with [getERepro()].
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the energy at a particular time step
#' e <- getEReproAndGrowth(params, n = N(sim)[15, , ],
#'                         n_pp = NResource(sim)[15, ], t = 15)
#' # Rate at this time for Sprat of size 2g
#' e["Sprat", "2"]
#' }
getEReproAndGrowth <- function(object, ...) {
    UseMethod("getEReproAndGrowth")
}

#' @export
getEReproAndGrowth.MizerParams <- function(object, n = initialN(object),
                               n_pp = initialNResource(object),
                               n_other = initialNOther(object),
                               t = 0, ...) {
    params <- validParams(object)
    encounter <- getEncounter(params, n, n_pp, n_other, t = t)
    feeding_level <- getFeedingLevel(params, n, n_pp, n_other, time_range = t)
    if (params@rates_funcs$EReproAndGrowth == "mizerEReproAndGrowth") {
        e <- projectEReproAndGrowth(
            params, n = n, n_pp = n_pp, n_other = n_other, t = t,
            encounter = encounter, feeding_level = feeding_level)
    } else {
        f <- get(params@rates_funcs$EReproAndGrowth)
        e <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
               encounter = encounter, feeding_level = feeding_level)
    }
    ArraySpeciesBySize(e, value_name = "Energy for growth and reproduction",
             units = "g/year", params = params)
}

#' @export
getEReproAndGrowth.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                                        time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "EReproAndGrowth",
                  slot = "e",
                  value_name = "Energy for growth and reproduction",
                  units = "g/year", ...)
}

#' Get predation rate
#'
#' Calculates the potential rate (in units 1/year) at which a prey individual of
#' a given size \eqn{w} is killed by predators from species \eqn{j}. In formulas
#' \deqn{{\tt pred\_rate}_j(w_p) = \int \phi_j(w,w_p) (1-f_j(w))
#'   \gamma_j(w) N_j(w) \, dw.}{pred_rate_j(w_p) = \int\phi_i(w,w_p) (1-f_i(w))
#'   \gamma_i(w) N_i(w) dw.}
#' This potential rate is used in [getPredMort()] to
#' calculate the realised predation mortality rate on the prey individual.
#'
#' @inherit mizerPredRate details sections
#'
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (predator species x prey
#'   size), where the prey size runs over fish community plus resource spectrum.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x predator
#'   species x prey size) with the predation rates at every time step.
#'   If `drop = TRUE` then dimensions of length 1 will be removed.
#' @export
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Predation rate in initial state
#' pred_rate <- getPredRate(params)
#' str(pred_rate)
#' # With constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the feeding level at one time step
#' pred_rate <- getPredRate(params, n = N(sim)[15, , ],
#'                          n_pp = NResource(sim)[15, ], t = 15)
#' }
getPredRate <- function(object, ...) {
    UseMethod("getPredRate")
}

#' @export
getPredRate.MizerParams <- function(object, n = initialN(object),
                        n_pp = initialNResource(object),
                        n_other = initialNOther(object),
                        t = 0, ...) {
    params <- validParams(object)
    feeding_level <- getFeedingLevel(params, n = n, n_pp = n_pp,
                                     n_other = n_other, time_range = t)
    if (params@rates_funcs$PredRate == "mizerPredRate") {
        pred_rate <- projectPredRate(
            params, n = n, n_pp = n_pp, n_other = n_other, t = t,
            feeding_level = feeding_level)
    } else {
        f <- get(params@rates_funcs$PredRate)
        pred_rate <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
                       feeding_level = feeding_level)
    }
    dimnames(pred_rate) <- list(sp = dimnames(params@initial_n)$sp,
                                w_prey = as.character(signif(params@w_full, 3)))
    ArraySpeciesBySize(pred_rate, value_name = "Predation rate",
                       units = "1/year", params = params)
}

#' @export
getPredRate.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                                 time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "PredRate",
                  slot = "pred_rate", value_name = "Predation rate",
                  units = "1/year", ...)
}

#' Get total predation mortality rate
#'
#' Calculates the total predation mortality rate \eqn{\mu_{p,i}(w_p)} (in units
#' of 1/year) on each prey species by prey size:
#' \deqn{\mu_{p.i}(w_p) = \sum_j {\tt pred\_rate}_j(w_p)\, \theta_{ji}.}{
#'   \mu_{p.i}(w_p) = \sum_j pred_rate_j(w_p) \theta_{ji}.}
#' The predation rate `pred_rate` is returned by [getPredRate()].
#'
#' @inherit mizerPredMort details sections
#' @template param_object_dots
#'
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (prey species x prey size)
#'   with the predation mortality rates.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x prey species
#'   x prey size) with the predation mortality at every time step.
#'   If `drop = TRUE` then dimensions of length 1 will be removed.
#' @family rate functions
#' @export
#' @examples
#' \donttest{
#' params <- NS_params
#' # Predation mortality in initial state
#' M2 <- getPredMort(params)
#' str(M2)
#' # With constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get predation mortality at one time step
#' M2 <- getPredMort(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ])
#' # Get predation mortality at all saved time steps
#' M2 <- getPredMort(sim)
#' str(M2)
#' # Get predation mortality over the years 15 - 20
#' M2 <- getPredMort(sim, time_range = c(15, 20))
#' }
getPredMort <- function(object, ...) {
    UseMethod("getPredMort")
}

#' @export
getPredMort.MizerParams <- function(object, n, n_pp, n_other,
                        time_range, drop = TRUE, ...) {
    params <- validParams(object)
    if (missing(n)) n <- params@initial_n
    if (missing(n_pp)) n_pp <- params@initial_n_pp
    if (missing(n_other)) n_other <- params@initial_n_other
    if (missing(time_range)) time_range <- 0
    t <- min(time_range)

    pred_rate <- getPredRate(params, n = n, n_pp = n_pp, n_other = n_other,
                             t = t)
    if (params@rates_funcs$PredMort == "mizerPredMort") {
        pred_mort <- projectPredMort(
            params, n = n, n_pp = n_pp, n_other = n_other, t = t,
            pred_rate = pred_rate)
    } else {
        f <- get(params@rates_funcs$PredMort)
        pred_mort <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
                       pred_rate = pred_rate)
    }
    # Predation mortality is a sink integrated against the prey density over the
    # prey bin, so under second-order bin-averaging it is a bin average and is
    # plotted at the geometric bin centre (see get_ArraySpeciesBySize_w()).
    ArraySpeciesBySize(pred_mort, value_name = "Predation mortality",
             units = "1/year", params = params, representation = "average")
}

#' @export
getPredMort.MizerSim <- function(object, n, n_pp, n_other,
                        time_range, drop = TRUE, ...) {
    sim <- object
    sim_size_rate(sim, time_range, drop, target = "PredMort",
                  slot = "pred_mort", value_name = "Predation mortality",
                  units = "1/year", representation = "average", ...)
}

#' Alias for `getPredMort()`
#'
#' `r lifecycle::badge("superseded")`
#' An alias provided for backward compatibility with mizer version <= 1.0
#' @inherit getPredMort
#' @export
#' @concept deprecated
getM2 <- getPredMort


#' Get predation mortality rate for resource
#'
#' Calculates the predation mortality rate \eqn{\mu_p(w)} on the resource
#' spectrum by resource size (in units 1/year).
#'
#' @inherit mizerResourceMort
#' @inheritParams mizerRates
#'
#' @return A vector of mortality rate by resource size.
#' @family rate functions
#' @export
#' @examples
#' \donttest{
#' params <- NS_params
#' # With constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get resource mortality at one time step
#' getResourceMort(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ])
#' }
getResourceMort <-
    function(params, n = initialN(params),
             n_pp = initialNResource(params),
             n_other = initialNOther(params),
             t = 0, ...) {
    UseMethod("getResourceMort")
}

#' @export
getResourceMort.MizerParams <- function(params, n = initialN(params),
             n_pp = initialNResource(params),
             n_other = initialNOther(params),
             t = 0, ...) {
    params <- validParams(params)

    pred_rate <- getPredRate(params, n = n, n_pp = n_pp, n_other = n_other,
                             t = t)
    if (params@rates_funcs$ResourceMort == "mizerResourceMort") {
        mort <- projectResourceMort(
            params, n = n, n_pp = n_pp, n_other = n_other,
            t = t, pred_rate = pred_rate)
    } else {
        f <- get(params@rates_funcs$ResourceMort)
        mort <- f(params, n = n, n_pp = n_pp, n_other = n_other,
                  t = t, pred_rate = pred_rate)
    }

    # Extensions such as mizerMR return a resource x size matrix here. Leave
    # such non-vector results unwrapped so the extension can wrap them in its
    # own class; only wrap the plain single-resource vector.
    if (is.null(dim(mort))) {
        names(mort) <- names(params@initial_n_pp)
        mort <- ArrayResourceBySize(mort, value_name = "Resource mortality",
                                    units = "1/year", params = params)
    }
    mort
}

#' Alias for `getResourceMort()`
#'
#' `r lifecycle::badge("superseded")`
#' An alias provided for backward compatibility with mizer version <= 1.0
#' @inherit getResourceMort
#' @export
#' @concept deprecated
getM2Background <- getResourceMort


#' Get the fishing mortality by time, gear, species and size
#'
#' Calculates the fishing mortality rate \eqn{F_{g,i,w}} by gear, species and
#' size and possibly time (in units 1/year).
#'
#' @param object A \linkS4class{MizerParams} or \linkS4class{MizerSim} object.
#' @param ... Additional arguments that depend on the class of `object`.
#'
#'   **For a \linkS4class{MizerParams} object:**
#'   \describe{
#'     \item{`effort`}{The effort for each fishing gear. See notes below.
#'       Defaults to the initial effort stored in `object`.}
#'     \item{`n`}{A matrix of species abundances (species x size). Defaults to
#'       the initial abundances stored in `object`.}
#'     \item{`n_pp`}{A vector of the resource abundance by size. Defaults to the
#'       initial resource abundance stored in `object`.}
#'     \item{`n_other`}{A named list of the abundances of other dynamical
#'       components. Defaults to the initial values stored in `object`.}
#'     \item{`t`}{The time for which to do the calculation. Defaults to 0.}
#'   }
#'
#'   **For a \linkS4class{MizerSim} object:**
#'   \describe{
#'     \item{`time_range`}{Subset the returned fishing mortalities by time. The
#'       time range is either a vector of values, a vector of min and max time,
#'       or a single value. Defaults to the whole time range of the
#'       simulation.}
#'   }
#'
#' @return An array. If the effort argument has a time dimension, or a
#'   `MizerSim` is passed in, the output array has four dimensions (time x
#'   gear x species x size). If the effort argument does not have a time
#'   dimension (i.e. it is a vector or a single numeric), the output array has
#'   three dimensions (gear x species x size).
#' @note Here: fishing mortality = catchability x selectivity x effort.
#'
#' The `effort` argument is only used if a `MizerParams` object is
#' passed in. The `effort` argument can be a two dimensional array (time x
#' gear), a vector of length equal to the number of gears (each gear has a
#' different effort that is constant in time), or a single numeric value (each
#' gear has the same effort that is constant in time). The order of gears in the
#' `effort` argument must be the same the same as in the `MizerParams`
#' object. If the `effort` argument is not supplied, its value is taken
#' from the `@initial_effort` slot in the params object.
#'
#' If the object argument is of class `MizerSim` then the effort slot of
#' the `MizerSim` object is used and the `effort` argument is not
#' used.
#' @export
#' @family rate functions
#' @examples
#' \donttest{
#' params <-NS_params
#' # Get the fishing mortality in initial state
#' F <- getFMortGear(params, effort = 1)
#' str(F)
#' # Get the initial fishing mortality when effort is different
#' # between the four gears:
#' F <- getFMortGear(params, effort = c(0.5, 1, 1.5, 0.75))
#' # Get the fishing mortality when effort is different
#' # between the four gears and changes with time:
#' effort <- array(NA, dim = c(20, 4))
#' effort[, 1] <- seq(from=0, to = 1, length = 20)
#' effort[, 2] <- seq(from=1, to = 0.5, length = 20)
#' effort[, 3] <- seq(from=1, to = 2, length = 20)
#' effort[, 4] <- seq(from=2, to = 1, length = 20)
#' F <- getFMortGear(params, effort = effort)
#' str(F)
#' # Get the fishing mortality using the effort already held in a MizerSim object.
#' sim <- project(params, t_max = 20, effort = 0.5)
#' F <- getFMortGear(sim)
#' F <- getFMortGear(sim, time_range = c(10, 20))
#' }
#'
getFMortGear <- function(object, ...) {
    UseMethod("getFMortGear")
}

#' @export
getFMortGear.MizerParams <- function(object, effort, time_range,
                                     n, n_pp, n_other, t = 0, ...) {
    params <- validParams(object)
    if (missing(effort)) {
        effort <- params@initial_effort
    }
    if (is(effort, "numeric")) {
            no_gear <- dim(params@catchability)[1]
            # If a single value, just repeat it for all gears
            if (length(effort) == 1) {
                effort <- rep(effort, no_gear)
            }
            if (length(effort) != no_gear) {
                stop("Effort must be a single value or a vector as long as the number of gears\n")
            }

            f <- mizerFMortGear(params, effort = effort)
            dimnames(f) <- dimnames(params@selectivity)
            return(f)
        } else {
            # assuming effort is a matrix, and object is of MizerParams class
            no_gear <- dim(params@catchability)[1]
            if (dim(effort)[2] != no_gear)
                stop("Effort array must have a single value or a vector as long as the number of gears for each time step\n")
            # Make the output array - note that we put time as last dimension
            # and then aperm before returning. This is because of the order of
            # the values when we call the other getFMortGear function.
            # Fill it up by calling the other function and passing in each line
            # of the effort matrix
            out <- array(NA, dim = c(dim(params@selectivity), dim(effort)[1]),
                         dimnames = c(dimnames(params@selectivity),
                                      list(time = dimnames(effort)[[1]])))
            out[] <- apply(effort, 1, function(x) mizerFMortGear(params, x))
            out <- aperm(out, c(4, 1, 2, 3))
            return(out)
        }
}

#' @export
getFMortGear.MizerSim <- function(object, effort, time_range,
                                  n, n_pp, n_other, t = 0, ...) {
    sim <- object
    time_elements <- get_sim_rate_time_elements(sim, time_range)
    # Validate the params once rather than at every saved time step.
    params <- validParams(sim@params)

    # Work slice by slice, like the other MizerSim rate methods, so any future
    # state-aware gear mortality calculation receives the matching simulation
    # state and time rather than only the effort matrix.
    f_mort_gear <- plyr::aaply(which(time_elements), 1, function(time_idx) {
        slice <- get_sim_rate_slice(sim, time_idx)
        getFMortGear(params, effort = slice$effort, n = slice$n,
                     n_pp = slice$n_pp, n_other = slice$n_other,
                     t = slice$t, ...)
    }, .drop = FALSE)
    names(dimnames(f_mort_gear))[[1]] <- "time"
    f_mort_gear
}

#' Get the total fishing mortality rate from all fishing gears by time, species
#' and size.
#'
#' Calculates the total fishing mortality  (in units 1/year) from all gears by
#' species and size and possibly time. See `setFishing()` for details of
#' how fishing gears are set up.
#'
#' The total fishing mortality is just the sum of the fishing mortalities
#' imposed by each gear, \eqn{F_i(w)=\sum_g F_{g,i,w}}.
#' The fishing mortality for each gear is obtained as catchability x
#' selectivity x effort.
#'
#' @param object A \linkS4class{MizerParams} or \linkS4class{MizerSim} object.
#' @param ... Additional arguments that depend on the class of `object`.
#'
#'   **For a \linkS4class{MizerParams} object:**
#'   \describe{
#'     \item{`effort`}{The effort of each fishing gear. See notes below.
#'       Defaults to the initial effort stored in `object`.}
#'     \item{`n`}{A matrix of species abundances (species x size). Defaults to
#'       the initial abundances stored in `object`.}
#'     \item{`n_pp`}{A vector of the resource abundance by size. Defaults to the
#'       initial resource abundance stored in `object`.}
#'     \item{`n_other`}{A named list of the abundances of other dynamical
#'       components. Defaults to the initial values stored in `object`.}
#'     \item{`t`}{The time for which to do the calculation. Defaults to 0.}
#'   }
#'
#'   **For a \linkS4class{MizerSim} object:**
#'   \describe{
#'     \item{`time_range`}{Subset the returned fishing mortalities by time. The
#'       time range is either a vector of values, a vector of min and max time,
#'       or a single value. Defaults to the whole time range of the
#'       simulation.}
#'     \item{`drop`}{Should dimensions of length 1 be dropped, e.g. if your
#'       community only has one species it might make presentation of results
#'       easier. Defaults to `TRUE`.}
#'   }
#'
#' @return
#' * `MizerParams` with vector effort: An `ArraySpeciesBySize` object
#'   (species x size) with the fishing mortality rates.
#' * `MizerParams` with time-dimensioned effort or `MizerSim`: An
#'   `ArrayTimeBySpeciesBySize` object (time x species x size).
#'
#' The `effort` argument is only used if a `MizerParams` object is
#' passed in. The `effort` argument can be a two dimensional array (time x
#' gear), a vector of length equal to the number of gears (each gear has a
#' different effort that is constant in time), or a single numeric value (each
#' gear has the same effort that is constant in time). The order of gears in the
#' `effort` argument must be the same as in the `MizerParams`
#' object.
#'
#' If the object argument is of class `MizerSim` then the effort slot of
#' the `MizerSim` object is used and the `effort` argument is not
#' used.
#'
#' @inheritSection mizerFMort Your own fishing mortality function
#'
#' @export
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Get the total fishing mortality in the initial state
#' F <- getFMort(params, effort = 1)
#' str(F)
#' # Get the initial total fishing mortality when effort is different
#' # between the four gears:
#' F <- getFMort(params, effort = c(0.5,1,1.5,0.75))
#' # Get the total fishing mortality when effort is different
#' # between the four gears and changes with time:
#' effort <- array(NA, dim = c(20,4))
#' effort[, 1] <- seq(from = 0, to = 1, length = 20)
#' effort[, 2] <- seq(from = 1, to = 0.5, length = 20)
#' effort[, 3] <- seq(from = 1, to = 2, length = 20)
#' effort[, 4] <- seq(from = 2, to = 1, length = 20)
#' F <- getFMort(params, effort = effort)
#' str(F)
#' # Get the total fishing mortality using the effort already held in a
#' # MizerSim object.
#' sim <- project(params, t_max = 20, effort = 0.5)
#' F <- getFMort(sim)
#' F <- getFMort(sim, time_range = c(10, 20))
#' }
getFMort <- function(object, ...) {
    UseMethod("getFMort")
}

#' @export
getFMort.MizerParams <- function(object, effort, time_range, drop = TRUE,
                                 n, n_pp, n_other, t, ...) {
    params <- validParams(object)
    if (missing(effort)) {
        effort <- params@initial_effort
    }
    if (missing(t)) {
        # Legacy support: before `getFMort()` accepted `t` directly,
        # `time_range` was used to set the time passed to custom FMort
        # functions. New code should pass `t`.
        if (missing(time_range)) time_range <- 0
        t <- min(time_range)
    }
    if (missing(n)) n <- params@initial_n
    if (missing(n_pp)) n_pp <- params@initial_n_pp
    if (missing(n_other)) n_other <- params@initial_n_other
    no_gears <- dim(params@catchability)[[1]]
    f <- if (params@rates_funcs$FMort == "mizerFMort") {
        projectFMort
    } else {
        get(params@rates_funcs$FMort)
    }
    if (length(dim(effort)) == 2) {
        times <- dimnames(effort)$time
        f_mort <- array(0,
                        dim = c(dim(effort)[[1]], dim(params@initial_n)),
                            dimnames = c(list(time = times),
                                         dimnames(params@initial_n)))
        times <- as.numeric(times)
        for (i in seq_len(dim(effort)[1])) {
            args <- list(
                params = params, n = n, n_pp = n_pp, n_other = n_other,
                effort = effort[i, ], t = times[i],
                e_growth = getEGrowth(params, n = n, n_pp = n_pp,
                                      n_other = n_other, t = times[i]),
                pred_mort = getPredMort(params, n = n, n_pp = n_pp,
                                        n_other = n_other,
                                        time_range = times[i]))
            f_mort[i, , ] <- do.call(f, c(args, list(...)))
        }
        return(f_mort)
    } else if (length(effort) <= 1) {
        args <- list(
            params = params, n = n, n_pp = n_pp, n_other = n_other,
            effort = rep(effort, no_gears), t = t,
            e_growth = getEGrowth(params, n = n, n_pp = n_pp,
                                  n_other = n_other, t = t),
            pred_mort = getPredMort(params, n = n, n_pp = n_pp,
                                    n_other = n_other, time_range = t))
        fmort <- do.call(f, c(args, list(...)))
        fmort <- ArraySpeciesBySize(fmort, value_name = "Fishing mortality",
                           units = "1/year", params = params,
                           representation = "average")
        return(fmort)
    } else if (length(effort) == no_gears) {
        args <- list(
            params = params, n = n, n_pp = n_pp, n_other = n_other,
            effort = effort, t = t,
            e_growth = getEGrowth(params, n = n, n_pp = n_pp,
                                  n_other = n_other, t = t),
            pred_mort = getPredMort(params, n = n, n_pp = n_pp,
                                    n_other = n_other, time_range = t))
        fmort <- do.call(f, c(args, list(...)))
        fmort <- ArraySpeciesBySize(fmort, value_name = "Fishing mortality",
                           units = "1/year", params = params,
                           representation = "average")
        return(fmort)
    } else {
        stop("Invalid effort argument")
    }
}
#'
#' @export
getFMort.MizerSim <- function(object, effort, time_range, drop = TRUE,
                              n, n_pp, n_other, t, ...) {
    sim <- object
    sim_size_rate(sim, time_range, drop, target = "FMort", slot = "f_mort",
                  value_name = "Fishing mortality", units = "1/year",
                  use_sim_effort = TRUE, representation = "average", ...)
}


#' Get total mortality rate
#'
#' Calculates the total mortality rate \eqn{\mu_i(w)}  (in units 1/year) on each
#' species by size from predation mortality, background mortality and fishing
#' mortality for a single time step.
#'
#' @inherit mizerMort details sections
#' @param object A \linkS4class{MizerParams} or \linkS4class{MizerSim} object.
#' @param ... Additional arguments that depend on the class of `object`.
#'
#'   **For a \linkS4class{MizerParams} object:**
#'   \describe{
#'     \item{`n`}{A matrix of species abundances (species x size). Defaults to
#'       the initial abundances stored in `object`.}
#'     \item{`n_pp`}{A vector of the resource abundance by size. Defaults to the
#'       initial resource abundance stored in `object`.}
#'     \item{`n_other`}{A named list of the abundances of other dynamical
#'       components. Defaults to the initial values stored in `object`.}
#'     \item{`effort`}{A numeric vector of the effort by gear or a single
#'       numeric effort value which is used for all gears. Defaults to the
#'       initial effort stored in `object`.}
#'     \item{`t`}{The time for which to do the calculation. Defaults to 0.}
#'   }
#'
#'   **For a \linkS4class{MizerSim} object:**
#'   \describe{
#'     \item{`time_range`}{The time range over which to return the rates. Either
#'       a vector of values, a vector of min and max time, or a single value.
#'       Defaults to the whole time range of the simulation.}
#'     \item{`drop`}{If `TRUE` then any dimension of length 1 is removed from the
#'       returned array.}
#'   }
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) with the
#'   total mortality rates.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the total mortality rates at every time step. If `drop = TRUE`
#'   then dimensions of length 1 will be removed.
#'
#' @export
#' @seealso [getPredMort()], [getFMort()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the total mortality at a particular time step
#' mort <- getMort(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ],
#'                 t = 15, effort = 0.5)
#' # Mortality rate at this time for Sprat of size 2g
#' mort["Sprat", "2"]
#' }
getMort <- function(object, ...) {
    UseMethod("getMort")
}

#' @export
getMort.MizerParams <- function(object,
                    n = initialN(object),
                    n_pp = initialNResource(object),
                    n_other = initialNOther(object),
                    effort = initial_effort(object),
                    t = 0, ...) {
    params <- validParams(object)
    rates_fns <- projectRateFunctions(params)
    r <- mizer_rates_subset(params, n = n, n_pp = n_pp, n_other = n_other,
                             t = t, effort = effort,
                             rates_fns = rates_fns, targets = "Mort", ...)
    return(ArraySpeciesBySize(r$mort, value_name = "Total mortality",
                     units = "1/year", params = params,
                     representation = "average"))
}

#' @export
getMort.MizerSim <- function(object, n, n_pp, n_other, effort, t, ...,
                             time_range, drop = TRUE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "Mort", slot = "mort",
                  value_name = "Total mortality", units = "1/year",
                  use_sim_effort = TRUE, representation = "average", ...)
}

#' Alias for `getMort()`
#'
#' `r lifecycle::badge("superseded")`
#' An alias provided for backward compatibility with mizer version <= 1.0
#' @inherit getMort
#' @export
#' @concept deprecated
getZ <- getMort


#' Get energy rate available for reproduction
#'
#' Calculates the energy rate (grams/year) available for reproduction after
#' growth and metabolism have been accounted for.
#'
#' @inherit mizerERepro details sections
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) holding
#'   \deqn{\psi_i(w)\max(0, E_{r.i}(w))}
#'   where \eqn{E_{r.i}(w)} is the rate at which energy becomes available for
#'   growth and reproduction, calculated with [getEReproAndGrowth()],
#'   and \eqn{\psi_i(w)} is the proportion of this energy that is used for
#'   reproduction. Negative values of \eqn{E_{r.i}(w)} are clipped to 0 before
#'   multiplying by \eqn{\psi_i(w)}. This proportion is taken from the `params`
#'   object and is set with [setReproduction()].
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the energy for reproduction at every time step. If `drop = TRUE`
#'   then dimensions of length 1 will be removed.
#' @export
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the rate at a particular time step
#' erepro <- getERepro(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ], t = 15)
#' # Rate at this time for Sprat of size 2g
#' erepro["Sprat", "2"]
#' }
getERepro <- function(object, ...) {
    UseMethod("getERepro")
}

#' @export
getERepro.MizerParams <- function(object, n = initialN(object),
                      n_pp = initialNResource(object),
                      n_other = initialNOther(object),
                      t = 0, ...) {
    params <- validParams(object)
    e <- getEReproAndGrowth(params, n = n, n_pp = n_pp, n_other = n_other,
                            t = t)
    if (params@rates_funcs$ERepro == "mizerERepro") {
        erepro <- projectERepro(params, n = n, n_pp = n_pp, n_other = n_other,
                                t = t, e = e)
    } else {
        f <- get(params@rates_funcs$ERepro)
        erepro <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
                    e = e)
    }
    # The per-capita reproductive investment is the integrand of the
    # reproduction integral, where (under second-order bin-averaging) it enters
    # as a bin average, so it is plotted at the geometric bin centre.
    ArraySpeciesBySize(erepro, value_name = "Energy for reproduction",
             units = "g/year", params = params, representation = "average")
}

#' @export
getERepro.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                               time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "ERepro", slot = "e_repro",
                  value_name = "Energy for reproduction", units = "g/year",
                  representation = "average", ...)
}

#' Alias for `getERepro()`
#'
#' `r lifecycle::badge("superseded")`
#' An alias provided for backward compatibility with mizer version <= 1.0
#' @inherit getERepro
#' @export
#' @concept deprecated
getESpawning <- getERepro


#' Get energy rate available for growth
#'
#' Calculates the energy rate \eqn{g_i(w)} (grams/year) available by species and
#' size for growth after metabolism, movement and reproduction have been
#' accounted for.
#'
#' @inherit mizerEGrowth details sections
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) with the
#'   somatic growth rates (grams/year).
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the growth rates at every time step. If `drop = TRUE` then
#'   dimensions of length 1 will be removed.
#' @export
#' @seealso [getERepro()], [getEReproAndGrowth()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the energy at a particular time step
#' growth <- getEGrowth(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ], t = 15)
#' # Growth rate at this time for Sprat of size 2g
#' growth["Sprat", "2"]
#' }
getEGrowth <- function(object, ...) {
    UseMethod("getEGrowth")
}

#' @export
getEGrowth.MizerParams <- function(object, n = initialN(object),
                       n_pp = initialNResource(object),
                       n_other = initialNOther(object),
                       t = 0, ...) {
    params <- validParams(object)
    e_repro <- getERepro(params, n = n, n_pp = n_pp, n_other = n_other,
                         t = t)
    e <- getEReproAndGrowth(params, n = n, n_pp = n_pp,
                            n_other = n_other, t = t)
    if (params@rates_funcs$EGrowth == "mizerEGrowth") {
        g <- projectEGrowth(params, n = n, n_pp = n_pp, n_other = n_other,
                            t = t, e_repro = e_repro, e = e)
    } else {
        f <- get(params@rates_funcs$EGrowth)
        g <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
               e_repro = e_repro, e = e)
    }
    ArraySpeciesBySize(g, value_name = "Growth rate",
             units = "g/year", params = params)
}

#' @export
getEGrowth.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                                time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t
    sim_size_rate(sim, time_range, drop, target = "EGrowth", slot = "e_growth",
                  value_name = "Growth rate", units = "g/year", ...)
}


#' Get density independent rate of egg production
#'
#' Calculates the density-independent rate of total egg production
#' \eqn{R_{di}}{R_di} (units 1/year) before density dependence, by species.
#'
#' @inherit mizerRDI details sections
#' @template param_object_dots_nodrop
#'
#' @return
#' * `MizerParams`: A numeric vector the length of the number of species.
#' * `MizerSim`: An `ArrayTimeBySpecies` object (time x species).
#' @export
#' @seealso [getRDD()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the density-independent reproduction rate at a particular time step
#' getRDI(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ], t = 15)
#' }
getRDI <- function(object, ...) {
    UseMethod("getRDI")
}

#' @export
getRDI.MizerParams <- function(object, n = initialN(object),
                   n_pp = initialNResource(object),
                   n_other = initialNOther(object),
                   t = 0, ...) {
    params <- validParams(object)
    e_repro <- getERepro(params, n = n, n_pp = n_pp, n_other = n_other,
                         t = t)
    e_growth <- getEGrowth(params, n = n, n_pp = n_pp, n_other = n_other,
                           t = t)
    diffusion <- getDiffusion(params, n = n, n_pp = n_pp, n_other = n_other,
                              t = t)
    mort <- getMort(params, n = n, n_pp = n_pp, n_other = n_other, t = t)
    if (params@rates_funcs$RDI == "mizerRDI") {
        rdi <- projectRDI(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
                          e_repro = e_repro, e_growth = e_growth, mort = mort,
                          diffusion = diffusion)
    } else {
        f <- get(params@rates_funcs$RDI)
        rdi <- f(params, n = n, n_pp = n_pp, n_other = n_other, t = t,
                 e_repro = e_repro, e_growth = e_growth, mort = mort,
                 diffusion = diffusion)
    }
    names(rdi) <- params@species_params$species
    rdi
}

#' @export
getRDI.MizerSim <- function(object, n, n_pp, n_other, t = 0,
                            time_range, ...) {
    sim <- object
    sim_species_rate(sim, time_range, target = "RDI", slot = "rdi",
                     value_name = "Density-independent reproduction rate",
                     units = "1/year", use_sim_effort = TRUE, ...)
}


#' Get density dependent reproduction rate
#'
#' Calculates the density dependent rate of egg production \eqn{R_i} (units
#' 1/year) for each species. This is the flux entering the smallest size class
#' of each species. The density dependent rate is the density independent
#' rate obtained with [getRDI()] after it has been put through the
#' density dependence function. This is the Beverton-Holt function
#' [BevertonHoltRDD()] by default, but this can be changed. See
#' [setReproduction()] for more details.
#'
#' @param object A \linkS4class{MizerParams} or \linkS4class{MizerSim} object.
#' @param ... Additional arguments that depend on the class of `object`.
#'
#'   **For a \linkS4class{MizerParams} object:**
#'   \describe{
#'     \item{`n`}{A matrix of species abundances (species x size). Defaults to
#'       the initial abundances stored in `object`.}
#'     \item{`n_pp`}{A vector of the resource abundance by size. Defaults to the
#'       initial resource abundance stored in `object`.}
#'     \item{`n_other`}{A named list of the abundances of other dynamical
#'       components. Defaults to the initial values stored in `object`.}
#'     \item{`t`}{The time for which to do the calculation. Defaults to 0.}
#'     \item{`rdi`}{A vector of density-independent reproduction rates for each
#'       species. If not specified, it is calculated internally using
#'       [getRDI()].}
#'   }
#'
#'   **For a \linkS4class{MizerSim} object:**
#'   \describe{
#'     \item{`time_range`}{The time range over which to return the rates. Either
#'       a vector of values, a vector of min and max time, or a single value.
#'       Defaults to the whole time range of the simulation.}
#'   }
#'
#' @return
#' * `MizerParams`: A numeric vector the length of the number of species.
#' * `MizerSim`: An `ArrayTimeBySpecies` object (time x species).
#' @export
#' @seealso [getRDI()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the rate at a particular time step
#' getRDD(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ], t = 15)
#' }
getRDD <- function(object, ...) {
    UseMethod("getRDD")
}

#' @export
getRDD.MizerParams <- function(object, n = initialN(object),
                   n_pp = initialNResource(object),
                   n_other = initialNOther(object),
                   t = 0,
                   rdi = getRDI(object, n = n, n_pp = n_pp,
                                n_other = n_other, t = t), ...) {
    params <- validParams(object)
    rdd <- projectRDD(params, rdi = rdi, species_params = params@species_params,
                      t = t, ...)
    names(rdd) <- params@species_params$species
    rdd
}

#' @export
getRDD.MizerSim <- function(object, n, n_pp, n_other, t = 0,
                            rdi, time_range, ...) {
    sim <- object
    sim_species_rate(sim, time_range, target = "RDD", slot = "rdd",
                     value_name = "Density-dependent reproduction rate",
                     units = "1/year", use_sim_effort = TRUE, ...)
}

#' Get flux into size bins
#'
#' Calculates the flux \eqn{J_i(w)} (numbers/year) entering each size class
#' from the one below it. This is composed of an advective flux from somatic
#' growth and a diffusive flux from the redistribution of individuals.
#'
#' At the recruitment size, the flux is simply the recruitment rate
#' \eqn{R_{dd,i}} (see [getRDD()]). For sizes below the recruitment size
#' the flux is zero.
#'
#' The flux at weight \eqn{w} is multiplied by \eqn{w} raised to the `power`
#' given by the `power` argument, similar to the `power` argument of
#' [plotSpectra()]. The default `power = 0` returns the flux of individuals
#' (numbers/year). With `power = 1` the result is the flux of biomass
#' (grams/year).
#'
#' @template param_object_dots
#' @param power The flux at weight \eqn{w} is multiplied by \eqn{w} raised to
#'   `power`. The default `power = 0` gives the flux of individuals
#'   (numbers/year), whereas `power = 1` gives the flux of biomass
#'   (grams/year).
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) with the
#'   flux entering each size class. The units are `numbers/year` when
#'   `power = 0` and `g^power/year` otherwise.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the flux at every time step. If `drop = TRUE` then dimensions
#'   of length 1 will be removed.
#' @export
#' @seealso [getEGrowth()], [getRDD()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' # Project with constant fishing effort for all gears for 20 time steps
#' sim <- project(params, t_max = 20, effort = 0.5)
#' # Get the flux at a particular time step
#' flux <- getFlux(params, n = N(sim)[15, , ], n_pp = NResource(sim)[15, ], t = 15)
#' # Flux for Sprat of size 2g
#' flux["Sprat", "2"]
#' }
getFlux <- function(object, ..., power = 0) {
    UseMethod("getFlux")
}

#' @rdname getFlux
#' @usage NULL
#' @export
getFlux.MizerParams <- function(object, n = initialN(object),
                    n_pp = initialNResource(object),
                    n_other = initialNOther(object),
                    t = 0, power = 0, ...) {
    params <- validParams(object)
    assert_that(is.number(power))

    g <- getEGrowth(params, n = n, n_pp = n_pp, n_other = n_other, t = t)
    d <- getDiffusion(params, n = n, n_pp = n_pp, n_other = n_other, t = t)
    rdd <- getRDD(params, n = n, n_pp = n_pp, n_other = n_other, t = t)

    flux <- flux_from_rates(params, n = n, g = g, d = d, rdd = rdd,
                            power = power,
                            flux_limiter = flux_limiter_scheme(params))

    ArraySpeciesBySize(flux, value_name = "Flux",
             units = flux_units(power), params = params)
}

#' Assemble the flux matrix from growth, diffusion and recruitment rates
#'
#' Internal helper holding the arithmetic shared by `getFlux.MizerParams` and
#' `getFlux.MizerSim`. Keeping it separate lets the `MizerSim` method resolve the
#' rate functions once and reuse them across all saved time steps.
#'
#' @param params A valid `MizerParams` object.
#' @param n A matrix of species abundances (species x size).
#' @param g Growth rate matrix (species x size), as from [getEGrowth()].
#' @param d Diffusion rate matrix (species x size), as from [getDiffusion()].
#' @param rdd Density-dependent reproduction rate vector (one per species), as
#'   from [getRDD()].
#' @param power The flux at weight \eqn{w} is multiplied by \eqn{w} raised to
#'   `power`. The default `power = 0` leaves the flux of individuals unchanged.
#' @param flux_limiter Advective-flux scheme: `"none"` (first-order upwind),
#'   `"van_leer"` or `"centred"` (second-order log-size scheme).  Defaults to
#'   `"none"`.
#'
#' @return A plain species x size matrix of fluxes (no mizer array class).
#' @keywords internal
flux_from_rates <- function(params, n, g, d, rdd, power = 0,
                             flux_limiter = "none") {
    no_sp <- nrow(params@species_params)
    no_w <- length(params@w)
    dw <- params@dw

    flux <- matrix(0, nrow = no_sp, ncol = no_w,
                   dimnames = dimnames(params@metab))

    idx <- 2:no_w
    idx_minus_1 <- idx - 1

    if (flux_limiter == "none") {
        # First-order upwind: advective velocity from the upwind cell, bin-width
        # diffusion denominator.
        # J_{i,j} = g_{i,j-1} N_{i,j-1} - 1/2 (d_{i,j} N_{i,j} - d_{i,j-1} N_{i,j-1}) / dw_{j-1}
        diff_term <- (d[, idx] * n[, idx] - d[, idx_minus_1] * n[, idx_minus_1]) /
            matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx_minus_1),
                   byrow = TRUE)
        flux[, idx] <- g[, idx_minus_1] * n[, idx_minus_1] - 0.5 * diff_term
    } else {
        # Second-order log-size scheme: advective velocity at the face, van Leer
        # or centred reconstruction, log-size diffusion denominator h*w_j.
        # J_{i,j} = g_{i,j} [N_{i,j-1} + chi_{i,j}/2 (N_{i,j} - N_{i,j-1})]
        #           - 1/2 (d_{i,j} N_{i,j} - d_{i,j-1} N_{i,j-1}) / (h w_j)
        h <- log_dx(params)
        w <- params@w
        chi <- flux_limiter_chi(params, n, g, flux_limiter)
        adv <- g[, idx] * (n[, idx_minus_1] +
                               0.5 * chi[, idx] * (n[, idx] - n[, idx_minus_1]))
        diff_term <- (d[, idx] * n[, idx] - d[, idx_minus_1] * n[, idx_minus_1]) /
            matrix(h * w[idx], nrow = no_sp, ncol = length(idx), byrow = TRUE)
        flux[, idx] <- adv - 0.5 * diff_term
    }

    # Apply recruitment boundary conditions
    j_start <- params@w_min_idx
    idxs <- cbind(1:no_sp, j_start)
    flux[idxs] <- rdd

    # Zero out elements for sizes smaller than w_min_idx
    w_idx_mat <- matrix(1:no_w, nrow = no_sp, ncol = no_w, byrow = TRUE)
    mask_below <- w_idx_mat < j_start

    if (any(mask_below)) {
        flux[mask_below] <- 0
    }

    if (power != 0) {
        flux <- sweep(flux, 2, params@w^power, "*")
    }

    flux
}

#' Units string for the flux returned by [getFlux()]
#'
#' @param power The power of weight the flux was multiplied by.
#' @return A character string with the units of the flux.
#' @keywords internal
flux_units <- function(power) {
    if (power == 0) {
        "1/year"
    } else if (power == 1) {
        "g/year"
    } else {
        paste0("g^", power, "/year")
    }
}

#' @rdname getFlux
#' @usage NULL
#' @export
getFlux.MizerSim <- function(object, n, n_pp, n_other, t, power = 0, ...,
                             time_range, drop = FALSE) {
    sim <- object
    assert_that(is.number(power))
    if (missing(time_range) && !missing(t)) time_range <- t

    # Resolve the rate functions once rather than at every saved time step. The
    # flux needs only the growth, diffusion and recruitment rates (and the rates
    # those depend on), which `mizer_rates_subset()` calculates selectively.
    params <- validParams(sim@params)
    rates_fns <- projectRateFunctions(params)
    flux_lim <- flux_limiter_scheme(params)

    get_species_size_rate_from_sim(
        sim, time_range, drop,
        function(slice) {
            r <- mizer_rates_subset(
                params, n = slice$n, n_pp = slice$n_pp,
                n_other = slice$n_other, t = slice$t, effort = slice$effort,
                rates_fns = rates_fns,
                targets = c("EGrowth", "Diffusion", "RDD"), ...)
            flux_from_rates(params, n = slice$n,
                            g = r$e_growth, d = r$diffusion, rdd = r$rdd,
                            power = power, flux_limiter = flux_lim)
        },
        value_name = "Flux", units = flux_units(power))
}


#' Get flux gradient
#'
#' `r lifecycle::badge("experimental")`
#' Calculates the flux divergence \eqn{(J_{j+1} - J_j)/\Delta w_j} that
#' appears as the second term in the discretised size-spectrum transport
#' equation
#' \deqn{\frac{\partial N_j}{\partial t} + \frac{J_{j+1} - J_j}{\Delta w_j}
#'       = -\mu_j N_j.}
#' The bin-boundary fluxes \eqn{J_j} are obtained from [getFlux()], which
#' uses the advective-flux scheme stored in the `flux` entry of the
#' [`second_order_w`][second_order_w()] slot of `params`.  The flux leaving
#' the largest size class through the upper boundary (\eqn{J_{K+1}}) is
#' evaluated with the same scheme using the boundary condition
#' \eqn{N_{K+1} = 0}.
#'
#' @template param_object_dots
#' @return
#' * `MizerParams`: An `ArraySpeciesBySize` object (species x size) giving the
#'   flux divergence in each size bin, in units of
#'   \eqn{g^{-1} \, \text{year}^{-1}}.
#' * `MizerSim`: An `ArrayTimeBySpeciesBySize` object (time step x species x
#'   size) with the flux divergence at every saved time step. If `drop = TRUE`
#'   then dimensions of length 1 will be removed.
#' @export
#' @seealso [getFlux()], [second_order_w()]
#' @family rate functions
#' @examples
#' \donttest{
#' params <- NS_params
#' fg <- getFluxGradient(params)
#' sim <- project(params, t_max = 5)
#' fg_sim <- getFluxGradient(sim)
#' }
getFluxGradient <- function(object, ...) {
    UseMethod("getFluxGradient")
}

#' @rdname getFluxGradient
#' @usage NULL
#' @export
getFluxGradient.MizerParams <- function(object,
                                         n = initialN(object),
                                         n_pp = initialNResource(object),
                                         n_other = initialNOther(object),
                                         t = 0, ...) {
    params <- validParams(object)

    flux <- getFlux(params, n = n, n_pp = n_pp, n_other = n_other, t = t)
    g <- getEGrowth(params, n = n, n_pp = n_pp, n_other = n_other, t = t)
    d <- getDiffusion(params, n = n, n_pp = n_pp, n_other = n_other, t = t)

    flux_lim <- flux_limiter_scheme(params)

    no_sp <- nrow(params@species_params)
    no_w <- length(params@w)
    dw <- params@dw

    # Upper boundary flux J_{K+1} with N[K+1] = 0: natural extension of each
    # scheme's flux formula to the phantom bin above the grid.
    if (flux_lim == "none") {
        # Upwind: J_{K+1} = g_K N_K - (0 - d_K N_K) / (2 dw_K)
        flux_upper <- g[, no_w] * n[, no_w] +
            0.5 * d[, no_w] * n[, no_w] / dw[no_w]
    } else {
        # Log-size second-order: J_{K+1} = g_K N_K - (0 - d_K N_K) / (2 h w_{K+1})
        # chi = 0 at the upper boundary (forced upwind there)
        h <- log_dx(params)
        beta <- params@w[2] / params@w[1]
        w_Kp1 <- params@w[no_w] * beta
        flux_upper <- g[, no_w] * n[, no_w] +
            0.5 * d[, no_w] * n[, no_w] / (h * w_Kp1)
    }

    # (J[j+1] - J[j]) / dw[j]  for j = 1..K
    flux_shifted <- cbind(flux[, 2:no_w, drop = FALSE], flux_upper)
    gradient <- (flux_shifted - flux) /
        matrix(dw, nrow = no_sp, ncol = no_w, byrow = TRUE)
    dimnames(gradient) <- dimnames(params@metab)

    ArraySpeciesBySize(gradient, value_name = "Flux gradient",
                       units = "g^-1/year", type = "density",
                       params = params)
}

#' @rdname getFluxGradient
#' @usage NULL
#' @export
getFluxGradient.MizerSim <- function(object, n, n_pp, n_other, t, ...,
                                      time_range, drop = FALSE) {
    sim <- object
    if (missing(time_range) && !missing(t)) time_range <- t

    params <- validParams(sim@params)
    rates_fns <- projectRateFunctions(params)
    flux_lim <- flux_limiter_scheme(params)

    no_w <- length(params@w)
    dw <- params@dw

    get_species_size_rate_from_sim(
        sim, time_range, drop,
        function(slice) {
            r <- mizer_rates_subset(
                params, n = slice$n, n_pp = slice$n_pp,
                n_other = slice$n_other, t = slice$t, effort = slice$effort,
                rates_fns = rates_fns,
                targets = c("EGrowth", "Diffusion", "RDD"), ...)
            no_sp <- nrow(slice$n)
            flux <- flux_from_rates(params, n = slice$n,
                                    g = r$e_growth, d = r$diffusion,
                                    rdd = r$rdd, flux_limiter = flux_lim)
            if (flux_lim == "none") {
                flux_upper <- r$e_growth[, no_w] * slice$n[, no_w] +
                    0.5 * r$diffusion[, no_w] * slice$n[, no_w] / dw[no_w]
            } else {
                h <- log_dx(params)
                w_Kp1 <- params@w[no_w] * params@w[2] / params@w[1]
                flux_upper <- r$e_growth[, no_w] * slice$n[, no_w] +
                    0.5 * r$diffusion[, no_w] * slice$n[, no_w] / (h * w_Kp1)
            }
            flux_shifted <- cbind(flux[, 2:no_w, drop = FALSE], flux_upper)
            gradient <- (flux_shifted - flux) /
                matrix(dw, nrow = no_sp, ncol = no_w, byrow = TRUE)
            dimnames(gradient) <- dimnames(params@metab)
            gradient
        },
        value_name = "Flux gradient", units = "g^-1/year",
        type = "density")
}

Try the mizer package in your browser

Any scripts or data that you put into this service are public.

mizer documentation built on Aug. 31, 2026, 5:08 p.m.