From 40392c28a55ecd34fd9da9d8138bb9a183d96166 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 05:07:26 +0000 Subject: [PATCH 01/47] Add vignette to document numerical scheme --- R/project_n.R | 27 ++++++- man/project_n.Rd | 41 +++++++++- pkgdown/_pkgdown.yml | 1 + vignettes/numerical_details.Rmd | 139 ++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 vignettes/numerical_details.Rmd diff --git a/R/project_n.R b/R/project_n.R index 4507b477b..b602b26b3 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -1,4 +1,29 @@ -#' Advance abundance density by one time step +#' Project values for first time step of Euler method +#' +#' This is an internal function used by the user-facing `project()` function. +#' It is of potential interest only to mizer extension authors. +#' +#' @details +#' The function calculates the abundance at the next time step using the +#' McKendrick-von Foerster equation: +#' \deqn{\frac{\partial N}{\partial t} + \frac{\partial g N}{\partial w} = -\mu N} +#' which is solved using a semi-implicit upwind finite difference scheme. +#' +#' @param params A \linkS4class{MizerParams} object. +#' @param r A list of rates as returned by `mizerRates()`. +#' @param n An array (species x size) with the number density at the current time step. +#' @param dt Time step. +#' @param a A matrix (species x size) used in the solver (transport term). +#' @param b A matrix (species x size) used in the solver (diagonal term). +#' @param S A matrix (species x size) used in the solver (source term). +#' @param idx Index vector for size bins (excluding the first one). +#' @param w_min_idx_array_ref Index vector for the start of the size spectrum for each species. +#' @param no_sp Number of species. +#' @param no_w Number of size bins. +#' +#' @return The updated abundance density matrix `n`. +#' @seealso \code{\link{project}}, \code{\link{mizerRates}} +#' @concept helper #' @export project_n <- function(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, no_sp, no_w) { diff --git a/man/project_n.Rd b/man/project_n.Rd index 721e7ae5c..44c151b4b 100644 --- a/man/project_n.Rd +++ b/man/project_n.Rd @@ -2,10 +2,47 @@ % Please edit documentation in R/project_n.R \name{project_n} \alias{project_n} -\title{Advance abundance density by one time step} +\title{Project values for first time step of Euler method} \usage{ project_n(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, no_sp, no_w) } +\arguments{ +\item{params}{A \linkS4class{MizerParams} object.} + +\item{r}{A list of rates as returned by \code{mizerRates()}.} + +\item{n}{An array (species x size) with the number density at the current time step.} + +\item{dt}{Time step.} + +\item{a}{A matrix (species x size) used in the solver (transport term).} + +\item{b}{A matrix (species x size) used in the solver (diagonal term).} + +\item{S}{A matrix (species x size) used in the solver (source term).} + +\item{idx}{Index vector for size bins (excluding the first one).} + +\item{w_min_idx_array_ref}{Index vector for the start of the size spectrum for each species.} + +\item{no_sp}{Number of species.} + +\item{no_w}{Number of size bins.} +} +\value{ +The updated abundance density matrix \code{n}. +} \description{ -Advance abundance density by one time step +This is an internal function used by the user-facing \code{project()} function. +It is of potential interest only to mizer extension authors. +} +\details{ +The function calculates the abundance at the next time step using the +McKendrick-von Foerster equation: +\deqn{\frac{\partial N}{\partial t} + \frac{\partial g N}{\partial w} = -\mu N} +which is solved using a semi-implicit upwind finite difference scheme. +} +\seealso{ +\code{\link{project}}, \code{\link{mizerRates}} } +\concept{helper} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index d0ea1a33b..27bef154c 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -79,6 +79,7 @@ articles: - title: Developer Guide navbar: For developers contents: + - numerical_details - developer_vignette - working_with_git - developer_FAQ diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd new file mode 100644 index 000000000..a2cd4cc4b --- /dev/null +++ b/vignettes/numerical_details.Rmd @@ -0,0 +1,139 @@ +--- +title: "The Numerical Scheme used in Mizer" +output: + html_document: + toc: yes + fig_width: 5 + fig_height: 5 +vignette: > + %\VignetteIndexEntry{The Numerical Scheme used in Mizer} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +In this vignette we explain the numerical scheme used in mizer. We will not go into the details of the model itself, which is described in the [model description vignette](model_description.html). We will focus on how the model is discretised and how the resulting difference equations are solved. + +# The weight grid + +The model dynamics are described by partial differential equations (PDEs) for the species number densities $N_i(w)$ and the resource number density $N_R(w)$. These depend on the individual weight $w$. To solve these equations numerically, we discretise the weight axis. + +We choose a grid of weights $w_1, w_2, \ldots, w_K$. The grid is logarithmically spaced, meaning that the ratio of consecutive weights is constant: +$$ w_{j+1} / w_j = 10^{\Delta x} = \text{const}. $$ +The logarithmic spacing is chosen because the weights of fish span many orders of magnitude, from milligrams to megagrams. + +This grid defines a set of bins. The $j$-th bin is the interval $[w_j, w_{j+1})$. The width of the $j$-th bin is +$$ \Delta w_j = w_{j+1} - w_j = w_j (10^{\Delta x} - 1). $$ +Note that $\Delta w_j$ increases with $w_j$, which is appropriate for a logarithmic grid. + +The number density $N_i(w)$ is approximated by a constant value $N_{i,j}$ within each bin. Thus $N_{i,j}$ represents the density at the start of the bin $w_j$. The number of individuals in the $j$-th bin is $N_{i,j} \Delta w_j$. + +# The Transport Equation + +The time evolution of the number density $N_i(w)$ is described by the McKendrick-von Foerster equation: +$$ +\frac{\partial N_i}{\partial t} + \frac{\partial g_i N_i}{\partial w} = -\mu_i N_i +$$ +where $g_i(w)$ is the somatic growth rate and $\mu_i(w)$ is the total mortality rate. + +We discretise this equation using a finite difference scheme. We use a **semi-implicit upwind scheme** which is stable and robust. + +## Discretisation of the Growth Term + +The term $\frac{\partial g_i N_i}{\partial w}$ represents the transport of biomass up the size spectrum due to growth. We use an upwind difference approximation for the derivative: +$$ +\frac{\partial g_i N_i}{\partial w} \approx \frac{g_i(w_j) N_{i,j} - g_i(w_{j-1}) N_{i,j-1}}{\Delta w_j}. +$$ +This approximation uses the flux of individuals *leaving* the bin $j$ (which is $g_i(w_j) N_{i,j}$) and the flux of individuals *entering* the bin $j$ from the bin below (which is $g_i(w_{j-1}) N_{i,j-1}$). + +## Discretisation of Time + +We denote the number density at time $t$ by $N_{i,j}^t$ and at time $t+\Delta t$ by $N_{i,j}^{t+1}$. +The time derivative is approximated by the forward difference: +$$ +\frac{\partial N_i}{\partial t} \approx \frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t}. +$$ + +For the growth and mortality terms on the right-hand side, we have a choice of evaluating them at time $t$ (explicit scheme) or at time $t+\Delta t$ (implicit scheme). The explicit scheme is only stable for very small time steps $\Delta t$ (the Courant-Friedrichs-Lewy condition), whereas the implicit scheme is unconditionally stable. + +Mizer uses a semi-implicit scheme where the fast dynamical variables (the densities $N_i$) are treated implicitly, while the slow dynamical variables (the growth and mortality rates $g_i$ and $\mu_i$) are treated explicitly. This means we evaluate $N_i$ at $t+\Delta t$ but $g_i$ and $\mu_i$ at time $t$. + +The discretised equation for $j > 1$ is: +$$ +\frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t} + \frac{g_i(w_j) N_{i,j}^{t+1} - g_i(w_{j-1}) N_{i,j-1}^{t+1}}{\Delta w_j} = -\mu_i(w_j) N_{i,j}^{t+1}. +$$ + +Rearranging this equation to solve for $N_{i,j}^{t+1}$ gives: +$$ +\left( 1 + \frac{\Delta t}{\Delta w_j} g_i(w_j) + \Delta t \mu_i(w_j) \right) N_{i,j}^{t+1} - \frac{\Delta t}{\Delta w_j} g_i(w_{j-1}) N_{i,j-1}^{t+1} = N_{i,j}^t. +$$ + +We can write this as a linear system: +$$ +B_{i,j} N_{i,j}^{t+1} + A_{i,j} N_{i,j-1}^{t+1} = S_{i,j} +$$ +where +$$ +\begin{aligned} +A_{i,j} &= -\frac{\Delta t}{\Delta w_j} g_i(w_{j-1}) \\ +B_{i,j} &= 1 + \frac{\Delta t}{\Delta w_j} g_i(w_j) + \Delta t \mu_i(w_j) \\ +S_{i,j} &= N_{i,j}^t +\end{aligned} +$$ + +This system can be solved nicely by iterating from the smallest size bin upwards. Once we know $N_{i,j-1}^{t+1}$, we can calculate $N_{i,j}^{t+1}$: +$$ +N_{i,j}^{t+1} = \frac{S_{i,j} - A_{i,j} N_{i,j-1}^{t+1}}{B_{i,j}}. +$$ + +## Boundary Condition at Smallest Size + +For the first size bin ($j=j_{min}$), there is no flux from a smaller bin ($g_i(w_{j_{min}-1}) N_{i,j_{min}-1}$ is not defined). Instead, there is an influx of new recruits into the smallest size class. Let $R_{dd, i}$ be the rate of recruitment (density-dependent reproduction rate, numbers per time). + +The discretised equation for the first bin is: +$$ +\frac{N_{i,j_{min}}^{t+1} - N_{i,j_{min}}^t}{\Delta t} + \frac{g_i(w_{j_{min}}) N_{i,j_{min}}^{t+1} - R_{dd, i}}{\Delta w_{j_{min}}} = -\mu_i(w_{j_{min}}) N_{i,j_{min}}^{t+1}. +$$ + +Solving for $N_{i,j_{min}}^{t+1}$: +$$ +\left( 1 + \frac{\Delta t}{\Delta w_{j_{min}}} g_i(w_{j_{min}}) + \Delta t \mu_i(w_{j_{min}}) \right) N_{i,j_{min}}^{t+1} = N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i}. +$$ +This gives the starting value for the iteration: +$$ +N_{i,j_{min}}^{t+1} = \frac{N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i}}{B_{i,j_{min}}}. +$$ + +# Resource Dynamics + +The resource spectrum $N_R(w)$ is also updated using a similar Euler method, but often with a different equation. For example, if semi-chemostat dynamics are used: +$$ +\frac{\partial N_R}{\partial t} = r_R(c_R - N_R) - \mu_R N_R. +$$ +The discretised update is: +$$ +\frac{N_{R,j}^{t+1} - N_{R,j}^t}{\Delta t} = r_R(w_j) (c_R(w_j) - N_{R,j}^{t+1}) - \mu_R(w_j) N_{R,j}^{t+1}. +$$ +Solving for $N_{R,j}^{t+1}$: +$$ +N_{R,j}^{t+1} = \frac{N_{R,j}^t + \Delta t r_R(w_j) c_R(w_j)}{1 + \Delta t (r_R(w_j) + \mu_R(w_j))}. +$$ + +# Summary of the Algorithm + +In each time step $\Delta t$, the `project()` function performs the following steps: + +1. **Calculate Rates**: Calculate the biological rates (growth $g_i$, mortality $\mu_i$, recruitment $R_{dd, i}$) based on the current population densities $N^t$. +2. **Resource Update**: Update the resource density $N_R^{t+1}$ using the explicit rates and implicit density. +3. **Consumer Update**: Update the consumer densities $N_i^{t+1}$ by iterating from small to large sizes: + - Calculate $N_{i,j_{min}}^{t+1}$ using the recruitment $R_{dd, i}$. + - Calculate $N_{i,j}^{t+1}$ for $j > j_{min}$ using the upwind scheme. +4. **Advance Time**: $t \leftarrow t + \Delta t$. + +This split between calculating rates explicitly and solving densities implicitly is what makes the scheme "semi-implicit". From 0ae59edf823651f7223f7b7e0e756ee7ba498bea Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 05:40:15 +0000 Subject: [PATCH 02/47] feat: Implement a semi-implicit upwind finite volume scheme with diffusion using the Thomas algorithm for projecting species abundance. --- R/project.R | 3 +- R/project_n.R | 199 +++++++++++++++++++++++++++----- vignettes/numerical_details.Rmd | 111 +++++++++--------- 3 files changed, 229 insertions(+), 84 deletions(-) diff --git a/R/project.R b/R/project.R index 117158be6..1d6ca6117 100644 --- a/R/project.R +++ b/R/project.R @@ -463,6 +463,7 @@ project_simple.MizerParams <- # Matrices for solver a <- matrix(0, nrow = no_sp, ncol = no_w) b <- matrix(0, nrow = no_sp, ncol = no_w) + c <- matrix(0, nrow = no_sp, ncol = no_w) S <- matrix(0, nrow = no_sp, ncol = no_w) # Loop over time steps ---- @@ -501,7 +502,7 @@ project_simple.MizerParams <- ) # * Update species ---- - n <- project_n(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, + n <- project_n(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) # * Update time ---- diff --git a/R/project_n.R b/R/project_n.R index b602b26b3..1c546a070 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -6,8 +6,8 @@ #' @details #' The function calculates the abundance at the next time step using the #' McKendrick-von Foerster equation: -#' \deqn{\frac{\partial N}{\partial t} + \frac{\partial g N}{\partial w} = -\mu N} -#' which is solved using a semi-implicit upwind finite difference scheme. +#' \deqn{\frac{\partial N}{\partial t} + \frac{\partial}{\partial w} \left( g N - \frac{1}{2}\frac{\partial(D N)}{\partial w} \right) = -\mu N} +#' which is solved using a semi-implicit upwind finite volume scheme. #' #' @param params A \linkS4class{MizerParams} object. #' @param r A list of rates as returned by `mizerRates()`. @@ -15,6 +15,7 @@ #' @param dt Time step. #' @param a A matrix (species x size) used in the solver (transport term). #' @param b A matrix (species x size) used in the solver (diagonal term). +#' @param c A matrix (species x size) used in the solver (transport term). #' @param S A matrix (species x size) used in the solver (source term). #' @param idx Index vector for size bins (excluding the first one). #' @param w_min_idx_array_ref Index vector for the start of the size spectrum for each species. @@ -25,32 +26,174 @@ #' @seealso \code{\link{project}}, \code{\link{mizerRates}} #' @concept helper #' @export -project_n <- function(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, +project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - # a_{ij} = - g_i(w_{j-1}) / dw_j dt - a[, idx] <- sweep( - -r$e_growth[, idx - 1, drop = FALSE] * dt, 2, - params@dw[idx], "/" - ) - # b_{ij} = 1 + g_i(w_j) / dw_j dt + \mu_i(w_j) dt - b[] <- 1 + sweep(r$e_growth * dt, 2, params@dw, "/") + r$mort * dt - # S_{ij} <- N_i(w_j) - S[, idx] <- n[, idx, drop = FALSE] - # Update first size group of n - n[w_min_idx_array_ref] <- - (n[w_min_idx_array_ref] + r$rdd * dt / - params@dw[params@w_min_idx]) / - b[w_min_idx_array_ref] - # Update n - # for (i in 1:no_sp) # number of species assumed small, so no need to - # vectorize this loop over species - # for (j in (params@w_min_idx[i]+1):no_w) - # n[i,j] <- (S[i,j] - A[i,j]*n[i,j-1]) / B[i,j] - # This is implemented via Rcpp - n <- inner_project_loop( - no_sp = no_sp, no_w = no_w, n = n, - A = a, B = b, S = S, - w_min_idx = params@w_min_idx - ) + # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j + + # Diffusion coefficient D_i(w) + d <- params@diffusion # species x size + + # Pre-calculate some common terms + # g_i(w_j) + g <- r$e_growth + # mu_i(w_j) + mu <- r$mort + # dw_j + dw <- params@dw + # Delta t / Delta w_j + dt_dw <- matrix(dt / dw, nrow = no_sp, ncol = no_w, byrow = TRUE) + + # We assume d is 0 at the boundaries for simplicity or it is handled by the loop constraints + # Actually for j=1, A is 0, for j=no_w, C is 0 (boundary condition) + + # A_j = - dt/dw_j * (g_{j-1} + D_{j-1} / (2 * dw_{j-1})) + # Note: efficient calculation avoiding loop: + # We compute A for j in idx (2:no_w). g_{j-1} corresponds to columns 1:(no_w-1) + + # Indices for j-1 and j + # idx is 2:no_w + idx_minus_1 <- idx - 1 + + term_diff_minus_1 <- 0.5 * d[, idx_minus_1] / + matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) + + a[, idx] <- -dt_dw[, idx] * (g[, idx_minus_1] + term_diff_minus_1) + + # C_j = - dt/dw_j * (D_{j+1} / (2 * dw_j)) + # Note: For j=no_w, we assume N_{j+1}=0, so we don't need C_{no_w} effectively, or it is 0 flux. + # The equation involves C_j * N_{j+1}. At j=no_w, N_{no_w+1} is 0. So C_{no_w} doesn't matter. + # We can compute C for j=1:(no_w-1). + # idx_plus_1 is 2:no_w + idx_j <- 1:(no_w - 1) + term_diff_plus_1 <- 0.5 * d[, idx_j + 1] / + matrix(dw[idx_j], nrow = no_sp, ncol = length(idx_j), byrow = TRUE) + + c[, idx_j] <- -dt_dw[, idx_j] * term_diff_plus_1 + c[, no_w] <- 0 # Boundary condition N_{no_w+1} = 0 + + # B_j + # B_j = 1 + dt * mu_j + dt/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) + # Careful with j=1 term for D_j / (2 * dw_{j-1}). dw_0 is not defined. + # At j=1 boundary condition comes from recruitment. + # Standard formula works for j > 1. + + term_diff_j <- 0.5 * d[, idx] / matrix(dw[idx], nrow = no_sp, ncol = length(idx), byrow = TRUE) + term_diff_j_minus_1 <- 0.5 * d[, idx] / matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) + + b[, idx] <- 1 + dt * mu[, idx] + dt_dw[, idx] * (g[, idx] + term_diff_j + term_diff_j_minus_1) + + # Boundary j=1 (approx w_min) + # Equation: N_1 - N_1^old / dt + (J_{3/2} - J_{1/2}) / dw_1 = -mu_1 N_1 + # J_{1/2} = R_dd (recruitment flux). + # J_{3/2} follows standard flux definition. + # result: (1 + dt*mu_1 + dt/dw_1 * (g_1 + D_1/(2*dw_1))) N_1 - dt/dw_1 * (D_2/(2*dw_1)) N_2 = N_1^old + dt/dw_1 * R_dd + # So for j=1: + # B_1 = 1 + dt*mu_1 + dt/dw_1 * (g_1 + d_1/(2*dw_1)) + # C_1 = - dt/dw_1 * d_2/(2*dw_1) (Matches standard formula) + # A_1 = 0 + # S_1 = N_1^old + dt/dw_1 * R_dd + + dw_1 <- dw[1] + dt_dw_1 <- dt / dw_1 + b[, 1] <- 1 + dt * mu[, 1] + dt_dw_1 * (g[, 1] + 0.5 * d[, 1] / dw_1) + # c[, 1] already computed correctly above + a[, 1] <- 0 + + # RHS S + S[] <- n + # Add recruitment to S[, 1] for each species + # We need to distribute R_dd correctly. + # r$rdd is vector of length no_sp. + # "r$rdd * dt / params@dw[params@w_min_idx]" + # Wait, the prompt says "w_min_idx_array_ref" handles the start index. + # Different species can have different w_min_idx. + # So the "j=1" above really refers to w_min_idx[i]. + # But currently 'a' and 'b' and 'c' are computed for the whole grid. + # Species i only exists from w_min_idx[i] to w_max_idx[i] (implicitly). + # We should iterate the Thomas algorithm for each species from w_min_idx[i] to no_w. + + # Let's adjust S for the recruitment term at the start index for each species. + # S[w_min_idx_array_ref] <- S[w_min_idx_array_ref] + r$rdd * dt / params@dw[params@w_min_idx] + # But wait, we need to respect the diffusion B_1 term calculation which might be different at the boundary. + # The B calculation above `b[, idx]` used `idx` which starts at 2. + # If w_min_idx[i] > 1, then the "standard" formula for B at w_min_idx[i] might be using w_{j-1} which is essentially 0 since N is 0 there? + # Actually, if we assume N is 0 below w_min_idx, then the flux from below is just the recruitment. + # So for j = w_min_idx[i], the term A_j should be effectively 0 (or replaced by recruitment boundary condition). + # The B_j term should not include diffusion from below (or handled as boundary). + # We can handle this by essentially running the solver from w_min_idx[i]. + + # Correct B matrix for start indices + # We need to loop over species to correct B at w_min_idx, because vectorization is hard with variable indices. + # However, w_min_idx might be same for all species or not. + + # Loop over species to apply boundary condition and solve + + # Temporary copy of C for modification during Thomas algo + c_prime <- c + # Temporary copy of S (d in Thomas algo context) + d_prime <- S + + for (i in 1:no_sp) { + # Start index for this species + j_start <- params@w_min_idx[i] + + # Apply boundary condition to S (RHS) + # S_j_start = N_old + dt/dw * R_dd + d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / dw[j_start] + + # Apply boundary condition to B (LHS) + # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux + # B_j = 1 + ... + dt/dw * (g_j + D_j/(2*dw_j) + D_j/(2*dw_{j-1})) + # The D_j/(2*dw_{j-1}) term came from flux J_j. + # At boundary, J_j is explicitly R_dd. + # So we should remove the D_j/(2*dw_{j-1}) term from B[i, j_start] + # and A[i, j_start] is 0. + + # Original B[i, j_start] contains: ... + dt/dw * ( ... + 0.5 * d[i, j_start] / dw[j_start-1]) + # We need to subtract that term + if (j_start > 1) { + # The term added was: dt/dw[j_start] * 0.5 * d[i, j_start] / dw[j_start-1] + correction <- (dt / dw[j_start]) * 0.5 * d[i, j_start] / dw[j_start - 1] + b[i, j_start] <- b[i, j_start] - correction + + # Also A[i, j_start] should be ignored/0. + a[i, j_start] <- 0 + } else { + # j_start == 1. The formula used 'dw[1]' for prev bin width in approximation? + # My code above: `b[, 1] <- ... + g[, 1] + 0.5 * d[, 1] / dw_1` + # This formula for j=1 was already "boundary-like" (omitted term from below). + # So if j_start == 1, B is already correct for the boundary condition derived. + } + + # Thomas Algorithm: Forward Elimination + # For j = j_start + # c'[j] = c[j] / b[j] + # d'[j] = d[j] / b[j] + + c_prime[i, j_start] <- c_prime[i, j_start] / b[i, j_start] + d_prime[i, j_start] <- d_prime[i, j_start] / b[i, j_start] + + # Loop j from j_start+1 to no_w + if (j_start < no_w) { + for (j in (j_start + 1):no_w) { + temp <- b[i, j] - a[i, j] * c_prime[i, j - 1] + if (j < no_w) { + c_prime[i, j] <- c_prime[i, j] / temp + } + d_prime[i, j] <- (d_prime[i, j] - a[i, j] * d_prime[i, j - 1]) / temp + } + } + + # Backward Substitution + n[i, no_w] <- d_prime[i, no_w] + if (no_w > j_start) { + for (j in (no_w - 1):j_start) { + n[i, j] <- d_prime[i, j] - c_prime[i, j] * n[i, j + 1] + } + } + # Species density is 0 below j_start? Mizer usually keeps it 0 or doesn't update. + # The loop range updates n for j >= j_start. + } + n } \ No newline at end of file diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index a2cd4cc4b..3d9503c3b 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -36,79 +36,80 @@ The number density $N_i(w)$ is approximated by a constant value $N_{i,j}$ within # The Transport Equation -The time evolution of the number density $N_i(w)$ is described by the McKendrick-von Foerster equation: +The time evolution of the number density $N_i(w)$ is described by the McKendrick-von Foerster equation with an added diffusion term: $$ -\frac{\partial N_i}{\partial t} + \frac{\partial g_i N_i}{\partial w} = -\mu_i N_i +\frac{\partial N_i}{\partial t} + \frac{\partial}{\partial w} \left( g_i N_i - \frac{1}{2}\frac{\partial(d_i N_i)}{\partial w} \right) = -\mu_i N_i $$ -where $g_i(w)$ is the somatic growth rate and $\mu_i(w)$ is the total mortality rate. +where $g_i(w)$ is the somatic growth rate, $d_i(w)$ is the diffusion coefficient and $\mu_i(w)$ is the total mortality rate. -We discretise this equation using a finite difference scheme. We use a **semi-implicit upwind scheme** which is stable and robust. +We discretise this equation using a finite volume scheme. -## Discretisation of the Growth Term +## Discretisation of the Fluxs -The term $\frac{\partial g_i N_i}{\partial w}$ represents the transport of biomass up the size spectrum due to growth. We use an upwind difference approximation for the derivative: -$$ -\frac{\partial g_i N_i}{\partial w} \approx \frac{g_i(w_j) N_{i,j} - g_i(w_{j-1}) N_{i,j-1}}{\Delta w_j}. -$$ -This approximation uses the flux of individuals *leaving* the bin $j$ (which is $g_i(w_j) N_{i,j}$) and the flux of individuals *entering* the bin $j$ from the bin below (which is $g_i(w_{j-1}) N_{i,j-1}$). +The term inside the derivative with respect to $w$ is the flux $J_i(w)$: +$$ J_i(w) = g_i(w) N_i(w) - \frac{1}{2}\frac{\partial(d_i(w) N_i(w))}{\partial w} $$ +We consider the $j$-th size bin $[w_j, w_{j+1}]$. Integrating the conservation equation over this bin gives: +$$ \Delta w_j \frac{\partial N_{i,j}}{\partial t} + J_i(w_{j+1}) - J_i(w_j) = -\int_{w_j}^{w_{j+1}} \mu_i(w) N_i(w) dw $$ +Approximating the integral and dividing by $\Delta w_j$: +$$ \frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t} + \frac{J_{i,j+1} - J_{i,j}}{\Delta w_j} = -\mu_i(w_j) N_{i,j}^{t+1} $$ +where $J_{i,j}$ represents the flux at the boundary $w_j$. -## Discretisation of Time +### Advective Flux +For the advective part of the flux $g_i N_i$, we use an **upwind** approximation. Since fish grow from smaller to larger sizes ($g_i > 0$), the flux at boundary $w_j$ is determined by the density in the bin below ($j-1$): +$$ J_{i,j}^{adv} = g_i(w_{j-1}) N_{i,j-1} $$ +(Note: for $j=j_{min}$, this boundary flux is the recruitment $R_{dd,i}$). -We denote the number density at time $t$ by $N_{i,j}^t$ and at time $t+\Delta t$ by $N_{i,j}^{t+1}$. -The time derivative is approximated by the forward difference: -$$ -\frac{\partial N_i}{\partial t} \approx \frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t}. -$$ +### Diffusive Flux +For the diffusive part of the flux $-\frac{1}{2}\frac{\partial(d_i N_i)}{\partial w}$, we use a **central difference** approximation at the boundary $w_j$. We approximate the gradient using the densities in the adjacent bins $j-1$ and $j$: +$$ J_{i,j}^{diff} \approx -\frac{1}{2} \frac{(d_i N_i)_j - (d_i N_i)_{j-1}}{w_j - w_{j-1}} = -\frac{1}{2} \frac{d_i(w_j) N_{i,j} - d_i(w_{j-1}) N_{i,j-1}}{\Delta w_{j-1}} $$ +Note the use of $N_{i,j}$ (density in bin $j$) and $N_{i,j-1}$ (density in bin $j-1$) to estimate the value at the interface $w_j$. -For the growth and mortality terms on the right-hand side, we have a choice of evaluating them at time $t$ (explicit scheme) or at time $t+\Delta t$ (implicit scheme). The explicit scheme is only stable for very small time steps $\Delta t$ (the Courant-Friedrichs-Lewy condition), whereas the implicit scheme is unconditionally stable. +The total flux at boundary $w_j$ is: +$$ J_{i,j} = g_i(w_{j-1}) N_{i,j-1} - \frac{1}{2} \frac{d_i(w_j) N_{i,j} - d_i(w_{j-1}) N_{i,j-1}}{\Delta w_{j-1}} $$ +And similarly at boundary $w_{j+1}$: +$$ J_{i,j+1} = g_i(w_j) N_{i,j} - \frac{1}{2} \frac{d_i(w_{j+1}) N_{i,j+1} - d_i(w_j) N_{i,j}}{\Delta w_j} $$ -Mizer uses a semi-implicit scheme where the fast dynamical variables (the densities $N_i$) are treated implicitly, while the slow dynamical variables (the growth and mortality rates $g_i$ and $\mu_i$) are treated explicitly. This means we evaluate $N_i$ at $t+\Delta t$ but $g_i$ and $\mu_i$ at time $t$. +## Limitation on Time Step -The discretised equation for $j > 1$ is: -$$ -\frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t} + \frac{g_i(w_j) N_{i,j}^{t+1} - g_i(w_{j-1}) N_{i,j-1}^{t+1}}{\Delta w_j} = -\mu_i(w_j) N_{i,j}^{t+1}. -$$ +With the diffusion term, an explicit time discretisation would require a very small time step for stability ($\Delta t \sim \Delta w^2$). Therefore, we use a semi-implicit scheme where the densities $N_i$ are evaluated at time $t+1$, but the rates ($g_i, \mu_i, d_i$) are evaluated at time $t$. -Rearranging this equation to solve for $N_{i,j}^{t+1}$ gives: -$$ -\left( 1 + \frac{\Delta t}{\Delta w_j} g_i(w_j) + \Delta t \mu_i(w_j) \right) N_{i,j}^{t+1} - \frac{\Delta t}{\Delta w_j} g_i(w_{j-1}) N_{i,j-1}^{t+1} = N_{i,j}^t. -$$ +## Discretised Equation -We can write this as a linear system: +Substituting the fluxes into the conservation equation: $$ -B_{i,j} N_{i,j}^{t+1} + A_{i,j} N_{i,j-1}^{t+1} = S_{i,j} +\frac{N_{i,j}^{t+1} - N_{i,j}^t}{\Delta t} + \frac{1}{\Delta w_j} \left( J_{i,j+1}^{t+1} - J_{i,j}^{t+1} \right) = -\mu_i(w_j) N_{i,j}^{t+1} $$ -where +This leads to a linear system of the form: $$ -\begin{aligned} -A_{i,j} &= -\frac{\Delta t}{\Delta w_j} g_i(w_{j-1}) \\ -B_{i,j} &= 1 + \frac{\Delta t}{\Delta w_j} g_i(w_j) + \Delta t \mu_i(w_j) \\ -S_{i,j} &= N_{i,j}^t -\end{aligned} +A_{i,j} N_{i,j-1}^{t+1} + B_{i,j} N_{i,j}^{t+1} + C_{i,j} N_{i,j+1}^{t+1} = S_{i,j} $$ +where $S_{i,j} = N_{i,j}^t$. +This is a **tridiagonal system** for each species $i$, which can be solved efficiently (e.g., using the Thomas algorithm). -This system can be solved nicely by iterating from the smallest size bin upwards. Once we know $N_{i,j-1}^{t+1}$, we can calculate $N_{i,j}^{t+1}$: +The coefficients are: $$ -N_{i,j}^{t+1} = \frac{S_{i,j} - A_{i,j} N_{i,j-1}^{t+1}}{B_{i,j}}. +\begin{aligned} +A_{i,j} &= -\frac{\Delta t}{\Delta w_j} \left( g_i(w_{j-1}) + \frac{1}{2} \frac{d_i(w_{j-1})}{\Delta w_{j-1}} \right) \\ +C_{i,j} &= -\frac{\Delta t}{\Delta w_j} \left( \frac{1}{2} \frac{d_i(w_{j+1})}{\Delta w_j} \right) \\ +B_{i,j} &= 1 + \Delta t \mu_i(w_j) + \frac{\Delta t}{\Delta w_j} \left( g_i(w_j) + \frac{1}{2} \frac{d_i(w_j)}{\Delta w_j} + \frac{1}{2} \frac{d_i(w_j)}{\Delta w_{j-1}} \right) +\end{aligned} $$ -## Boundary Condition at Smallest Size - -For the first size bin ($j=j_{min}$), there is no flux from a smaller bin ($g_i(w_{j_{min}-1}) N_{i,j_{min}-1}$ is not defined). Instead, there is an influx of new recruits into the smallest size class. Let $R_{dd, i}$ be the rate of recruitment (density-dependent reproduction rate, numbers per time). +## Boundary Conditions -The discretised equation for the first bin is: -$$ -\frac{N_{i,j_{min}}^{t+1} - N_{i,j_{min}}^t}{\Delta t} + \frac{g_i(w_{j_{min}}) N_{i,j_{min}}^{t+1} - R_{dd, i}}{\Delta w_{j_{min}}} = -\mu_i(w_{j_{min}}) N_{i,j_{min}}^{t+1}. -$$ +**At the smallest size ($j=j_{min}$):** +The flux entering the grid is determined by recruitment. +$$ J_{i, j_{min}} = R_{dd, i} $$ +(We assume diffusive flux at the lower boundary is negligible or incorporated into $R_{dd}$). +The equation for the first bin becomes: +$$ \frac{N_{i,j_{min}}^{t+1} - N_{i,j_{min}}^t}{\Delta t} + \frac{J_{i, j_{min}+1}^{t+1} - R_{dd,i}}{\Delta w_{j_{min}}} = -\mu_i(w_{j_{min}}) N_{i,j_{min}}^{t+1} $$ +This involves $N_{i, j_{min}}^{t+1}$ and $N_{i, j_{min}+1}^{t+1}$. -Solving for $N_{i,j_{min}}^{t+1}$: -$$ -\left( 1 + \frac{\Delta t}{\Delta w_{j_{min}}} g_i(w_{j_{min}}) + \Delta t \mu_i(w_{j_{min}}) \right) N_{i,j_{min}}^{t+1} = N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i}. -$$ -This gives the starting value for the iteration: -$$ -N_{i,j_{min}}^{t+1} = \frac{N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i}}{B_{i,j_{min}}}. -$$ +**At the largest size ($j=j_{max}$):** +We typically assume that densities drop to zero beyond the maximum size, $N_{i, j_{max}+1} = 0$. +The flux leaving the grid is: +$$ J_{i, j_{max}+1} = g_i(w_{j_{max}}) N_{i, j_{max}} - \frac{1}{2} \frac{0 - d_i(w_{j_{max}}) N_{i, j_{max}}}{\Delta w_{j_{max}}} $$ +This closes the system. # Resource Dynamics @@ -129,11 +130,11 @@ $$ In each time step $\Delta t$, the `project()` function performs the following steps: -1. **Calculate Rates**: Calculate the biological rates (growth $g_i$, mortality $\mu_i$, recruitment $R_{dd, i}$) based on the current population densities $N^t$. +1. **Calculate Rates**: Calculate the biological rates (growth $g_i$, mortality $\mu_i$, recruitment $R_{dd, i}$) and diffusion coefficients $d_i$ based on the current population densities $N^t$. 2. **Resource Update**: Update the resource density $N_R^{t+1}$ using the explicit rates and implicit density. -3. **Consumer Update**: Update the consumer densities $N_i^{t+1}$ by iterating from small to large sizes: - - Calculate $N_{i,j_{min}}^{t+1}$ using the recruitment $R_{dd, i}$. - - Calculate $N_{i,j}^{t+1}$ for $j > j_{min}$ using the upwind scheme. +3. **Consumer Update**: Update the consumer densities $N_i^{t+1}$ by solving the tridiagonal system for each species. + - Combine the recruitment flux $R_{dd, i}$ with the boundary conditions. + - Use a tridiagonal solver (e.g., Thomas algorithm). 4. **Advance Time**: $t \leftarrow t + \Delta t$. This split between calculating rates explicitly and solving densities implicitly is what makes the scheme "semi-implicit". From b575811e72a30136409a467525b13511212e8830 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 09:18:41 +0000 Subject: [PATCH 03/47] test: Add analytical test for transport equation solver with corresponding vignette and test file. --- pkgdown/_pkgdown.yml | 1 + tests/testthat/test-analytic_transport.R | 171 +++++++++++ vignettes/analytic_test.Rmd | 368 +++++++++++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 tests/testthat/test-analytic_transport.R create mode 100644 vignettes/analytic_test.Rmd diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 27bef154c..b86a01b36 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -80,6 +80,7 @@ articles: navbar: For developers contents: - numerical_details + - analytic_test - developer_vignette - working_with_git - developer_FAQ diff --git a/tests/testthat/test-analytic_transport.R b/tests/testthat/test-analytic_transport.R new file mode 100644 index 000000000..bf3ee7346 --- /dev/null +++ b/tests/testthat/test-analytic_transport.R @@ -0,0 +1,171 @@ +library(mizer) + +# Parameters +p <- 0.7 +A <- 1 +B <- 0.5 +K <- 0.1 + +# Helper functions defined at top level for visibility +# We assign to global environment so that mizer can find them with get() +assign("start_growth", function(params, ...) { + matrix(A * params@w^p, nrow = 1, byrow = TRUE) +}, envir = globalenv()) + +assign("start_mort", function(params, ...) { + matrix(B * params@w^(p - 1), nrow = 1, byrow = TRUE) +}, envir = globalenv()) + +assign("constant_rdd", function(rdi, species_params, params, ...) { + w_min <- 1e-3 + # Recalculate lambda from A, B, K, p + a_quad <- K + b_quad <- -(2 * A - K) + c_quad <- -2 * B + det <- b_quad^2 - 4 * a_quad * c_quad + x <- (-b_quad - sqrt(det)) / (2 * a_quad) + lambda <- p - x + + J_min <- w_min^(p - lambda) * (A - 0.5 * K * (p + 1 - lambda)) + structure(rep(J_min, length(rdi)), names = names(rdi)) +}, envir = globalenv()) + +assign("N_analytic", function(w, t, w0, t0, params) { + U <- A - 0.5 * K + V <- 0.5 * K * (1 - p) + b <- B / (1 - p) + nu <- sqrt((U/V)^2 + 4 * b / V) + dt <- t - t0 + x <- w^(1 - p) / (1 - p) + x0 <- w0^(1 - p) / (1 - p) + z <- 2 * sqrt(x * x0) / (V * dt) + bessel_scaled <- besselI(z, nu, expon.scaled = TRUE) + log_N_tilde <- -log(V * dt) + (U / (2 * V)) * log(x / x0) - (x + x0) / (V * dt) + z + log(bessel_scaled) + N_tilde <- exp(log_N_tilde) + N <- N_tilde * w^(-p) + return(N) +}, envir = globalenv()) + +assign("time_dep_rdd", function(rdi, species_params, params, t, ...) { + t0 <- 0 + w0 <- 10 + U <- A - 0.5 * K + V <- 0.5 * K * (1 - p) + b <- B / (1 - p) + nu <- sqrt((U/V)^2 + 4 * b / V) + dt <- t - t0 + if (dt <= 0) return(structure(rep(0, length(rdi)), names = names(rdi))) + + w_min <- min(params@w) + x <- w_min^(1 - p) / (1 - p) + x0 <- w0^(1 - p) / (1 - p) + z <- 2 * sqrt(x * x0) / (V * dt) + + I_nu <- besselI(z, nu, expon.scaled = TRUE) + I_nu_plus_1 <- besselI(z, nu + 1, expon.scaled = TRUE) + + ratio <- if (I_nu == 0) 0 else I_nu_plus_1 / I_nu + log_G <- -log(V * dt) + (U / (2 * V)) * log(x / x0) - (x + x0) / (V * dt) + z + log(I_nu) + G <- exp(log_G) + + term <- U/2 + x/dt - (V * z / 2) * ratio - (V * nu / 2) + J <- G * term + + structure(rep(J, length(rdi)), names = names(rdi)) +}, envir = globalenv()) + +# 1. Steady State Test +test_that("Exact steady state is maintained", { + # Calculate lambda for initial condition + a_quad <- K + b_quad <- -(2 * A - K) + c_quad <- -2 * B + det <- b_quad^2 - 4 * a_quad * c_quad + x <- (-b_quad - sqrt(det)) / (2 * a_quad) + lambda <- p - x + + # Setup Params + params <- newMultispeciesParams(data.frame(species = "Test", + w_inf = 1000, + w_max = 1000, + w_mat = 100, + beta = 100, + sigma = 1, + k_vb = 0.1), + no_w = 1000, min_w = 1e-3) + + params <- setRateFunction(params, "EGrowth", "start_growth") + params <- setRateFunction(params, "Mort", "start_mort") + params <- setRateFunction(params, "RDD", "constant_rdd") + params@diffusion[1, ] <- K * params@w^(p + 1) + params <- setResource(params, resource_dynamics = "resource_constant") + initialNResource(params) <- 0 + + initialN(params) <- matrix(params@w^(-lambda), nrow = 1, byrow = TRUE) + + # Run + sim <- project(params, t_max = 1, dt = 0.001) + + # Compare + n0 <- initialN(params)[1, ] + n1 <- finalN(sim)[1, ] + # Exclude boundaries + idx <- 10:(length(params@w) - 10) + rel_err <- abs(n1[idx] - n0[idx]) / n0[idx] + + expect_lt(max(rel_err), 0.05) +}) + +# 2. Time Dependent Test +test_that("Exact time-dependent solution is followed", { + # Setup Params + params <- newMultispeciesParams(data.frame(species = "Test", + w_inf = 1000, + w_max = 1000, + w_mat = 100, + beta = 100, + sigma = 1, + k_vb = 0.1), + no_w = 1000, min_w = 1e-3) + + params <- setRateFunction(params, "EGrowth", "start_growth") + params <- setRateFunction(params, "Mort", "start_mort") + params <- setRateFunction(params, "RDD", "time_dep_rdd") + params@diffusion[1, ] <- K * params@w^(p + 1) + params <- setResource(params, resource_dynamics = "resource_constant") + initialNResource(params) <- 0 + + w0 <- 10 + t_start <- 1 + t_end <- 2 + + initial_n <- N_analytic(params@w, t_start, w0, 0, params) + initialN(params) <- matrix(initial_n, nrow = 1, byrow = TRUE) + + sim <- project(params, t_max = t_end - t_start, dt = 0.001) + + final_n_num <- finalN(sim)[1, ] + final_n_ana <- N_analytic(params@w, t_end, w0, 0, params) + + # Metrics + total_n_num <- sum(final_n_num * params@dw) + total_n_ana <- sum(final_n_ana * params@dw) + rel_err_total <- abs(total_n_num - total_n_ana) / total_n_ana + + peak_idx_num <- which.max(final_n_num) + peak_idx_ana <- which.max(final_n_ana) + peak_w_num <- params@w[peak_idx_num] + peak_w_ana <- params@w[peak_idx_ana] + rel_err_peak_loc <- abs(peak_w_num - peak_w_ana) / peak_w_ana + + peak_val_num <- max(final_n_num) + peak_val_ana <- max(final_n_ana) + rel_err_peak_val <- abs(peak_val_num - peak_val_ana) / peak_val_ana + + expect_lt(rel_err_total, 0.05) + expect_lt(rel_err_peak_loc, 0.05) + expect_lt(rel_err_peak_val, 0.4) +}) + +# Cleanup +rm(list = c("start_growth", "start_mort", "constant_rdd", "N_analytic", "time_dep_rdd"), envir = globalenv()) diff --git a/vignettes/analytic_test.Rmd b/vignettes/analytic_test.Rmd new file mode 100644 index 000000000..a0dd50b97 --- /dev/null +++ b/vignettes/analytic_test.Rmd @@ -0,0 +1,368 @@ +--- +title: "Analytic Test" +output: + html_document: + toc: yes + fig_width: 5 + fig_height: 5 +vignette: > + %\VignetteIndexEntry{Analytic Test} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +This vignette describes an analytical test for the transport equation solver used in mizer. + +## The transport equation + +The time evolution of the size spectrum $N(w)$ is described by the McKendrick-von Foerster equation with an added diffusion term: + +\begin{equation} + \frac{\partial N}{\partial t} + \frac{\partial}{\partial w} \left( g N - \frac{1}{2}\frac{\partial(D N)}{\partial w} \right) = -\mu N +\end{equation} + +where $g(w)$ is the growth rate, $\mu(w)$ is the mortality rate and $D(w)$ is the diffusion rate. + +## Analytical solution for power-law rates + +We look for a steady state solution $N(w)$ when the rates are power laws of the form: + +\begin{align} +g(w) &= A w^p \\ +\mu(w) &= B w^{p-1} \\ +D(w) &= K w^{p+1} +\end{align} + +We try a power-law ansatz for the solution: +$$ N(w) = C w^{-\lambda} $$ +Substituting these forms into the transport equation at steady state ($\partial N / \partial t = 0$): + +$$ \frac{\partial}{\partial w} \left( A w^p C w^{-\lambda} - \frac{1}{2}\frac{\partial}{\partial w}(K w^{p+1} C w^{-\lambda}) \right) = - B w^{p-1} C w^{-\lambda} $$ + +Simplifying the term inside the derivative: +$$ D N = K C w^{p+1-\lambda} $$ +$$ \frac{\partial(D N)}{\partial w} = K C (p+1-\lambda) w^{p-\lambda} $$ +$$ g N - \frac{1}{2}\frac{\partial(D N)}{\partial w} = \left( A - \frac{1}{2} K (p+1-\lambda) \right) C w^{p-\lambda} $$ +Let $J_0 = C \left( A - \frac{1}{2} K (p+1-\lambda) \right)$. Then the flux is $J = J_0 w^{p-\lambda}$. + +Differentiating flux with respect to $w$: +$$ \frac{\partial J}{\partial w} = J_0 (p-\lambda) w^{p-\lambda-1} $$ + +The RHS is: +$$ -\mu N = - B C w^{p-1-\lambda} $$ + +Equating LHS and RHS: +$$ C \left( A - \frac{1}{2} K (p+1-\lambda) \right) (p-\lambda) w^{p-\lambda-1} = - B C w^{p-\lambda-1} $$ + +Dividing by $C w^{p-\lambda-1}$ (assuming $C \neq 0$ and $w \neq 0$): +$$ \left( A - \frac{1}{2} K (p+1-\lambda) \right) (p-\lambda) + B = 0 $$ + +Let $x = p - \lambda$. Then $p + 1 - \lambda = x + 1$. The equation becomes: +$$ \left( A - \frac{1}{2} K (x+1) \right) x + B = 0 $$ +$$ Ax - \frac{1}{2} K x^2 - \frac{1}{2} K x + B = 0 $$ +Multiply by -2: +$$ K x^2 - (2A - K) x - 2B = 0 $$ + +This is a quadratic equation for $x = p - \lambda$. The solutions are: +$$ x = \frac{(2A - K) \pm \sqrt{(2A - K)^2 + 8KB}}{2K} $$ + +We are interested in the solution that corresponds to the limit of small diffusion $K \to 0$. +In that limit, $g N \sim C A w^{p-\lambda}$ and $\frac{\partial (gN)}{\partial w} \sim C A (p-\lambda) w^{p-\lambda-1}$. +The transport equation without diffusion is $\frac{\partial (gN)}{\partial w} = -\mu N$. +$$ A (p-\lambda) = -B \implies x = p-\lambda = -B/A $$ +Since $A, B > 0$, $x$ should be negative. +Let's check the roots. $(2A-K)^2 + 8KB > (2A-K)^2$, so the square root is larger than $|2A-K|$. +The term $(2A-K)$ is positive for small $K$. +The positive root is $\frac{(2A-K) + \text{larger}}{2K} > 0$. +The negative root is $\frac{(2A-K) - \text{larger}}{2K} < 0$. +So we need the negative root. + +$$ \lambda = p - \frac{(2A - K) - \sqrt{(2A - K)^2 + 8KB}}{2K} $$ + +## Numerical verification + +We verify this analytical solution by considering a single species in mizer and checking if the `project()` function keeps the system in this steady state. + +```{r} +library(mizer) + +# Parameters +p <- 0.7 +A <- 1 +B <- 0.5 +K <- 0.1 + +# Calculate lambda +# coefficients for K x^2 - (2A - K) x - 2B = 0 +a_quad <- K +b_quad <- -(2*A - K) +c_quad <- -2*B + +det <- b_quad^2 - 4 * a_quad * c_quad +x <- (-b_quad - sqrt(det)) / (2 * a_quad) +lambda <- p - x + +# Set up mizer params +# We create a dummy species +params <- newMultispeciesParams(data.frame(species = "Test", + w_inf = 1000, + w_mat = 100, + beta = 100, + sigma = 1, + k_vb = 0.1), + no_w = 1000, min_w = 1e-3) + +# Define custom rate functions +# Growth +start_growth <- function(params, ...) { + matrix(A * params@w^p, nrow = 1, byrow = TRUE) +} +# Mort +start_mort <- function(params, ...) { + matrix(B * params@w^(p-1), nrow = 1, byrow = TRUE) +} +# RDD (Constant Flux) +constant_rdd <- function(rdi, species_params, params, ...) { + w_min <- 1e-3 # Use the strict lower boundary of the system + # Flux J = N(w) * (g(w) - 0.5 * d/dw D(w)) + # J = C * w^(p-lambda) * (A - 0.5 * K (p + 1 - lambda)) + # C = 1 (from initial N) + J_min <- w_min^(p - lambda) * (A - 0.5 * K * (p + 1 - lambda)) + structure(rep(J_min, length(rdi)), names = names(rdi)) +} + +# Assign to params +params <- setRateFunction(params, "EGrowth", "start_growth") +params <- setRateFunction(params, "Mort", "start_mort") +params <- setRateFunction(params, "RDD", "constant_rdd") + +# Set diffusion +params@diffusion[1, ] <- K * w(params)^(p+1) + +# We also need to switch off resource dynamics and other things to avoid interference +params <- setResource(params, resource_dynamics = "resource_constant") +initialNResource(params) <- 0 + +# Set initial N to analytical solution +initialN(params) <- matrix(w(params)^(-lambda), nrow = 1, byrow = TRUE) + +# Run project +# We verify that N stays constant. +sim <- project(params, t_max = 1, dt = 0.001) + +# Compare final N with initial N +n0 <- initialN(params)[1, ] +n1 <- finalN(sim)[1, ] + +# Plot +plot(w(params), n0, log="xy", type="l", col="blue", lwd=2, + main="Comparison of numerical and analytical solution", + xlab="Size", ylab="Density") +lines(w(params), n1, col="red", lty=2, lwd=2) +legend("topright", legend=c("Analytical", "Numerical"), + col=c("blue", "red"), lty=c(1, 2)) + +# Calculate relative error +# Ignore the boundaries where boundary conditions apply +idx <- 10:(length(w(params))-10) +rel_err <- abs(n1[idx] - n0[idx]) / n0[idx] +max_rel_err <- max(rel_err) +print(paste("Maximum relative error (excluding boundaries):", max_rel_err)) + +if (max_rel_err < 0.05) { + print("Test passed: Numerical solution stays close to analytical steady state.") +} else { + print("Test failed: Numerical solution deviates from analytical steady state.") +} +``` + +## Time-dependent analytical solution + +To facilitate an analytical solution for time-dependent problems, we first transform the size variable $w$ to a new variable $x$: +$$ x = \frac{w^{1-p}}{1-p} $$ +Assuming $p \neq 1$. Then $w = ((1-p)x)^{\frac{1}{1-p}}$ and $\frac{dx}{dw} = w^{-p}$. + +We define the density in $x$-space, $\tilde{N}(x, t)$, such that $\tilde{N}(x, t) dx = N(w, t) dw$. Thus: +$$ \tilde{N}(x, t) = N(w, t) \frac{dw}{dx} = N(w, t) w^p $$ + +Substituting this into the transport equation and simplifying leads to a PDE of the form: +$$ \frac{\partial \tilde{N}}{\partial t} = V x \frac{\partial^2 \tilde{N}}{\partial x^2} + (V - U) \frac{\partial \tilde{N}}{\partial x} - \frac{b}{x} \tilde{N} $$ +where: +* $U = A - \frac{1}{2}K$ +* $V = \frac{1}{2} K (1-p)$ +* $b = \frac{B}{1-p}$ + +The fundamental solution (Green's function) for this equation, describing the evolution of an initial Dirac delta distribution $\tilde{N}(x, 0) = \delta(x - x_0)$, is given by: +$$ G(x, t; x_0) = \frac{1}{Vt} \left( \frac{x}{x_0} \right)^{\frac{U}{2V}} \exp\left( -\frac{x+x_0}{Vt} \right) I_\nu \left( \frac{2\sqrt{xx_0}}{Vt} \right) $$ +where $I_\nu$ is the modified Bessel function of the first kind of order $\nu$, given by: +$$ \nu = \frac{1}{V} \sqrt{U^2 + 4Vb} $$ + +The solution in terms of the original size distribution $N(w, t)$ is then: +$$ N(w, t) = G(x(w), t; x(w_0)) w^{-p} $$ + +### Numerical verification + +We verify this time-dependent solution by starting the simulation with the analytical distribution at a small time $t_{start} > 0$ (to avoid the singularity at $t=0$) and projecting it to a later time $t_{end}$. + +```{r} +# Function to calculate N analytic +N_analytic <- function(w, t, w0, t0, params) { + # Parameters + p <- 0.7 + A <- 1 + B <- 0.5 + K <- 0.1 + + # Transformed parameters + U <- A - 0.5 * K + V <- 0.5 * K * (1 - p) + b <- B / (1 - p) + nu <- sqrt((U/V)^2 + 4 * b / V) + + # Time elapsed + dt <- t - t0 + if (dt <= 0) stop("t must be greater than t0") + + # Transform to x + x <- w^(1 - p) / (1 - p) + x0 <- w0^(1 - p) / (1 - p) + + # Argument for Bessel + z <- 2 * sqrt(x * x0) / (V * dt) + + # Logarithm of N_tilde using scaled Bessel to avoid overflow + bessel_scaled <- besselI(z, nu, expon.scaled = TRUE) + + log_N_tilde <- -log(V * dt) + + (U / (2 * V)) * log(x / x0) - + (x + x0) / (V * dt) + + z + + log(bessel_scaled) + + N_tilde <- exp(log_N_tilde) + + # Transform back to N(w) + N <- N_tilde * w^(-p) + + return(N) +} + +# Initial Condition +w0 <- 10 +t_start <- 1 +t_end <- 2 + +# Set RDD to exact analytical flux +time_dep_rdd <- function(rdi, species_params, params, t, ...) { + # Parameters (must match those used in N_analytic) + p <- 0.7 + A <- 1 + B <- 0.5 + K <- 0.1 + w0 <- 10 + t0 <- 0 + + # Transformed parameters + U <- A - 0.5 * K + V <- 0.5 * K * (1 - p) + b <- B / (1 - p) + nu <- sqrt((U/V)^2 + 4 * b / V) + + # Time elapsed + dt <- t - t0 + if (dt <= 0) return(structure(rep(0, length(rdi)), names = names(rdi))) + + # Boundary w_min + w_min <- min(params@w) + x <- w_min^(1 - p) / (1 - p) + x0 <- w0^(1 - p) / (1 - p) + + # Argument for Bessel + z <- 2 * sqrt(x * x0) / (V * dt) + + # Calculate scaled Bessel ratio I_{nu+1}/I_nu + # besselI returns I_nu * exp(-z) with expon.scaled=TRUE + I_nu <- besselI(z, nu, expon.scaled = TRUE) + I_nu_plus_1 <- besselI(z, nu + 1, expon.scaled = TRUE) + + if (I_nu == 0) { + J <- 0 + } else { + ratio <- I_nu_plus_1 / I_nu + + # Calculate G (N_tilde) at boundary + # log(G) = ... + log_G <- -log(V * dt) + + (U / (2 * V)) * log(x / x0) - + (x + x0) / (V * dt) + + z + + log(I_nu) # I_nu is already scaled, so we add z back... wait. + # The formula in N_analytic: log_N_tilde = ... + z + log(bessel_scaled) + # This reconstructs the unscaled log value. Correct. + + G <- exp(log_G) + + # Flux J = G * [ U/2 + x/dt - (V*z/2) * ratio - (V*nu/2) ] + term <- U/2 + x/dt - (V * z / 2) * ratio - (V * nu / 2) + J <- G * term + } + + structure(rep(J, length(rdi)), names = names(rdi)) +} + +params <- setRateFunction(params, "RDD", "time_dep_rdd") + +# Set initial N from analytical solution +initial_n <- N_analytic(w(params), t_start, w0, 0, params) +initialN(params) <- matrix(initial_n, nrow = 1, byrow = TRUE) + +# Run project +sim <- project(params, t_max = t_end - t_start, dt = 0.001) + +# Compare +final_n_num <- finalN(sim)[1, ] +final_n_ana <- N_analytic(w(params), t_end, w0, 0, params) + +# Plot +plot(w(params), final_n_num, log="xy", type="l", col="red", lwd=2, + main="Time Dependent Check", xlab="Size", ylab="Density") +lines(w(params), final_n_ana, col="blue", lty=2, lwd=2) +legend("topright", legend=c("Numerical", "Analytical"), col=c("red", "blue"), lty=c(1, 2)) + +# Robust comparison metrics +# 1. Total Abundance (Conservation) +total_n_num <- sum(final_n_num * params@dw) +total_n_ana <- sum(final_n_ana * params@dw) +rel_err_total <- abs(total_n_num - total_n_ana) / total_n_ana + +# 2. Peak Location +peak_idx_num <- which.max(final_n_num) +peak_idx_ana <- which.max(final_n_ana) +peak_w_num <- w(params)[peak_idx_num] +peak_w_ana <- w(params)[peak_idx_ana] +rel_err_peak_loc <- abs(peak_w_num - peak_w_ana) / peak_w_ana + +# 3. Peak Height +peak_val_num <- max(final_n_num) +peak_val_ana <- max(final_n_ana) +rel_err_peak_val <- abs(peak_val_num - peak_val_ana) / peak_val_ana + +print(paste("Total Abundance Error:", rel_err_total)) +print(paste("Peak Location Error:", rel_err_peak_loc)) +print(paste("Peak Height Error:", rel_err_peak_val)) + +# Pass conditions: < 5% mass error, < 5% location shift, < 40% height difference (diffusive flattening) +if (rel_err_total < 0.05 && rel_err_peak_loc < 0.05 && rel_err_peak_val < 0.4) { + print("Time-dependent test passed.") +} else { + print("Time-dependent test failed.") +} +``` + From a165dea22d77d87a9d56711539f90af9a521602b Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 10:17:04 +0000 Subject: [PATCH 04/47] Avoid warnings in tests --- tests/testthat/test-analytic_transport.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-analytic_transport.R b/tests/testthat/test-analytic_transport.R index bf3ee7346..5da021a4c 100644 --- a/tests/testthat/test-analytic_transport.R +++ b/tests/testthat/test-analytic_transport.R @@ -92,7 +92,8 @@ test_that("Exact steady state is maintained", { beta = 100, sigma = 1, k_vb = 0.1), - no_w = 1000, min_w = 1e-3) + no_w = 1000, min_w = 1e-3, + info_level = 0) params <- setRateFunction(params, "EGrowth", "start_growth") params <- setRateFunction(params, "Mort", "start_mort") @@ -126,7 +127,8 @@ test_that("Exact time-dependent solution is followed", { beta = 100, sigma = 1, k_vb = 0.1), - no_w = 1000, min_w = 1e-3) + no_w = 1000, min_w = 1e-3, + info_level = 0) params <- setRateFunction(params, "EGrowth", "start_growth") params <- setRateFunction(params, "Mort", "start_mort") From c1846ec797793c9c8beb18342b4be3645f17db67 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 12:10:17 +0000 Subject: [PATCH 05/47] feat: Implement diffusion in steady state calculation --- R/numerical_methods.R | 42 +++++++++ R/project_n.R | 108 ++-------------------- R/steadySingleSpecies.R | 93 +++++++++++++++---- R/transport.R | 99 ++++++++++++++++++++ man/project_n.Rd | 8 +- tests/testthat/test-steadySingleSpecies.R | 73 +++++++++++++++ 6 files changed, 304 insertions(+), 119 deletions(-) create mode 100644 R/numerical_methods.R create mode 100644 R/transport.R diff --git a/R/numerical_methods.R b/R/numerical_methods.R new file mode 100644 index 000000000..f93e44d3b --- /dev/null +++ b/R/numerical_methods.R @@ -0,0 +1,42 @@ +#' Thomas algorithm for solving tridiagonal system +#' +#' Solves a tridiagonal system of linear equations A * x = d +#' where A is a tridiagonal matrix defined by diagonals a, b, c. +#' +#' @param a Lower diagonal (length N). a[1] is ignored/0. +#' @param b Main diagonal (length N). +#' @param c Upper diagonal (length N). c[N] is ignored/0. +#' @param d Right hand side vector (length N). +#' +#' @return Solution vector x (length N). +#' @noRd +thomas_solve <- function(a, b, c, d) { + n <- length(d) + c_prime <- numeric(n) + d_prime <- numeric(n) + + # Forward elimination + c_prime[1] <- c[1] / b[1] + d_prime[1] <- d[1] / b[1] + + if (n > 1) { + for (i in 2:n) { + temp <- b[i] - a[i] * c_prime[i - 1] + if (i < n) { + c_prime[i] <- c[i] / temp + } + d_prime[i] <- (d[i] - a[i] * d_prime[i - 1]) / temp + } + } + + # Backward substitution + x <- numeric(n) + x[n] <- d_prime[n] + if (n > 1) { + for (i in (n - 1):1) { + x[i] <- d_prime[i] - c_prime[i] * x[i + 1] + } + } + + return(x) +} diff --git a/R/project_n.R b/R/project_n.R index 1c546a070..6744e24af 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -28,104 +28,12 @@ #' @export project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j - - # Diffusion coefficient D_i(w) - d <- params@diffusion # species x size - - # Pre-calculate some common terms - # g_i(w_j) - g <- r$e_growth - # mu_i(w_j) - mu <- r$mort - # dw_j - dw <- params@dw - # Delta t / Delta w_j - dt_dw <- matrix(dt / dw, nrow = no_sp, ncol = no_w, byrow = TRUE) - - # We assume d is 0 at the boundaries for simplicity or it is handled by the loop constraints - # Actually for j=1, A is 0, for j=no_w, C is 0 (boundary condition) - - # A_j = - dt/dw_j * (g_{j-1} + D_{j-1} / (2 * dw_{j-1})) - # Note: efficient calculation avoiding loop: - # We compute A for j in idx (2:no_w). g_{j-1} corresponds to columns 1:(no_w-1) - - # Indices for j-1 and j - # idx is 2:no_w - idx_minus_1 <- idx - 1 - - term_diff_minus_1 <- 0.5 * d[, idx_minus_1] / - matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) - - a[, idx] <- -dt_dw[, idx] * (g[, idx_minus_1] + term_diff_minus_1) - - # C_j = - dt/dw_j * (D_{j+1} / (2 * dw_j)) - # Note: For j=no_w, we assume N_{j+1}=0, so we don't need C_{no_w} effectively, or it is 0 flux. - # The equation involves C_j * N_{j+1}. At j=no_w, N_{no_w+1} is 0. So C_{no_w} doesn't matter. - # We can compute C for j=1:(no_w-1). - # idx_plus_1 is 2:no_w - idx_j <- 1:(no_w - 1) - term_diff_plus_1 <- 0.5 * d[, idx_j + 1] / - matrix(dw[idx_j], nrow = no_sp, ncol = length(idx_j), byrow = TRUE) - - c[, idx_j] <- -dt_dw[, idx_j] * term_diff_plus_1 - c[, no_w] <- 0 # Boundary condition N_{no_w+1} = 0 - - # B_j - # B_j = 1 + dt * mu_j + dt/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) - # Careful with j=1 term for D_j / (2 * dw_{j-1}). dw_0 is not defined. - # At j=1 boundary condition comes from recruitment. - # Standard formula works for j > 1. - - term_diff_j <- 0.5 * d[, idx] / matrix(dw[idx], nrow = no_sp, ncol = length(idx), byrow = TRUE) - term_diff_j_minus_1 <- 0.5 * d[, idx] / matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) - - b[, idx] <- 1 + dt * mu[, idx] + dt_dw[, idx] * (g[, idx] + term_diff_j + term_diff_j_minus_1) - - # Boundary j=1 (approx w_min) - # Equation: N_1 - N_1^old / dt + (J_{3/2} - J_{1/2}) / dw_1 = -mu_1 N_1 - # J_{1/2} = R_dd (recruitment flux). - # J_{3/2} follows standard flux definition. - # result: (1 + dt*mu_1 + dt/dw_1 * (g_1 + D_1/(2*dw_1))) N_1 - dt/dw_1 * (D_2/(2*dw_1)) N_2 = N_1^old + dt/dw_1 * R_dd - # So for j=1: - # B_1 = 1 + dt*mu_1 + dt/dw_1 * (g_1 + d_1/(2*dw_1)) - # C_1 = - dt/dw_1 * d_2/(2*dw_1) (Matches standard formula) - # A_1 = 0 - # S_1 = N_1^old + dt/dw_1 * R_dd - - dw_1 <- dw[1] - dt_dw_1 <- dt / dw_1 - b[, 1] <- 1 + dt * mu[, 1] + dt_dw_1 * (g[, 1] + 0.5 * d[, 1] / dw_1) - # c[, 1] already computed correctly above - a[, 1] <- 0 - - # RHS S - S[] <- n - # Add recruitment to S[, 1] for each species - # We need to distribute R_dd correctly. - # r$rdd is vector of length no_sp. - # "r$rdd * dt / params@dw[params@w_min_idx]" - # Wait, the prompt says "w_min_idx_array_ref" handles the start index. - # Different species can have different w_min_idx. - # So the "j=1" above really refers to w_min_idx[i]. - # But currently 'a' and 'b' and 'c' are computed for the whole grid. - # Species i only exists from w_min_idx[i] to w_max_idx[i] (implicitly). - # We should iterate the Thomas algorithm for each species from w_min_idx[i] to no_w. - - # Let's adjust S for the recruitment term at the start index for each species. - # S[w_min_idx_array_ref] <- S[w_min_idx_array_ref] + r$rdd * dt / params@dw[params@w_min_idx] - # But wait, we need to respect the diffusion B_1 term calculation which might be different at the boundary. - # The B calculation above `b[, idx]` used `idx` which starts at 2. - # If w_min_idx[i] > 1, then the "standard" formula for B at w_min_idx[i] might be using w_{j-1} which is essentially 0 since N is 0 there? - # Actually, if we assume N is 0 below w_min_idx, then the flux from below is just the recruitment. - # So for j = w_min_idx[i], the term A_j should be effectively 0 (or replaced by recruitment boundary condition). - # The B_j term should not include diffusion from below (or handled as boundary). - # We can handle this by essentially running the solver from w_min_idx[i]. - - # Correct B matrix for start indices - # We need to loop over species to correct B at w_min_idx, because vectorization is hard with variable indices. - # However, w_min_idx might be same for all species or not. - + coefs <- get_transport_coefs(params, n, n_pp, n_other, r, dt) + a <- coefs$a + b <- coefs$b + c <- coefs$c + S <- coefs$S + # Loop over species to apply boundary condition and solve # Temporary copy of C for modification during Thomas algo @@ -139,7 +47,7 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, # Apply boundary condition to S (RHS) # S_j_start = N_old + dt/dw * R_dd - d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / dw[j_start] + d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / params@dw[j_start] # Apply boundary condition to B (LHS) # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux @@ -153,7 +61,7 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, # We need to subtract that term if (j_start > 1) { # The term added was: dt/dw[j_start] * 0.5 * d[i, j_start] / dw[j_start-1] - correction <- (dt / dw[j_start]) * 0.5 * d[i, j_start] / dw[j_start - 1] + correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] b[i, j_start] <- b[i, j_start] - correction # Also A[i, j_start] should be ignored/0. diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index 7fcaffb4f..f4380e1d0 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -34,44 +34,105 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, biomass <- getBiomass(params, use_cutoff = TRUE) number <- getN(params) + # Use growth and mortality from current abundances # Use growth and mortality from current abundances growth_all <- getEGrowth(params) mort_all <- getMort(params) # Loop through all species and calculate their steady state abundances # using the current growth and mortality rates + + # We can use get_transport_coefs with an arbitrary dt (e.g. 1) and then transform. + # We need to construct the rates list + rates <- list(e_growth = growth_all, mort = mort_all) + + # We use a dummy dt = 1 + dt <- 1 + + coefs <- get_transport_coefs(params, params@initial_n, params@initial_n_pp, + params@initial_n_other, rates, dt) + + # Loop over species for (sp in species) { - growth <- growth_all[sp, ] - mort <- mort_all[sp, ] - w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) - idx <- w_min_idx:(w_max_idx - 1) - + idx <- w_min_idx:w_max_idx + # Check that species can grow to maturity at least w_mat_idx <- sum(params@w <= params@species_params[sp, "w_mat"]) - - # Find first index where growth becomes zero + + # Check growth (existing check) + growth <- growth_all[sp, ] zero_growth_idx <- which(growth[w_min_idx:w_max_idx] == 0) if (length(zero_growth_idx) > 0) { - # Convert to absolute index first_zero_idx <- w_min_idx + zero_growth_idx[1] - 1 - if (first_zero_idx < w_mat_idx) { - # Growth stops before maturity - this is an error stop(sp, " cannot grow to maturity") } else { - # Growth stops at or after maturity - issue a warning warning(sp, " has zero growth rate after maturity size") } } - + + # Extract coefficients for this species + # We need to perform the same boundary adjustments as in project_n? + # project_n modifies B and S at w_min_idx. + # "d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / params@dw[j_start]" + # Here S_rhs (right hand side) for steady state is just the recruitment flux term? + # N_old cancels out with "1" in B. + # Net equation: Trans(N) + mu N = Source + # Source at j_start is R_dd / dw[j_start]. + + # Let's extract A, B, C for the specific species range + # Note: the coefficients in coefs passed cover all w. + # We only solve for w_min_idx:w_max_idx. + # At w_max_idx, we assume N_{w_max_idx+1} = 0 (handled by C=0). + # At w_min_idx, we have the boundary condition. + + a_sp <- coefs$a[sp, ] + b_sp <- coefs$b[sp, ] + c_sp <- coefs$c[sp, ] + + # Adjust B for steady state (subtract 1 because B includes +1 from time stepping N_new) + b_sp <- b_sp - 1 + + # Adjust B at boundary w_min_idx same as in project_n? + # In project_n: + # if (j_start > 1) { + # correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] + # b[i, j_start] <- b[i, j_start] - correction + # a[i, j_start] <- 0 + # } + # This correction removes the "influx from below" due to diffusion, + # because we implement influx explicitly via R_dd. + # We must apply the same correction here. + # Keep egg density constant - N0 <- params@initial_n[sp, w_min_idx] - # Steady state solution of the upwind-difference scheme used in project + N_fixed <- params@initial_n[sp, w_min_idx] + + # Right hand side vector + rhs <- numeric(length(params@w)) + rhs[w_min_idx] <- N_fixed + + # Modify coefficients for fixed boundary condition + # Row for w_min_idx: 1 * N_{w_min_idx} + 0 * N_{w_min_idx+1} = N_fixed + b_sp[w_min_idx] <- 1 + c_sp[w_min_idx] <- 0 + a_sp[w_min_idx] <- 0 # Should be 0 already + + # Solve system + idx_solve <- w_min_idx:w_max_idx + + a_sub <- a_sp[idx_solve] + b_sub <- b_sp[idx_solve] + c_sub <- c_sp[idx_solve] + rhs_sub <- rhs[idx_solve] + + # Solver + n_opt <- thomas_solve(a_sub, b_sub, c_sub, rhs_sub) + + # Update params params@initial_n[sp, ] <- 0 - params@initial_n[sp, w_min_idx:w_max_idx] <- - get_steady_state_n(growth, mort, params@dw, idx, N0) + params@initial_n[sp, idx_solve] <- n_opt } if (any(is.infinite(params@initial_n))) { diff --git a/R/transport.R b/R/transport.R new file mode 100644 index 000000000..f669acd9e --- /dev/null +++ b/R/transport.R @@ -0,0 +1,99 @@ +#' Helper function to calculate the transport coefficients for the upwind-difference scheme +#' +#' @param params A \linkS4class{MizerParams} object. +#' @param n An array (species x size) with the number density at the current time step. +#' @param n_pp A vector (size) with the resource number density at the current time step. +#' @param n_other A list with the abundances of other components. +#' @param rates A list of rates as returned by [mizerRates()]. +#' @param dt Time step. +#' +#' @return A list with the coefficients A, B, C and S. +#' @noRd +get_transport_coefs <- function(params, n, n_pp, n_other, rates, dt) { + # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j + + no_sp <- nrow(params@species_params) + no_w <- length(params@w) + + # Diffusion coefficient D_i(w) + d <- params@diffusion # species x size + + # Pre-calculate some common terms + # g_i(w_j) + g <- rates$e_growth + # mu_i(w_j) + mu <- rates$mort + # dw_j + dw <- params@dw + # Delta t / Delta w_j + dt_dw <- matrix(dt / dw, nrow = no_sp, ncol = no_w, byrow = TRUE) + + # Initialize matrices + a <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, NULL)) + b <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, NULL)) + c <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, NULL)) + S <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, NULL)) + + # We assume d is 0 at the boundaries for simplicity or it is handled by the loop constraints + # Actually for j=1, A is 0, for j=no_w, C is 0 (boundary condition) + + # A_j = - dt/dw_j * (g_{j-1} + D_{j-1} / (2 * dw_{j-1})) + # Note: efficient calculation avoiding loop: + # We compute A for j in idx (2:no_w). g_{j-1} corresponds to columns 1:(no_w-1) + + # Indices for j-1 and j + idx <- 2:no_w + idx_minus_1 <- idx - 1 + + term_diff_minus_1 <- 0.5 * d[, idx_minus_1] / + matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) + + a[, idx] <- -dt_dw[, idx] * (g[, idx_minus_1] + term_diff_minus_1) + + # C_j = - dt/dw_j * (D_{j+1} / (2 * dw_j)) + # Note: For j=no_w, we assume N_{j+1}=0, so we don't need C_{no_w} effectively, or it is 0 flux. + # The equation involves C_j * N_{j+1}. At j=no_w, N_{no_w+1} is 0. So C_{no_w} doesn't matter. + # We can compute C for j=1:(no_w-1). + idx_j <- 1:(no_w - 1) + term_diff_plus_1 <- 0.5 * d[, idx_j + 1] / + matrix(dw[idx_j], nrow = no_sp, ncol = length(idx_j), byrow = TRUE) + + c[, idx_j] <- -dt_dw[, idx_j] * term_diff_plus_1 + c[, no_w] <- 0 # Boundary condition N_{no_w+1} = 0 + + # B_j + # B_j = 1 + dt * mu_j + dt/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) + # Careful with j=1 term for D_j / (2 * dw_{j-1}). dw_0 is not defined. + # At j=1 boundary condition comes from recruitment. + # Standard formula works for j > 1. + + term_diff_j <- 0.5 * d[, idx] / matrix(dw[idx], nrow = no_sp, ncol = length(idx), byrow = TRUE) + term_diff_j_minus_1 <- 0.5 * d[, idx] / matrix(dw[idx_minus_1], nrow = no_sp, ncol = length(idx), byrow = TRUE) + + b[, idx] <- 1 + dt * mu[, idx] + dt_dw[, idx] * (g[, idx] + term_diff_j + term_diff_j_minus_1) + + # Boundary j=1 (approx w_min) + # Equation: N_1 - N_1^old / dt + (J_{3/2} - J_{1/2}) / dw_1 = -mu_1 N_1 + # J_{1/2} = R_dd (recruitment flux). + # J_{3/2} follows standard flux definition. + # result: (1 + dt*mu_1 + dt/dw_1 * (g_1 + D_1/(2*dw_1))) N_1 - dt/dw_1 * (D_2/(2*dw_1)) N_2 = N_1^old + dt/dw_1 * R_dd + # So for j=1: + # B_1 = 1 + dt*mu_1 + dt/dw_1 * (g_1 + d_1/(2*dw_1)) + # C_1 = - dt/dw_1 * d_2/(2*dw_1) (Matches standard formula) + # A_1 = 0 + + dw_1 <- dw[1] + dt_dw_1 <- dt / dw_1 + b[, 1] <- 1 + dt * mu[, 1] + dt_dw_1 * (g[, 1] + 0.5 * d[, 1] / dw_1) + # c[, 1] already computed correctly above + a[, 1] <- 0 + + # RHS S + S[] <- n + + return(list(a = a, b = b, c = c, S = S)) +} diff --git a/man/project_n.Rd b/man/project_n.Rd index 44c151b4b..7faa80617 100644 --- a/man/project_n.Rd +++ b/man/project_n.Rd @@ -4,7 +4,7 @@ \alias{project_n} \title{Project values for first time step of Euler method} \usage{ -project_n(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, no_sp, no_w) +project_n(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) } \arguments{ \item{params}{A \linkS4class{MizerParams} object.} @@ -19,6 +19,8 @@ project_n(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, no_sp, no_w) \item{b}{A matrix (species x size) used in the solver (diagonal term).} +\item{c}{A matrix (species x size) used in the solver (transport term).} + \item{S}{A matrix (species x size) used in the solver (source term).} \item{idx}{Index vector for size bins (excluding the first one).} @@ -39,8 +41,8 @@ It is of potential interest only to mizer extension authors. \details{ The function calculates the abundance at the next time step using the McKendrick-von Foerster equation: -\deqn{\frac{\partial N}{\partial t} + \frac{\partial g N}{\partial w} = -\mu N} -which is solved using a semi-implicit upwind finite difference scheme. +\deqn{\frac{\partial N}{\partial t} + \frac{\partial}{\partial w} \left( g N - \frac{1}{2}\frac{\partial(D N)}{\partial w} \right) = -\mu N} +which is solved using a semi-implicit upwind finite volume scheme. } \seealso{ \code{\link{project}}, \code{\link{mizerRates}} diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 206806842..229f004bd 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -26,6 +26,79 @@ test_that("steadySingleSpecies `keep` argument works", { expect_gt(getBiomass(params)[3], getBiomass(NS_params)[3]) }) +test_that("steadySingleSpecies produces steady state with diffusion", { + params <- NS_params + + # Enable diffusion for Cod + species <- "Cod" + p <- params@species_params[species, "p"] + d <- 0.1 * params@w^p + diffusion(params)[species, ] <- d + + # Keep original params to calculate rates that steadySingleSpecies used + params_orig <- params + + params <- steadySingleSpecies(params, species = species) + + # Since N is fixed at the boundary (and potentially inconsistent with R_dd), + # projecting with `project()` (which uses R_dd) will immediately change the boundary + # and propagate effects. + # Instead, we verify that the calculated N satisfies the steady state transport equation + # with the fixed boundary condition. + + # We need to access internal functions + get_transport_coefs <- mizer:::get_transport_coefs + + # Calculate coefficients + # steadySingleSpecies uses rates from the *original* state + # and dt=1 + growth <- getEGrowth(params_orig) + mort <- getMort(params_orig) + rates <- list(e_growth = growth, mort = mort) + dt <- 1 + + coefs <- get_transport_coefs(params_orig, params_orig@initial_n, params_orig@initial_n_pp, + params_orig@initial_n_other, rates, dt) + + # Check residual for Cod + sp <- species + n <- params@initial_n[sp, ] + + a <- coefs$a[sp, ] + b <- coefs$b[sp, ] - 1 # Adjust for steady state + c <- coefs$c[sp, ] + + # Boundary correction used in steadySingleSpecies + w_min_idx <- params@w_min_idx[sp] + # In steadySingleSpecies we set b[w_min_idx] = 1 and c[w_min_idx] = 0. + # We should replicate that here to verify the interior. + b[w_min_idx] <- 1 + c[w_min_idx] <- 0 + a[w_min_idx] <- 0 + + # Calculate residual A*N_{i-1} + B*N_i + C*N_{i+1} + # For interior points within the solved range + w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) + + residuals <- numeric(w_max_idx) + # Start from w_min_idx + 1 (the first interior node) + # End at w_max_idx (the last solved node) + for (i in (w_min_idx + 1):w_max_idx) { + # Note: n[i+1] will be 0 if i = w_max_idx, which is consistent with the solver + # (assuming 0 flux from above or just absorbing boundary) + val_next <- if (i < length(n)) n[i+1] else 0 + residuals[i] <- a[i] * n[i-1] + b[i] * n[i] + c[i] * val_next + } + + # Check max residual relative to N + # We exclude the boundary point because we fixed it explicitly. + + valid_range <- (w_min_idx + 1):w_max_idx + max_rel_resid <- max(abs(residuals[valid_range]) / n[valid_range]) + + expect_lt(max_rel_resid, 1e-10) +}) + test_that("steadySingleSpecies errors when growth stops before maturity", { # Create a simple params object params <- newSingleSpeciesParams() From 51cafba59e8cb9110041239524e2e1729570c143 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 12:28:08 +0000 Subject: [PATCH 06/47] feat: Account for diffusion in the `get_required_reproduction` calculation at the minimum size. --- R/wrapper_functions.R | 56 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index da1f9f1ab..b7df80960 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -613,15 +613,57 @@ get_required_reproduction <- function(params) { assert_that(is(params, "MizerParams")) no_sp <- nrow(params@species_params) - mumu <- getMort(params) - gg <- getEGrowth(params) + + # Calculate rates + rates_fns <- lapply(params@rates_funcs, get) + rates <- mizerRates(params, n = params@initial_n, n_pp = params@initial_n_pp, + n_other = params@initial_n_other, t = 0, + effort = params@initial_effort, rates_fns = rates_fns) + + # Calculate transport coefficients + dt <- 1 + coefs <- get_transport_coefs(params, params@initial_n, params@initial_n_pp, + params@initial_n_other, rates, dt) + reproduction <- params@species_params$erepro # vector of correct length + for (i in (1:no_sp)) { - gg0 <- gg[i, params@w_min_idx[i]] - mumu0 <- mumu[i, params@w_min_idx[i]] - DW <- params@dw[params@w_min_idx[i]] - reproduction[i] <- params@initial_n[i, params@w_min_idx[i]] * - (gg0 + DW * mumu0) + w_min_idx <- params@w_min_idx[i] + + # Get coefficients for this species at the boundary + # The equation for the first node is: + # (N_new - N_old)/dt = -(Flux_matrix * N) + R/dw + # In steady state N_new = N_old, so: + # Flux_matrix * N = R/dw + # The rows of coefs correspond to the linear system A*N_{j-1} + B*N_j + C*N_{j+1} = ... + # For the first node j=w_min_idx: + # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? + # No, let's look at project_n again. + # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source + # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N + R * dt / dw + # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt + + # Extract coefficients + a <- coefs$a[i, w_min_idx] + b <- coefs$b[i, w_min_idx] + c <- coefs$c[i, w_min_idx] + + # Boundary correction for diffusion (if not at global min size) + if (w_min_idx > 1) { + correction <- (dt / params@dw[w_min_idx]) * 0.5 * params@diffusion[i, w_min_idx] / params@dw[w_min_idx - 1] + b <- b - correction + a <- 0 + } + + n_current <- params@initial_n[i, w_min_idx] + n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 + n_prev <- if (w_min_idx > 1) params@initial_n[i, w_min_idx - 1] else 0 # Should be irrelevant if A=0 or boundary + + # Calculate R + # R = ( A * n_prev + (B - 1) * n_current + C * n_next ) * dw / dt + + total_rate <- a * n_prev + (b - 1) * n_current + c * n_next + reproduction[i] <- total_rate * params@dw[w_min_idx] / dt } return(reproduction) } From 8aaf7e7a55088ae101c11d0e04a2e999781276b5 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 12:45:58 +0000 Subject: [PATCH 07/47] feat: Add diffusion support to `get_steady_state_n` and refactor steady-state calculations to use it. --- R/get_initial_n.R | 2 +- R/helpers.R | 88 +++++++++++++++++++++++- R/newSingleSpeciesParams.R | 2 +- R/steadySingleSpecies.R | 80 +++------------------ R/wrapper_functions.R | 2 +- tests/testthat/test-get_steady_state_n.R | 58 ++++++++++++++++ 6 files changed, 158 insertions(+), 74 deletions(-) create mode 100644 tests/testthat/test-get_steady_state_n.R diff --git a/R/get_initial_n.R b/R/get_initial_n.R index 3f31e3bea..73cf459d5 100644 --- a/R/get_initial_n.R +++ b/R/get_initial_n.R @@ -68,7 +68,7 @@ get_initial_n <- function(params, n0_mult = NULL, a = 0.35) { idx <- idxs[1:(length(idxs) - 1)] # Steady state solution of the upwind-difference scheme used in project p@initial_n[i, idxs] <- - get_steady_state_n(growth, mort, p@dw, idx) + get_steady_state_n(growth, mort, p@dw, p@diffusion[i, ], idx) } p <- matchBiomasses(p) return(p@initial_n) diff --git a/R/helpers.R b/R/helpers.R index 1a0266568..08701fe39 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -94,7 +94,93 @@ w2l <- function(w, species_params) { #' @param N0 The initial egg density. #' @return A numeric vector representing the steady state abundances. #' @keywords internal -get_steady_state_n <- function(growth, mort, dw, idx, N0) { +get_steady_state_n <- function(growth, mort, dw, diffusion = rep(0, length(dw)), + idx, N0) { + if (any(diffusion[idx] > 0)) { + # Steady state solution of the upwind-difference scheme used in project + # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = 0 + # The N_j are the densities at the size classes j in min_idx:max_idx + min_idx <- min(idx) + max_idx <- max(idx) + 1 + n <- max_idx - min_idx + 1 + + # We need to subset the rate arrays to the range min_idx:max_idx + # However, the coefficients at j depend on j-1, j, j+1. + # So we need to be careful with indexing. + # We will construct a, b, c vectors of length n. + # The j-th element of these vectors corresponds to the size class min_idx + j - 1. + + # Ranges for the relevant size classes + j_range <- min_idx:max_idx + + # Diffusion coefficient D_i(w) + d <- diffusion + # Growth rate g_i(w) + g <- growth + # Mortality rate mu_i(w) + mu <- mort + + # Initialize vectors + a <- numeric(n) + b <- numeric(n) + c <- numeric(n) + rhs <- numeric(n) + + # Calculate coefficients for the inner points + # The indices into the full arrays are j + # The indices into the small arrays are k = j - min_idx + 1 + + # We only need to form the equations for j from min_idx to max_idx. + # But for j = min_idx we have the boundary condition N = N0. + + # Boundary condition at min_idx + # N_{min_idx} = N0 + b[1] <- 1 + rhs[1] <- N0 + # a[1] and c[1] are 0 + + # Now loop or vectorize for the rest + # We iterate k from 2 to n. + # This corresponds to j from min_idx + 1 to max_idx. + if (n > 1) { + k <- 2:n + j <- j_range[k] + + # Using formulas from transport.R, divided by dt + # a_j = - 1/dw_j * (g_{j-1} + D_{j-1} / (2 * dw_{j-1})) + term_diff_minus_1 <- 0.5 * d[j - 1] / dw[j - 1] + a[k] <- - (g[j - 1] + term_diff_minus_1) / dw[j] + + # c_j = - 1/dw_j * (D_{j+1} / (2 * dw_j)) + # Note: At the last bin j=max_idx, we assume N_{j+1} = 0 (or flux is handled) + # If j < length(dw), we compute c normally. + # If j == length(dw), term_diff_plus_1 involves d[length+1]?? + # In project_n/transport, c[no_w] is set to 0. + # Here max_idx could be the last bin. + c[k] <- 0 # Default to 0 + + # Create a mask for valid j+1 + valid_c <- j < length(dw) + if (any(valid_c)) { + # Only compute for valid j + # Indices in k that are valid + k_valid <- k[valid_c] + j_valid <- j[valid_c] + term_diff_plus_1 <- 0.5 * d[j_valid + 1] / dw[j_valid] + c[k_valid] <- - term_diff_plus_1 / dw[j_valid] + } + + # b_j = mu_j + 1/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) + term_diff_j <- 0.5 * d[j] / dw[j] + term_diff_j_minus_1 <- 0.5 * d[j] / dw[j - 1] + b[k] <- mu[j] + (g[j] + term_diff_j + term_diff_j_minus_1) / dw[j] + } + + # Solve + n_exact <- thomas_solve(a, b, c, rhs) + return(n_exact) + } + # Steady state solution of the upwind-difference scheme used in project n_exact <- c(1, cumprod(growth[idx] / ((growth + mort * dw)[idx + 1]))) if (!missing(N0)) { diff --git a/R/newSingleSpeciesParams.R b/R/newSingleSpeciesParams.R index 93202a495..6054706a2 100644 --- a/R/newSingleSpeciesParams.R +++ b/R/newSingleSpeciesParams.R @@ -200,7 +200,7 @@ newSingleSpeciesParams <- idxs <- 1:i_inf gg <- hbar * w^n * (1 - params@psi[1, ]) # Growth rate # Steady state solution of the upwind-difference scheme used in project - initial_n[1, idxs] <- get_steady_state_n(gg, mumu, dw, idx) + initial_n[1, idxs] <- get_steady_state_n(gg, mumu, dw, params@diffusion[1, ], idx) # The resource was already set up by newMultispeciesParams() initial_n_pp <- params@initial_n_pp diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index f4380e1d0..af4a4aa48 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -42,21 +42,10 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, # Loop through all species and calculate their steady state abundances # using the current growth and mortality rates - # We can use get_transport_coefs with an arbitrary dt (e.g. 1) and then transform. - # We need to construct the rates list - rates <- list(e_growth = growth_all, mort = mort_all) - - # We use a dummy dt = 1 - dt <- 1 - - coefs <- get_transport_coefs(params, params@initial_n, params@initial_n_pp, - params@initial_n_other, rates, dt) - # Loop over species for (sp in species) { w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) - idx <- w_min_idx:w_max_idx # Check that species can grow to maturity at least w_mat_idx <- sum(params@w <= params@species_params[sp, "w_mat"]) @@ -73,66 +62,17 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, } } - # Extract coefficients for this species - # We need to perform the same boundary adjustments as in project_n? - # project_n modifies B and S at w_min_idx. - # "d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / params@dw[j_start]" - # Here S_rhs (right hand side) for steady state is just the recruitment flux term? - # N_old cancels out with "1" in B. - # Net equation: Trans(N) + mu N = Source - # Source at j_start is R_dd / dw[j_start]. - - # Let's extract A, B, C for the specific species range - # Note: the coefficients in coefs passed cover all w. - # We only solve for w_min_idx:w_max_idx. - # At w_max_idx, we assume N_{w_max_idx+1} = 0 (handled by C=0). - # At w_min_idx, we have the boundary condition. - - a_sp <- coefs$a[sp, ] - b_sp <- coefs$b[sp, ] - c_sp <- coefs$c[sp, ] - - # Adjust B for steady state (subtract 1 because B includes +1 from time stepping N_new) - b_sp <- b_sp - 1 - - # Adjust B at boundary w_min_idx same as in project_n? - # In project_n: - # if (j_start > 1) { - # correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] - # b[i, j_start] <- b[i, j_start] - correction - # a[i, j_start] <- 0 - # } - # This correction removes the "influx from below" due to diffusion, - # because we implement influx explicitly via R_dd. - # We must apply the same correction here. - - # Keep egg density constant - N_fixed <- params@initial_n[sp, w_min_idx] - - # Right hand side vector - rhs <- numeric(length(params@w)) - rhs[w_min_idx] <- N_fixed - - # Modify coefficients for fixed boundary condition - # Row for w_min_idx: 1 * N_{w_min_idx} + 0 * N_{w_min_idx+1} = N_fixed - b_sp[w_min_idx] <- 1 - c_sp[w_min_idx] <- 0 - a_sp[w_min_idx] <- 0 # Should be 0 already - - # Solve system - idx_solve <- w_min_idx:w_max_idx - - a_sub <- a_sp[idx_solve] - b_sub <- b_sp[idx_solve] - c_sub <- c_sp[idx_solve] - rhs_sub <- rhs[idx_solve] - - # Solver - n_opt <- thomas_solve(a_sub, b_sub, c_sub, rhs_sub) - - # Update params + N0 <- params@initial_n[sp, w_min_idx] params@initial_n[sp, ] <- 0 - params@initial_n[sp, idx_solve] <- n_opt + + if (w_min_idx == w_max_idx) { + params@initial_n[sp, w_min_idx] <- N0 + } else { + idx <- w_min_idx:(w_max_idx - 1) + n_exact <- get_steady_state_n(growth, mort_all[sp, ], params@dw, + params@diffusion[sp, ], idx, N0) + params@initial_n[sp, w_min_idx:w_max_idx] <- n_exact + } } if (any(is.infinite(params@initial_n))) { diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index b7df80960..a48324188 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -508,7 +508,7 @@ newTraitParams <- function(no_sp = 11, gg <- hbar * w^n * (1 - params@psi[i, ]) # Growth rate idx <- w_min_idx[i]:(i_inf - 2) # Steady state solution of the upwind-difference scheme used in project - n_exact <- get_steady_state_n(gg, mumu, dw, idx) + n_exact <- get_steady_state_n(gg, mumu, dw, params@diffusion[i, ], idx) # Use the first species for normalisation if (i == 1) { dist_sp <- bins_per_sp * dx diff --git a/tests/testthat/test-get_steady_state_n.R b/tests/testthat/test-get_steady_state_n.R new file mode 100644 index 000000000..afba427a4 --- /dev/null +++ b/tests/testthat/test-get_steady_state_n.R @@ -0,0 +1,58 @@ +test_that("get_steady_state_n works with no diffusion", { + # Simple case: constant growth, constant mortality + # dN/dw = - (mu/g) N + # N(w) = N0 * exp(- mu/g * (w - w0)) + # But we work with bins. + + # 3 bins + growth <- c(1, 1, 1) + mort <- c(0.5, 0.5, 0.5) + dw <- c(1, 1, 1) + # diffusion 0 + idx <- 1:2 + N0 <- 100 + + # Analytical/recursive result from old implementation + # n_exact <- c(1, cumprod(growth[idx] / ((growth + mort * dw)[idx + 1]))) * N0 + # idx=1: g[1] / (g[2] + mort[2]*dw[2]) = 1 / (1 + 0.5*1) = 1/1.5 = 2/3 + # idx=2: g[2] / (g[3] + mort[3]*dw[3]) = 1 / 1.5 = 2/3 + # n[1] = 100 + # n[2] = 100 * 2/3 + # n[3] = 100 * 2/3 * 2/3 = 100 * 4/9 + + n_expected <- c(100, 100 * 2/3, 100 * 4/9) + + # Using new implementation with default diffusion (0) + n_calc <- mizer:::get_steady_state_n(growth, mort, dw, idx = idx, N0 = N0) + + expect_equal(n_calc, n_expected) +}) + +test_that("get_steady_state_n works with diffusion", { + # 3 bins + # Diffusion dominates or mixes + # If we have strong diffusion, distribution should flatten or change + + growth <- c(1, 1, 1) + mort <- c(0.1, 0.1, 0.1) + dw <- c(1, 1, 1) + diffusion <- c(1, 1, 1) # Non-zero + idx <- 1:2 + N0 <- 100 + + n_calc <- mizer:::get_steady_state_n(growth, mort, dw, diffusion, idx, N0) + + # Check it runs and returns vector of correct length + expect_length(n_calc, 3) + expect_equal(n_calc[1], N0) # Boundary condition + expect_true(all(n_calc > 0)) # Should be positive + + # Compare with no diffusion + n_nodiff <- mizer:::get_steady_state_n(growth, mort, dw, diffusion = rep(0, 3), idx, N0) + expect_false(isTRUE(all.equal(n_calc, n_nodiff))) +}) + +test_that("get_steady_state_n matches analytical steady state for advection-diffusion?", { + # Hard to test exact analytical without solving ODE, but we can check consistency + # or rely on the fact it solves the system we defined. +}) From 5cd629997a5d45fd2665f4d13e73b8c95f243935 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 13:37:55 +0000 Subject: [PATCH 08/47] Refactor project_n to use thomas_solve --- R/project_n.R | 55 ++++++++---------------- tests/testthat/_snaps/project_methods.md | 14 +++--- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/R/project_n.R b/R/project_n.R index 6744e24af..cd03dd895 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -36,18 +36,13 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, # Loop over species to apply boundary condition and solve - # Temporary copy of C for modification during Thomas algo - c_prime <- c - # Temporary copy of S (d in Thomas algo context) - d_prime <- S - for (i in 1:no_sp) { # Start index for this species j_start <- params@w_min_idx[i] # Apply boundary condition to S (RHS) # S_j_start = N_old + dt/dw * R_dd - d_prime[i, j_start] <- d_prime[i, j_start] + r$rdd[i] * dt / params@dw[j_start] + S[i, j_start] <- S[i, j_start] + r$rdd[i] * dt / params@dw[j_start] # Apply boundary condition to B (LHS) # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux @@ -66,41 +61,29 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, # Also A[i, j_start] should be ignored/0. a[i, j_start] <- 0 - } else { - # j_start == 1. The formula used 'dw[1]' for prev bin width in approximation? - # My code above: `b[, 1] <- ... + g[, 1] + 0.5 * d[, 1] / dw_1` - # This formula for j=1 was already "boundary-like" (omitted term from below). - # So if j_start == 1, B is already correct for the boundary condition derived. } - # Thomas Algorithm: Forward Elimination - # For j = j_start - # c'[j] = c[j] / b[j] - # d'[j] = d[j] / b[j] + # Thomas Algorithm + # We need to pass the sub-vectors for the current species i, starting from j_start + # We are solving for n[i, j_start:no_w] - c_prime[i, j_start] <- c_prime[i, j_start] / b[i, j_start] - d_prime[i, j_start] <- d_prime[i, j_start] / b[i, j_start] + # Extract the relevant parts of the vectors + # Note: thomas_solve accepts vectors of length N + # a, b, c, d are vectors of length N - # Loop j from j_start+1 to no_w - if (j_start < no_w) { - for (j in (j_start + 1):no_w) { - temp <- b[i, j] - a[i, j] * c_prime[i, j - 1] - if (j < no_w) { - c_prime[i, j] <- c_prime[i, j] / temp - } - d_prime[i, j] <- (d_prime[i, j] - a[i, j] * d_prime[i, j - 1]) / temp - } - } + # Correctly slicing from j_start to no_w + # a[i, j_start] is effectively 0 or ignored by thomas_solve if it's the first element passed + # c[i, no_w] is 0 or ignored by thomas_solve - # Backward Substitution - n[i, no_w] <- d_prime[i, no_w] - if (no_w > j_start) { - for (j in (no_w - 1):j_start) { - n[i, j] <- d_prime[i, j] - c_prime[i, j] * n[i, j + 1] - } - } - # Species density is 0 below j_start? Mizer usually keeps it 0 or doesn't update. - # The loop range updates n for j >= j_start. + # We solve for the segment of the size spectrum inhabited by the species + relevant_indices <- j_start:no_w + + n[i, relevant_indices] <- thomas_solve( + a = a[i, relevant_indices], + b = b[i, relevant_indices], + c = c[i, relevant_indices], + d = S[i, relevant_indices] + ) } n diff --git a/tests/testthat/_snaps/project_methods.md b/tests/testthat/_snaps/project_methods.md index 165c20bd2..be0e39451 100644 --- a/tests/testthat/_snaps/project_methods.md +++ b/tests/testthat/_snaps/project_methods.md @@ -67,7 +67,7 @@ ] } }, - "value": [0, 0, 7.61877819e-16, 6.65569128e-07, 0, 0, 1.15661812e-14, 1.51469051e-14, 0, 0, 0, 9.94693624e-15, 0, 0, 2.07581149e-15, 9.22652518e-07, 0, 0, 2.19090801e-15, 2.11986392e-14, 0, 2.75314939e-15, 0, 1.89180985e-14, 0, 0, 1.11327868e-15, 1.27619096e-06, 0, 0, 1.01733097e-14, 8.5049195e-15, 0, 0, 0, 1.43391924e-14, 0, 0, 1.79188978e-15, 1.76126283e-06, 0, 0, 5.89662748e-16, 1.32577992e-14, 0, 0, 0, 3.54231688e-14, 0, 0, 1.71116943e-15, 2.42528019e-06, 0, 0, 1.20596703e-14, 1.27115444e-14, 0, 0, 0, 3.89657725e-14, 0, 0, 2.88454276e-15, 3.33217203e-06, 0, 0, 0, 1.03770203e-14, 0, 6.98320097e-15, 0, 5.78741947e-14, 0, 0, 2.96602702e-15, 4.56792475e-06, 0, 0, 1.33308247e-14, 1.37219492e-14, 0, 2.1544438e-14, 0, 3.55597305e-14, 0, 0, 3.32455775e-15, 6.24791186e-06, 0, 0, 7.41506754e-15, 1.10492655e-14, 0, 2.56186509e-14, 0, 7.1119461e-14, 0, 1.24065899e-10, 2.8030585e-15, 8.52655537e-06, 0, 0, 3.52011998e-15, 9.77811104e-15, 0, 1.10818592e-14, 0, 4.31540634e-14, 0, 1.69227607e-10, 4.6934933e-15, 0.00001161, 0, 1.30374814e-16, 0, 1.14566868e-14, 0, 2.65312746e-14, 0, 7.71167024e-14, 0, 3.69817074e-10, 2.21637184e-15, 0.00001577, 0, 0, 1.11470466e-14, 1.32004499e-14, 0, 2.21637184e-14, 0, 4.71956826e-14, 0, 4.77845586e-10, 3.52011998e-15, 0.00002138, 0, 0, 4.88905552e-16, 1.23856073e-14, 0, 3.51360123e-14, 0, 7.74426395e-14, 0, 7.83066896e-10, 2.34674665e-15, 0.00002892, 0, 0, 0, 4.04161923e-15, 0, 1.8904348e-14, 0, 5.73649181e-14, 0, 1.13207925e-09, 4.56311849e-15, 0.00003902, 0, 0, 0, 1.23856073e-15, 0, 3.45493257e-14, 0, 6.46659077e-14, 0, 1.92940405e-09, 2.86824591e-15, 0.00005253, 0, 0, 1.69487258e-15, 7.43136439e-15, 0, 4.22414397e-14, 0, 5.00639285e-14, 0, 2.81588494e-09, 3.65049479e-15, 0.00007056, 0, 0, 0, 0, 0, 6.36229092e-14, 0, 6.36229092e-14, 0, 4.36103766e-09, 3.38974516e-15, 0.00009456, 0, 0, 4.95424293e-15, 1.10818592e-14, 0, 9.33483668e-14, 0, 4.79779315e-14, 0, 6.42120756e-09, 3.65049479e-15, 0.00012644, 0, 1.04299851e-15, 0, 5.606117e-15, 0, 1.1186159e-13, 0, 6.25799107e-14, 0, 9.53999931e-09, 5.73649181e-15, 0.00016868, 1.56449777e-15, 5.21499256e-16, 1.04299851e-15, 1.1212234e-14, 0, 1.60621771e-13, 0, 1.87739732e-14, 0, 1.39424559e-08, 3.12899553e-15, 0.00022451, 0, 2.08599702e-15, 0, 6.25799107e-15, 0, 2.08599702e-13, 0, 5.84079166e-14, 0, 2.04951033e-08, 4.17199404e-15, 0.00029814, 0, 0, 0, 0, 0, 2.95168579e-13, 0, 4.17199404e-14, 0, 2.9749107e-08, 4.6934933e-15, 0.00039501, 0, 0, 0, 0, 0, 4.43274367e-13, 0, 0, 0, 4.29615081e-08, 3.65049479e-15, 0.00052214, 0, 0, 0, 4.17199404e-15, 5.21499256e-16, 6.56046064e-13, 0, 2.92039583e-14, 0, 6.17313667e-08, 3.12899553e-15, 0.00068861, 0, 1.04299851e-15, 0, 2.08599702e-15, 2.60749628e-15, 9.88762589e-13, 0, 1.25159821e-14, 0, 8.84960248e-08, 0, 0.00090606, 1.04299851e-15, 0, 0, 1.04299851e-15, 3.12899553e-15, 1.52694982e-12, 0, 0, 0, 1.26063668e-07, 5.21499256e-16, 0.00118942, 0, 0, 0, 0, 0, 2.32588668e-12, 0, 2.50319643e-14, 0, 1.78607404e-07, 1.04299851e-15, 0.00155777, 8.34398809e-15, 0, 2.92039583e-14, 3.12899553e-15, 0, 3.59625887e-12, 8.34398809e-15, 0, 0, 2.51966227e-07, 0, 0.00203548, 1.35589806e-14, 0, 2.50319643e-14, 1.04299851e-15, 0, 5.47782818e-12, 0, 1.66879762e-14, 0, 3.53751561e-07, 2.08599702e-15, 0.0026535, 1.66879762e-14, 4.17199404e-15, 8.76118749e-14, 0, 0, 8.37736404e-12, 0, 4.17199404e-14, 0, 4.94396964e-07, 0, 0.00345113, 2.71179613e-14, 0, 1.46019792e-13, 8.34398809e-15, 0, 1.26745179e-11, 0, 0, 0, 6.88280942e-07, 0, 0.00447807, 5.84079166e-14, 0, 2.75351607e-13, 8.34398809e-15, 0, 1.91536247e-11, 0, 0, 0, 9.54070789e-07, 0, 0.00579704, 8.9697872e-14, 0, 4.42231369e-13, 1.66879762e-14, 0, 2.88159629e-11, 0, 0, 0, 1.31770339e-06, 0, 0.00748695, 1.79395744e-13, 0, 7.55130922e-13, 4.38059375e-14, 0, 4.31843104e-11, 0, 1.66879762e-14, 0, 1.81369885e-06, 0, 0.00964688, 3.19157544e-13, 0, 1.27663018e-12, 8.9697872e-14, 0, 6.43279762e-11, 0, 0, 0, 2.48854078e-06, 0, 0.01240079, 5.50703214e-13, 8.34398809e-15, 2.07348104e-12, 1.66879762e-13, 6.25799107e-15, 9.54802557e-11, 0, 3.33759524e-14, 1.45127205e-13, 3.40513695e-06, 0, 0.01590344, 9.1783869e-13, 0, 3.38765916e-12, 2.75351607e-13, 0, 1.40988367e-10, 0, 3.33759524e-14, 0, 4.64829932e-06, 0, 0.02034747, 1.54780979e-12, 2.08599702e-15, 5.45696821e-12, 4.88123303e-13, 0, 2.07389824e-10, 0, 0, 2.25174074e-11, 6.33263905e-06, 0, 0.02597203, 2.49902443e-12, 1.04299851e-14, 8.76118749e-12, 8.09366845e-13, 4.17199404e-15, 3.03721166e-10, 0, 0, 1.92858084e-11, 8.61378327e-06, 4.17199404e-15, 0.03307314, 4.02180226e-12, 0, 1.39010842e-11, 1.37258604e-12, 0, 4.42999016e-10, 1.66879762e-14, 3.33759524e-14, 1.23153035e-10, 0.0000117, 0, 0.04201629, 6.47493476e-12, 0, 2.19864086e-11, 2.27790875e-12, 0, 6.43721993e-10, 1.66879762e-14, 0, 1.27659498e-10, 0.00001589, 0, 0.05325137, 1.02881373e-11, 0, 3.44773588e-11, 3.72976268e-12, 1.66879762e-14, 9.31906654e-10, 0, 0, 2.5875209e-10, 0.00002156, 0, 0.06733068, 1.62707768e-11, 0, 5.36852194e-11, 6.06190735e-12, 1.66879762e-14, 1.34420814e-09, 5.00639285e-14, 1.66879762e-14, 3.3112178e-10, 0.00002926, 0, 0.08493031, 2.55492915e-11, 0, 8.29976495e-11, 9.80418601e-12, 1.25159821e-14, 1.93256777e-09, 3.33759524e-14, 3.33759524e-14, 5.96914306e-10, 0.00003973, 0, 0.1068755, 3.97090393e-11, 0, 1.27554546e-10, 1.56449777e-11, 1.66879762e-14, 2.76958659e-09, 8.34398809e-14, 6.67519047e-14, 1.03092476e-09, 0.00005399, 0, 0.13417065, 6.14618163e-11, 4.17199404e-15, 1.94656898e-10, 2.48483965e-11, 2.08599702e-14, 3.95740336e-09, 0, 0, 1.85581559e-09, 0.00007344, 0, 0.16803472, 9.44706332e-11, 0, 2.95277051e-10, 3.91666801e-11, 7.50958928e-14, 5.63865021e-09, 0, 3.33759524e-14, 3.38972847e-09, 0.00010003, 6.25799107e-15, 0.20994275, 1.44109018e-10, 0, 4.45018261e-10, 6.12114966e-11, 1.58535774e-13, 8.01314896e-09, 2.33631667e-13, 2.67007619e-13, 6.04708842e-09, 0.00013645, 0, 0.26167444, 2.1855408e-10, 4.17199404e-15, 6.66267449e-10, 9.50880883e-11, 3.00383571e-13, 1.13596055e-08, 6.67519047e-14, 0, 1.02589667e-08, 0.00018642, 0, 0.32537069, 3.29228738e-10, 0, 9.91682984e-10, 1.46503743e-10, 5.17327262e-13, 1.60672669e-08, 3.33759524e-14, 1.66879762e-13, 1.69019828e-08, 0.0002551, 0, 0.4035993, 4.92829313e-10, 4.17199404e-15, 1.46685642e-09, 2.24211304e-10, 9.42870654e-13, 2.26778916e-08, 6.67519047e-14, 6.67519047e-14, 2.70927291e-08, 0.00034966, 2.08599702e-15, 0.49943087, 7.33244641e-10, 0, 2.15673735e-09, 3.4071841e-10, 1.67714161e-12, 3.19463936e-08, 1.00127857e-13, 0, 4.1845968e-08, 0.00048001, 0, 0.61652609, 1.08471011e-09, 0, 3.15254227e-09, 5.1433177e-10, 2.89536387e-12, 4.49223965e-08, 3.33759524e-14, 1.00127857e-13, 6.26549398e-08, 0.0006599, 8.34398809e-15, 0.75923593, 1.5955374e-09, 8.34398809e-15, 4.58153367e-09, 7.71151379e-10, 5.01473684e-12, 6.3064663e-08, 0, 1.33503809e-13, 9.15430281e-08, 0.00090839, 2.71179613e-14, 0.93271583, 2.33428073e-09, 2.92039583e-14, 6.62123824e-09, 1.14901722e-09, 8.64437166e-12, 8.8398713e-08, 0, 2.00255714e-13, 1.31060156e-07, 0.00125187, 5.00639285e-14, 1.14305547, 3.39742163e-09, 4.17199404e-14, 9.51715282e-09, 1.70122236e-09, 1.4677075e-11, 1.23734268e-07, 2.33631667e-13, 6.67519047e-14, 1.84974334e-07, 0.00172686, 9.1783869e-14, 1.39742561, 4.92039137e-09, 9.5955863e-14, 1.36083771e-08, 2.50361363e-09, 2.46064209e-11, 1.72965833e-07, 2.00255714e-13, 0, 2.58497552e-07, 0.00238393, 1.85653735e-13, 1.7042432, 7.09280708e-09, 1.83567738e-13, 1.93607058e-08, 3.66286058e-09, 4.09189176e-11, 2.41485996e-07, 3.67135476e-13, 3.33759524e-14, 3.60994301e-07, 0.00329301, 3.44189509e-13, 2.07335639, 1.01791481e-08, 3.04555565e-13, 2.7412938e-08, 5.32900481e-09, 6.74110798e-11, 3.36751744e-07, 7.67646904e-13, 0, 5.04964809e-07, 0.00455089, 6.63347053e-13, 2.51625063, 1.45479769e-08, 6.27885104e-13, 3.86380384e-08, 7.71134691e-09, 1.09990451e-10, 4.69068738e-07, 1.63542167e-12, 1.66879762e-13, 7.10949438e-07, 0.00629149, 1.22656625e-12, 3.04627708, 2.07110133e-08, 1.1931903e-12, 5.42279624e-08, 1.11020099e-08, 1.77768666e-10, 6.52657937e-07, 3.43772309e-12, 0, 1.00808406e-06, 0.00870022, 2.20489885e-12, 3.67890431, 2.93778303e-08, 2.12563097e-12, 7.58073346e-08, 1.59061779e-08, 2.84663498e-10, 9.07121104e-07, 6.64181452e-12, 1.00127857e-13, 1.43686839e-06, 0.01203391, 3.99677029e-12, 4.43199411, 4.15314831e-08, 3.81946055e-12, 1.05589231e-07, 2.26860854e-08, 4.51576635e-10, 1.25944443e-06, 1.28497417e-11, 2.33631667e-13, 2.05171218e-06, 0.01664877, 7.13410982e-12, 5.32610177, 5.85302395e-08, 6.76906034e-12, 1.465875e-07, 3.22193421e-08, 7.1016517e-10, 1.74673003e-06, 2.51320921e-11, 6.67519047e-14, 2.92438706e-06, 0.02303937, 1.25472721e-11, 6.38480085, 8.22499448e-08, 1.18276031e-11, 2.02908568e-07, 4.55799194e-08, 1.10714709e-09, 2.41992466e-06, 4.72937245e-11, 1.16815833e-13, 4.14156586e-06, 0.0318934, 2.17840669e-11, 7.63503209, 1.1527832e-07, 2.03885349e-11, 2.80150034e-07, 6.42509779e-08, 1.71118508e-09, 3.3488932e-06, 8.8262706e-11, 3.17071547e-13, 5.80653138e-06, 0.04416866, 3.74165286e-11, 9.10747533, 1.61180184e-07, 3.47819144e-11, 3.85951303e-07, 9.02783465e-08, 2.62300775e-09, 4.6293252e-06, 1.61973497e-10, 5.34015238e-13, 8.03760432e-06, 0.0612015, 6.34685454e-11, 10.83694278, 2.24864622e-07, 5.86248603e-11, 5.30749267e-07, 1.26484129e-07, 3.98790064e-09, 6.39212068e-06, 2.92306591e-10, 1.16815833e-12, 0.00001097, 0.08485961, 1.06419224e-10, 12.86279109, 3.13084423e-07, 9.76830686e-11, 7.28818923e-07, 1.76762448e-07, 6.0147805e-09, 8.8161425e-06, 5.19162939e-10, 2.60332428e-12, 0.00001476, 0.11775779, 1.7642737e-10, 15.22934902, 4.3512411e-07, 1.60996207e-10, 9.99706647e-07, 2.46487916e-07, 9.00144846e-09, 0.00001215, 9.076924e-10, 5.77403976e-12, 0.00001961, 0.16356253, 2.89256863e-10, 17.98635615, 6.03742066e-07, 2.62451801e-10, 1.37019988e-06, 3.4308111e-07, 1.33692342e-08, 0.00001671, 1.56217814e-09, 1.25410141e-11, 0.00002581, 0.22742178, 4.69063548e-10, 21.18940744, 8.36462194e-07, 4.23300946e-10, 1.87703237e-06, 4.76793402e-07, 1.9710202e-08, 0.00002297, 2.64778105e-09, 2.665487e-11, 0.00003379, 0.31657108, 7.52581834e-10, 24.9003971, 1.157347e-06, 6.7569407e-10, 2.57058887e-06, 6.61791967e-07, 2.88505112e-08, 0.00003154, 4.41984387e-09, 5.5312297e-11, 0.00004428, 0.44118716, 1.19499521e-09, 29.18795381, 1.59943084e-06, 1.06771445e-09, 3.51996787e-06, 9.17660913e-07, 4.1936275e-08, 0.00004326, 7.26851478e-09, 1.1220578e-10, 0.00005838, 0.6155875, 1.87844345e-09, 34.12785869, 2.20806097e-06, 1.67075011e-09, 4.81987785e-06, 1.27147428e-06, 6.05480119e-08, 0.00005927, 1.17791746e-08, 2.22200403e-10, 0.00007787, 0.85991234, 2.92411881e-09, 39.80343533, 3.04548781e-06, 2.5897215e-09, 6.60000499e-06, 1.76065509e-06, 8.68543618e-08, 0.00008113, 1.88174746e-08, 4.3001577e-10, 0.0001054, 1.20247609, 4.50929976e-09, 46.3059007, 4.19717583e-06, 3.97769959e-09, 9.03769291e-06, 2.4369085e-06, 1.23817879e-07, 0.00011096, 2.96446878e-08, 8.12948502e-10, 0.00014491, 1.68304375, 6.89135348e-09, 53.73466396, 5.78048132e-06, 6.05623396e-09, 0.00001238, 3.37162199e-06, 1.75471495e-07, 0.00015161, 4.60735205e-08, 1.50219529e-09, 0.00020211, 2.35737938, 1.0441322e-08, 62.19755927, 7.95659067e-06, 9.14391945e-09, 0.00001694, 4.66325997e-06, 2.47291584e-07, 0.00020699, 7.06768082e-08, 2.71371525e-09, 0.0002852, 3.30353524, 1.56907721e-08, 71.81099774, 0.00001095, 1.36960403e-08, 0.00002319, 6.44746381e-06, 3.46705144e-07, 0.00028241, 1.07065299e-07, 4.79445347e-09, 0.00040585, 4.63051011, 2.33969444e-08, 82.70002253, 0.00001506, 2.0359841e-08, 0.00003172, 8.91080849e-06, 4.83784938e-07, 0.00038505, 1.60257122e-07, 8.28759733e-09, 0.00058058, 6.49011511, 3.46331985e-08, 94.99825081, 0.0000207, 3.00512516e-08, 0.00004336, 0.00001231, 6.72211928e-07, 0.00052467, 2.37169705e-07, 1.40232248e-08, 0.00083255, 9.093157, 5.09140912e-08, 108.84768548, 0.00002846, 4.40616716e-08, 0.00005923, 0.00001699, 9.30621902e-07, 0.00071452, 3.47278007e-07, 2.32402399e-08, 0.00119387, 12.73140016, 7.43680092e-08, 124.39837999, 0.00003912, 6.42066028e-08, 0.00008081, 0.00002345, 1.28450813e-06, 0.00097255, 5.03501292e-07, 3.77485072e-08, 0.00170841, 17.80721572, 1.07974592e-07, 141.80793944, 0.00005376, 9.30331032e-08, 0.00011014, 0.00003232, 1.76893347e-06, 0.00132303, 7.23405102e-07, 6.01398973e-08, 0.00243503, 24.8733947, 1.55890017e-07, 161.24084225, 0.00007389, 1.34109939e-07, 0.0001499, 0.00004451, 2.43242257e-06, 0.00179882, 1.03084329e-06, 9.40633119e-08, 0.00345131, 34.68631382, 2.23890751e-07, 182.86756772, 0.00010154, 1.92434787e-07, 0.00020374, 0.00006123, 3.3425729e-06, 0.00244421, 1.45821134e-06, 1.44584692e-07, 0.00485814, 48.27652786, 3.1997587e-07, 206.86351652, 0.00013953, 2.75007171e-07, 0.00027649, 0.00008413, 4.59416049e-06, 0.00331889, 2.04954898e-06, 2.18664977e-07, 0.00678631, 67.04194945, 4.55180167e-07, 233.40771352, 0.00019171, 3.91637096e-07, 0.00037463, 0.00011543, 6.32084727e-06, 0.00450307, 2.86481614e-06, 3.25808672e-07, 0.00940782, 92.87009596, 6.44665504e-07, 262.68128513, 0.00026334, 5.56088881e-07, 0.00050681, 0.00015815, 8.71205216e-06, 0.00610421, 3.98578103e-06, 4.78962345e-07, 0.01295659, 128.29745996, 9.09177012e-07, 294.86570669, 0.00036164, 7.87702159e-07, 0.00068454, 0.00021636, 0.00001204, 0.00826589, 5.52411103e-06, 6.95779838e-07, 0.01776516, 176.71591499, 1.27697533e-06, 330.14081933, 0.00049643, 1.11369139e-06, 0.00092319, 0.00029554, 0.00001668, 0.01117929, 7.63246125e-06, 1.00041883e-06, 0.02432472, 242.63820846, 1.78638764e-06, 368.6826205, 0.0006811, 1.57240857e-06, 0.00124316, 0.00040305, 0.00002319, 0.01509802, 0.00001052, 1.42609376e-06, 0.03337327, 332.03700466, 2.48916243e-06, 410.66083676, 0.00093383, 2.21796919e-06, 0.00167161, 0.00054882, 0.00003234, 0.02035713, 0.00001447, 2.01868087e-06, 0.04600978, 452.77457976, 3.45486976e-06, 456.23629337, 0.00127925, 3.12680034e-06, 0.00224459, 0.00074616, 0.00004525, 0.02739737, 0.00001988, 2.84175992e-06, 0.06382061, 615.14304832, 4.77666933e-06, 505.55810054, 0.00175067, 4.40688638e-06, 0.00300997, 0.00101292, 0.00006349, 0.03679579, 0.00002727, 3.9835838e-06, 0.08899142, 832.53777328, 6.578883e-06, 558.76068225, 0.00239292, 6.21077991e-06, 0.00403113, 0.00137305, 0.00008928, 0.0493042, 0.00003739, 5.56661061e-06, 0.12436864, 1122.28915981, 9.02697621e-06, 615.96067959, 0.0032662, 8.75383652e-06, 0.00539197, 0.00185863, 0.00012574, 0.06589692, 0.00005123, 7.76042926e-06, 0.17344058, 1506.68004948, 0.00001234, 677.25376659, 0.00445104, 0.00001234, 0.00720328, 0.00251262, 0.00017726, 0.08782982, 0.00007016, 0.0000108, 0.24023632, 2014.17700313, 0.00001681, 742.71142227, 0.00605469, 0.0000174, 0.00961108, 0.00339246, 0.00024989, 0.11671232, 0.00009606, 0.00001501, 0.32919653, 2680.90336294, 0.00002283, 812.3777081, 0.00821944, 0.00002452, 0.01280737, 0.00457487, 0.00035208, 0.15459466, 0.00013152, 0.00002082, 0.44514755, 3552.37947455, 0.00003091, 886.26610508, 0.01113318, 0.00003455, 0.01704388, 0.00616222, 0.0004954, 0.20407253, 0.00018011, 0.00002885, 0.5935887, 4685.5500819, 0.00004174, 964.35646878, 0.01504285, 0.00004866, 0.02264971, 0.00829082, 0.00069573, 0.26841102, 0.00024676, 0.00003993, 0.7815579, 6151.10985417, 0.00005626, 1046.59216395, 0.0202713, 0.00006846, 0.03005356, 0.01114184, 0.00097469, 0.35169016, 0.00033835, 0.00005519, 1.01935184, 8036.12441165, 0.00007571, 1132.87744292, 0.02723846, 0.0000962, 0.03981185, 0.01495541, 0.00136161, 0.4589734, 0.00046443, 0.00007622, 1.32334654, 10446.92527244, 0.0001018, 1223.0751326, 0.03648739, 0.00013496, 0.0526438, 0.02004891, 0.00189607, 0.59650047, 0.00063832, 0.0001052, 1.72013019, 13512.23218098, 0.00013687, 1317.00469514, 0.04871638, 0.00018894, 0.06947498, 0.02684039, 0.00263126, 0.77190478, 0.00087869, 0.0001452, 2.25218507, 17386.42491261, 0.00018414, 1414.44072516, 0.06481779, 0.00026387, 0.09149091, 0.03587827, 0.00363843, 0.99445496, 0.00121166, 0.00020056, 2.98550241, 22252.84891546, 0.0002481, 1515.1119435, 0.08592491, 0.00036753, 0.12020241, 0.0478789, 0.00501263, 1.27531865, 0.00167381, 0.00027745, 4.01981698, 28326.9956882, 0.00033499, 1618.70074252, 0.11346754, 0.00051037, 0.15752453, 0.0637734, 0.00688023, 1.62784486, 0.00231638, 0.00038474, 5.50259672, 35859.35099982, 0.00045356, 1724.84333155, 0.14923753, 0.00070642, 0.20587103, 0.08476554, 0.00940881, 2.06785969, 0.00321105, 0.00053518, 7.64848824, 45137.65424644, 0.00061612, 1833.13052308, 0.19546488, 0.00097444, 0.2682659, 0.11240259, 0.01281987, 2.61396775, 0.00445807, 0.00074713, 10.76658037, 56488.26373405, 0.00083998, 1943.10919109, 0.25490513, 0.00133937, 0.34847381, 0.14866088, 0.01740543, 3.28784907, 0.00619738, 0.00104695, 15.298564, 70276.27983693, 0.0011495, 2054.28442191, 0.33093825, 0.00183426, 0.45115025, 0.19604799, 0.02354944, 4.11453913, 0.00862417, 0.00147233, 21.87143711, 86904.04612996, 0.00157887, 2166.12236622, 0.42767899, 0.00250283, 0.58201203, 0.25772308, 0.03175522, 5.12267636, 0.01201034, 0.00207681, 31.36826784, 106807.63379795, 0.002176, 2278.05378809, 0.55009792, 0.00340269, 0.74802757, 0.33763652, 0.04268073, 6.34469956, 0.01673413, 0.00293579, 45.01872543, 130450.92333919, 0.00300766, 2389.47829312, 0.70415177, 0.00460963, 0.95762514, 0.44068939, 0.05718318, 7.81697495, 0.02332099, 0.00415469, 64.50677381, 158316.93611797, 0.00416655, 2499.76920427, 0.89692083, 0.00622314, 1.22091577, 0.57291244, 0.07637542, 9.57983087, 0.03249951, 0.00587961, 92.08669456, 190896.14219191, 0.00578044, 2608.27903966, 1.13675002, 0.00837367, 1.54992536, 0.74166294, 0.10169623, 11.67747729, 0.0452775, 0.0083116, 130.69442463, 228671.58396567, 0.00802436, 2714.34553302, 1.43338948, 0.01123195, 1.95882849, 0.95583638, 0.13499719, 14.15778668, 0.06304443, 0.01172524, 184.04774816, 272100.80910598, 0.01113616, 2817.29812451, 1.79812876, 0.01502107, 2.4641735, 1.22608804, 0.17864862, 17.07191426, 0.08770753, 0.01649315, 256.75850397, 321594.79906816, 0.01543638, 2916.46483741, 2.24391776, 0.02003204, 3.08508616, 1.56505758, 0.23566699, 20.47373766, 0.12187003, 0.02311769, 354.54334192, 377494.30590555, 0.02135318, 3011.17944524, 2.78546619, 0.02664346, 3.84343593, 1.98758722, 0.30986538, 24.41909996, 0.16906034, 0.03227168, 484.71625249, 440044.25980241, 0.0294531, 3100.78882508, 3.43931176, 0.03534667, 4.76394681, 2.51092185, 0.40602814, 28.96484579, 0.23402107, 0.04484989, 657.25445401, 509367.16862096, 0.0404782, 3184.66038493, 4.22384665, 0.04677698, 5.87423212, 3.15487676, 0.5301087, 34.16764683, 0.32306542, 0.06203302, 886.80261746, 585436.68028721, 0.05539061, 3262.18944848, 5.15929025, 0.06175226, 7.20473154, 3.94195661, 0.68944782, 40.08262203, 0.44450619, 0.0853658, 1195.95761349, 668052.69751603, 0.07542404, 3332.80647734, 6.26759642, 0.08132008, 8.78852797, 4.89740699, 0.89300665, 46.76176744, 0.60915918, 0.11685098, 1620.00877969, 756819.59892522, 0.10214281, 3395.98401067, 7.57228277, 0.10681421, 10.66102295, 6.04917883, 1.15160494, 54.25222159, 0.83091737, 0.15906138, 2212.99983319, 851129.20790552, 0.13750687, 3451.24320437, 9.09817054, 0.13992113, 12.85945172, 7.42778487, 1.47815128, 62.5944035, 1.12738751, 0.21527401, 3054.61228207, 950150.14000512, 0.183942, 3498.1598567, 10.87102492, 0.18275611, 15.42222323, 9.06602833, 1.88784672, 71.82007179, 1.52057464, 0.28963319, 4257.11616624, 1052825.03519498, 0.24441251, 3536.36981456, 12.91708802, 0.23795055, 18.38807696, 10.99858473, 2.39833889, 81.95036365, 2.03759667, 0.38735563, 5971.71625023, 1157876.9345259, 0.32249151, 3565.57366431, 15.26249975, 0.30874479, 21.79505615, 13.26142165, 3.02979876, 92.99388197, 2.71140902, 0.51499876, 8394.26548938, 1263825.69187614, 0.42242682, 3585.54062291, 17.93260641, 0.39908849, 25.67930724, 15.89104479, 3.80488859, 104.94490553, 3.5815229, 0.68082531, 11771.69308891, 1369014.83177012, 0.54919004, 3596.11155888, 20.95116228, 0.51373896, 30.07372654, 18.92356557, 4.74858736, 117.78180121, 4.69470647, 0.89531276, 16412.6544395, 1471648.69570601, 0.70851404, 3597.20108813, 24.33943503, 0.65835649, 35.00648702, 22.39359234, 5.88784025, 131.46571872, 6.10567074, 1.17187283, 22708.66502974, 1569839.09458531, 0.90688941, 3588.79870637, 28.11523342, 0.8395819, 40.49949092, 26.33295696, 7.25100139, 145.9396448, 7.87775386, 1.52786442, 31174.64303837, 1661660.04491543, 1.1515359, 3570.96893733, 32.2918828, 1.06509092, 46.56680509, 30.76929808, 8.86704753, 161.12788658, 10.08363075, 1.98600215, 42518.81778252, 1745208.55854205, 1.45031536, 3543.85049433, 36.87717947, 1.34361255, 53.21314609, 35.72453388, 10.76454954, 176.93604309, 12.80607528, 2.57627308, 57748.79181959, 1818668.92894594, 1.81159906, 3507.65447063, 41.87236753, 1.68488832, 60.43248884, 41.2132682, 12.97040651, 193.25150992, 16.13879058, 3.33848014, 78310.05674361, 1880377.55852335, 2.24407099, 3462.66159181, 47.27117471, 2.099581, 68.20687783, 47.241185, 15.50836524, 209.9445399, 20.18729044, 4.32551801, 106233.40263289, 1928885.14085391, 2.75646435, 3409.21858057, 53.05897268, 2.59908005, 76.50551763, 53.80349672, 18.39737045, 226.86986724, 25.06974289, 5.60745287, 144240.02584129, 1963012.9787655, 3.35721887, 3347.73369986, 59.2120842, 3.19522523, 85.28421457, 60.88351885, 21.64981915, 243.86887532, 30.91759203, 7.27639834, 195725.39755693, 1981900.39751078, 4.05413701, 3278.67155455, 65.69732731, 3.89992109, 94.48523183, 68.45145383, 25.26981051, 260.77226654, 37.87565549, 9.45204212, 264529.10509379, 1985040.60027546, 4.85387683, 3202.54724448, 72.47179941, 4.72468785, 104.03760438, 76.46346049, 29.25150749, 277.40317129, 46.10126241, 12.28748703, 354414.54907999, 1972302.89076637, 5.76154984, 3119.91997152, 79.48298321, 5.67999323, 113.85793751, 84.86109664, 33.57774177, 293.58060425, 55.76187367, 15.97474331, 468241.10316922, 1943939.91853951, 6.78022986, 3031.38621154, 86.66915503, 6.77469453, 123.85169567, 93.57120249, 38.21899254, 309.12317683, 67.03057921, 20.7488884, 606909.47196729, 1900579.43701452, 7.91045267, 2937.57256771, 93.960161, 8.01523961, 133.91495446, 102.50629755, 43.13287688, 323.85293871, 80.07883881, 26.88944116, 768276.14832709, 1843200.94214538, 9.14994888, 2839.12842412, 101.27854328, 9.40484564, 143.93657368, 111.56552334, 48.26425519, 337.59922002, 95.06606268, 34.71717892, 946325.65781997, 1773098.41741, 10.49314537, 2736.71851941, 108.54095872, 10.94287253, 153.80070428, 120.63616715, 53.54603669, 350.20237106, 112.12580346, 44.58440388, 1130915.51452061, 1691831.18536039, 11.93102725, 2631.01555809, 115.65992974, 12.62419114, 163.38955419, 129.59576676, 58.90071618, 361.51722075, 131.34887723, 56.85683772, 1308337.77523214, 1601165.50156908, 13.45114095, 2522.6929724, 122.54579434, 14.43867826, 172.58628134, 138.31476527, 64.24263766, 371.4161994, 152.76426304, 71.88583223, 1462771.91678513, 1503009.97941032, 15.03773501, 2412.41794141, 129.10893707, 16.37078922, 181.27790006, 146.65962865, 69.48087463, 379.7919751, 176.31901419, 89.97105175, 1578472.14070042, 1399348.17533044, 16.67203777, 2300.84476516, 135.26190962, 18.39916268, 189.35812239, 154.49637675, 74.5226145, 386.55952464, 201.85943947, 111.3152929, 1642303.0434488, 1292171.68347361, 18.33228085, 2188.6086816, 140.92176314, 20.49788324, 196.72984584, 161.69436891, 79.27691853, 391.65765908, 229.11576576, 135.97549863, 1646085.52743442, 1183416.89329037, 19.99520542, 2076.32020241, 146.0122677, 22.63680005, 203.30742434, 168.13019983, 83.65843627, 395.04974265, 257.69291305, 163.8161362, 1588199.00078764, 1074908.17842178, 21.63549326, 1964.56003101, 150.46564519, 24.78040373, 209.01843412, 173.69164043, 87.59106411, 396.72385747, 287.06984535, 194.47282077, 1474027.43070602, 968309.74794877, 23.2269174, 1853.87461303, 154.2248322, 26.89070462, 213.80506501, 178.28112332, 91.01121114, 396.69240144, 316.60926168, 227.33455411, 1315106.87656295, 865087.7500224, 24.74701422, 1744.77235535, 157.24428745, 28.92982746, 217.62469372, 181.81913279, 93.87014631, 394.99094694, 345.5781821, 261.55207619, 1127156.27334065, 766483.52591583, 26.16998776, 1637.72053578, 159.4915506, 30.86248779, 220.45034244, 184.24683816, 96.13580401, 391.67625083, 373.18010722, 296.07563537, 927452.96096643, 673498.22265258, 27.47508988, 1533.1429137, 160.94692065, 32.6485726, 222.27019022, 185.52802352, 97.79353603, 386.82476817, 398.59510033, 329.72384302, 732161.06563819, 586888.33299314, 28.64268527, 1431.41803582, 161.60536322, 34.25526548, 223.08682492, 185.65052523, 98.84604641, 380.52943111, 421.02713776, 361.27613798, 554189.41149968, 507171.18278087, 29.65938181, 1332.87822094, 161.47353483, 35.65631432, 222.91634715, 184.62605546, 99.31157172, 372.89732738, 439.75424599, 389.58028512, 401963.82881534, 434638.95653173, 30.50694733, 1237.80920368, 160.57229559, 36.82542442, 221.78680461, 182.49015576, 99.22258307, 364.04644489, 454.17297565, 413.65593867, 279219.88988886, 369379.55940165, 31.17734906, 1146.45037971, 158.93325259, 37.74535204, 219.73617395, 179.30024637, 98.62141797, 354.10399458, 463.83889208, 432.78595831, 185654.39878714, 311302.46153309, 31.66533638, 1058.99564115, 156.59797143, 38.42625792, 216.81167273, 175.13348174, 97.56124002, 343.20103683, 468.49345703, 446.57532692, 118099.9045403, 260167.65217027, 31.99921196, 975.59471271, 153.61856121, 38.84964623, 213.06719918, 170.08409222, 96.09680946, 331.47084419, 468.08485389, 454.96457052, 71842.7672043, 215615.92939901, 32.13797198, 896.35495326, 150.05017044, 39.027497, 208.56154354, 164.25967479, 94.28509443, 319.04873118, 462.75987113, 458.21164435, 41776.08029833, 177198.94374631, 32.1224466, 821.34355562, 145.95847937, 38.96610602, 203.35755384, 157.77740962, 92.18234732, 306.06344309, 452.85054174, 456.82848661, 23212.57002298, 144407.67315777, 31.90561262, 750.59009627, 141.40731732, 38.6728959, 197.5210829, 150.75846436, 89.83796048, 292.64515688, 438.84471491, 451.48444111, 12320.44951391, 116698.30502948, 31.60114356, 684.08930594, 136.45894381, 38.20931495, 191.11850853, 143.32937672, 87.29902309, 278.90875669, 421.35863761, 442.9182199, 6244.68681786, 93514.8114522, 31.16303699, 621.80414996, 131.19042792, 37.45579095, 184.21695431, 135.61176402, 84.60793186, 264.97945296, 401.09573952, 431.85241895, 3021.78421938, 74307.80352285, 30.52864796, 563.66882841, 125.65743269, 36.74677375, 176.88445746, 127.71948809, 81.79242322, 250.95407563, 378.75908178, 418.89127384, 1395.68176776, 58549.52116353, 29.86329016, 509.59224531, 119.91816775, 35.88941474, 169.1919447, 119.76222126, 78.87817603, 236.93060531, 355.08577271, 404.51969654, 615.16737254, 45745.04349326, 28.85261899, 459.46103727, 114.02843567, 34.86394375, 161.2064934, 111.83943621, 75.88528008, 222.99562917, 330.76045494, 389.04721201, 258.70739111, 35439.98407349, 28.06670614, 413.14300694, 108.06749762, 33.70653389, 152.98298135, 104.03493545, 72.82322068, 209.21516927, 306.38868785, 372.69143364, 103.79285894, 27225.06302907, 27.13738093, 370.4900986, 102.08949686, 32.67967302, 144.6035327, 96.40797695, 69.68476582, 195.67168838, 282.44299875, 355.57766431, 39.72027067, 20738.02613766, 26.02916352, 331.34154787, 96.12793819, 31.42344244, 136.12104588, 89.03995761, 66.51020806, 182.44905727, 259.42981277, 337.78591081, 14.49751227, 15663.41473092, 25.06881912, 295.52655898, 90.22783644, 30.34931725, 127.59688164, 81.95508099, 63.26174677, 169.55624271, 237.69526097, 319.3859739, 0, 11730.68704982, 24.17956372, 262.86798723, 84.40327526, 28.65176777, 119.09961491, 75.19560014, 59.99585913, 157.0932408, 217.41650497, 300.43783506, 0, 0, 23.19297329, 233.18327532, 78.71482892, 27.64181263, 110.65676504, 68.79432308, 56.68577839, 144.94827454, 198.65896712, 281.33896504, 0, 0, 22.00676022, 206.28771641, 73.09584877, 26.45046533, 102.36957803, 62.76034057, 53.38455183, 133.28331289, 181.58771616, 262.21333821, 0, 0, 20.87010708, 181.99793766, 67.72681077, 25.23165029, 94.26741859, 57.04291341, 50.09886446, 122.26651844, 166.09464613, 243.34870614, 0, 0, 20.06872408, 160.13044785, 62.58458389, 24.10953426, 86.26504476, 51.69886412, 46.73747908, 111.55035286, 152.14064595, 224.92382969, 0, 0, 19.35857702, 140.50009457, 57.41886202, 23.32181444, 78.70269507, 46.74893787, 43.37847303, 101.54040293, 139.53335359, 207.35675775, 0, 0, 17.98664124, 122.94178037, 52.50990106, 22.43352845, 71.4305872, 42.09492525, 39.99277332, 91.9839163, 128.22293383, 191.06237152, 0, 0, 0, 107.28561694, 47.91885561, 21.2860961, 64.37611261, 37.79770891, 36.76409961, 82.86901396, 117.96303458, 176.12971119, 0, 0, 0, 93.36420821, 43.49947891, 20.35672832, 57.70816004, 33.87648093, 33.63008881, 74.45604197, 108.62574271, 162.53634981, 0, 0, 0, 81.01855634, 39.21553424, 19.3332257, 51.58209691, 30.22139026, 30.65190478, 66.57126871, 100.04207875, 150.29783846, 0, 0, 0, 70.12124039, 35.57485576, 18.05664406, 45.88045427, 26.82823932, 27.7250899, 59.27292834, 92.23013084, 139.10147482, 0, 0, 0, 60.50725061, 31.8523625, 16.77874097, 40.56683965, 23.69673628, 24.96049075, 52.67315095, 84.61451704, 129.07957816, 0, 0, 0, 52.07094504, 28.55643767, 15.86788946, 35.50407378, 20.7936669, 22.37875886, 46.6499049, 77.14206416, 119.88560938, 0, 0, 0, 0, 0, 15.05348974, 30.83801921, 18.08768876, 19.9212635, 40.91937407, 70.25600106, 111.21174966, 0, 0, 0, 0, 0, 14.18606415, 26.86854816, 15.8335644, 17.59696977, 35.955284, 63.65237685, 102.81067505, 0, 0, 0, 0, 0, 13.05387134, 23.16060595, 13.67202662, 15.43526654, 31.43022364, 57.19106395, 94.57368986, 0, 0, 0, 0, 0, 12.11571451, 19.85917009, 11.72712569, 13.40183937, 26.86623898, 50.83642311, 86.39815979, 0, 0, 0, 0, 0, 11.07964652, 16.98232236, 0, 11.52664662, 23.12135444, 44.5286131, 78.14438303, 0, 0, 0, 0, 0, 9.89598077, 14.3998692, 0, 9.89531829, 19.86580103, 38.74534465, 70.03086135, 0, 0, 0, 0, 0, 8.98033532, 0, 0, 8.39908065, 16.96159119, 33.273771, 61.82427455, 0, 0, 0, 0, 0, 8.15189378, 0, 0, 7.11202352, 14.35595934, 27.92547443, 53.73770599, 0, 0, 0, 0, 0, 0, 0, 0, 5.89378979, 12.02010529, 23.40601287, 46.03209262, 0, 0, 0, 0, 0, 0, 0, 0, 4.85980014, 10.06087974, 19.25580003, 38.7550108, 0, 0, 0, 0, 0, 0, 0, 0, 4.00985446, 8.46079152, 15.66401762, 31.97412412, 0, 0, 0, 0, 0, 0, 0, 0, 3.23710701, 7.08557108, 12.53112734, 25.91892633, 0, 0, 0, 0, 0, 0, 0, 0, 2.58722273, 5.80042072, 9.83944195, 20.56566662, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.68963678, 7.59922836, 15.99119715, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.85718805, 5.77886375, 12.1105052, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.31718972, 8.99873425, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.15596889, 6.51670902, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.29115499, 4.64010741, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.62692856, 3.19657424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1457137, 2.17232696, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.7667804, 1.42109092, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.49416172, 0.89299226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.24100328, 0.56587656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.10462914, 0.31305067, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.06201695, 0.13135629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01854727, 0.0518948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00871507, 0.01248019, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + "value": [0, 9.34416769e-12, 0, 6.65569212e-07, 0, 0, 9.78218526e-15, 1.51469051e-14, 0, 0, 1.50654209e-14, 0, 0, 0, 0, 9.22652742e-07, 0, 0, 1.26606167e-15, 2.11986392e-14, 0, 2.66860947e-15, 1.21228206e-14, 0, 0, 0, 0, 1.27619104e-06, 0, 0, 9.32179919e-15, 8.5049195e-15, 0, 0, 0, 0, 0, 0, 0, 1.76126304e-06, 0, 0, 2.35016835e-15, 1.32577992e-14, 0, 1.22954296e-15, 1.06632497e-14, 0, 0, 0, 0, 2.42528024e-06, 0, 0, 1.05277662e-14, 1.27115444e-14, 0, 0, 1.22226388e-17, 0, 0, 0, 0, 3.33217226e-06, 0, 0, 1.89450901e-15, 1.03770203e-14, 0, 1.071518e-14, 1.50684765e-14, 1.78735721e-14, 0, 0, 0, 4.5679248e-06, 0, 0, 1.68835384e-14, 1.37219492e-14, 0, 0, 1.74702251e-14, 4.53052478e-15, 0, 0, 0, 6.24791206e-06, 0, 0, 5.54092959e-16, 1.10492655e-14, 0, 1.50908847e-14, 1.90836134e-14, 2.54882761e-14, 0, 1.69743149e-10, 0, 8.52655541e-06, 0, 0, 9.45217401e-15, 9.77811104e-15, 0, 0, 2.43800902e-14, 1.73398502e-14, 0, 1.76454659e-10, 0, 0.00001161, 0, 0, 0, 1.14566868e-14, 0, 0, 2.61075565e-14, 2.78350228e-14, 8.96864998e-12, 3.18689163e-10, 0, 0.00001577, 0, 0, 1.14729836e-14, 1.32004499e-14, 0, 0, 3.88516945e-14, 2.76394605e-14, 0, 4.53047312e-10, 0, 0.00002138, 0, 0, 8.4743629e-16, 1.23856073e-14, 0, 1.3298231e-14, 2.38585909e-14, 3.49404501e-14, 0, 7.74617255e-10, 0, 0.00002892, 0, 0, 1.19944829e-14, 4.04161923e-15, 0, 0, 3.96339434e-14, 1.25159821e-14, 0, 1.12888938e-09, 0, 0.00003902, 0, 0, 0, 1.23856073e-15, 0, 3.38974516e-15, 3.10292057e-14, 3.52011998e-14, 0, 1.90922918e-09, 0, 0.00005253, 0, 0, 1.19944829e-14, 7.43136439e-15, 0, 7.30098958e-15, 4.32844382e-14, 1.66879762e-14, 1.90263393e-11, 2.7933499e-09, 0, 0.00007056, 0, 0, 0, 0, 0, 2.58142132e-14, 3.72871968e-14, 2.0338471e-14, 2.9708509e-11, 4.33510373e-09, 0, 0.00009456, 2.86824591e-15, 0, 1.51234784e-14, 1.10818592e-14, 0, 5.1106927e-14, 4.902093e-14, 1.04299851e-14, 2.63209461e-11, 6.43672754e-09, 0, 0.00012644, 0, 0, 0, 5.606117e-15, 0, 7.45743936e-14, 4.95424293e-14, 2.81609598e-14, 1.5271037e-11, 9.5802434e-09, 0, 0.00016868, 6.77949032e-15, 0, 1.35589806e-14, 1.1212234e-14, 2.60749628e-16, 1.1212234e-13, 4.2762939e-14, 0, 2.8705916e-11, 1.40364762e-08, 0, 0.00022451, 3.12899553e-15, 0, 0, 6.25799107e-15, 0, 1.7522375e-13, 5.1106927e-14, 1.46019792e-14, 0, 2.04949883e-08, 0, 0.00029814, 4.17199404e-15, 0, 1.61664769e-14, 0, 0, 2.53448638e-13, 3.12899553e-14, 1.25159821e-14, 2.89202553e-11, 2.9717475e-08, 0, 0.00039501, 0, 0, 0, 0, 0, 4.11984412e-13, 3.75479464e-14, 0, 0, 4.29908487e-08, 0, 0.00052214, 6.25799107e-15, 0, 1.25159821e-14, 4.17199404e-15, 2.08599702e-15, 6.09111131e-13, 4.17199404e-14, 1.66879762e-14, 5.25097183e-11, 6.18651407e-08, 0, 0.00068861, 6.25799107e-15, 0, 2.08599702e-15, 2.08599702e-15, 1.04299851e-15, 9.68945617e-13, 3.12899553e-14, 0, 0, 8.85673137e-08, 0, 0.00090606, 1.04299851e-14, 0, 9.3869866e-15, 1.04299851e-15, 0, 1.49148787e-12, 1.66879762e-14, 0, 4.69936684e-12, 1.26176414e-07, 0, 0.00118942, 2.08599702e-15, 0, 5.21499256e-15, 0, 0, 2.3112847e-12, 2.08599702e-14, 0, 2.24842717e-11, 1.78724136e-07, 0, 0.00155777, 1.46019792e-14, 0, 4.38059375e-14, 3.12899553e-15, 0, 3.55245293e-12, 4.17199404e-14, 0, 3.84488671e-11, 2.5210186e-07, 0, 0.00203548, 1.56449777e-14, 0, 3.75479464e-14, 1.04299851e-15, 0, 5.44862422e-12, 1.25159821e-14, 1.66879762e-14, 0, 3.53891748e-07, 0, 0.0026535, 2.29459672e-14, 0, 1.14729836e-13, 0, 0, 8.3398161e-12, 4.17199404e-14, 0, 1.95069954e-12, 4.94597954e-07, 0, 0.00345113, 3.96339434e-14, 0, 1.46019792e-13, 8.34398809e-15, 0, 1.26599159e-11, 0, 0, 0, 6.88416649e-07, 0, 0.00447807, 6.67519047e-14, 0, 2.96211577e-13, 8.34398809e-15, 0, 1.91452807e-11, 0, 0, 0, 9.54257778e-07, 0, 0.00579704, 1.02213854e-13, 0, 4.58919345e-13, 1.66879762e-14, 0, 2.88076189e-11, 0, 0, 0, 1.31787978e-06, 0, 0.00748695, 1.79395744e-13, 0, 7.80162886e-13, 4.38059375e-14, 0, 4.31676224e-11, 0, 0, 0, 1.81392971e-06, 0, 0.00964688, 3.23329538e-13, 0, 1.27245818e-12, 8.9697872e-14, 0, 6.43029442e-11, 0, 0, 0, 2.48873116e-06, 0, 0.01240079, 5.59047202e-13, 0, 2.10685699e-12, 1.66879762e-13, 0, 9.54885997e-11, 0, 3.33759524e-14, 0, 3.4053368e-06, 0, 0.01590344, 9.1783869e-13, 0, 3.38765916e-12, 2.75351607e-13, 0, 1.41000883e-10, 0, 0, 0, 4.64840853e-06, 0, 0.02034747, 1.5394658e-12, 0, 5.48200017e-12, 4.88123303e-13, 0, 2.07389824e-10, 0, 0, 0, 6.33281328e-06, 0, 0.02597203, 2.49902443e-12, 2.08599702e-15, 8.7570155e-12, 8.09366845e-13, 0, 3.0372951e-10, 8.34398809e-15, 0, 6.55233177e-11, 8.61398032e-06, 4.17199404e-15, 0.03307314, 4.01345827e-12, 4.17199404e-15, 1.39261161e-11, 1.37258604e-12, 0, 4.4300736e-10, 6.67519047e-14, 5.00639285e-14, 6.65496037e-11, 0.0000117, 0, 0.04201629, 6.47910675e-12, 0, 2.20114406e-11, 2.27790875e-12, 0, 6.43713649e-10, 1.66879762e-14, 0, 1.73118066e-10, 0.00001589, 0, 0.05325137, 1.02714493e-11, 0, 3.45023908e-11, 3.72976268e-12, 0, 9.31923342e-10, 0, 0, 2.53951461e-10, 0.00002156, 0, 0.06733068, 1.62499168e-11, 4.17199404e-15, 5.36852194e-11, 6.06190735e-12, 8.34398809e-15, 1.34422483e-09, 1.66879762e-14, 0, 3.09600549e-10, 0.00002926, 0, 0.08493031, 2.55576355e-11, 2.08599702e-15, 8.30226815e-11, 9.80418601e-12, 4.17199404e-15, 1.93260115e-09, 5.00639285e-14, 1.00127857e-13, 5.38914202e-10, 0.00003973, 0, 0.1068755, 3.97257273e-11, 0, 1.27554546e-10, 1.56449777e-11, 1.66879762e-14, 2.76972009e-09, 1.16815833e-13, 1.66879762e-14, 1.0419722e-09, 0.00005399, 0, 0.13417065, 6.14868482e-11, 1.25159821e-14, 1.94706962e-10, 2.48483965e-11, 2.08599702e-14, 3.9574868e-09, 1.66879762e-14, 0, 1.88750189e-09, 0.00007344, 0, 0.16803472, 9.44706332e-11, 0, 2.95285395e-10, 3.91666801e-11, 8.34398809e-14, 5.63868358e-09, 0, 0, 3.46669342e-09, 0.00010003, 6.25799107e-15, 0.20994275, 1.44142394e-10, 0, 4.45051637e-10, 6.12114966e-11, 1.58535774e-13, 8.01319903e-09, 2.67007619e-13, 2.67007619e-13, 6.10771584e-09, 0.00013645, 0, 0.26167444, 2.1855408e-10, 2.08599702e-15, 6.66284137e-10, 9.50880883e-11, 3.04555565e-13, 1.13596556e-08, 1.00127857e-13, 0, 1.02995519e-08, 0.00018642, 0, 0.32537069, 3.29237082e-10, 0, 9.9171636e-10, 1.46503743e-10, 4.83951309e-13, 1.60673337e-08, 3.33759524e-14, 2.00255714e-13, 1.70055817e-08, 0.0002551, 2.08599702e-15, 0.4035993, 4.92829313e-10, 4.17199404e-15, 1.46683973e-09, 2.24211304e-10, 9.42870654e-13, 2.26779083e-08, 6.67519047e-14, 3.33759524e-14, 2.71333142e-08, 0.00034966, 6.25799107e-15, 0.49943087, 7.33269673e-10, 0, 2.15675404e-09, 3.4071841e-10, 1.65210964e-12, 3.19464603e-08, 3.33759524e-14, 0, 4.19196621e-08, 0.00048001, 6.25799107e-15, 0.61652609, 1.08471011e-09, 0, 3.15254227e-09, 5.1433177e-10, 2.88701988e-12, 4.49223464e-08, 6.67519047e-14, 1.33503809e-13, 6.27062053e-08, 0.0006599, 1.46019792e-14, 0.75923593, 1.59554575e-09, 0, 4.58155036e-09, 7.71151379e-10, 5.00639285e-12, 6.30647631e-08, 0, 1.33503809e-13, 9.16135181e-08, 0.00090839, 2.92039583e-14, 0.93271583, 2.33426404e-09, 2.50319643e-14, 6.62122156e-09, 1.14901722e-09, 8.65271565e-12, 8.83987631e-08, 0, 2.00255714e-13, 1.31162687e-07, 0.00125187, 5.21499256e-14, 1.14305547, 3.39742163e-09, 6.25799107e-14, 9.51718619e-09, 1.70122236e-09, 1.4677075e-11, 1.23734301e-07, 2.00255714e-13, 0, 1.84948701e-07, 0.00172686, 9.80418601e-14, 1.39742561, 4.92036634e-09, 1.00127857e-13, 1.36083604e-08, 2.50361363e-09, 2.46064209e-11, 1.729659e-07, 2.67007619e-13, 1.00127857e-13, 2.5863426e-07, 0.00238393, 1.83567738e-13, 1.7042432, 7.09279039e-09, 1.83567738e-13, 1.93607558e-08, 3.66286058e-09, 4.09189176e-11, 2.41486029e-07, 4.67263333e-13, 3.33759524e-14, 3.60994301e-07, 0.00329301, 3.48361503e-13, 2.07335639, 1.01791148e-08, 3.17071547e-13, 2.74129213e-08, 5.32900481e-09, 6.74194238e-11, 3.36751811e-07, 8.67774761e-13, 3.33759524e-14, 5.05101517e-07, 0.00455089, 6.61261056e-13, 2.51625063, 1.45479936e-08, 6.38315089e-13, 3.86380551e-08, 7.71134691e-09, 1.09990451e-10, 4.69068772e-07, 1.66879762e-12, 2.00255714e-13, 7.10778554e-07, 0.00629149, 1.22865225e-12, 3.04627708, 2.071098e-08, 1.1890183e-12, 5.42279624e-08, 1.11020099e-08, 1.77785354e-10, 6.5265797e-07, 3.504475e-12, 0, 1.00815241e-06, 0.00870022, 2.22784482e-12, 3.67890431, 2.93778637e-08, 2.12980296e-12, 7.58073179e-08, 1.59061779e-08, 2.8464681e-10, 9.07121238e-07, 6.70856642e-12, 1.66879762e-13, 1.43666333e-06, 0.01203391, 4.00094229e-12, 4.43199411, 4.15314664e-08, 3.82571854e-12, 1.05589214e-07, 2.26860854e-08, 4.51584979e-10, 1.2594445e-06, 1.28831176e-11, 3.00383571e-13, 2.05157547e-06, 0.01664877, 7.13828181e-12, 5.32610177, 5.85302061e-08, 6.77323233e-12, 1.46587533e-07, 3.22193421e-08, 7.10173514e-10, 1.74673007e-06, 2.51320921e-11, 3.33759524e-14, 2.92445542e-06, 0.02303937, 1.25514441e-11, 6.38480085, 8.22499614e-08, 1.18296891e-11, 2.02908502e-07, 4.55799194e-08, 1.10713875e-09, 2.41992463e-06, 4.73604764e-11, 1.83567738e-13, 4.14142915e-06, 0.0318934, 2.17924109e-11, 7.63503209, 1.1527832e-07, 2.03927069e-11, 2.80150051e-07, 6.42509779e-08, 1.71118508e-09, 3.34889326e-06, 8.8296082e-11, 3.33759524e-13, 5.80639467e-06, 0.04416866, 3.74207006e-11, 9.10747533, 1.61180184e-07, 3.47860863e-11, 3.85951269e-07, 9.02783465e-08, 2.62297438e-09, 4.62932523e-06, 1.61839993e-10, 6.17455119e-13, 8.03746762e-06, 0.0612015, 6.34727174e-11, 10.83694278, 2.24864605e-07, 5.86269463e-11, 5.30749301e-07, 1.26484129e-07, 3.98786726e-09, 6.39212064e-06, 2.92339967e-10, 1.18484631e-12, 0.00001097, 0.08485961, 1.06419224e-10, 12.86279109, 3.13084406e-07, 9.76914126e-11, 7.28818907e-07, 1.76762448e-07, 6.01477216e-09, 8.81614253e-06, 5.19196315e-10, 2.70345214e-12, 0.00001476, 0.11775779, 1.76434671e-10, 15.22934902, 4.3512411e-07, 1.60999336e-10, 9.99706647e-07, 2.46487916e-07, 9.00144012e-09, 0.00001215, 9.07675712e-10, 5.80741571e-12, 0.00001961, 0.16356253, 2.89262078e-10, 17.98635615, 6.0374205e-07, 2.62452844e-10, 1.37019984e-06, 3.4308111e-07, 1.33692384e-08, 0.00001671, 1.56226158e-09, 1.25577021e-11, 0.00002581, 0.22742178, 4.69069806e-10, 21.18940744, 8.36462194e-07, 4.23308247e-10, 1.8770324e-06, 4.76793402e-07, 1.97102104e-08, 0.00002297, 2.64778105e-09, 2.67049339e-11, 0.00003379, 0.31657108, 7.52584963e-10, 24.9003971, 1.157347e-06, 6.75688855e-10, 2.5705889e-06, 6.61791967e-07, 2.88505028e-08, 0.00003154, 4.41986056e-09, 5.5370705e-11, 0.00004428, 0.44118716, 1.1949999e-09, 29.18795381, 1.59943085e-06, 1.0677207e-09, 3.51996787e-06, 9.17660913e-07, 4.19362792e-08, 0.00004326, 7.26850643e-09, 1.12253758e-10, 0.00005838, 0.6155875, 1.87844971e-09, 34.12785869, 2.20806096e-06, 1.67074281e-09, 4.81987785e-06, 1.27147428e-06, 6.05480077e-08, 0.00005927, 1.1779183e-08, 2.22271327e-10, 0.00007787, 0.85991234, 2.92412351e-09, 39.80343533, 3.04548782e-06, 2.58972463e-09, 6.600005e-06, 1.76065509e-06, 8.68543493e-08, 0.00008113, 1.88174579e-08, 4.30059576e-10, 0.0001054, 1.20247609, 4.50930237e-09, 46.3059007, 4.19717583e-06, 3.9776975e-09, 9.03769291e-06, 2.4369085e-06, 1.23817879e-07, 0.00011096, 2.96447045e-08, 8.12994394e-10, 0.00014491, 1.68304375, 6.89135452e-09, 53.73466396, 5.7804813e-06, 6.05623448e-09, 0.00001238, 3.37162199e-06, 1.75471493e-07, 0.00015161, 4.60734955e-08, 1.50223493e-09, 0.00020211, 2.35737938, 1.04413251e-08, 62.19755927, 7.95659065e-06, 9.14391893e-09, 0.00001694, 4.66325997e-06, 2.47291582e-07, 0.00020699, 7.06768208e-08, 2.71371316e-09, 0.0002852, 3.30353524, 1.56907734e-08, 71.81099774, 0.00001095, 1.36960398e-08, 0.00002319, 6.44746381e-06, 3.46705129e-07, 0.00028241, 1.0706532e-07, 4.79448685e-09, 0.00040585, 4.63051011, 2.33969473e-08, 82.70002253, 0.00001506, 2.03598399e-08, 0.00003172, 8.91080849e-06, 4.83784936e-07, 0.00038505, 1.60257122e-07, 8.28764427e-09, 0.00058058, 6.49011511, 3.46332008e-08, 94.99825081, 0.0000207, 3.00512526e-08, 0.00004336, 0.00001231, 6.72211914e-07, 0.00052467, 2.37169672e-07, 1.40232532e-08, 0.00083255, 9.093157, 5.09140915e-08, 108.84768548, 0.00002846, 4.40616703e-08, 0.00005923, 0.00001699, 9.30621896e-07, 0.00071452, 3.47278017e-07, 2.32402628e-08, 0.00119387, 12.73140016, 7.43680104e-08, 124.39837999, 0.00003912, 6.42066037e-08, 0.00008081, 0.00002345, 1.28450813e-06, 0.00097255, 5.03501284e-07, 3.77485416e-08, 0.00170841, 17.80721572, 1.07974593e-07, 141.80793944, 0.00005376, 9.30331027e-08, 0.00011014, 0.00003232, 1.76893346e-06, 0.00132303, 7.234051e-07, 6.01399025e-08, 0.00243503, 24.8733947, 1.55890018e-07, 161.24084225, 0.00007389, 1.34109941e-07, 0.0001499, 0.00004451, 2.43242257e-06, 0.00179882, 1.03084328e-06, 9.40633461e-08, 0.00345131, 34.68631382, 2.2389075e-07, 182.86756772, 0.00010154, 1.92434788e-07, 0.00020374, 0.00006123, 3.3425729e-06, 0.00244421, 1.45821134e-06, 1.44584728e-07, 0.00485814, 48.27652786, 3.19975868e-07, 206.86351652, 0.00013953, 2.7500717e-07, 0.00027649, 0.00008413, 4.59416048e-06, 0.00331889, 2.04954898e-06, 2.18664991e-07, 0.00678631, 67.04194945, 4.55180166e-07, 233.40771352, 0.00019171, 3.91637097e-07, 0.00037463, 0.00011543, 6.32084727e-06, 0.00450307, 2.86481616e-06, 3.25808694e-07, 0.00940782, 92.87009596, 6.44665503e-07, 262.68128513, 0.00026334, 5.56088882e-07, 0.00050681, 0.00015815, 8.71205216e-06, 0.00610421, 3.98578105e-06, 4.78962363e-07, 0.01295659, 128.29745996, 9.0917701e-07, 294.86570669, 0.00036164, 7.87702159e-07, 0.00068454, 0.00021636, 0.00001204, 0.00826589, 5.52411106e-06, 6.95779818e-07, 0.01776516, 176.71591499, 1.27697532e-06, 330.14081933, 0.00049643, 1.11369139e-06, 0.00092319, 0.00029554, 0.00001668, 0.01117929, 7.63246127e-06, 1.00041884e-06, 0.02432472, 242.63820846, 1.78638764e-06, 368.6826205, 0.0006811, 1.57240857e-06, 0.00124316, 0.00040305, 0.00002319, 0.01509802, 0.00001052, 1.42609376e-06, 0.03337327, 332.03700466, 2.48916243e-06, 410.66083676, 0.00093383, 2.21796919e-06, 0.00167161, 0.00054882, 0.00003234, 0.02035713, 0.00001447, 2.01868087e-06, 0.04600978, 452.77457976, 3.45486976e-06, 456.23629337, 0.00127925, 3.12680034e-06, 0.00224459, 0.00074616, 0.00004525, 0.02739737, 0.00001988, 2.84175991e-06, 0.06382061, 615.14304832, 4.77666932e-06, 505.55810054, 0.00175067, 4.40688638e-06, 0.00300997, 0.00101292, 0.00006349, 0.03679579, 0.00002727, 3.98358379e-06, 0.08899142, 832.53777328, 6.578883e-06, 558.76068225, 0.00239292, 6.21077991e-06, 0.00403113, 0.00137305, 0.00008928, 0.0493042, 0.00003739, 5.56661059e-06, 0.12436864, 1122.28915981, 9.0269762e-06, 615.96067959, 0.0032662, 8.75383653e-06, 0.00539197, 0.00185863, 0.00012574, 0.06589692, 0.00005123, 7.76042924e-06, 0.17344058, 1506.68004948, 0.00001234, 677.25376659, 0.00445104, 0.00001234, 0.00720328, 0.00251262, 0.00017726, 0.08782982, 0.00007016, 0.0000108, 0.24023632, 2014.17700313, 0.00001681, 742.71142227, 0.00605469, 0.0000174, 0.00961108, 0.00339246, 0.00024989, 0.11671232, 0.00009606, 0.00001501, 0.32919653, 2680.90336294, 0.00002283, 812.3777081, 0.00821944, 0.00002452, 0.01280737, 0.00457487, 0.00035208, 0.15459466, 0.00013152, 0.00002082, 0.44514755, 3552.37947455, 0.00003091, 886.26610508, 0.01113318, 0.00003455, 0.01704388, 0.00616222, 0.0004954, 0.20407253, 0.00018011, 0.00002885, 0.5935887, 4685.5500819, 0.00004174, 964.35646878, 0.01504285, 0.00004866, 0.02264971, 0.00829082, 0.00069573, 0.26841102, 0.00024676, 0.00003993, 0.7815579, 6151.10985417, 0.00005626, 1046.59216395, 0.0202713, 0.00006846, 0.03005356, 0.01114184, 0.00097469, 0.35169016, 0.00033835, 0.00005519, 1.01935184, 8036.12441165, 0.00007571, 1132.87744292, 0.02723846, 0.0000962, 0.03981185, 0.01495541, 0.00136161, 0.4589734, 0.00046443, 0.00007622, 1.32334654, 10446.92527244, 0.0001018, 1223.0751326, 0.03648739, 0.00013496, 0.0526438, 0.02004891, 0.00189607, 0.59650047, 0.00063832, 0.0001052, 1.72013019, 13512.23218098, 0.00013687, 1317.00469514, 0.04871638, 0.00018894, 0.06947498, 0.02684039, 0.00263126, 0.77190478, 0.00087869, 0.0001452, 2.25218507, 17386.42491261, 0.00018414, 1414.44072516, 0.06481779, 0.00026387, 0.09149091, 0.03587827, 0.00363843, 0.99445496, 0.00121166, 0.00020056, 2.98550241, 22252.84891546, 0.0002481, 1515.1119435, 0.08592491, 0.00036753, 0.12020241, 0.0478789, 0.00501263, 1.27531865, 0.00167381, 0.00027745, 4.01981698, 28326.9956882, 0.00033499, 1618.70074252, 0.11346754, 0.00051037, 0.15752453, 0.0637734, 0.00688023, 1.62784486, 0.00231638, 0.00038474, 5.50259672, 35859.35099982, 0.00045356, 1724.84333155, 0.14923753, 0.00070642, 0.20587103, 0.08476554, 0.00940881, 2.06785969, 0.00321105, 0.00053518, 7.64848824, 45137.65424644, 0.00061612, 1833.13052308, 0.19546488, 0.00097444, 0.2682659, 0.11240259, 0.01281987, 2.61396775, 0.00445807, 0.00074713, 10.76658037, 56488.26373405, 0.00083998, 1943.10919109, 0.25490513, 0.00133937, 0.34847381, 0.14866088, 0.01740543, 3.28784907, 0.00619738, 0.00104695, 15.298564, 70276.27983693, 0.0011495, 2054.28442191, 0.33093825, 0.00183426, 0.45115025, 0.19604799, 0.02354944, 4.11453913, 0.00862417, 0.00147233, 21.87143711, 86904.04612996, 0.00157887, 2166.12236622, 0.42767899, 0.00250283, 0.58201203, 0.25772308, 0.03175522, 5.12267636, 0.01201034, 0.00207681, 31.36826784, 106807.63379795, 0.002176, 2278.05378809, 0.55009792, 0.00340269, 0.74802757, 0.33763652, 0.04268073, 6.34469956, 0.01673413, 0.00293579, 45.01872543, 130450.92333919, 0.00300766, 2389.47829312, 0.70415177, 0.00460963, 0.95762514, 0.44068939, 0.05718318, 7.81697495, 0.02332099, 0.00415469, 64.50677381, 158316.93611797, 0.00416655, 2499.76920427, 0.89692083, 0.00622314, 1.22091577, 0.57291244, 0.07637542, 9.57983087, 0.03249951, 0.00587961, 92.08669456, 190896.14219191, 0.00578044, 2608.27903966, 1.13675002, 0.00837367, 1.54992536, 0.74166294, 0.10169623, 11.67747729, 0.0452775, 0.0083116, 130.69442463, 228671.58396567, 0.00802436, 2714.34553302, 1.43338948, 0.01123195, 1.95882849, 0.95583638, 0.13499719, 14.15778668, 0.06304443, 0.01172524, 184.04774816, 272100.80910598, 0.01113616, 2817.29812451, 1.79812876, 0.01502107, 2.4641735, 1.22608804, 0.17864862, 17.07191426, 0.08770753, 0.01649315, 256.75850397, 321594.79906816, 0.01543638, 2916.46483741, 2.24391776, 0.02003204, 3.08508616, 1.56505758, 0.23566699, 20.47373766, 0.12187003, 0.02311769, 354.54334192, 377494.30590555, 0.02135318, 3011.17944524, 2.78546619, 0.02664346, 3.84343593, 1.98758722, 0.30986538, 24.41909996, 0.16906034, 0.03227168, 484.71625249, 440044.25980241, 0.0294531, 3100.78882508, 3.43931176, 0.03534667, 4.76394681, 2.51092185, 0.40602814, 28.96484579, 0.23402107, 0.04484989, 657.25445401, 509367.16862096, 0.0404782, 3184.66038493, 4.22384665, 0.04677698, 5.87423212, 3.15487676, 0.5301087, 34.16764683, 0.32306542, 0.06203302, 886.80261746, 585436.68028721, 0.05539061, 3262.18944848, 5.15929025, 0.06175226, 7.20473154, 3.94195661, 0.68944782, 40.08262203, 0.44450619, 0.0853658, 1195.95761349, 668052.69751603, 0.07542404, 3332.80647734, 6.26759642, 0.08132008, 8.78852797, 4.89740699, 0.89300665, 46.76176744, 0.60915918, 0.11685098, 1620.00877969, 756819.59892522, 0.10214281, 3395.98401067, 7.57228277, 0.10681421, 10.66102295, 6.04917883, 1.15160494, 54.25222159, 0.83091737, 0.15906138, 2212.99983319, 851129.20790552, 0.13750687, 3451.24320437, 9.09817054, 0.13992113, 12.85945172, 7.42778487, 1.47815128, 62.5944035, 1.12738751, 0.21527401, 3054.61228207, 950150.14000512, 0.183942, 3498.1598567, 10.87102492, 0.18275611, 15.42222323, 9.06602833, 1.88784672, 71.82007179, 1.52057464, 0.28963319, 4257.11616624, 1052825.03519498, 0.24441251, 3536.36981456, 12.91708802, 0.23795055, 18.38807696, 10.99858473, 2.39833889, 81.95036365, 2.03759667, 0.38735563, 5971.71625023, 1157876.9345259, 0.32249151, 3565.57366431, 15.26249975, 0.30874479, 21.79505615, 13.26142165, 3.02979876, 92.99388197, 2.71140902, 0.51499876, 8394.26548938, 1263825.69187614, 0.42242682, 3585.54062291, 17.93260641, 0.39908849, 25.67930724, 15.89104479, 3.80488859, 104.94490553, 3.5815229, 0.68082531, 11771.69308891, 1369014.83177012, 0.54919004, 3596.11155888, 20.95116228, 0.51373896, 30.07372654, 18.92356557, 4.74858736, 117.78180121, 4.69470647, 0.89531276, 16412.6544395, 1471648.69570601, 0.70851404, 3597.20108813, 24.33943503, 0.65835649, 35.00648702, 22.39359234, 5.88784025, 131.46571872, 6.10567074, 1.17187283, 22708.66502974, 1569839.09458531, 0.90688941, 3588.79870637, 28.11523342, 0.8395819, 40.49949092, 26.33295696, 7.25100139, 145.9396448, 7.87775386, 1.52786442, 31174.64303837, 1661660.04491543, 1.1515359, 3570.96893733, 32.2918828, 1.06509092, 46.56680509, 30.76929808, 8.86704753, 161.12788658, 10.08363075, 1.98600215, 42518.81778252, 1745208.55854205, 1.45031536, 3543.85049433, 36.87717947, 1.34361255, 53.21314609, 35.72453388, 10.76454954, 176.93604309, 12.80607528, 2.57627308, 57748.79181959, 1818668.92894594, 1.81159906, 3507.65447063, 41.87236753, 1.68488832, 60.43248884, 41.2132682, 12.97040651, 193.25150992, 16.13879058, 3.33848014, 78310.05674361, 1880377.55852335, 2.24407099, 3462.66159181, 47.27117471, 2.099581, 68.20687783, 47.241185, 15.50836524, 209.9445399, 20.18729044, 4.32551801, 106233.40263289, 1928885.14085391, 2.75646435, 3409.21858057, 53.05897268, 2.59908005, 76.50551763, 53.80349672, 18.39737045, 226.86986724, 25.06974289, 5.60745287, 144240.02584129, 1963012.9787655, 3.35721887, 3347.73369986, 59.2120842, 3.19522523, 85.28421457, 60.88351885, 21.64981915, 243.86887532, 30.91759203, 7.27639834, 195725.39755693, 1981900.39751078, 4.05413701, 3278.67155455, 65.69732731, 3.89992109, 94.48523183, 68.45145383, 25.26981051, 260.77226654, 37.87565549, 9.45204212, 264529.10509379, 1985040.60027546, 4.85387683, 3202.54724448, 72.47179941, 4.72468785, 104.03760438, 76.46346049, 29.25150749, 277.40317129, 46.10126241, 12.28748703, 354414.54907999, 1972302.89076638, 5.76154984, 3119.91997152, 79.48298321, 5.67999323, 113.85793751, 84.86109664, 33.57774177, 293.58060425, 55.76187367, 15.97474331, 468241.10316922, 1943939.91853951, 6.78022986, 3031.38621154, 86.66915503, 6.77469453, 123.85169567, 93.57120249, 38.21899254, 309.12317683, 67.03057921, 20.7488884, 606909.47196729, 1900579.43701452, 7.91045267, 2937.57256771, 93.960161, 8.01523961, 133.91495446, 102.50629755, 43.13287688, 323.85293871, 80.07883881, 26.88944116, 768276.14832709, 1843200.94214538, 9.14994888, 2839.12842412, 101.27854328, 9.40484564, 143.93657368, 111.56552334, 48.26425519, 337.59922002, 95.06606268, 34.71717892, 946325.65781997, 1773098.41741, 10.49314537, 2736.71851941, 108.54095872, 10.94287253, 153.80070428, 120.63616715, 53.54603669, 350.20237106, 112.12580346, 44.58440388, 1130915.51452061, 1691831.18536039, 11.93102725, 2631.01555809, 115.65992974, 12.62419114, 163.38955419, 129.59576676, 58.90071618, 361.51722075, 131.34887723, 56.85683772, 1308337.77523214, 1601165.50156909, 13.45114095, 2522.6929724, 122.54579434, 14.43867826, 172.58628134, 138.31476527, 64.24263766, 371.4161994, 152.76426304, 71.88583223, 1462771.91678513, 1503009.97941032, 15.03773501, 2412.41794141, 129.10893707, 16.37078922, 181.27790006, 146.65962865, 69.48087463, 379.7919751, 176.31901419, 89.97105175, 1578472.14070042, 1399348.17533044, 16.67203777, 2300.84476516, 135.26190962, 18.39916268, 189.35812239, 154.49637675, 74.5226145, 386.55952464, 201.85943947, 111.3152929, 1642303.0434488, 1292171.68347361, 18.33228085, 2188.6086816, 140.92176314, 20.49788324, 196.72984584, 161.69436891, 79.27691853, 391.65765908, 229.11576576, 135.97549863, 1646085.52743442, 1183416.89329037, 19.99520542, 2076.32020241, 146.0122677, 22.63680005, 203.30742434, 168.13019983, 83.65843627, 395.04974265, 257.69291305, 163.8161362, 1588199.00078764, 1074908.17842178, 21.63549326, 1964.56003101, 150.46564519, 24.78040373, 209.01843412, 173.69164043, 87.59106411, 396.72385747, 287.06984535, 194.47282077, 1474027.43070602, 968309.74794877, 23.2269174, 1853.87461303, 154.2248322, 26.89070462, 213.80506501, 178.28112332, 91.01121114, 396.69240144, 316.60926168, 227.33455411, 1315106.87656295, 865087.7500224, 24.74701422, 1744.77235535, 157.24428745, 28.92982746, 217.62469372, 181.81913279, 93.87014631, 394.99094694, 345.5781821, 261.55207619, 1127156.27334065, 766483.52591583, 26.16998776, 1637.72053578, 159.4915506, 30.86248779, 220.45034244, 184.24683816, 96.13580401, 391.67625083, 373.18010722, 296.07563537, 927452.96096643, 673498.22265258, 27.47508988, 1533.1429137, 160.94692065, 32.6485726, 222.27019022, 185.52802352, 97.79353603, 386.82476817, 398.59510033, 329.72384302, 732161.06563819, 586888.33299314, 28.64268527, 1431.41803582, 161.60536322, 34.25526548, 223.08682492, 185.65052523, 98.84604641, 380.52943111, 421.02713776, 361.27613798, 554189.41149968, 507171.18278087, 29.65938181, 1332.87822094, 161.47353483, 35.65631432, 222.91634715, 184.62605546, 99.31157172, 372.89732738, 439.75424599, 389.58028512, 401963.82881534, 434638.95653173, 30.50694733, 1237.80920368, 160.57229559, 36.82542442, 221.78680461, 182.49015576, 99.22258307, 364.04644489, 454.17297565, 413.65593867, 279219.88988886, 369379.55940165, 31.17734906, 1146.45037971, 158.93325259, 37.74535204, 219.73617395, 179.30024637, 98.62141797, 354.10399458, 463.83889208, 432.78595831, 185654.39878715, 311302.46153309, 31.66533638, 1058.99564115, 156.59797143, 38.42625792, 216.81167273, 175.13348174, 97.56124002, 343.20103683, 468.49345703, 446.57532692, 118099.9045403, 260167.65217027, 31.99921196, 975.59471271, 153.61856121, 38.84964623, 213.06719918, 170.08409222, 96.09680946, 331.47084419, 468.08485389, 454.96457052, 71842.7672043, 215615.92939901, 32.13797198, 896.35495326, 150.05017044, 39.027497, 208.56154354, 164.25967479, 94.28509443, 319.04873118, 462.75987113, 458.21164435, 41776.08029833, 177198.94374631, 32.1224466, 821.34355562, 145.95847937, 38.96610602, 203.35755384, 157.77740962, 92.18234732, 306.06344309, 452.85054174, 456.82848661, 23212.57002298, 144407.67315777, 31.90561262, 750.59009627, 141.40731732, 38.6728959, 197.5210829, 150.75846436, 89.83796048, 292.64515688, 438.84471491, 451.48444111, 12320.44951391, 116698.30502948, 31.60114356, 684.08930594, 136.45894381, 38.20931495, 191.11850853, 143.32937672, 87.29902309, 278.90875669, 421.35863761, 442.9182199, 6244.68681786, 93514.8114522, 31.16303699, 621.80414996, 131.19042792, 37.45579095, 184.21695431, 135.61176402, 84.60793186, 264.97945296, 401.09573952, 431.85241895, 3021.78421938, 74307.80352285, 30.52864796, 563.66882841, 125.65743269, 36.74677375, 176.88445746, 127.71948809, 81.79242322, 250.95407563, 378.75908178, 418.89127384, 1395.68176776, 58549.52116353, 29.86329016, 509.59224531, 119.91816775, 35.88941474, 169.1919447, 119.76222126, 78.87817603, 236.93060531, 355.08577271, 404.51969654, 615.16737254, 45745.04349326, 28.85261899, 459.46103727, 114.02843567, 34.86394375, 161.2064934, 111.83943621, 75.88528008, 222.99562917, 330.76045494, 389.04721201, 258.70739111, 35439.98407349, 28.06670614, 413.14300694, 108.06749762, 33.70653389, 152.98298135, 104.03493545, 72.82322068, 209.21516927, 306.38868785, 372.69143364, 103.79285894, 27225.06302907, 27.13738093, 370.4900986, 102.08949686, 32.67967302, 144.6035327, 96.40797695, 69.68476582, 195.67168838, 282.44299875, 355.57766431, 39.72027067, 20738.02613766, 26.02916352, 331.34154787, 96.12793819, 31.42344244, 136.12104588, 89.03995761, 66.51020806, 182.44905727, 259.42981277, 337.78591081, 14.49751227, 15663.41473092, 25.06881912, 295.52655898, 90.22783644, 30.34931725, 127.59688164, 81.95508099, 63.26174677, 169.55624271, 237.69526097, 319.3859739, 0, 11730.68704982, 24.17956372, 262.86798723, 84.40327526, 28.65176777, 119.09961491, 75.19560014, 59.99585913, 157.0932408, 217.41650497, 300.43783506, 0, 0, 23.19297329, 233.18327532, 78.71482892, 27.64181263, 110.65676504, 68.79432308, 56.68577839, 144.94827454, 198.65896712, 281.33896504, 0, 0, 22.00676022, 206.28771641, 73.09584877, 26.45046533, 102.36957803, 62.76034057, 53.38455183, 133.28331289, 181.58771616, 262.21333821, 0, 0, 20.87010708, 181.99793766, 67.72681077, 25.23165029, 94.26741859, 57.04291341, 50.09886446, 122.26651844, 166.09464613, 243.34870614, 0, 0, 20.06872408, 160.13044785, 62.58458389, 24.10953426, 86.26504476, 51.69886412, 46.73747908, 111.55035286, 152.14064595, 224.92382969, 0, 0, 19.35857702, 140.50009457, 57.41886202, 23.32181444, 78.70269507, 46.74893787, 43.37847303, 101.54040293, 139.53335359, 207.35675775, 0, 0, 17.98664124, 122.94178037, 52.50990106, 22.43352845, 71.4305872, 42.09492525, 39.99277332, 91.9839163, 128.22293383, 191.06237152, 0, 0, 0, 107.28561694, 47.91885561, 21.2860961, 64.37611261, 37.79770891, 36.76409961, 82.86901396, 117.96303458, 176.12971119, 0, 0, 0, 93.36420821, 43.49947891, 20.35672832, 57.70816004, 33.87648093, 33.63008881, 74.45604197, 108.62574271, 162.53634981, 0, 0, 0, 81.01855634, 39.21553424, 19.3332257, 51.58209691, 30.22139026, 30.65190478, 66.57126871, 100.04207875, 150.29783846, 0, 0, 0, 70.12124039, 35.57485576, 18.05664406, 45.88045427, 26.82823932, 27.7250899, 59.27292834, 92.23013084, 139.10147482, 0, 0, 0, 60.50725061, 31.8523625, 16.77874097, 40.56683965, 23.69673628, 24.96049075, 52.67315095, 84.61451704, 129.07957816, 0, 0, 0, 52.07094504, 28.55643767, 15.86788946, 35.50407378, 20.7936669, 22.37875886, 46.6499049, 77.14206416, 119.88560938, 0, 0, 0, 0, 0, 15.05348974, 30.83801921, 18.08768876, 19.9212635, 40.91937407, 70.25600106, 111.21174966, 0, 0, 0, 0, 0, 14.18606415, 26.86854816, 15.8335644, 17.59696977, 35.955284, 63.65237685, 102.81067505, 0, 0, 0, 0, 0, 13.05387134, 23.16060595, 13.67202662, 15.43526654, 31.43022364, 57.19106395, 94.57368986, 0, 0, 0, 0, 0, 12.11571451, 19.85917009, 11.72712569, 13.40183937, 26.86623898, 50.83642311, 86.39815979, 0, 0, 0, 0, 0, 11.07964652, 16.98232236, 0, 11.52664662, 23.12135444, 44.5286131, 78.14438303, 0, 0, 0, 0, 0, 9.89598077, 14.3998692, 0, 9.89531829, 19.86580103, 38.74534465, 70.03086135, 0, 0, 0, 0, 0, 8.98033532, 0, 0, 8.39908065, 16.96159119, 33.273771, 61.82427455, 0, 0, 0, 0, 0, 8.15189378, 0, 0, 7.11202352, 14.35595934, 27.92547443, 53.73770599, 0, 0, 0, 0, 0, 0, 0, 0, 5.89378979, 12.02010529, 23.40601287, 46.03209262, 0, 0, 0, 0, 0, 0, 0, 0, 4.85980014, 10.06087974, 19.25580003, 38.7550108, 0, 0, 0, 0, 0, 0, 0, 0, 4.00985446, 8.46079152, 15.66401762, 31.97412412, 0, 0, 0, 0, 0, 0, 0, 0, 3.23710701, 7.08557108, 12.53112734, 25.91892633, 0, 0, 0, 0, 0, 0, 0, 0, 2.58722273, 5.80042072, 9.83944195, 20.56566662, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.68963678, 7.59922836, 15.99119715, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.85718805, 5.77886375, 12.1105052, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.31718972, 8.99873425, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.15596889, 6.51670902, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.29115499, 4.64010741, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.62692856, 3.19657424, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.1457137, 2.17232696, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.7667804, 1.42109092, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.49416172, 0.89299226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.24100328, 0.56587656, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.10462914, 0.31305067, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.06201695, 0.13135629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01854727, 0.0518948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00871507, 0.01248019, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.41303731e-14, 0] } # getPredMort for MizerParams @@ -103,7 +103,7 @@ ] } }, - "value": [8636.11518838, 155936.75819189, 12011.83670094, 15295.06220908, 23102.61431469, 18229.22017149, 14352.79860425, 14479.58806049, 18646.88573464, 22454.35426719, 23887.82966476, 4028.06135169, 10184.09506557, 185528.93813555, 14172.34706228, 17936.05255692, 27381.29364541, 21544.87798605, 17004.76741575, 17121.24936671, 22084.37699671, 26577.73402706, 28267.44710679, 4741.56219829, 11951.98900357, 219252.93256094, 16629.97965972, 20937.20592541, 32256.59848161, 25319.99543439, 20027.04281107, 20128.97132173, 26001.32593173, 31271.6634151, 33255.40799449, 5552.63833363, 13955.41858123, 257341.02657929, 19401.60960057, 24319.74989473, 37763.62018199, 29581.47007256, 23441.73820823, 23524.03284074, 30426.28104053, 36568.44988268, 38887.29863896, 6466.80322114, 16207.91043468, 299960.28757659, 22499.53155521, 28099.71940856, 43928.70728588, 34349.43523927, 27265.68003268, 27322.25783849, 35380.91448654, 42491.53505931, 45189.72605754, 7488.05817635, 18720.76896193, 347194.48174424, 25930.22788876, 32286.68749229, 50767.26415513, 39635.5477444, 31509.12540401, 31532.60982364, 40878.34411838, 49053.08401833, 52178.03580184, 8618.48307036, 21503.63869638, 399026.13349214, 29693.20427677, 36882.76271709, 58281.90545392, 45441.53658516, 36174.7691269, 36155.96049199, 46921.81593256, 56251.66600396, 59854.36265071, 9857.84538995, 24566.0531589, 455319.68592383, 33779.98249576, 41882.02895588, 66461.22571308, 51758.2077549, 41257.22555481, 41184.17547333, 53503.98216392, 64070.18497507, 68206.26778765, 11203.25575218, 27920.15070449, 515806.8269462, 38173.33751002, 47270.56682987, 75279.39865379, 58565.06822037, 46743.13098529, 46599.64096435, 60606.96105671, 72474.22018663, 77206.17600345, 12648.89780998, 31584.51564557, 580075.09453889, 42846.85062412, 53027.11670361, 84696.71750488, 65830.65677043, 52611.92547309, 52375.30611304, 68203.2566928, 81410.92011265, 86811.7308785, 14185.85669939, 35588.83551381, 647560.85499934, 47764.82668404, 59124.34204672, 94661.05377395, 73513.5714895, 58837.2631768, 58475.25123383, 76257.48066343, 90808.56222924, 96967.06120067, 15802.0638549, 39978.87587017, 717547.65458621, 52882.60108969, 65530.57501944, 105110.10776274, 81564.10689295, 65388.91718311, 64855.73999179, 84728.71351308, 100576.85883466, 107604.85376041, 17482.3697536, 44821.32566357, 789170.77963308, 58147.25554056, 72211.93417402, 115974.32991063, 89926.41705515, 72235.05441483, 71466.71332106, 93573.35512992, 110608.07259403, 118649.13090482, 19208.75356796, 50208.52291877, 861428.63091343, 63498.78431449, 79134.85895083, 127180.58944154, 98541.26413021, 79344.92583026, 78253.77350647, 102748.52308929, 120779.02323359, 130018.81188204, 20960.68360992, 56264.05710744, 933201.23813931, 68871.81681937, 86269.45453372, 138657.11859436, 107349.74050761, 86692.39808733, 85160.91904472, 112216.53038084, 130954.13364781, 141632.55365916, 22715.6578677, 63151.80008965, 1003275.92681249, 74198.11163108, 93594.5993811, 150340.9881902, 116298.88312908, 94261.36220863, 92134.63518994, 121951.73022747, 140989.78602178, 153416.03956254, 24449.98112782, 71092.90484527, 1070379.81790154, 79410.1817008, 101106.48625458, 162190.30785568, 125350.78230938, 102054.83799249, 99130.39017132, 131951.98806942, 150740.42397478, 165313.75193983, 26139.87144425, 80397.2308094, 1133218.49566419, 84446.54835727, 108832.95993514, 174204.2469173, 134497.44566739, 110110.34774832, 106123.01513203, 142257.97781222, 160066.99326742, 177308.10030326, 27763.02321849, 91516.38672169, 1190519.80001579, 89259.15756219, 116856.26209607, 186454.28698909, 143783.9059869, 118524.40397946, 113122.59024999, 152983.83471897, 168848.34046993, 189449.064506, 29300.76172558, 105123.2530759, 1241081.2330189, 93823.26474456, 125345.89386563, 199128.91738358, 153341.18158149, 127487.97960632, 120196.87282577, 164361.48149668, 176995.8816746, 201896.39023347, 30740.86217322, 122215.18574474, 1283818.84006237, 98149.40855872, 134600.40771315, 212590.14474293, 163427.88583006, 137331.68044583, 127499.44224089, 176797.0256481, 184470.96261666, 214972.79398305, 32080.92361213, 144223.61391848, 1317814.59241359, 102295.82059083, 145091.48166081, 227432.9682406, 174473.99712135, 148573.40835912, 135299.24424529, 190930.24684298, 191302.69414315, 229219.90737266, 33331.85035763, 173092.59466843, 1342358.33327497, 106377.86027697, 157496.05008648, 244528.95298624, 187112.96743401, 161953.06738235, 144002.37454395, 207677.94717456, 197601.79551725, 245439.35813991, 34520.52833877, 211268.66481528, 1356979.52548866, 110569.3101234, 172694.66977202, 265024.99742189, 202181.00224417, 178430.60992832, 154152.09310471, 228231.66814832, 203563.73778066, 264692.03763587, 35690.32013253, 261534.24516017, 1361463.83115766, 115089.50488272, 191710.52516951, 290263.40852329, 220658.6994934, 199119.61662004, 166390.65772402, 253975.17461904, 209453.38880115, 288223.9673274, 36897.77558636, 326629.02723499, 1355850.51508533, 120171.35611368, 215568.08158786, 321595.49672925, 243534.70083316, 225133.60532122, 181369.52004652, 286293.33026067, 215564.77220615, 317292.86202141, 38204.24438308, 408646.61466703, 1340409.16337799, 126009.10055841, 245066.53819225, 360082.25598173, 271586.64030511, 257339.80593679, 199604.75673094, 326265.81261691, 222154.39836821, 352889.38396024, 39662.07567832, 508265.26829751, 1315598.12149585, 132690.87707327, 280490.18563026, 406111.33881595, 305100.76746534, 296044.42576241, 221291.84609492, 374275.5531986, 229354.68706492, 395380.29096133, 41296.75291663, 623955.60480044, 1282011.57954596, 140128.5982732, 321309.39996019, 459001.35987027, 343582.23706919, 340667.79732625, 246114.12532455, 429604.54396493, 237083.45356259, 444139.6469557, 43088.26229521, 751375.78121914, 1240325.97942115, 148003.52059224, 365951.49417693, 516698.27196556, 385532.73418915, 389495.49858552, 273095.56666274, 490124.1106684, 244973.05879819, 497265.67129221, 44956.56687093, 883183.85406187, 1191257.84035454, 155747.62244677, 411727.88782815, 575678.14299864, 428379.12149197, 439599.39933498, 300553.15318949, 552196.538889, 252345.03218635, 551489.73765292, 46756.51236074, 1009445.22823926, 1135543.11120462, 162576.41704025, 454984.61655762, 631144.97896683, 468618.00014695, 487001.46015325, 326191.73044571, 610878.65771049, 258250.25614222, 602360.11631758, 48286.30681718, 1118689.78538515, 1073942.70035621, 167578.09964692, 491496.85113222, 677550.97565525, 502196.23815115, 527102.73468214, 347354.60775008, 660455.31975142, 261581.08213741, 644725.99329228, 49310.88054922, 1199504.4083909, 1007271.10389008, 169849.22344697, 517064.60691146, 709382.66231928, 525086.09903397, 555331.0281171, 361402.62838087, 695244.88291121, 261242.92800397, 673469.13445145, 49597.54348419, 1242379.52859418, 936437.25497179, 168652.53748879, 528203.9963815, 722073.35028121, 533952.81841152, 567892.38507433, 366154.29003772, 710533.85887999, 256354.26526854, 684353.20425138, 48957.50612348, 1241417.44071106, 862481.40121093, 163562.9488846, 522786.67397025, 712847.1687814, 526772.12276639, 562466.25948494, 360292.84982457, 703441.51325093, 246431.51567859, 674809.39497074, 47284.27202181, 1195498.57776167, 786591.01476069, 154566.5439491, 500475.75993953, 681294.19725716, 503250.95787554, 538679.48036614, 343643.55227814, 673509.30593394, 231514.05104862, 644471.64090222, 44579.63732075, 1108605.1060742, 710083.19832305, 142086.57678761, 462845.30247593, 629527.4420875, 464942.19890304, 498236.26238563, 317248.87831818, 622862.4758963, 212195.95922216, 595322.41651934, 40960.40643991, 989198.08398592, 634349.81714061, 126927.45829835, 413144.33636837, 561870.21134729, 415015.69329628, 444661.93409308, 283216.9725915, 555891.11974598, 189553.14380825, 531401.21387237, 36643.46085477, 848780.74945706, 560772.16388229, 110148.32921692, 355755.42403558, 484139.85264254, 357733.91712053, 382714.59885176, 244375.13722205, 478518.21012362, 164980.62293492, 458137.14785153, 31912.24938285, 699984.43025527, 490621.02806295, 92895.57174451, 295474.22192554, 402695.12265588, 297754.67559049, 317602.24210044, 203809.24146686, 397225.62399381, 139977.60263943, 381461.50264291, 27072.47017987, 554620.4572446, 424962.60410925, 76232.86437708, 236776.77342648, 323467.54376147, 239422.13343982, 254186.45460948, 164395.53638628, 318063.55621384, 115929.74993123, 306905.46378544, 22407.15102102, 422118.5086326, 364589.22368091, 61005.38082121, 183232.5680431, 251185.63232504, 186199.04935126, 196344.52710504, 128425.81446604, 245856.97910548, 93935.4852374, 238877.59802324, 18140.80457147, 308631.95596426, 309987.06952004, 47762.49468978, 137168.79518351, 188931.34847929, 140342.1935888, 146604.50752591, 97393.23428609, 183751.69634075, 74707.45399011, 180250.8529489, 14419.08583418, 216887.46758499, 261343.32798273, 36745.68219709, 99614.77920995, 138067.02918735, 102848.97004181, 106084.7104838, 71957.29676562, 133139.16084527, 58557.67336813, 132294.71833964, 11305.71656887, 146660.92345013, 218586.00382083, 27931.20794584, 70482.14098842, 98474.03180853, 73632.1967699, 74689.39871155, 52059.52371219, 93899.9876003, 45452.98687705, 94897.76209722, 8793.89761577, 95634.76400667, 181443.67479576, 21106.19252306, 48888.5138398, 68981.20211422, 51833.80680268, 51460.46908652, 37130.90907714, 64841.54342957, 35113.34162748, 66967.00088491, 6826.5378056, 60360.06595485, 149511.04788784, 15953.77759109, 33520.68466868, 47845.51082021, 36176.7543291, 34972.00486168, 26324.58175834, 44188.86549669, 27121.82305183, 46875.84275008, 5318.86371875, 37098.19073119, 122308.72350146, 12127.94010089, 22952.47617382, 33172.812675, 25273.09913029, 23675.59642654, 18719.56262491, 30014.34246305, 21021.31386653, 32856.28515705, 4178.28108011, 22414.43636882, 99330.28631362, 9306.37185643, 15869.22132575, 23215.18450714, 17841.70103895, 16144.24560312, 13464.77796612, 20541.20913108, 16383.17925858, 23276.06382328, 3318.38187155, 13496.73351456, 80074.80281119, 7219.61359103, 11188.85418731, 16531.43245011, 12825.86498491, 11203.79560283, 9857.02467244, 14307.14355877, 12845.26871325, 16788.42202773, 2666.69971885, 8244.74309964, 64066.29746039, 5659.51426583, 8096.28881428, 12033.12593598, 9426.9263254, 7970.36632451, 7363.35766454, 10210.87539189, 10123.6175394, 12375.04881186, 2166.93438897, 5208.48876948, 50863.7279613, 4474.9586104, 6021.37054509, 8955.69386028, 7083.47543025, 5826.17965388, 5607.50379743, 7482.20197433, 8007.51198563, 9319.38741314, 1777.83174397, 3454.42244961, 40064.869781, 3560.83504555, 4589.02915871, 6792.9064547, 5423.06879798, 4365.4512389, 4338.25953359, 5614.69063911, 6345.71622375, 7145.68177816, 1470.31781081, 2418.19654392, 31307.05344108, 2845.30355926, 3564.39624718, 5224.66616624, 4209.56280789, 3334.79390126, 3394.67246337, 4291.57074925, 5030.86749267, 5551.6889729, 1224.07657744, 1777.25738138, 24266.493239, 2280.10461964, 2805.59601493, 4054.36267979, 3297.55035375, 2581.48450309, 2676.19256982, 3321.47859835, 3986.65660599, 4350.70166819, 1025.16681066, 1355.64288346, 18656.86705704, 1831.07620877, 2227.69716441, 3161.81195949, 2597.59938153, 2014.80673667, 2119.5462093, 2590.26131503, 3156.69635125, 3427.3373688, 863.3748644, 1060.38309612, 14227.67024349, 1473.54286155, 1779.06501905, 2471.76427857, 2053.14104269, 1580.07248939, 1683.75509117, 2028.74093455, 2498.06072258, 2708.48763003, 731.13710162, 843.04349254, 10762.32039833, 1189.24759299, 1427.18201753, 1934.97059584, 1627.20680116, 1242.98668507, 1341.17909535, 1593.28490369, 1977.33673058, 2145.77206286, 622.74541524, 673.75206471, 8075.53191652, 962.96169614, 1148.12578439, 1514.7366615, 1291.53164241, 978.81975699, 1070.47064475, 1252.226061, 1566.59486177, 1702.59324593, 533.3578408, 250.88277763, 77.71207898, 357.84489037, 416.91542209, 342.4333396, 376.5996489, 250.70665498, 337.01472644, 309.86647864, 426.58744621, 491.86930307, 319.50880385, 229.29781601, 71.21447702, 328.18632766, 379.1447512, 314.00257441, 344.61770519, 230.03291594, 308.29469744, 284.23519458, 390.77381737, 450.27695925, 295.1936476, 209.21994499, 65.14835467, 300.40423785, 344.19388832, 287.42364551, 314.82202223, 210.57578199, 281.47165028, 260.2354516, 357.28777039, 411.56434216, 271.94154064, 190.55383047, 59.48144673, 274.56471988, 311.92552849, 262.56424612, 287.15276933, 192.20792397, 256.53356786, 237.72574669, 326.0079814, 375.63021785, 249.84808837, 173.31457626, 54.23791829, 250.76855378, 282.3730865, 239.39018843, 261.76558309, 175.0845178, 233.58418013, 216.74890079, 297.205373, 342.53968584, 229.18720836, 157.29224057, 49.32662922, 228.19428718, 255.00139226, 217.73039967, 238.00565909, 159.03742939, 212.08398539, 197.1019138, 270.10381343, 311.7703699, 209.8641484, 141.38110738, 43.9405666, 194.01273856, 224.87080743, 196.07043006, 210.89192993, 143.72126099, 187.05164064, 177.501125, 235.78463811, 277.91127606, 187.15442352, 127.81580272, 39.80215487, 175.96610258, 202.36051801, 177.56135099, 191.09288963, 129.79382668, 169.25221871, 160.68453949, 213.53201785, 252.31636702, 171.3170819, 115.29262947, 35.96974114, 159.40994075, 181.7775623, 160.36550115, 172.78765275, 116.92154808, 152.82026135, 145.09082329, 193.04675541, 228.74696014, 156.91305078, 103.84967053, 32.46009682, 144.2389799, 163.06011477, 144.70408242, 155.92925563, 105.11897505, 137.72874746, 130.80534685, 174.24710019, 207.19654065, 143.73194423, 93.0929874, 29.17370523, 130.33532919, 145.82983008, 129.85536119, 140.22202157, 94.0002075, 123.73400641, 117.32768494, 156.9929468, 187.05608981, 131.75605372, 83.20283569, 26.15054288, 117.62685823, 130.16340176, 116.22566476, 125.91822079, 83.69252113, 110.91013697, 104.93626685, 141.22796948, 168.4545692, 120.74746463, 52.64945108, 18.24536031, 90.249466, 79.03878284, 70.1385217, 87.55271683, 55.58045823, 81.16164622, 66.70888044, 107.69161575, 122.61352326, 104.02681856, 47.20593304, 16.36658457, 81.69906803, 71.19150828, 62.86114556, 78.93891423, 49.44616653, 72.91368646, 59.70662294, 97.07545908, 110.68707234, 95.2780778, 41.9331076, 14.55994548, 73.45054787, 63.59953245, 55.82935832, 70.52136348, 43.57546772, 64.93092549, 52.96547495, 86.89208261, 99.12086489, 86.77584469, 36.95224168, 12.82328237, 65.37488765, 56.29405787, 49.17846942, 62.47588163, 38.13320248, 57.30660791, 46.60368616, 76.91545092, 87.94738313, 78.38239169, 30.49828223, 10.61131887, 54.73567011, 46.68421094, 40.71461174, 51.19253131, 32.03266467, 41.40752456, 38.98566165, 64.13828785, 73.79854684, 68.95329164, 26.45659448, 9.22817774, 48.14821042, 40.77258716, 35.32334184, 44.72208008, 27.61580429, 36.22316328, 33.79223547, 56.19593738, 64.50751056, 61.3000728, 19.14725005, 7.22670157, 41.72012939, 32.87033762, 25.77481038, 36.47481896, 14.93829001, 30.12241538, 24.2957102, 48.36687985, 52.70236512, 53.62722476, 16.29701813, 6.15733302, 35.81585382, 28.10988272, 21.94677736, 31.30338474, 12.70721867, 25.7910257, 20.69182435, 41.42845101, 44.95873992, 46.3326035, 11.83783375, 4.65723399, 28.25061309, 21.10904588, 16.10501466, 21.46767567, 9.3225206, 19.1883336, 15.31009316, 32.28881876, 34.85074282, 38.78786148, 9.77180302, 3.86271576, 23.59785147, 17.54084447, 13.30605744, 17.82235045, 7.69076347, 15.95862306, 12.651877, 26.94412072, 28.92813822, 32.54316083, 8.00006518, 3.17981111, 19.48465222, 14.43756332, 10.90650773, 14.66878161, 6.29344009, 13.14536874, 10.37847112, 22.27922869, 23.75507568, 26.84457466, 6.44234966, 2.58136353, 15.8642302, 11.71140701, 8.79407177, 11.90247475, 5.06131273, 10.67629389, 8.370606, 18.18753769, 19.21063221, 21.78499561, 5.0982459, 2.05612964, 12.65339019, 9.31795234, 6.96868474, 9.47388712, 4.00275441, 8.50160244, 6.63898136, 14.54527137, 15.23858668, 17.31119287, 3.2157361, 1.45355321, 9.76258752, 6.72785089, 4.28428123, 6.82057954, 2.31796329, 6.32475215, 3.71746318, 11.21721347, 11.17446436, 13.43050468, 2.46824683, 1.13189028, 7.56900462, 5.2112513, 3.29473824, 5.29556043, 1.77211352, 4.90746605, 2.85803606, 8.76678588, 8.60234981, 10.25617668, 1.60869317, 0.57406935, 4.05771494, 2.88320247, 2.08041262, 2.82259738, 1.22393033, 2.63045214, 1.80940455, 4.06336997, 5.27591307, 6.8757346, 1.1749578, 0.41866757, 2.94812093, 2.09991807, 1.51889883, 2.05708757, 0.89395746, 1.91531693, 1.32068893, 2.95430644, 3.84395157, 4.98544682, 0.85146249, 0.30248027, 2.11345939, 1.51302109, 1.0998314, 1.484101, 0.64786235, 1.3792493, 0.95579352, 2.12097693, 2.77162829, 3.55899348, 0.60296271, 0.21320646, 1.47174389, 1.06197356, 0.77789478, 1.04379, 0.45881999, 0.96724752, 0.67546066, 1.48035767, 1.94756396, 2.4619381, 0.42329347, 0.14887672, 1.01319615, 0.73792169, 0.54533647, 0.72700077, 0.32213142, 0.67142538, 0.47307699, 1.02188602, 1.35505452, 1.68145894, 0.28274259, 0.09911011, 0.66842763, 0.4897269, 0.36394381, 0.48320175, 0.21518278, 0.44531157, 0.31553208, 0.67533483, 0.90003865, 1.10358555, 0.18183303, 0.06350518, 0.42403692, 0.31272734, 0.23383135, 0.30906874, 0.13839358, 0.28416466, 0.20259596, 0.42924955, 0.57526768, 0.69605259, 0.09087201, 0.03306913, 0.24525907, 0.1689701, 0.11813089, 0.16406697, 0.0691139, 0.1546899, 0.10310168, 0.24345827, 0.30779748, 0.42600539, 0.04058406, 0.0154408, 0.12635345, 0.08186055, 0.05339988, 0.07811986, 0.03084201, 0.07547996, 0.04698072, 0.12332687, 0.1477061, 0.22967589, 0.02314418, 0.00828018, 0.05890562, 0.04168112, 0.02995092, 0.04076055, 0.01760785, 0.03804472, 0.02606113, 0.05891728, 0.07622548, 0.1001571, 0.0071337, 0.00267924, 0.02133669, 0.01405698, 0.00935309, 0.01347998, 0.00542257, 0.01293558, 0.00820954, 0.02092007, 0.02543146, 0.03832497, 0.00315186, 0.00106739, 0.00651412, 0.00510275, 0.00402129, 0.00511643, 0.00240012, 0.0046078, 0.0034653, 0.00671494, 0.00946249, 0.01010576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + "value": [8636.11518838, 155936.75819189, 12011.83670094, 15295.06220908, 23102.61431469, 18229.22017149, 14352.79860425, 14479.58806049, 18646.88573464, 22454.35426719, 23887.82966476, 4028.06135169, 10184.09506557, 185528.93813555, 14172.34706228, 17936.05255692, 27381.29364541, 21544.87798605, 17004.76741575, 17121.24936671, 22084.37699671, 26577.73402706, 28267.44710679, 4741.56219829, 11951.98900357, 219252.93256094, 16629.97965972, 20937.20592541, 32256.59848161, 25319.99543439, 20027.04281107, 20128.97132173, 26001.32593173, 31271.6634151, 33255.40799449, 5552.63833363, 13955.41858123, 257341.02657929, 19401.60960057, 24319.74989473, 37763.62018199, 29581.47007256, 23441.73820823, 23524.03284074, 30426.28104053, 36568.44988268, 38887.29863896, 6466.80322114, 16207.91043468, 299960.28757659, 22499.53155521, 28099.71940856, 43928.70728588, 34349.43523927, 27265.68003268, 27322.25783849, 35380.91448654, 42491.53505931, 45189.72605754, 7488.05817635, 18720.76896193, 347194.48174424, 25930.22788876, 32286.68749229, 50767.26415513, 39635.5477444, 31509.12540401, 31532.60982364, 40878.34411838, 49053.08401833, 52178.03580184, 8618.48307036, 21503.63869638, 399026.13349214, 29693.20427677, 36882.76271709, 58281.90545392, 45441.53658516, 36174.7691269, 36155.96049199, 46921.81593256, 56251.66600396, 59854.36265071, 9857.84538995, 24566.0531589, 455319.68592383, 33779.98249576, 41882.02895588, 66461.22571308, 51758.2077549, 41257.22555481, 41184.17547333, 53503.98216392, 64070.18497507, 68206.26778765, 11203.25575218, 27920.15070449, 515806.8269462, 38173.33751002, 47270.56682987, 75279.39865379, 58565.06822037, 46743.13098529, 46599.64096435, 60606.96105671, 72474.22018663, 77206.17600345, 12648.89780998, 31584.51564557, 580075.09453889, 42846.85062412, 53027.11670361, 84696.71750488, 65830.65677043, 52611.92547309, 52375.30611304, 68203.2566928, 81410.92011265, 86811.7308785, 14185.85669939, 35588.83551381, 647560.85499934, 47764.82668404, 59124.34204672, 94661.05377395, 73513.5714895, 58837.2631768, 58475.25123383, 76257.48066343, 90808.56222924, 96967.06120067, 15802.0638549, 39978.87587017, 717547.65458621, 52882.60108969, 65530.57501944, 105110.10776274, 81564.10689295, 65388.91718311, 64855.73999179, 84728.71351308, 100576.85883466, 107604.85376041, 17482.3697536, 44821.32566357, 789170.77963308, 58147.25554056, 72211.93417402, 115974.32991063, 89926.41705515, 72235.05441483, 71466.71332106, 93573.35512992, 110608.07259403, 118649.13090482, 19208.75356796, 50208.52291877, 861428.63091343, 63498.78431449, 79134.85895083, 127180.58944154, 98541.26413021, 79344.92583026, 78253.77350647, 102748.52308929, 120779.02323359, 130018.81188204, 20960.68360992, 56264.05710744, 933201.23813931, 68871.81681937, 86269.45453372, 138657.11859436, 107349.74050761, 86692.39808733, 85160.91904472, 112216.53038084, 130954.13364781, 141632.55365916, 22715.6578677, 63151.80008965, 1003275.92681249, 74198.11163108, 93594.5993811, 150340.9881902, 116298.88312908, 94261.36220863, 92134.63518994, 121951.73022747, 140989.78602178, 153416.03956254, 24449.98112782, 71092.90484527, 1070379.81790154, 79410.1817008, 101106.48625458, 162190.30785568, 125350.78230938, 102054.83799249, 99130.39017132, 131951.98806942, 150740.42397478, 165313.75193983, 26139.87144425, 80397.2308094, 1133218.49566419, 84446.54835727, 108832.95993514, 174204.2469173, 134497.44566739, 110110.34774832, 106123.01513203, 142257.97781222, 160066.99326742, 177308.10030326, 27763.02321849, 91516.38672169, 1190519.80001579, 89259.15756219, 116856.26209607, 186454.28698909, 143783.9059869, 118524.40397946, 113122.59024999, 152983.83471897, 168848.34046993, 189449.064506, 29300.76172558, 105123.2530759, 1241081.2330189, 93823.26474456, 125345.89386563, 199128.91738358, 153341.18158149, 127487.97960632, 120196.87282577, 164361.48149668, 176995.8816746, 201896.39023347, 30740.86217322, 122215.18574474, 1283818.84006237, 98149.40855872, 134600.40771315, 212590.14474293, 163427.88583006, 137331.68044583, 127499.44224089, 176797.0256481, 184470.96261666, 214972.79398305, 32080.92361213, 144223.61391848, 1317814.59241359, 102295.82059083, 145091.48166081, 227432.9682406, 174473.99712135, 148573.40835912, 135299.24424529, 190930.24684298, 191302.69414315, 229219.90737266, 33331.85035763, 173092.59466843, 1342358.33327497, 106377.86027697, 157496.05008648, 244528.95298624, 187112.96743401, 161953.06738235, 144002.37454395, 207677.94717456, 197601.79551725, 245439.35813991, 34520.52833877, 211268.66481528, 1356979.52548866, 110569.3101234, 172694.66977202, 265024.99742189, 202181.00224417, 178430.60992832, 154152.09310471, 228231.66814832, 203563.73778066, 264692.03763587, 35690.32013253, 261534.24516017, 1361463.83115766, 115089.50488272, 191710.52516951, 290263.40852329, 220658.6994934, 199119.61662004, 166390.65772402, 253975.17461904, 209453.38880115, 288223.9673274, 36897.77558636, 326629.02723499, 1355850.51508533, 120171.35611368, 215568.08158786, 321595.49672925, 243534.70083316, 225133.60532122, 181369.52004652, 286293.33026067, 215564.77220615, 317292.86202141, 38204.24438308, 408646.61466703, 1340409.16337799, 126009.10055841, 245066.53819225, 360082.25598173, 271586.64030511, 257339.80593679, 199604.75673094, 326265.81261691, 222154.39836821, 352889.38396024, 39662.07567832, 508265.26829751, 1315598.12149585, 132690.87707327, 280490.18563026, 406111.33881595, 305100.76746534, 296044.42576241, 221291.84609492, 374275.55319861, 229354.68706492, 395380.29096133, 41296.75291663, 623955.60480044, 1282011.57954596, 140128.5982732, 321309.39996019, 459001.35987027, 343582.23706919, 340667.79732625, 246114.12532455, 429604.54396493, 237083.45356259, 444139.6469557, 43088.26229521, 751375.78121914, 1240325.97942115, 148003.52059224, 365951.49417693, 516698.27196556, 385532.73418915, 389495.49858552, 273095.56666274, 490124.1106684, 244973.05879819, 497265.67129221, 44956.56687093, 883183.85406187, 1191257.84035454, 155747.62244677, 411727.88782815, 575678.14299864, 428379.12149197, 439599.39933498, 300553.15318949, 552196.538889, 252345.03218636, 551489.73765292, 46756.51236074, 1009445.22823926, 1135543.11120462, 162576.41704025, 454984.61655762, 631144.97896683, 468618.00014695, 487001.46015325, 326191.73044571, 610878.65771049, 258250.25614222, 602360.11631758, 48286.30681718, 1118689.78538515, 1073942.70035621, 167578.09964692, 491496.85113222, 677550.97565525, 502196.23815115, 527102.73468214, 347354.60775008, 660455.31975142, 261581.08213741, 644725.99329228, 49310.88054922, 1199504.4083909, 1007271.10389008, 169849.22344697, 517064.60691146, 709382.66231928, 525086.09903397, 555331.0281171, 361402.62838087, 695244.88291121, 261242.92800397, 673469.13445145, 49597.54348419, 1242379.52859418, 936437.25497179, 168652.53748879, 528203.9963815, 722073.35028121, 533952.81841152, 567892.38507433, 366154.29003773, 710533.85887999, 256354.26526854, 684353.20425138, 48957.50612348, 1241417.44071106, 862481.40121093, 163562.9488846, 522786.67397025, 712847.1687814, 526772.12276639, 562466.25948494, 360292.84982457, 703441.51325093, 246431.51567859, 674809.39497074, 47284.27202181, 1195498.57776167, 786591.01476069, 154566.5439491, 500475.75993953, 681294.19725716, 503250.95787554, 538679.48036614, 343643.55227814, 673509.30593394, 231514.05104862, 644471.64090222, 44579.63732075, 1108605.1060742, 710083.19832305, 142086.57678761, 462845.30247593, 629527.4420875, 464942.19890304, 498236.26238563, 317248.87831818, 622862.4758963, 212195.95922216, 595322.41651934, 40960.40643991, 989198.08398592, 634349.81714061, 126927.45829835, 413144.33636837, 561870.2113473, 415015.69329628, 444661.93409308, 283216.9725915, 555891.11974598, 189553.14380825, 531401.21387237, 36643.46085477, 848780.74945706, 560772.16388229, 110148.32921692, 355755.42403558, 484139.85264254, 357733.91712053, 382714.59885176, 244375.13722205, 478518.21012362, 164980.62293492, 458137.14785153, 31912.24938285, 699984.43025527, 490621.02806295, 92895.57174451, 295474.22192554, 402695.12265588, 297754.67559049, 317602.24210044, 203809.24146686, 397225.62399381, 139977.60263943, 381461.50264291, 27072.47017987, 554620.4572446, 424962.60410925, 76232.86437708, 236776.77342648, 323467.54376147, 239422.13343982, 254186.45460948, 164395.53638628, 318063.55621384, 115929.74993123, 306905.46378544, 22407.15102102, 422118.5086326, 364589.22368091, 61005.38082121, 183232.5680431, 251185.63232504, 186199.04935126, 196344.52710504, 128425.81446604, 245856.97910548, 93935.4852374, 238877.59802324, 18140.80457147, 308631.95596426, 309987.06952004, 47762.49468978, 137168.79518351, 188931.34847929, 140342.1935888, 146604.50752591, 97393.23428609, 183751.69634075, 74707.45399011, 180250.8529489, 14419.08583418, 216887.46758499, 261343.32798273, 36745.68219709, 99614.77920995, 138067.02918736, 102848.97004181, 106084.7104838, 71957.29676562, 133139.16084527, 58557.67336813, 132294.71833964, 11305.71656887, 146660.92345013, 218586.00382083, 27931.20794584, 70482.14098842, 98474.03180853, 73632.1967699, 74689.39871155, 52059.52371219, 93899.9876003, 45452.98687705, 94897.76209722, 8793.89761577, 95634.76400667, 181443.67479576, 21106.19252306, 48888.5138398, 68981.20211422, 51833.80680268, 51460.46908652, 37130.90907714, 64841.54342957, 35113.34162748, 66967.00088491, 6826.5378056, 60360.06595485, 149511.04788784, 15953.77759109, 33520.68466868, 47845.51082021, 36176.7543291, 34972.00486168, 26324.58175834, 44188.86549669, 27121.82305183, 46875.84275008, 5318.86371875, 37098.19073119, 122308.72350146, 12127.94010089, 22952.47617382, 33172.812675, 25273.09913029, 23675.59642654, 18719.56262491, 30014.34246306, 21021.31386653, 32856.28515705, 4178.28108011, 22414.43636882, 99330.28631362, 9306.37185643, 15869.22132575, 23215.18450714, 17841.70103895, 16144.24560312, 13464.77796612, 20541.20913108, 16383.17925858, 23276.06382328, 3318.38187155, 13496.73351456, 80074.80281119, 7219.61359103, 11188.85418731, 16531.43245011, 12825.86498491, 11203.79560283, 9857.02467244, 14307.14355877, 12845.26871325, 16788.42202773, 2666.69971885, 8244.74309964, 64066.29746039, 5659.51426583, 8096.28881428, 12033.12593598, 9426.9263254, 7970.36632451, 7363.35766454, 10210.87539189, 10123.6175394, 12375.04881186, 2166.93438897, 5208.48876948, 50863.7279613, 4474.9586104, 6021.37054509, 8955.69386028, 7083.47543025, 5826.17965388, 5607.50379743, 7482.20197433, 8007.51198563, 9319.38741314, 1777.83174397, 3454.42244961, 40064.869781, 3560.83504555, 4589.02915871, 6792.9064547, 5423.06879798, 4365.4512389, 4338.25953359, 5614.69063911, 6345.71622375, 7145.68177816, 1470.31781081, 2418.19654392, 31307.05344108, 2845.30355926, 3564.39624718, 5224.66616624, 4209.56280789, 3334.79390126, 3394.67246337, 4291.57074925, 5030.86749267, 5551.6889729, 1224.07657744, 1777.25738138, 24266.493239, 2280.10461964, 2805.59601493, 4054.36267979, 3297.55035375, 2581.48450309, 2676.19256982, 3321.47859835, 3986.65660599, 4350.70166819, 1025.16681066, 1355.64288346, 18656.86705704, 1831.07620877, 2227.69716441, 3161.81195949, 2597.59938153, 2014.80673667, 2119.5462093, 2590.26131503, 3156.69635125, 3427.3373688, 863.3748644, 1060.38309612, 14227.67024349, 1473.54286155, 1779.06501905, 2471.76427857, 2053.14104269, 1580.07248939, 1683.75509117, 2028.74093455, 2498.06072258, 2708.48763003, 731.13710162, 843.04349254, 10762.32039833, 1189.24759299, 1427.18201753, 1934.97059584, 1627.20680116, 1242.98668507, 1341.17909535, 1593.28490369, 1977.33673058, 2145.77206286, 622.74541524, 673.75206471, 8075.53191652, 962.96169614, 1148.12578439, 1514.7366615, 1291.53164241, 978.81975699, 1070.47064475, 1252.226061, 1566.59486177, 1702.59324593, 533.3578408, 250.88277763, 77.71207898, 357.84489037, 416.91542209, 342.4333396, 376.5996489, 250.70665498, 337.01472644, 309.86647864, 426.58744621, 491.86930307, 319.50880385, 229.29781601, 71.21447702, 328.18632766, 379.1447512, 314.00257441, 344.61770519, 230.03291594, 308.29469744, 284.23519458, 390.77381737, 450.27695925, 295.1936476, 209.21994499, 65.14835467, 300.40423785, 344.19388832, 287.42364551, 314.82202223, 210.57578199, 281.47165028, 260.2354516, 357.28777039, 411.56434216, 271.94154064, 190.55383047, 59.48144673, 274.56471988, 311.92552849, 262.56424612, 287.15276933, 192.20792397, 256.53356786, 237.72574669, 326.0079814, 375.63021785, 249.84808837, 173.31457626, 54.23791829, 250.76855378, 282.3730865, 239.39018843, 261.76558309, 175.0845178, 233.58418013, 216.74890079, 297.205373, 342.53968584, 229.18720836, 157.29224057, 49.32662922, 228.19428718, 255.00139226, 217.73039967, 238.00565909, 159.03742939, 212.08398539, 197.1019138, 270.10381343, 311.7703699, 209.8641484, 141.38110738, 43.9405666, 194.01273856, 224.87080743, 196.07043006, 210.89192993, 143.72126099, 187.05164064, 177.501125, 235.78463811, 277.91127606, 187.15442352, 127.81580272, 39.80215487, 175.96610258, 202.36051801, 177.56135099, 191.09288963, 129.79382668, 169.25221871, 160.68453949, 213.53201785, 252.31636702, 171.3170819, 115.29262947, 35.96974114, 159.40994075, 181.7775623, 160.36550115, 172.78765275, 116.92154808, 152.82026135, 145.09082329, 193.04675541, 228.74696014, 156.91305078, 103.84967053, 32.46009682, 144.2389799, 163.06011477, 144.70408242, 155.92925563, 105.11897505, 137.72874746, 130.80534685, 174.24710019, 207.19654065, 143.73194423, 93.0929874, 29.17370523, 130.33532919, 145.82983008, 129.85536119, 140.22202157, 94.0002075, 123.73400641, 117.32768494, 156.9929468, 187.05608981, 131.75605372, 83.20283569, 26.15054288, 117.62685823, 130.16340176, 116.22566476, 125.91822079, 83.69252113, 110.91013697, 104.93626685, 141.22796948, 168.4545692, 120.74746463, 52.64945108, 18.24536031, 90.249466, 79.03878284, 70.1385217, 87.55271683, 55.58045823, 81.16164622, 66.70888044, 107.69161575, 122.61352326, 104.02681856, 47.20593304, 16.36658457, 81.69906803, 71.19150828, 62.86114556, 78.93891423, 49.44616653, 72.91368646, 59.70662294, 97.07545908, 110.68707234, 95.2780778, 41.9331076, 14.55994548, 73.45054787, 63.59953245, 55.82935832, 70.52136348, 43.57546772, 64.93092549, 52.96547495, 86.89208261, 99.12086489, 86.77584469, 36.95224168, 12.82328237, 65.37488765, 56.29405787, 49.17846942, 62.47588163, 38.13320248, 57.30660791, 46.60368616, 76.91545092, 87.94738313, 78.38239169, 30.49828223, 10.61131887, 54.73567011, 46.68421094, 40.71461174, 51.19253131, 32.03266467, 41.40752456, 38.98566165, 64.13828785, 73.79854684, 68.95329164, 26.45659448, 9.22817774, 48.14821042, 40.77258716, 35.32334184, 44.72208008, 27.61580429, 36.22316328, 33.79223547, 56.19593738, 64.50751056, 61.3000728, 19.14725005, 7.22670157, 41.72012939, 32.87033762, 25.77481038, 36.47481896, 14.93829001, 30.12241538, 24.2957102, 48.36687985, 52.70236512, 53.62722476, 16.29701813, 6.15733302, 35.81585382, 28.10988272, 21.94677736, 31.30338474, 12.70721867, 25.7910257, 20.69182435, 41.42845101, 44.95873992, 46.3326035, 11.83783375, 4.65723399, 28.25061309, 21.10904588, 16.10501466, 21.46767567, 9.3225206, 19.1883336, 15.31009316, 32.28881876, 34.85074282, 38.78786148, 9.77180302, 3.86271576, 23.59785147, 17.54084447, 13.30605744, 17.82235045, 7.69076347, 15.95862306, 12.651877, 26.94412072, 28.92813822, 32.54316083, 8.00006518, 3.17981111, 19.48465222, 14.43756332, 10.90650773, 14.66878161, 6.29344009, 13.14536874, 10.37847112, 22.27922869, 23.75507568, 26.84457466, 6.44234966, 2.58136353, 15.8642302, 11.71140701, 8.79407177, 11.90247475, 5.06131273, 10.67629389, 8.370606, 18.18753769, 19.21063221, 21.78499561, 5.0982459, 2.05612964, 12.65339019, 9.31795234, 6.96868474, 9.47388712, 4.00275441, 8.50160244, 6.63898136, 14.54527137, 15.23858668, 17.31119287, 3.2157361, 1.45355321, 9.76258752, 6.72785089, 4.28428123, 6.82057954, 2.31796329, 6.32475215, 3.71746318, 11.21721347, 11.17446436, 13.43050468, 2.46824683, 1.13189028, 7.56900462, 5.2112513, 3.29473824, 5.29556043, 1.77211352, 4.90746605, 2.85803606, 8.76678588, 8.60234981, 10.25617668, 1.60869317, 0.57406935, 4.05771494, 2.88320247, 2.08041262, 2.82259738, 1.22393033, 2.63045214, 1.80940455, 4.06336997, 5.27591307, 6.8757346, 1.1749578, 0.41866757, 2.94812093, 2.09991807, 1.51889883, 2.05708757, 0.89395746, 1.91531693, 1.32068893, 2.95430644, 3.84395157, 4.98544682, 0.85146249, 0.30248027, 2.11345939, 1.51302109, 1.0998314, 1.484101, 0.64786235, 1.3792493, 0.95579352, 2.12097693, 2.77162829, 3.55899348, 0.60296271, 0.21320646, 1.47174389, 1.06197356, 0.77789478, 1.04379, 0.45881999, 0.96724752, 0.67546066, 1.48035767, 1.94756396, 2.4619381, 0.42329347, 0.14887672, 1.01319615, 0.73792169, 0.54533647, 0.72700077, 0.32213142, 0.67142538, 0.47307699, 1.02188602, 1.35505452, 1.68145894, 0.28274259, 0.09911011, 0.66842763, 0.4897269, 0.36394381, 0.48320175, 0.21518278, 0.44531157, 0.31553208, 0.67533483, 0.90003865, 1.10358555, 0.18183303, 0.06350518, 0.42403692, 0.31272734, 0.23383135, 0.30906874, 0.13839358, 0.28416466, 0.20259596, 0.42924955, 0.57526768, 0.69605259, 0.09087201, 0.03306913, 0.24525907, 0.1689701, 0.11813089, 0.16406697, 0.0691139, 0.1546899, 0.10310168, 0.24345827, 0.30779748, 0.42600539, 0.04058406, 0.0154408, 0.12635345, 0.08186055, 0.05339988, 0.07811986, 0.03084201, 0.07547996, 0.04698072, 0.12332687, 0.1477061, 0.22967589, 0.02314418, 0.00828018, 0.05890562, 0.04168112, 0.02995092, 0.04076055, 0.01760785, 0.03804472, 0.02606113, 0.05891728, 0.07622548, 0.1001571, 0.0071337, 0.00267924, 0.02133669, 0.01405698, 0.00935309, 0.01347998, 0.00542257, 0.01293558, 0.00820954, 0.02092007, 0.02543146, 0.03832497, 0.00315186, 0.00106739, 0.00651412, 0.00510275, 0.00402129, 0.00511643, 0.00240012, 0.0046078, 0.0034653, 0.00671494, 0.00946249, 0.01010576, 4.77012702e-15, 1.40504697e-15, 4.59268998e-15, 5.71968255e-15, 5.88498967e-15, 6.22596662e-15, 3.64015775e-15, 4.97152903e-15, 4.95180143e-15, 5.59242596e-15, 1.11142033e-14, 2.95247031e-15] } # getResourceMort @@ -117,7 +117,7 @@ "value": ["8.73e-13", "1.04e-12", "1.24e-12", "1.48e-12", "1.77e-12", "2.11e-12", "2.52e-12", "3.01e-12", "3.59e-12", "4.28e-12", "5.11e-12", "6.1e-12", "7.28e-12", "8.69e-12", "1.04e-11", "1.24e-11", "1.48e-11", "1.76e-11", "2.1e-11", "2.51e-11", "2.99e-11", "3.57e-11", "4.26e-11", "5.09e-11", "6.07e-11", "7.25e-11", "8.65e-11", "1.03e-10", "1.23e-10", "1.47e-10", "1.75e-10", "2.09e-10", "2.5e-10", "2.98e-10", "3.56e-10", "4.25e-10", "5.07e-10", "6.05e-10", "7.21e-10", "8.61e-10", "1.03e-09", "1.23e-09", "1.46e-09", "1.75e-09", "2.08e-09", "2.49e-09", "2.97e-09", "3.54e-09", "4.23e-09", "5.04e-09", "6.02e-09", "7.18e-09", "8.57e-09", "1.02e-08", "1.22e-08", "1.46e-08", "1.74e-08", "2.07e-08", "2.48e-08", "2.95e-08", "3.53e-08", "4.21e-08", "5.02e-08", "5.99e-08", "7.15e-08", "8.53e-08", "1.02e-07", "1.22e-07", "1.45e-07", "1.73e-07", "2.07e-07", "2.46e-07", "2.94e-07", "3.51e-07", "4.19e-07", "5e-07", "5.96e-07", "7.12e-07", "8.49e-07", "1.01e-06", "1.21e-06", "1.44e-06", "1.72e-06", "2.06e-06", "2.45e-06", "2.93e-06", "3.49e-06", "4.17e-06", "4.98e-06", "5.94e-06", "7.09e-06", "8.46e-06", "1.01e-05", "1.2e-05", "1.44e-05", "1.71e-05", "2.05e-05", "2.44e-05", "2.91e-05", "3.48e-05", "4.15e-05", "4.95e-05", "5.91e-05", "7.05e-05", "8.42e-05", "1e-04", "0.00012", "0.000143", "0.000171", "0.000204", "0.000243", "0.00029", "0.000346", "0.000413", "0.000493", "0.000588", "0.000702", "0.000838", "0.001", "0.00119", "0.00142", "0.0017", "0.00203", "0.00242", "0.00289", "0.00345", "0.00411", "0.00491", "0.00586", "0.00699", "0.00834", "0.00995", "0.0119", "0.0142", "0.0169", "0.0202", "0.0241", "0.0288", "0.0343", "0.0409", "0.0489", "0.0583", "0.0696", "0.083", "0.0991", "0.118", "0.141", "0.168", "0.201", "0.24", "0.286", "0.342", "0.408", "0.486", "0.58", "0.693", "0.827", "0.987", "1.18", "1.4", "1.68", "2", "2.39", "2.85", "3.4", "4.06", "4.84", "5.78", "6.9", "8.23", "9.82", "11.7", "14", "16.7", "19.9", "23.8", "28.4", "33.8", "40.4", "48.2", "57.5", "68.7", "81.9", "97.8", "117", "139", "166", "198", "237", "282", "337", "402", "480", "573", "683", "816", "973", "1160", "1390", "1650", "1970", "2360", "2810", "3350", "4000", "4780", "5700", "6800", "8120", "9690", "11600", "13800", "16500", "19600", "23400", "28000", "33400", "39900"] } }, - "value": [6.65569166e-07, 9.22652565e-07, 1.27619099e-06, 1.76126288e-06, 2.42528025e-06, 3.33217211e-06, 4.56792483e-06, 6.24791198e-06, 8.5266795e-06, 0.00001161, 0.00001577, 0.00002138, 0.00002892, 0.00003902, 0.00005253, 0.00007056, 0.00009456, 0.00012644, 0.00016869, 0.00022452, 0.00029816, 0.00039504, 0.00052219, 0.00068867, 0.00090615, 0.00118954, 0.00155795, 0.00203573, 0.00265386, 0.00345163, 0.00447876, 0.00579799, 0.00748827, 0.00964869, 0.01240328, 0.01590685, 0.02035212, 0.02597836, 0.03308176, 0.04202799, 0.05326725, 0.06735225, 0.08495958, 0.10691524, 0.13422464, 0.16810817, 0.2100428, 0.26181091, 0.32555714, 0.40385445, 0.49978059, 0.61700619, 0.75989597, 0.93362441, 1.14430761, 1.39915285, 1.70662766, 2.07665015, 2.52080256, 3.05257002, 3.68760656, 4.44403089, 5.34275458, 6.40784589, 7.66693344, 9.15165507, 10.89815959, 12.94767171, 15.34713541, 18.14995733, 21.41688121, 25.21703794, 29.6292346, 34.74357222, 40.66351819, 47.50860899, 55.41802599, 64.55537766, 75.11514165, 87.33137993, 101.48954853, 117.94249564, 137.13209192, 159.61838558, 186.11874295, 217.56014893, 255.14872901, 300.46164447, 355.5678358, 423.18567162, 506.88742163, 611.36261751, 742.75477962, 909.08862484, 1120.80764276, 1391.44469327, 1738.45082428, 2184.20953632, 2757.26482743, 3493.79101567, 4439.32990882, 5650.81560661, 7198.89822584, 9170.5642597, 11672.0323085, 14831.8779238, 18804.30996345, 23772.48326575, 29951.68928473, 37592.21897691, 46981.64297451, 58446.20622352, 72350.99212816, 89098.47994782, 109125.10424479, 132895.43207513, 160893.60782017, 193611.78318118, 231535.35878751, 275125.02428977, 324795.80633182, 380893.62347607, 443670.19360508, 513257.50652458, 589643.39741628, 672649.95266661, 761916.47696244, 856888.52899453, 956814.15624481, 1060748.08094338, 1167564.42474278, 1275978.83460451, 1384581.76820912, 1491886.28872111, 1596395.84873849, 1696699.56607094, 1791602.91854724, 1880298.08903526, 1962567.30546189, 2038992.43003225, 2111116.38325321, 2181474.42446798, 2253399.84747092, 2330525.89834131, 2415965.17653482, 2511246.74275038, 2615207.20104822, 2723125.66621252, 2826419.53956004, 2913147.61536613, 2969398.33204254, 2981409.60527673, 2938037.53758799, 2833039.04005269, 2666617.18844486, 2445819.12933875, 2183645.75524787, 1897055.80879644, 1604326.13257713, 1322375.73608454, 1064629.34756526, 839803.87412072, 651722.14230717, 499990.3217424, 381201.38721215, 290283.39375542, 221682.47441448, 170203.9109235, 131474.04577417, 102083.4759375, 79521.19022506, 62004.83446581, 48289.1514073, 37499.81163743, 29005.64116434, 22334.00449273, 17118.53596196, 13060.02829881, 1223.81596337, 1123.43962842, 1028.94557297, 940.20950653, 857.85996828, 780.65935854, 692.39024952, 628.05327971, 568.93389415, 514.7910577, 464.72966692, 418.84935014, 306.28758601, 276.90348237, 248.5167479, 221.20467154, 185.38296606, 162.83317529, 129.43905272, 111.28305706, 87.35200057, 72.93149071, 60.10878772, 48.77273176, 38.79275202, 28.2800623, 21.746557, 13.31592398, 9.67267791, 6.93126241, 4.82350281, 3.31804066, 2.18787131, 1.38715398, 0.80687984, 0.41767981, 0.19337324, 0.07044207, 0.02119526, 0] + "value": [6.65578596e-07, 9.22652779e-07, 1.27619106e-06, 1.76126307e-06, 2.42528027e-06, 3.33217231e-06, 4.56792486e-06, 6.24791213e-06, 8.52672522e-06, 0.00001161, 0.00001577, 0.00002138, 0.00002892, 0.00003902, 0.00005253, 0.00007056, 0.00009456, 0.00012644, 0.00016869, 0.00022452, 0.00029816, 0.00039504, 0.00052219, 0.00068867, 0.00090615, 0.00118954, 0.00155795, 0.00203573, 0.00265386, 0.00345163, 0.00447876, 0.00579799, 0.00748827, 0.00964869, 0.01240328, 0.01590685, 0.02035212, 0.02597836, 0.03308176, 0.04202799, 0.05326725, 0.06735225, 0.08495958, 0.10691524, 0.13422464, 0.16810817, 0.2100428, 0.26181091, 0.32555714, 0.40385445, 0.49978059, 0.61700619, 0.75989597, 0.93362441, 1.14430761, 1.39915285, 1.70662766, 2.07665015, 2.52080256, 3.05257002, 3.68760656, 4.44403089, 5.34275458, 6.40784589, 7.66693344, 9.15165507, 10.89815959, 12.94767171, 15.34713541, 18.14995733, 21.41688121, 25.21703794, 29.6292346, 34.74357222, 40.66351819, 47.50860899, 55.41802599, 64.55537766, 75.11514165, 87.33137993, 101.48954853, 117.94249564, 137.13209192, 159.61838558, 186.11874295, 217.56014893, 255.14872901, 300.46164447, 355.5678358, 423.18567162, 506.88742163, 611.36261751, 742.75477962, 909.08862484, 1120.80764276, 1391.44469327, 1738.45082428, 2184.20953632, 2757.26482743, 3493.79101567, 4439.32990882, 5650.81560661, 7198.89822584, 9170.5642597, 11672.0323085, 14831.8779238, 18804.30996345, 23772.48326575, 29951.68928473, 37592.21897691, 46981.64297451, 58446.20622352, 72350.99212816, 89098.47994782, 109125.10424479, 132895.43207513, 160893.60782017, 193611.78318118, 231535.35878751, 275125.02428977, 324795.80633182, 380893.62347607, 443670.19360508, 513257.50652458, 589643.39741628, 672649.95266661, 761916.47696244, 856888.52899453, 956814.15624482, 1060748.08094338, 1167564.42474278, 1275978.83460451, 1384581.76820912, 1491886.28872111, 1596395.84873849, 1696699.56607094, 1791602.91854724, 1880298.08903526, 1962567.30546189, 2038992.43003226, 2111116.38325322, 2181474.42446798, 2253399.84747092, 2330525.89834131, 2415965.17653482, 2511246.74275038, 2615207.20104822, 2723125.66621252, 2826419.53956004, 2913147.61536613, 2969398.33204254, 2981409.60527673, 2938037.53758799, 2833039.04005269, 2666617.18844486, 2445819.12933875, 2183645.75524787, 1897055.80879644, 1604326.13257713, 1322375.73608454, 1064629.34756526, 839803.87412072, 651722.14230717, 499990.3217424, 381201.38721215, 290283.39375542, 221682.47441448, 170203.9109235, 131474.04577418, 102083.4759375, 79521.19022506, 62004.83446581, 48289.1514073, 37499.81163743, 29005.64116434, 22334.00449273, 17118.53596196, 13060.02829881, 1223.81596337, 1123.43962842, 1028.94557297, 940.20950653, 857.85996828, 780.65935854, 692.39024952, 628.05327971, 568.93389415, 514.7910577, 464.72966692, 418.84935014, 306.28758601, 276.90348237, 248.5167479, 221.20467154, 185.38296606, 162.83317529, 129.43905272, 111.28305706, 87.35200057, 72.93149071, 60.10878772, 48.77273176, 38.79275202, 28.2800623, 21.746557, 13.31592398, 9.67267791, 6.93126241, 4.82350281, 3.31804066, 2.18787131, 1.38715398, 0.80687984, 0.41767981, 0.19337324, 0.07044207, 0.02119526, 1.41303731e-14] } # getFmortGear @@ -233,7 +233,7 @@ ] } }, - "value": [8636.30224795, 155936.93990395, 12011.96596703, 15295.14868644, 23102.70167274, 18229.27675968, 14352.86155177, 14479.65669762, 18646.92744785, 22454.39111746, 23887.84723066, 4028.078946, 10184.28212514, 185529.11984761, 14172.47632836, 17936.13903429, 27381.38100346, 21544.93457425, 17004.83036327, 17121.31800383, 22084.41870992, 26577.77087733, 28267.46467269, 4741.5797926, 11952.17606314, 219253.114273, 16630.1089258, 20937.29240277, 32256.68583966, 25320.05202259, 20027.10575859, 20129.03995886, 26001.36764494, 31271.70026537, 33255.42556039, 5552.65592794, 13955.6056408, 257341.20829135, 19401.73886665, 24319.8363721, 37763.70754004, 29581.52666076, 23441.80115575, 23524.10147787, 30426.32275374, 36568.48673295, 38887.31620486, 6466.82081545, 16208.09749425, 299960.46928865, 22499.66082129, 28099.80588592, 43928.79464392, 34349.49182746, 27265.7429802, 27322.32647562, 35380.95619975, 42491.57190958, 45189.74362344, 7488.07577066, 18720.95602151, 347194.6634563, 25930.35715484, 32286.77396965, 50767.35151318, 39635.6043326, 31509.18835152, 31532.67846077, 40878.38583159, 49053.1208686, 52178.05336774, 8618.50066467, 21503.82575595, 399026.3152042, 29693.33354285, 36882.84919445, 58281.99281196, 45441.59317335, 36174.83207442, 36156.02912912, 46921.85764577, 56251.70285423, 59854.38021661, 9857.86298425, 24566.24021847, 455319.86763589, 33780.11176185, 41882.11543324, 66461.31307113, 51758.26434309, 41257.28850233, 41184.24411046, 53504.02387713, 64070.22182534, 68206.28535355, 11203.27334648, 27920.33776406, 515807.00865826, 38173.4667761, 47270.65330723, 75279.48601183, 58565.12480856, 46743.19393281, 46599.70960148, 60607.00276992, 72474.2570369, 77206.19356935, 12648.91540429, 31584.70270515, 580075.27625095, 42846.9798902, 53027.20318097, 84696.80486293, 65830.71335863, 52611.98842061, 52375.37475016, 68203.29840601, 81410.95696292, 86811.7484444, 14185.87429369, 35589.02257338, 647561.0367114, 47764.95595012, 59124.42852408, 94661.141132, 73513.62807769, 58837.32612432, 58475.31987095, 76257.52237664, 90808.59907951, 96967.07876657, 15802.0814492, 39979.06292974, 717547.83629827, 52882.73035577, 65530.6614968, 105110.19512078, 81564.16348115, 65388.98013063, 64855.80862891, 84728.75522629, 100576.89568493, 107604.87132631, 17482.38734791, 44821.51272314, 789170.96134514, 58147.38480664, 72212.02065138, 115974.41726868, 89926.47364334, 72235.11736235, 71466.78195819, 93573.39684313, 110608.1094443, 118649.14847072, 19208.77116226, 50208.70997835, 861428.81262549, 63498.91358057, 79134.94542819, 127180.67679959, 98541.3207184, 79344.98877778, 78253.8421436, 102748.5648025, 120779.06008386, 130018.82944794, 20960.70120422, 56264.24416701, 933201.41985137, 68871.94608546, 86269.54101108, 138657.20595241, 107349.7970958, 86692.46103485, 85160.98768185, 112216.57209405, 130954.17049808, 141632.57122506, 22715.67546201, 63151.98714922, 1003276.10852455, 74198.24089716, 93594.68585846, 150341.07554825, 116298.93971727, 94261.42515615, 92134.70382706, 121951.77194068, 140989.82287205, 153416.05712844, 24449.99872213, 71093.09190484, 1070379.99961359, 79410.31096688, 101106.57273194, 162190.39521372, 125350.83889758, 102054.90094001, 99130.45880845, 131952.02978263, 150740.46082505, 165313.76950573, 26139.88903855, 80397.41786897, 1133218.67737625, 84446.67762335, 108833.04641251, 174204.33427535, 134497.50225559, 110110.41069584, 106123.08376915, 142258.01952543, 160067.03011768, 177308.11786916, 27763.0408128, 91516.57378127, 1190519.98172785, 89259.28682828, 116856.34857343, 186454.37434714, 143783.96257509, 118524.46692698, 113122.65888712, 152983.87643218, 168848.3773202, 189449.0820719, 29300.77931988, 105123.44013548, 1241081.41473096, 93823.39401065, 125345.98034299, 199129.00474162, 153341.23816969, 127488.04255384, 120196.9414629, 164361.52320989, 176995.91852487, 201896.40779937, 30740.87976752, 122215.37280431, 1283819.02177443, 98149.5378248, 134600.49419051, 212590.23210098, 163427.94241825, 137331.74339335, 127499.51087802, 176797.06736131, 184470.99946693, 214972.81154895, 32080.94120644, 144223.80097805, 1317814.77412565, 102295.94985692, 145091.56813817, 227433.05559864, 174474.05370955, 148573.47130664, 135299.31288242, 190930.28855619, 191302.73099342, 229219.92493856, 33331.86795193, 173092.781728, 1342358.51498703, 106377.98954306, 157496.13656384, 244529.04034428, 187113.0240222, 161953.13032987, 144002.44318108, 207677.98888777, 197601.83236752, 245439.37570581, 34520.54593308, 211268.85187485, 1356979.70720072, 110569.43938948, 172694.75624938, 265025.08477993, 202181.05883236, 178430.67287584, 154152.16174183, 228231.70986153, 203563.77463093, 264692.05520177, 35690.33772684, 261534.43221974, 1361464.01286972, 115089.6341488, 191710.61164687, 290263.49588134, 220658.7560816, 199119.67956756, 166390.72636115, 253975.21633225, 209453.42565142, 288223.9848933, 36897.79318066, 326629.21429456, 1355850.69679739, 120171.48537976, 215568.16806522, 321595.5840873, 243534.75742135, 225133.66826874, 181369.58868365, 286293.37197388, 215564.80905641, 317292.87958731, 38204.26197738, 408646.8017266, 1340409.34509005, 126009.22982449, 245066.62466961, 360082.34333978, 271586.6968933, 257339.86888431, 199604.82536807, 326265.85433012, 222154.43521848, 352889.40152614, 39662.09327263, 508265.45535708, 1315598.30320791, 132691.00633935, 280490.27210762, 406111.426174, 305100.82405353, 296044.48870993, 221291.91473204, 374275.59491181, 229354.72391519, 395380.30852723, 41296.77051093, 623955.79186001, 1282011.76125802, 140128.72753928, 321309.48643755, 459001.44722831, 343582.29365739, 340667.86027377, 246114.19396168, 429604.58567814, 237083.49041286, 444139.6645216, 43088.27988952, 751375.96827871, 1240326.16113321, 148003.64985833, 365951.58065429, 516698.35932361, 385532.79077734, 389495.56153304, 273095.63529987, 490124.15238161, 244973.09564846, 497265.68885811, 44956.58446524, 883184.04112145, 1191258.0220666, 155747.75171285, 411727.97430551, 575678.23035669, 428379.17808017, 439599.4622825, 300553.22182661, 552196.58060221, 252345.06903662, 551489.75521882, 46756.52995504, 1009445.41529883, 1135543.29291668, 162576.54630634, 454984.70303498, 631145.06632487, 468618.05673515, 487001.52310077, 326191.79908283, 610878.6994237, 258250.29299249, 602360.13388348, 48286.32441148, 1118689.97244472, 1073942.88206827, 167578.228913, 491496.93760958, 677551.0630133, 502196.29473934, 527102.79762965, 347354.67638721, 660455.36146463, 261581.11898768, 644726.01085818, 49310.89814352, 1199504.59545047, 1007271.28560214, 169849.35271305, 517064.69338882, 709382.74967733, 525086.15562216, 555331.09106462, 361402.69701799, 695244.92462442, 261242.96485424, 673469.15201735, 49597.56107849, 1242379.71565375, 936437.43668385, 168652.66675487, 528204.08285886, 722073.43763926, 533952.87499972, 567892.44802185, 366154.35867485, 710533.9005932, 256354.30211881, 684353.22181728, 48957.52371778, 1241417.62777063, 862481.58292299, 163563.07815068, 522786.76044761, 712847.25613944, 526772.17935458, 562466.32243246, 360292.91846169, 703441.55496414, 246431.55252886, 674809.41253664, 47284.28961611, 1195498.76482124, 786591.19647275, 154566.67321518, 500475.84641689, 681294.2846152, 503251.01446373, 538679.54331366, 343643.62091527, 673509.34764715, 231514.08789889, 644471.65846812, 44579.65491506, 1108605.29313377, 710083.38003511, 142086.70605369, 462845.38895329, 629527.52944554, 464942.25549123, 498236.32533315, 317248.9469553, 622862.51760951, 212195.99607243, 595322.43408524, 40960.42403422, 989198.27104549, 634349.99885267, 126927.58756444, 413144.42284573, 561870.29870534, 415015.74988447, 444661.9970406, 283217.04122862, 555891.16145919, 189553.18065852, 531401.23143827, 36643.47844908, 848780.93651663, 560772.34559435, 110148.458483, 355755.51051294, 484139.94000059, 357733.97370872, 382714.66179928, 244375.20585918, 478518.25183683, 164980.65978518, 458137.16541743, 31912.26697715, 699984.61731484, 490621.20977501, 92895.70101059, 295474.3084029, 402695.21001393, 297754.73217868, 317602.30504795, 203809.31010399, 397225.66570702, 139977.6394897, 381461.52020881, 27072.48777417, 554620.64430418, 424962.7858213, 76232.99364316, 236776.85990384, 323467.63111952, 239422.19002801, 254186.517557, 164395.60502341, 318063.59792705, 115929.7867815, 306905.48135134, 22407.16861532, 422118.69569217, 364589.40539297, 61005.51008729, 183232.65452046, 251185.71968309, 186199.10593945, 196344.59005256, 128425.88310316, 245857.02081869, 93935.52208767, 238877.61558914, 18140.82216578, 308632.14302383, 309987.2512321, 47762.62395586, 137168.88166087, 188931.43583733, 140342.25017699, 146604.57047343, 97393.30292322, 183751.73805396, 74707.49084038, 180250.8705148, 14419.10342849, 216887.65464456, 261343.50969479, 36745.81146317, 99614.86568731, 138067.1165454, 102849.02663, 106084.77343132, 71957.36540275, 133139.20255848, 58557.7102184, 132294.73590554, 11305.73416317, 146661.1105097, 218586.18553289, 27931.33721192, 70482.22746578, 98474.11916657, 73632.2533581, 74689.46165907, 52059.59234932, 93900.02931351, 45453.02372732, 94897.77966312, 8793.91521008, 95634.95106624, 181443.85650782, 21106.32178914, 48888.60031716, 68981.28947226, 51833.86339088, 51460.53203404, 37130.97771427, 64841.58514278, 35113.37847775, 66967.01845081, 6826.55539991, 60360.25301443, 149511.7295999, 15953.90685717, 33520.77114604, 47845.59817826, 36176.81091729, 34972.0678092, 26324.65039547, 44188.9072099, 27121.8599021, 46875.86031598, 5318.88131306, 37098.37779076, 122309.40521352, 12128.06936697, 22952.56265119, 33172.90003304, 25273.15571849, 23675.65937406, 18719.63126204, 30014.38417626, 21021.3507168, 32856.30272295, 4178.29867442, 22414.6234284, 99330.96802568, 9306.50112251, 15869.30780311, 23215.27186518, 17841.75762714, 16144.30855064, 13464.84660324, 20541.25084429, 16383.21610885, 23276.08138918, 3318.39946586, 13496.92057413, 80075.48452325, 7219.74285711, 11188.94066467, 16531.51980815, 12825.9215731, 11203.85855035, 9857.09330956, 14307.18527198, 12845.30556352, 16788.43959363, 2666.71731316, 8244.93015921, 64066.97917245, 5659.64353191, 8096.37529164, 12033.21329402, 9426.98291359, 7970.42927203, 7363.42630167, 10210.9171051, 10123.65438967, 12375.06637776, 2166.95198328, 5208.67582905, 50864.40967336, 4475.08787648, 6021.45702245, 8955.78121833, 7083.53201844, 5826.2426014, 5607.57243456, 7482.24368754, 8007.5488359, 9319.40497904, 1777.84933827, 3454.60950919, 40065.55149306, 3560.96431163, 4589.11563607, 6792.99381274, 5423.12538617, 4365.51418642, 4338.32817072, 5614.73235232, 6345.75307402, 7145.69934406, 1470.33540511, 2418.8836035, 31307.73515314, 2845.43282534, 3564.48272455, 5224.75352429, 4209.61939609, 3334.85684878, 3394.7411005, 4291.61246246, 5030.90434294, 5551.7065388, 1224.09417174, 1777.94444095, 24267.17495106, 2280.23388572, 2805.68249229, 4054.45003783, 3297.60694195, 2581.54745061, 2676.26120695, 3321.52031156, 3986.69345626, 4350.71923409, 1025.18440497, 1356.32994303, 18657.5487691, 1831.20547485, 2227.78364177, 3161.89931753, 2597.65596972, 2014.86968419, 2119.61484643, 2590.30302824, 3156.73320152, 3427.3549347, 863.39245871, 1061.07015569, 14228.35195555, 1474.17212763, 1779.15149641, 2472.35163662, 2053.19763088, 1580.13543691, 1683.8237283, 2028.78264776, 2498.09757285, 2708.50519593, 731.15469593, 843.73055212, 10763.00211039, 1189.87685907, 1427.26849489, 1935.55795388, 1627.26338936, 1243.04963259, 1341.24773248, 1593.3266169, 1977.37358084, 2145.78962876, 622.76300954, 674.43912428, 8076.21362858, 963.59096222, 1148.21226176, 1515.32401955, 1291.5882306, 978.88270451, 1070.53928188, 1252.26777421, 1566.63171203, 1702.61081183, 533.37543511, 251.5698372, 78.39379104, 358.47415645, 417.00189945, 343.02069764, 376.65623709, 250.7696025, 337.58336357, 309.90819185, 426.62429647, 491.88686897, 319.52639815, 229.98487559, 71.89618908, 328.81559374, 379.23122856, 314.58993245, 344.67429338, 230.09586346, 308.86333457, 284.27690779, 390.81066764, 450.29452515, 295.2112419, 209.90700457, 65.83006673, 301.03350393, 344.28036568, 288.01100355, 314.87861043, 210.63872951, 282.04028741, 260.27716481, 357.32462066, 411.58190806, 271.95913494, 191.24089004, 60.16315879, 275.19398596, 312.01200585, 263.15160416, 287.20935752, 192.27087149, 257.10220499, 237.7674599, 326.04483167, 375.64778375, 249.86568268, 174.00163583, 54.91963035, 251.39781986, 282.45956386, 239.97754648, 262.32217129, 175.64746532, 234.15281726, 216.790614, 297.24222327, 342.55725174, 229.20480266, 157.97930014, 50.00834128, 228.82355326, 255.08786962, 218.31775771, 238.56224728, 159.60037691, 212.65262252, 197.143627, 270.1406637, 311.7879358, 209.88174271, 142.06816695, 44.62227866, 194.64200464, 225.45728479, 196.6577881, 211.44851813, 144.28420851, 187.62027776, 178.04283821, 235.82148838, 277.92884196, 187.17201783, 128.50286229, 40.48386692, 176.59536866, 202.94699537, 178.14870904, 191.64947782, 130.3567742, 169.82085583, 161.2262527, 213.56886812, 252.33393292, 171.33467621, 115.97968904, 36.6514532, 160.03920683, 182.36403966, 160.9528592, 173.34424095, 117.4844956, 153.38889848, 145.6325365, 193.58360568, 228.76452604, 156.93064508, 104.5367301, 33.14180888, 144.86824598, 163.64659213, 145.29144047, 156.48584382, 105.68192257, 138.29738459, 131.34706006, 174.78395046, 207.21410655, 143.74953854, 93.78004697, 29.85541729, 130.96459527, 146.41630744, 130.44271923, 140.77860976, 94.56315502, 124.30264354, 117.86939815, 157.52979707, 187.07365571, 131.77364802, 83.88989526, 26.83225494, 118.25612431, 130.74987912, 116.81302281, 126.47480898, 84.25546865, 111.47877409, 105.47798006, 141.76481975, 168.4721351, 120.76505894, 53.33651065, 18.92707237, 90.87873209, 79.6252602, 70.72587975, 88.10930502, 56.14340574, 81.73028335, 67.25059365, 108.22846602, 122.63108916, 104.04441286, 47.89299261, 17.04829663, 82.32833411, 71.77798564, 63.44850361, 79.49550243, 50.00911405, 73.48232359, 60.24833615, 97.61230935, 110.70463824, 95.29567211, 42.62016718, 15.24165754, 74.07981395, 64.18600981, 56.41671636, 71.07795167, 44.13841524, 65.49956262, 53.50718816, 87.42893288, 99.13843079, 86.79343899, 37.63930125, 13.50499443, 66.00415373, 56.88053523, 49.76582747, 63.03246982, 38.69615, 57.87524503, 47.14539937, 77.45230119, 87.96494903, 78.399986, 31.1853418, 11.29303093, 55.36493619, 47.2706883, 41.30196979, 51.74911951, 32.59561219, 41.97616169, 39.52737486, 64.67513812, 73.81611274, 68.97088594, 27.14365405, 9.9098898, 48.7774765, 41.35906452, 35.91069989, 45.27866827, 28.1787518, 36.79180041, 34.33394868, 56.73278765, 64.52507646, 61.3176671, 19.83430962, 7.90841363, 42.34939547, 33.45681498, 26.36216842, 37.03140716, 15.50123753, 30.69105251, 24.83742341, 48.90373012, 52.71993102, 53.64481906, 16.9840777, 6.83904508, 36.4451199, 28.69636008, 22.53413541, 31.85997294, 13.27016619, 26.35966282, 21.23353756, 41.96530128, 44.97630582, 46.85019781, 12.52489332, 5.33894605, 28.87987917, 21.69552324, 16.69237271, 22.02426386, 9.88546812, 19.75697073, 15.85180637, 32.82566903, 34.86830872, 39.30545578, 10.45886259, 4.54442782, 24.22711755, 18.12732183, 13.89341549, 18.37893864, 8.25371099, 16.52726019, 13.19359021, 27.48097099, 29.44570412, 33.06075514, 8.68712475, 3.86152316, 20.1139183, 15.02404068, 11.49386578, 15.2253698, 6.85638761, 13.71400586, 10.92018433, 22.81607896, 24.27264158, 27.36216897, 7.12940923, 3.26307559, 16.49349628, 12.29788437, 9.38142982, 12.45906294, 5.62426025, 11.24493102, 8.91231921, 18.72438796, 19.72819811, 22.30258991, 5.78530548, 2.7378417, 13.28265627, 9.9044297, 7.55604279, 10.03047531, 4.56570193, 9.07023957, 7.18069457, 15.08212164, 15.75615258, 17.82878717, 3.90279567, 2.13526527, 10.3918536, 7.31432825, 4.87163927, 7.37716773, 2.88091081, 6.89338928, 4.25917639, 11.75406374, 11.69203026, 13.94809898, 3.1553064, 1.81360234, 8.1982707, 5.79772866, 3.88209629, 5.85214863, 2.33506104, 5.47610318, 3.39974927, 9.30363615, 9.11991572, 10.77377098, 2.29575275, 1.25578141, 4.68698103, 3.46967983, 2.66777067, 3.37918557, 1.78687785, 3.19908926, 2.35111776, 4.60022024, 5.79347897, 7.39332891, 1.86201737, 1.10037963, 3.57738702, 2.68639543, 2.10625688, 2.61367576, 1.45690498, 2.48395406, 1.86240214, 3.49115671, 4.36151747, 5.50304112, 1.53852206, 0.98419233, 2.74272547, 2.09949845, 1.68718945, 2.04068919, 1.21080987, 1.94788643, 1.49750673, 2.6578272, 3.28919419, 4.07658778, 1.29002229, 0.89491852, 2.10100997, 1.64845093, 1.36525283, 1.6003782, 1.02176751, 1.53588465, 1.21717387, 2.01720794, 2.46512986, 2.9795324, 1.11035304, 0.83058878, 1.64246223, 1.32439905, 1.13269452, 1.28358897, 0.88507894, 1.2400625, 1.0147902, 1.55873629, 1.87262042, 2.19905324, 0.96980216, 0.78082217, 1.29769371, 1.07620426, 0.95130186, 1.03978994, 0.7781303, 1.0139487, 0.85724529, 1.2121851, 1.41760455, 1.62117986, 0.8688926, 0.74521724, 1.05330301, 0.8992047, 0.8211894, 0.86565694, 0.7013411, 0.85280179, 0.74430917, 0.96609982, 1.09283358, 1.2136469, 0.77793159, 0.71478119, 0.87452515, 0.75544747, 0.70548894, 0.72065516, 0.63206142, 0.72332703, 0.64481489, 0.78030854, 0.82536338, 0.9435997, 0.72764363, 0.69715286, 0.75561953, 0.66833791, 0.64075793, 0.63470806, 0.59378953, 0.64411709, 0.58869393, 0.66017714, 0.665272, 0.7472702, 0.71020376, 0.68999224, 0.6881717, 0.62815848, 0.61730896, 0.59734875, 0.58055536, 0.60668185, 0.56777434, 0.59576755, 0.59379139, 0.61775141, 0.69419327, 0.6843913, 0.65060278, 0.60053434, 0.59671114, 0.57006817, 0.56837008, 0.58157271, 0.54992275, 0.55777034, 0.54299736, 0.55591928, 0.69021144, 0.68277945, 0.6357802, 0.59158012, 0.59137934, 0.56170463, 0.56534764, 0.57324493, 0.54517851, 0.54356521, 0.52702839, 0.52770007, 0.68705957, 0.68171206, 0.62926608, 0.58647736, 0.58735805, 0.55658819, 0.56294752, 0.56863713, 0.54171321, 0.53685027, 0.5175659, 0.51759431] + "value": [8636.30224795, 155936.93990395, 12011.96596703, 15295.14868644, 23102.70167274, 18229.27675968, 14352.86155177, 14479.65669762, 18646.92744785, 22454.39111746, 23887.84723066, 4028.078946, 10184.28212514, 185529.11984761, 14172.47632836, 17936.13903429, 27381.38100346, 21544.93457425, 17004.83036327, 17121.31800383, 22084.41870992, 26577.77087733, 28267.46467269, 4741.5797926, 11952.17606314, 219253.114273, 16630.1089258, 20937.29240277, 32256.68583966, 25320.05202259, 20027.10575859, 20129.03995886, 26001.36764494, 31271.70026537, 33255.42556039, 5552.65592794, 13955.6056408, 257341.20829135, 19401.73886665, 24319.8363721, 37763.70754004, 29581.52666076, 23441.80115575, 23524.10147787, 30426.32275374, 36568.48673295, 38887.31620486, 6466.82081545, 16208.09749425, 299960.46928865, 22499.66082129, 28099.80588592, 43928.79464392, 34349.49182746, 27265.7429802, 27322.32647562, 35380.95619975, 42491.57190958, 45189.74362344, 7488.07577066, 18720.95602151, 347194.6634563, 25930.35715484, 32286.77396965, 50767.35151318, 39635.6043326, 31509.18835152, 31532.67846077, 40878.38583159, 49053.1208686, 52178.05336774, 8618.50066467, 21503.82575595, 399026.3152042, 29693.33354285, 36882.84919445, 58281.99281196, 45441.59317335, 36174.83207442, 36156.02912912, 46921.85764577, 56251.70285423, 59854.38021661, 9857.86298425, 24566.24021847, 455319.86763589, 33780.11176185, 41882.11543324, 66461.31307113, 51758.26434309, 41257.28850233, 41184.24411046, 53504.02387713, 64070.22182534, 68206.28535355, 11203.27334648, 27920.33776406, 515807.00865826, 38173.4667761, 47270.65330723, 75279.48601183, 58565.12480856, 46743.19393281, 46599.70960148, 60607.00276992, 72474.2570369, 77206.19356935, 12648.91540429, 31584.70270515, 580075.27625095, 42846.9798902, 53027.20318097, 84696.80486293, 65830.71335863, 52611.98842061, 52375.37475016, 68203.29840601, 81410.95696292, 86811.7484444, 14185.87429369, 35589.02257338, 647561.0367114, 47764.95595012, 59124.42852408, 94661.141132, 73513.62807769, 58837.32612432, 58475.31987095, 76257.52237664, 90808.59907951, 96967.07876657, 15802.0814492, 39979.06292974, 717547.83629827, 52882.73035577, 65530.6614968, 105110.19512078, 81564.16348115, 65388.98013063, 64855.80862891, 84728.75522629, 100576.89568493, 107604.87132631, 17482.38734791, 44821.51272314, 789170.96134514, 58147.38480664, 72212.02065138, 115974.41726868, 89926.47364334, 72235.11736235, 71466.78195819, 93573.39684313, 110608.1094443, 118649.14847072, 19208.77116226, 50208.70997835, 861428.81262549, 63498.91358057, 79134.94542819, 127180.67679959, 98541.3207184, 79344.98877778, 78253.8421436, 102748.5648025, 120779.06008386, 130018.82944794, 20960.70120422, 56264.24416701, 933201.41985137, 68871.94608546, 86269.54101108, 138657.20595241, 107349.7970958, 86692.46103485, 85160.98768185, 112216.57209405, 130954.17049808, 141632.57122506, 22715.67546201, 63151.98714922, 1003276.10852455, 74198.24089716, 93594.68585846, 150341.07554825, 116298.93971727, 94261.42515615, 92134.70382706, 121951.77194068, 140989.82287205, 153416.05712844, 24449.99872213, 71093.09190484, 1070379.9996136, 79410.31096688, 101106.57273194, 162190.39521372, 125350.83889758, 102054.90094001, 99130.45880845, 131952.02978263, 150740.46082505, 165313.76950573, 26139.88903855, 80397.41786897, 1133218.67737625, 84446.67762335, 108833.04641251, 174204.33427535, 134497.50225559, 110110.41069584, 106123.08376915, 142258.01952543, 160067.03011769, 177308.11786916, 27763.0408128, 91516.57378127, 1190519.98172785, 89259.28682828, 116856.34857343, 186454.37434714, 143783.96257509, 118524.46692698, 113122.65888712, 152983.87643218, 168848.3773202, 189449.0820719, 29300.77931988, 105123.44013548, 1241081.41473096, 93823.39401065, 125345.98034299, 199129.00474162, 153341.23816969, 127488.04255384, 120196.9414629, 164361.52320989, 176995.91852487, 201896.40779937, 30740.87976752, 122215.37280431, 1283819.02177443, 98149.5378248, 134600.49419051, 212590.23210098, 163427.94241825, 137331.74339335, 127499.51087802, 176797.06736131, 184470.99946693, 214972.81154895, 32080.94120644, 144223.80097805, 1317814.77412565, 102295.94985692, 145091.56813817, 227433.05559864, 174474.05370955, 148573.47130664, 135299.31288242, 190930.28855619, 191302.73099342, 229219.92493856, 33331.86795193, 173092.781728, 1342358.51498703, 106377.98954306, 157496.13656384, 244529.04034428, 187113.0240222, 161953.13032987, 144002.44318108, 207677.98888777, 197601.83236752, 245439.37570581, 34520.54593308, 211268.85187485, 1356979.70720072, 110569.43938948, 172694.75624938, 265025.08477993, 202181.05883236, 178430.67287584, 154152.16174183, 228231.70986153, 203563.77463093, 264692.05520177, 35690.33772684, 261534.43221974, 1361464.01286972, 115089.6341488, 191710.61164687, 290263.49588134, 220658.7560816, 199119.67956756, 166390.72636115, 253975.21633225, 209453.42565142, 288223.9848933, 36897.79318066, 326629.21429456, 1355850.69679739, 120171.48537976, 215568.16806522, 321595.5840873, 243534.75742135, 225133.66826874, 181369.58868365, 286293.37197388, 215564.80905642, 317292.87958731, 38204.26197738, 408646.8017266, 1340409.34509005, 126009.22982449, 245066.62466961, 360082.34333978, 271586.6968933, 257339.86888431, 199604.82536807, 326265.85433012, 222154.43521848, 352889.40152614, 39662.09327263, 508265.45535708, 1315598.30320791, 132691.00633935, 280490.27210762, 406111.426174, 305100.82405353, 296044.48870993, 221291.91473204, 374275.59491181, 229354.72391519, 395380.30852723, 41296.77051093, 623955.79186001, 1282011.76125802, 140128.72753928, 321309.48643755, 459001.44722831, 343582.29365739, 340667.86027377, 246114.19396168, 429604.58567814, 237083.49041286, 444139.6645216, 43088.27988952, 751375.96827872, 1240326.16113321, 148003.64985833, 365951.58065429, 516698.35932361, 385532.79077734, 389495.56153304, 273095.63529987, 490124.15238161, 244973.09564846, 497265.68885811, 44956.58446524, 883184.04112145, 1191258.0220666, 155747.75171285, 411727.97430551, 575678.23035669, 428379.17808017, 439599.4622825, 300553.22182661, 552196.58060221, 252345.06903662, 551489.75521882, 46756.52995504, 1009445.41529883, 1135543.29291668, 162576.54630634, 454984.70303498, 631145.06632487, 468618.05673515, 487001.52310077, 326191.79908284, 610878.6994237, 258250.29299249, 602360.13388348, 48286.32441148, 1118689.97244472, 1073942.88206827, 167578.228913, 491496.93760958, 677551.0630133, 502196.29473934, 527102.79762966, 347354.67638721, 660455.36146463, 261581.11898768, 644726.01085818, 49310.89814352, 1199504.59545047, 1007271.28560214, 169849.35271305, 517064.69338882, 709382.74967733, 525086.15562216, 555331.09106462, 361402.69701799, 695244.92462442, 261242.96485424, 673469.15201735, 49597.56107849, 1242379.71565375, 936437.43668385, 168652.66675487, 528204.08285886, 722073.43763926, 533952.87499972, 567892.44802185, 366154.35867485, 710533.9005932, 256354.30211881, 684353.22181728, 48957.52371778, 1241417.62777063, 862481.58292299, 163563.07815068, 522786.76044761, 712847.25613944, 526772.17935458, 562466.32243246, 360292.91846169, 703441.55496414, 246431.55252886, 674809.41253664, 47284.28961611, 1195498.76482124, 786591.19647275, 154566.67321518, 500475.84641689, 681294.2846152, 503251.01446373, 538679.54331366, 343643.62091527, 673509.34764715, 231514.08789889, 644471.65846812, 44579.65491506, 1108605.29313377, 710083.38003511, 142086.70605369, 462845.38895329, 629527.52944554, 464942.25549123, 498236.32533315, 317248.9469553, 622862.51760951, 212195.99607243, 595322.43408524, 40960.42403422, 989198.27104549, 634349.99885267, 126927.58756444, 413144.42284573, 561870.29870534, 415015.74988447, 444661.9970406, 283217.04122862, 555891.16145919, 189553.18065852, 531401.23143827, 36643.47844908, 848780.93651663, 560772.34559435, 110148.458483, 355755.51051294, 484139.94000059, 357733.97370872, 382714.66179928, 244375.20585918, 478518.25183683, 164980.65978518, 458137.16541743, 31912.26697715, 699984.61731484, 490621.20977501, 92895.70101059, 295474.3084029, 402695.21001393, 297754.73217868, 317602.30504795, 203809.31010399, 397225.66570702, 139977.6394897, 381461.52020881, 27072.48777417, 554620.64430418, 424962.7858213, 76232.99364316, 236776.85990384, 323467.63111952, 239422.19002801, 254186.517557, 164395.60502341, 318063.59792705, 115929.7867815, 306905.48135134, 22407.16861532, 422118.69569217, 364589.40539297, 61005.51008729, 183232.65452046, 251185.71968309, 186199.10593945, 196344.59005256, 128425.88310316, 245857.02081869, 93935.52208767, 238877.61558914, 18140.82216578, 308632.14302383, 309987.2512321, 47762.62395586, 137168.88166087, 188931.43583733, 140342.25017699, 146604.57047343, 97393.30292322, 183751.73805396, 74707.49084038, 180250.8705148, 14419.10342849, 216887.65464456, 261343.50969479, 36745.81146317, 99614.86568731, 138067.1165454, 102849.02663, 106084.77343132, 71957.36540275, 133139.20255848, 58557.7102184, 132294.73590554, 11305.73416317, 146661.1105097, 218586.18553289, 27931.33721192, 70482.22746578, 98474.11916657, 73632.2533581, 74689.46165907, 52059.59234932, 93900.02931351, 45453.02372732, 94897.77966312, 8793.91521008, 95634.95106624, 181443.85650782, 21106.32178914, 48888.60031716, 68981.28947226, 51833.86339088, 51460.53203404, 37130.97771427, 64841.58514278, 35113.37847775, 66967.01845081, 6826.55539991, 60360.25301443, 149511.7295999, 15953.90685717, 33520.77114604, 47845.59817826, 36176.81091729, 34972.0678092, 26324.65039547, 44188.9072099, 27121.8599021, 46875.86031598, 5318.88131306, 37098.37779076, 122309.40521352, 12128.06936697, 22952.56265119, 33172.90003304, 25273.15571849, 23675.65937406, 18719.63126204, 30014.38417626, 21021.3507168, 32856.30272295, 4178.29867442, 22414.6234284, 99330.96802568, 9306.50112251, 15869.30780311, 23215.27186518, 17841.75762714, 16144.30855064, 13464.84660324, 20541.25084429, 16383.21610885, 23276.08138918, 3318.39946586, 13496.92057413, 80075.48452325, 7219.74285711, 11188.94066467, 16531.51980815, 12825.9215731, 11203.85855035, 9857.09330956, 14307.18527198, 12845.30556352, 16788.43959363, 2666.71731316, 8244.93015921, 64066.97917245, 5659.64353191, 8096.37529164, 12033.21329402, 9426.98291359, 7970.42927203, 7363.42630167, 10210.9171051, 10123.65438967, 12375.06637776, 2166.95198328, 5208.67582905, 50864.40967336, 4475.08787648, 6021.45702245, 8955.78121833, 7083.53201844, 5826.2426014, 5607.57243456, 7482.24368754, 8007.5488359, 9319.40497904, 1777.84933827, 3454.60950919, 40065.55149306, 3560.96431163, 4589.11563607, 6792.99381274, 5423.12538617, 4365.51418642, 4338.32817072, 5614.73235232, 6345.75307402, 7145.69934406, 1470.33540511, 2418.8836035, 31307.73515314, 2845.43282534, 3564.48272455, 5224.75352429, 4209.61939609, 3334.85684878, 3394.7411005, 4291.61246246, 5030.90434294, 5551.7065388, 1224.09417174, 1777.94444095, 24267.17495106, 2280.23388572, 2805.68249229, 4054.45003783, 3297.60694195, 2581.54745061, 2676.26120695, 3321.52031156, 3986.69345626, 4350.71923409, 1025.18440497, 1356.32994303, 18657.5487691, 1831.20547485, 2227.78364177, 3161.89931753, 2597.65596972, 2014.86968419, 2119.61484643, 2590.30302824, 3156.73320152, 3427.3549347, 863.39245871, 1061.07015569, 14228.35195555, 1474.17212763, 1779.15149641, 2472.35163662, 2053.19763088, 1580.13543691, 1683.8237283, 2028.78264776, 2498.09757285, 2708.50519593, 731.15469593, 843.73055212, 10763.00211039, 1189.87685907, 1427.26849489, 1935.55795388, 1627.26338936, 1243.04963259, 1341.24773248, 1593.3266169, 1977.37358084, 2145.78962876, 622.76300954, 674.43912428, 8076.21362858, 963.59096222, 1148.21226176, 1515.32401955, 1291.5882306, 978.88270451, 1070.53928188, 1252.26777421, 1566.63171203, 1702.61081183, 533.37543511, 251.5698372, 78.39379104, 358.47415645, 417.00189945, 343.02069764, 376.65623709, 250.7696025, 337.58336357, 309.90819185, 426.62429647, 491.88686897, 319.52639815, 229.98487559, 71.89618908, 328.81559374, 379.23122856, 314.58993245, 344.67429338, 230.09586346, 308.86333457, 284.27690779, 390.81066764, 450.29452515, 295.2112419, 209.90700457, 65.83006673, 301.03350393, 344.28036568, 288.01100355, 314.87861043, 210.63872951, 282.04028741, 260.27716481, 357.32462066, 411.58190806, 271.95913494, 191.24089004, 60.16315879, 275.19398596, 312.01200585, 263.15160416, 287.20935752, 192.27087149, 257.10220499, 237.7674599, 326.04483167, 375.64778375, 249.86568268, 174.00163583, 54.91963035, 251.39781986, 282.45956386, 239.97754648, 262.32217129, 175.64746532, 234.15281726, 216.790614, 297.24222327, 342.55725174, 229.20480266, 157.97930014, 50.00834128, 228.82355326, 255.08786962, 218.31775771, 238.56224728, 159.60037691, 212.65262252, 197.143627, 270.1406637, 311.7879358, 209.88174271, 142.06816695, 44.62227866, 194.64200464, 225.45728479, 196.6577881, 211.44851813, 144.28420851, 187.62027776, 178.04283821, 235.82148838, 277.92884196, 187.17201783, 128.50286229, 40.48386692, 176.59536866, 202.94699537, 178.14870904, 191.64947782, 130.3567742, 169.82085583, 161.2262527, 213.56886812, 252.33393292, 171.33467621, 115.97968904, 36.6514532, 160.03920683, 182.36403966, 160.9528592, 173.34424095, 117.4844956, 153.38889848, 145.6325365, 193.58360568, 228.76452604, 156.93064508, 104.5367301, 33.14180888, 144.86824598, 163.64659213, 145.29144047, 156.48584382, 105.68192257, 138.29738459, 131.34706006, 174.78395046, 207.21410655, 143.74953854, 93.78004697, 29.85541729, 130.96459527, 146.41630744, 130.44271923, 140.77860976, 94.56315502, 124.30264354, 117.86939815, 157.52979707, 187.07365571, 131.77364802, 83.88989526, 26.83225494, 118.25612431, 130.74987912, 116.81302281, 126.47480898, 84.25546865, 111.47877409, 105.47798006, 141.76481975, 168.4721351, 120.76505894, 53.33651065, 18.92707237, 90.87873209, 79.6252602, 70.72587975, 88.10930502, 56.14340574, 81.73028335, 67.25059365, 108.22846602, 122.63108916, 104.04441286, 47.89299261, 17.04829663, 82.32833411, 71.77798564, 63.44850361, 79.49550243, 50.00911405, 73.48232359, 60.24833615, 97.61230935, 110.70463824, 95.29567211, 42.62016718, 15.24165754, 74.07981395, 64.18600981, 56.41671636, 71.07795167, 44.13841524, 65.49956262, 53.50718816, 87.42893288, 99.13843079, 86.79343899, 37.63930125, 13.50499443, 66.00415373, 56.88053523, 49.76582747, 63.03246982, 38.69615, 57.87524503, 47.14539937, 77.45230119, 87.96494903, 78.399986, 31.1853418, 11.29303093, 55.36493619, 47.2706883, 41.30196979, 51.74911951, 32.59561219, 41.97616169, 39.52737486, 64.67513812, 73.81611274, 68.97088594, 27.14365405, 9.9098898, 48.7774765, 41.35906452, 35.91069989, 45.27866827, 28.1787518, 36.79180041, 34.33394868, 56.73278765, 64.52507646, 61.3176671, 19.83430962, 7.90841363, 42.34939547, 33.45681498, 26.36216842, 37.03140716, 15.50123753, 30.69105251, 24.83742341, 48.90373012, 52.71993102, 53.64481906, 16.9840777, 6.83904508, 36.4451199, 28.69636008, 22.53413541, 31.85997294, 13.27016619, 26.35966282, 21.23353756, 41.96530128, 44.97630582, 46.85019781, 12.52489332, 5.33894605, 28.87987917, 21.69552324, 16.69237271, 22.02426386, 9.88546812, 19.75697073, 15.85180637, 32.82566903, 34.86830872, 39.30545578, 10.45886259, 4.54442782, 24.22711755, 18.12732183, 13.89341549, 18.37893864, 8.25371099, 16.52726019, 13.19359021, 27.48097099, 29.44570412, 33.06075514, 8.68712475, 3.86152316, 20.1139183, 15.02404068, 11.49386578, 15.2253698, 6.85638761, 13.71400586, 10.92018433, 22.81607896, 24.27264158, 27.36216897, 7.12940923, 3.26307559, 16.49349628, 12.29788437, 9.38142982, 12.45906294, 5.62426025, 11.24493102, 8.91231921, 18.72438796, 19.72819811, 22.30258991, 5.78530548, 2.7378417, 13.28265627, 9.9044297, 7.55604279, 10.03047531, 4.56570193, 9.07023957, 7.18069457, 15.08212164, 15.75615258, 17.82878717, 3.90279567, 2.13526527, 10.3918536, 7.31432825, 4.87163927, 7.37716773, 2.88091081, 6.89338928, 4.25917639, 11.75406374, 11.69203026, 13.94809898, 3.1553064, 1.81360234, 8.1982707, 5.79772866, 3.88209629, 5.85214863, 2.33506104, 5.47610318, 3.39974927, 9.30363615, 9.11991572, 10.77377098, 2.29575275, 1.25578141, 4.68698103, 3.46967983, 2.66777067, 3.37918557, 1.78687785, 3.19908926, 2.35111776, 4.60022024, 5.79347897, 7.39332891, 1.86201737, 1.10037963, 3.57738702, 2.68639543, 2.10625688, 2.61367576, 1.45690498, 2.48395406, 1.86240214, 3.49115671, 4.36151747, 5.50304112, 1.53852206, 0.98419233, 2.74272547, 2.09949845, 1.68718945, 2.04068919, 1.21080987, 1.94788643, 1.49750673, 2.6578272, 3.28919419, 4.07658778, 1.29002229, 0.89491852, 2.10100997, 1.64845093, 1.36525283, 1.6003782, 1.02176751, 1.53588465, 1.21717387, 2.01720794, 2.46512986, 2.9795324, 1.11035304, 0.83058878, 1.64246223, 1.32439905, 1.13269452, 1.28358897, 0.88507894, 1.2400625, 1.0147902, 1.55873629, 1.87262042, 2.19905324, 0.96980216, 0.78082217, 1.29769371, 1.07620426, 0.95130186, 1.03978994, 0.7781303, 1.0139487, 0.85724529, 1.2121851, 1.41760455, 1.62117986, 0.8688926, 0.74521724, 1.05330301, 0.8992047, 0.8211894, 0.86565694, 0.7013411, 0.85280179, 0.74430917, 0.96609982, 1.09283358, 1.2136469, 0.77793159, 0.71478119, 0.87452515, 0.75544747, 0.70548894, 0.72065516, 0.63206142, 0.72332703, 0.64481489, 0.78030854, 0.82536338, 0.9435997, 0.72764363, 0.69715286, 0.75561953, 0.66833791, 0.64075793, 0.63470806, 0.59378953, 0.64411709, 0.58869393, 0.66017714, 0.665272, 0.7472702, 0.71020376, 0.68999224, 0.6881717, 0.62815848, 0.61730896, 0.59734875, 0.58055536, 0.60668185, 0.56777434, 0.59576755, 0.59379139, 0.61775141, 0.69419327, 0.6843913, 0.65060278, 0.60053434, 0.59671114, 0.57006817, 0.56837008, 0.58157271, 0.54992275, 0.55777034, 0.54299736, 0.55591928, 0.69021144, 0.68277945, 0.6357802, 0.59158012, 0.59137934, 0.56170463, 0.56534764, 0.57324493, 0.54517851, 0.54356521, 0.52702839, 0.52770007, 0.68705957, 0.68171206, 0.62926608, 0.58647736, 0.58735805, 0.55658819, 0.56294752, 0.56863713, 0.54171321, 0.53685027, 0.5175659, 0.51759431] } # getEReproAndGrowth @@ -269,7 +269,7 @@ ] } }, - "value": [0, 0.14003569, 0, 0.16166917, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.15516856, 0, 0.18171766, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.17074229, 0, 0.20425055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.18592246, 0, 0.22957537, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.19931068, 0, 0.25803764, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.20878045, 0, 0.29002552, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.21147359, 0, 0.32597508, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.20420514, 0, 0.36637613, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.18447594, 0, 0.4117788, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.15185233, 0, 0.46280092, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.10878655, 0, 0.52013618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.05993608, 0, 0.58456336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0102479, 0, 0.65695649, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.73829629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.82968277, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.93234916, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.04767728, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.17721437, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.32269134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.48604242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.6694259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.87524576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.10617333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.36516804, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.65549556, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.9807409, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.34481256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.75193245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.2066034, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.71354288, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5.27756672, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5.9034, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6.5953844, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7.35704041, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.19043179, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9.09526805, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10.06767486, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.09856898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12.171609, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.26077642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14.32779915, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15.31987889, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.16851041, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.7904892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.09228898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.97855166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.36423489, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15.18817278, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.42424084, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.08601057, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.22269342, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.90710338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.21936681, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.89157854, 0, 0, 5.33227104, 0, 0, 0, 0, 0, 0, 0, 0, 10.64607442, 0, 0, 15.88911261, 0, 0, 0, 0, 0, 0, 0, 0, 23.16351162, 0, 0, 30.46999669, 0, 0, 0, 0, 0, 0, 0, 0, 41.08667202, 0, 0, 50.60735737, 0, 0, 0, 0, 0, 0, 0, 0, 59.76469708, 0, 0, 71.34568062, 0, 0, 0, 0, 2.72897895, 0, 0, 0, 84.28402189, 0, 1.73953588, 98.1293925, 0, 0, 1.45013581, 0, 31.81585224, 2.92502179, 0, 0, 112.9487484, 0, 18.78095022, 129.38881615, 0, 0, 10.14853139, 0, 75.24893094, 32.17985068, 0, 0, 142.43918408, 0, 41.40894665, 160.57855829, 0, 0, 22.04144061, 0, 133.90652196, 72.527281, 0, 0, 174.45105631, 0, 70.65590108, 193.97961867, 0, 7.52959139, 37.29462136, 16.09860612, 208.74236874, 126.93900325, 0, 0, 210.26315192, 0, 111.04713613, 231.32066013, 10.8116472, 26.23863481, 57.94531953, 51.49606665, 303.1305361, 200.78963382, 0, 0, 246.93128067, 0, 158.89843483, 270.33693602, 33.67404992, 50.89827669, 82.86930774, 100.73401017, 412.70482579, 289.92944402, 0, 0, 286.1861729, 0, 216.8266646, 312.31587875, 64.32124384, 82.40707287, 112.45582179, 163.34991559, 537.02072415, 393.92626312, 0, 0, 330.48757785, 0, 283.95797189, 358.72848067, 108.01319779, 125.10750686, 146.72304227, 250.60423449, 675.30724861, 512.80373286, 0, 0, 376.81882728, 0, 352.66928647, 408.61157368, 161.36088507, 172.13096674, 182.21381725, 345.28842649, 820.71610967, 638.1068284, 0, 0, 427.89778101, 0, 425.97617302, 463.35350823, 224.55125776, 224.08699529, 220.0543426, 450.97599813, 975.40727828, 772.76990541, 0, 0, 484.12753242, 0, 502.35755052, 523.83370516, 290.71736118, 280.31182168, 259.45377328, 564.78897662, 1139.62035544, 914.68758077, 0, 0, 546.42103787, 0, 580.48363958, 590.78104501, 357.30464215, 338.71481931, 300.45332001, 682.89222562, 1313.65843626, 1065.7874405, 0, 0, 616.21772789, 0, 669.05965294, 665.89636417, 435.48694021, 404.15246149, 346.17859943, 814.54103024, 1505.28397117, 1229.57382453, 0, 0, 693.62492165, 0, 762.8089542, 749.3805164, 515.07365487, 471.14896665, 394.87574029, 947.9277658, 1712.89543074, 1405.22273795, 0, 0, 780.29753854, 0, 865.11674356, 842.78940728, 598.54433713, 542.90973693, 448.08349178, 1092.32129674, 1940.66702465, 1597.24370446, 0, 0, 877.17346316, 36.19313244, 976.89414551, 947.28489764, 684.62184657, 618.24187536, 506.31333724, 1244.62301033, 2191.5209098, 1808.27233008, 0, 0, 985.87964101, 131.5057724, 1102.30672345, 1064.68951473, 781.26829957, 702.45416498, 571.47212027, 1414.33581268, 2471.44445098, 2042.45580606, 0, 0, 1107.86145291, 269.34561657, 1242.4756503, 1196.44079673, 887.58184903, 795.50850049, 644.19682744, 1603.25858958, 2784.0154993, 2303.1615114, 0, 0, 1244.71462622, 452.13742121, 1398.86298314, 1344.298982, 1004.75563401, 898.17021871, 725.38290044, 1811.44702749, 3133.52929074, 2594.11166139, 0, 0, 1398.32777231, 677.13155035, 1573.55055133, 1510.28179567, 1134.39941634, 1012.73740821, 816.08285576, 2043.04089731, 3524.72330422, 2919.21690481, 0, 0, 1570.77179603, 944.99128743, 1769.2363023, 1696.62085958, 1279.18804836, 1140.42964953, 917.72186802, 2301.51830614, 3963.22709718, 3283.56524759, 0, 0, 1764.34524695, 1248.36968319, 1988.63948351, 1905.84448476, 1440.6755152, 1283.10246786, 1031.64158121, 2590.24003929, 4455.17350563, 3691.80716137, 0, 0, 1981.66180949, 1551.74813793, 2234.53535688, 2140.76115079, 1620.87668453, 1442.69049267, 1159.3754769, 2913.00671953, 5006.9847039, 4149.57199958, 0, 0, 2225.69806678, 1897.65234821, 2510.52209931, 2404.57412253, 1822.95142781, 1621.73753009, 1302.73135102, 3275.09793172, 5626.49724008, 4663.35999521, 0, 0, 2499.69798267, 2251.56392951, 2820.1024097, 2700.82325558, 2049.1135158, 1822.43512666, 1463.56644888, 3680.9242559, 6321.98361031, 5240.0331974, 0, 0, 2807.3515777, 2627.06772053, 3167.61605127, 3033.49596466, 2302.74084575, 2047.59379224, 1644.1277031, 4136.10595823, 7102.94019086, 5887.45200897, 0, 0, 3152.81406524, 3045.75958887, 3557.83051986, 3407.08575784, 2587.56512373, 2300.28552457, 1846.88116918, 4647.17071412, 7980.01705069, 6614.48781257, 0, 0, 3540.7257535, 3498.38726782, 3995.81964936, 3826.61704476, 2907.01922359, 2583.93103437, 2074.50227096, 5220.92032141, 8965.0329159, 7430.95638696, 0, 0, 3976.27728615, 3985.53528188, 4487.55146119, 4297.7307427, 3265.57426822, 2902.26124713, 2330.08671538, 5864.7793038, 10071.30134984, 8347.83987605, 0, 0, 4465.33746517, 4519.24268522, 5039.61605805, 4826.77086344, 3668.001855, 3259.6538616, 2617.06978242, 6587.80004836, 11313.79395771, 9377.58157612, 0, 0, 5014.46369256, 5105.39911385, 5659.44394042, 5420.85067548, 4119.81999661, 3660.87672094, 2939.32333478, 7399.51367231, 12709.29529344, 10534.07023182, 0, 0, 5631.03051187, 5757.01516838, 6355.35333491, 6087.96110559, 4627.09017369, 4111.35261253, 3301.18127524, 8310.97800787, 14276.66211727, 11832.9392501, 0, 0, 6323.31119468, 6483.66570829, 7136.69488875, 6837.06985089, 5196.65039833, 4617.13331447, 3707.51834889, 9334.46561357, 16037.06643568, 13291.72023901, 0, 0, 7100.59197006, 7296.66158165, 8013.95160166, 7678.2445856, 5836.16290806, 5185.01573576, 4163.79950851, 10483.7730389, 18014.27497987, 14930.08724571, 0, 0, 7973.29441111, 8204.91404334, 8998.88169344, 8622.78547218, 6554.22987315, 5822.61856296, 4676.15770191, 11774.32841359, 20234.96574481, 16770.1516867, 0, 0, 8953.11869064, 9223.10395164, 10104.69199576, 9683.37989111, 7360.50280984, 6538.50655607, 5251.47947814, 13223.51452947, 22729.10415152, 18836.72336974, 0, 0, 10053.19587965, 10364.07564918, 11346.19836256, 10874.26822339, 8265.80843991, 7342.2797065, 5897.49430768, 14850.81189635, 25530.33019577, 21157.66002316, 0, 0, 11288.26459343, 11644.12162411, 12740.03202202, 12211.4369947, 9282.30280673, 8244.71505658, 6622.87930465, 16678.1001209, 28676.41664164, 23764.24805033, 0, 0, 12674.86633239, 13080.19650301, 14304.86164409, 13712.83079386, 10423.63592967, 9257.91630265, 7437.37557617, 18729.9351693, 32209.77749776, 26691.60797778, 0, 0, 14231.56643934, 14691.81859381, 16061.63569265, 15398.59222995, 11705.11773876, 10395.46058897, 8351.91632534, 21033.87550703, 36178.0356839, 29979.16855373, 0, 0, 15979.20166302, 16500.58524014, 18033.86437564, 17291.33096024, 13143.93727431, 11672.59009804, 9378.77382239, 23620.86431671, 40634.66113185, 33671.20582331] + "value": [0, 0.14003569, 0, 0.16166917, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.15516856, 0, 0.18171766, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.17074229, 0, 0.20425055, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.18592246, 0, 0.22957537, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.19931068, 0, 0.25803764, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.20878045, 0, 0.29002552, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.21147359, 0, 0.32597508, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.20420514, 0, 0.36637613, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.18447594, 0, 0.4117788, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.15185233, 0, 0.46280092, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.10878655, 0, 0.52013618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.05993608, 0, 0.58456336, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0102479, 0, 0.65695649, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.73829629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.82968277, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.93234916, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.04767728, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.17721437, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.32269134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.48604242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.6694259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.87524576, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.10617333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.36516804, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.65549556, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.9807409, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.34481256, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.75193245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.2066034, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.71354288, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5.27756672, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5.9034, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6.5953844, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7.35704041, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.19043179, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9.09526805, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10.06767486, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.09856898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12.171609, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.26077642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14.32779915, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15.31987889, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.16851041, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.7904892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17.09228898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.97855166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16.36423489, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15.18817278, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13.42424084, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11.08601057, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8.22269342, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.90710338, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.21936681, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.89157854, 0, 0, 5.33227104, 0, 0, 0, 0, 0, 0, 0, 0, 10.64607442, 0, 0, 15.88911261, 0, 0, 0, 0, 0, 0, 0, 0, 23.16351162, 0, 0, 30.46999669, 0, 0, 0, 0, 0, 0, 0, 0, 41.08667202, 0, 0, 50.60735737, 0, 0, 0, 0, 0, 0, 0, 0, 59.76469708, 0, 0, 71.34568062, 0, 0, 0, 0, 2.72897895, 0, 0, 0, 84.28402189, 0, 1.73953588, 98.1293925, 0, 0, 1.45013581, 0, 31.81585224, 2.92502179, 0, 0, 112.9487484, 0, 18.78095022, 129.38881615, 0, 0, 10.14853139, 0, 75.24893094, 32.17985068, 0, 0, 142.43918408, 0, 41.40894665, 160.57855829, 0, 0, 22.04144061, 0, 133.90652196, 72.527281, 0, 0, 174.45105631, 0, 70.65590108, 193.97961867, 0, 7.52959139, 37.29462136, 16.09860612, 208.74236874, 126.93900325, 0, 0, 210.26315192, 0, 111.04713613, 231.32066013, 10.8116472, 26.23863481, 57.94531953, 51.49606665, 303.1305361, 200.78963382, 0, 0, 246.93128067, 0, 158.89843483, 270.33693602, 33.67404992, 50.89827669, 82.86930774, 100.73401017, 412.70482579, 289.92944402, 0, 0, 286.1861729, 0, 216.8266646, 312.31587875, 64.32124384, 82.40707287, 112.45582179, 163.34991559, 537.02072415, 393.92626312, 0, 0, 330.48757785, 0, 283.95797189, 358.72848067, 108.01319779, 125.10750686, 146.72304227, 250.60423449, 675.30724861, 512.80373286, 0, 0, 376.81882728, 0, 352.66928647, 408.61157368, 161.36088507, 172.13096674, 182.21381725, 345.28842649, 820.71610967, 638.1068284, 0, 0, 427.89778101, 0, 425.97617302, 463.35350823, 224.55125776, 224.08699529, 220.0543426, 450.97599813, 975.40727828, 772.76990541, 0, 0, 484.12753242, 0, 502.35755052, 523.83370516, 290.71736118, 280.31182168, 259.45377328, 564.78897662, 1139.62035544, 914.68758077, 0, 0, 546.42103787, 0, 580.48363958, 590.78104501, 357.30464215, 338.71481931, 300.45332001, 682.89222562, 1313.65843626, 1065.7874405, 0, 0, 616.21772789, 0, 669.05965294, 665.89636417, 435.48694021, 404.15246149, 346.17859943, 814.54103024, 1505.28397117, 1229.57382453, 0, 0, 693.62492165, 0, 762.8089542, 749.3805164, 515.07365487, 471.14896665, 394.87574029, 947.9277658, 1712.89543074, 1405.22273795, 0, 0, 780.29753854, 0, 865.11674356, 842.78940728, 598.54433713, 542.90973693, 448.08349178, 1092.32129674, 1940.66702465, 1597.24370446, 0, 0, 877.17346316, 36.19313244, 976.89414551, 947.28489764, 684.62184657, 618.24187536, 506.31333724, 1244.62301033, 2191.5209098, 1808.27233008, 0, 0, 985.87964101, 131.5057724, 1102.30672345, 1064.68951473, 781.26829957, 702.45416498, 571.47212027, 1414.33581268, 2471.44445098, 2042.45580606, 0, 0, 1107.86145291, 269.34561657, 1242.4756503, 1196.44079673, 887.58184903, 795.50850049, 644.19682744, 1603.25858958, 2784.0154993, 2303.1615114, 0, 0, 1244.71462622, 452.13742121, 1398.86298314, 1344.298982, 1004.75563401, 898.17021871, 725.38290044, 1811.44702749, 3133.52929074, 2594.11166139, 0, 0, 1398.32777231, 677.13155035, 1573.55055133, 1510.28179567, 1134.39941634, 1012.73740821, 816.08285576, 2043.04089731, 3524.72330422, 2919.21690481, 0, 0, 1570.77179603, 944.99128743, 1769.2363023, 1696.62085958, 1279.18804836, 1140.42964953, 917.72186802, 2301.51830614, 3963.22709718, 3283.56524759, 0, 0, 1764.34524695, 1248.36968319, 1988.63948351, 1905.84448476, 1440.6755152, 1283.10246786, 1031.64158121, 2590.24003929, 4455.17350563, 3691.80716137, 0, 0, 1981.66180949, 1551.74813793, 2234.53535688, 2140.76115079, 1620.87668453, 1442.69049267, 1159.3754769, 2913.00671953, 5006.9847039, 4149.57199959, 0, 0, 2225.69806678, 1897.65234821, 2510.52209931, 2404.57412253, 1822.95142781, 1621.73753009, 1302.73135102, 3275.09793172, 5626.49724008, 4663.35999521, 0, 0, 2499.69798267, 2251.56392951, 2820.1024097, 2700.82325558, 2049.1135158, 1822.43512666, 1463.56644888, 3680.9242559, 6321.98361031, 5240.0331974, 0, 0, 2807.3515777, 2627.06772053, 3167.61605127, 3033.49596466, 2302.74084575, 2047.59379224, 1644.1277031, 4136.10595823, 7102.94019086, 5887.45200897, 0, 0, 3152.81406524, 3045.75958887, 3557.83051986, 3407.08575784, 2587.56512373, 2300.28552457, 1846.88116918, 4647.17071412, 7980.01705069, 6614.48781257, 0, 0, 3540.7257535, 3498.38726782, 3995.81964936, 3826.61704476, 2907.01922359, 2583.93103437, 2074.50227096, 5220.92032141, 8965.0329159, 7430.95638696, 0, 0, 3976.27728615, 3985.53528188, 4487.55146119, 4297.7307427, 3265.57426822, 2902.26124713, 2330.08671538, 5864.7793038, 10071.30134984, 8347.83987605, 0, 0, 4465.33746517, 4519.24268522, 5039.61605805, 4826.77086344, 3668.001855, 3259.6538616, 2617.06978242, 6587.80004836, 11313.79395771, 9377.58157612, 0, 0, 5014.46369256, 5105.39911385, 5659.44394042, 5420.85067548, 4119.81999661, 3660.87672094, 2939.32333478, 7399.51367231, 12709.29529344, 10534.07023182, 0, 0, 5631.03051187, 5757.01516838, 6355.35333491, 6087.96110559, 4627.09017369, 4111.35261253, 3301.18127524, 8310.97800787, 14276.66211727, 11832.9392501, 0, 0, 6323.31119468, 6483.66570829, 7136.69488875, 6837.06985089, 5196.65039833, 4617.13331447, 3707.51834889, 9334.46561357, 16037.06643568, 13291.72023901, 0, 0, 7100.59197006, 7296.66158165, 8013.95160166, 7678.2445856, 5836.16290806, 5185.01573576, 4163.79950851, 10483.7730389, 18014.27497987, 14930.08724571, 0, 0, 7973.29441111, 8204.91404334, 8998.88169344, 8622.78547218, 6554.22987315, 5822.61856296, 4676.15770191, 11774.32841359, 20234.96574481, 16770.1516867, 0, 0, 8953.11869064, 9223.10395164, 10104.69199576, 9683.37989111, 7360.50280984, 6538.50655607, 5251.47947814, 13223.51452947, 22729.10415152, 18836.72336974, 0, 0, 10053.19587965, 10364.07564918, 11346.19836256, 10874.26822339, 8265.80843991, 7342.2797065, 5897.49430768, 14850.81189635, 25530.33019577, 21157.66002316, 0, 0, 11288.26459343, 11644.12162411, 12740.03202202, 12211.4369947, 9282.30280673, 8244.71505658, 6622.87930465, 16678.1001209, 28676.41664164, 23764.24805033, 0, 0, 12674.86633239, 13080.19650301, 14304.86164409, 13712.83079386, 10423.63592967, 9257.91630265, 7437.37557617, 18729.9351693, 32209.77749776, 26691.60797778, 0, 0, 14231.56643934, 14691.81859381, 16061.63569265, 15398.59222995, 11705.11773876, 10395.46058897, 8351.91632534, 21033.87550703, 36178.0356839, 29979.16855373, 0, 0, 15979.20166302, 16500.58524014, 18033.86437564, 17291.33096024, 13143.93727431, 11672.59009804, 9378.77382239, 23620.86431671, 40634.66113185, 33671.20582331] } # getERepro @@ -319,7 +319,7 @@ "value": ["Sprat", "Sandeel", "N.pout", "Herring", "Dab", "Whiting", "Sole", "Gurnard", "Plaice", "Haddock", "Cod", "Saithe"] } }, - "value": [0, 0, 2.2628242448321072e+20, 2.6338839730380059e+20, 2.0549728269823954e+20, 2.9011169434526591e+20, 1.19069463112741e+20, 8.1145572432839524e+19, 9.2213694306277999e+19, 2.4998211566208795e+20, 2.544531472613532e+20, 2.8163976453107129e+20] + "value": [0, 0, 2.262824244832119e+20, 2.6338839730380082e+20, 2.0549728269823915e+20, 2.9011169434526738e+20, 1.1906946311274134e+20, 8.1145572432839524e+19, 9.2213694306277605e+19, 2.4998211566208716e+20, 2.5445314726135382e+20, 2.8163976453107279e+20] } # getRDD @@ -333,7 +333,7 @@ "value": ["Sprat", "Sandeel", "N.pout", "Herring", "Dab", "Whiting", "Sole", "Gurnard", "Plaice", "Haddock", "Cod", "Saithe"] } }, - "value": [0, 0, 10499999512777.029, 1109999995322.1174, 11199999999.38958, 547999998964.86768, 38699999987.421707, 1649999966449.1868, 407998194809732.62, 1839999986456.6311, 8259999999.7318649, 111999999955.46085] + "value": [0, 0, 10499999512777.031, 1109999995322.1174, 11199999999.389578, 547999998964.86768, 38699999987.421715, 1649999966449.1868, 407998194809732.62, 1839999986456.6313, 8259999999.7318659, 111999999955.46085] } # getEGrowth is working From 16de5213a063d7c869728b8e49dfaaa088797df5 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 14:34:17 +0000 Subject: [PATCH 09/47] Keep fast projection code if no diffusion --- R/project.R | 12 ++++++++++-- R/project_n.R | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/R/project.R b/R/project.R index 1d6ca6117..f75ebfe5d 100644 --- a/R/project.R +++ b/R/project.R @@ -466,6 +466,9 @@ project_simple.MizerParams <- c <- matrix(0, nrow = no_sp, ncol = no_w) S <- matrix(0, nrow = no_sp, ncol = no_w) + # Do we have diffusion? + has_diffusion <- any(params@diffusion > 0) + # Loop over time steps ---- for (i_time in 1:steps) { r <- rates_fns$Rates( @@ -502,8 +505,13 @@ project_simple.MizerParams <- ) # * Update species ---- - n <- project_n(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, - no_sp, no_w) + if (has_diffusion) { + n <- project_n(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, + no_sp, no_w) + } else { + n <- project_n_no_diffusion(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, + no_sp, no_w) + } # * Update time ---- t <- t + dt diff --git a/R/project_n.R b/R/project_n.R index cd03dd895..b7ed5a5b8 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -86,5 +86,35 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, ) } + n +} + +project_n_no_diffusion <- function(params, r, n, dt, a, b, S, idx, w_min_idx_array_ref, + no_sp, no_w) { + # a_{ij} = - g_i(w_{j-1}) / dw_j dt + a[, idx] <- sweep( + -r$e_growth[, idx - 1, drop = FALSE] * dt, 2, + params@dw[idx], "/" + ) + # b_{ij} = 1 + g_i(w_j) / dw_j dt + \mu_i(w_j) dt + b[] <- 1 + sweep(r$e_growth * dt, 2, params@dw, "/") + r$mort * dt + # S_{ij} <- N_i(w_j) + S[, idx] <- n[, idx, drop = FALSE] + # Update first size group of n + n[w_min_idx_array_ref] <- + (n[w_min_idx_array_ref] + r$rdd * dt / + params@dw[params@w_min_idx]) / + b[w_min_idx_array_ref] + # Update n + # for (i in 1:no_sp) # number of species assumed small, so no need to + # vectorize this loop over species + # for (j in (params@w_min_idx[i]+1):no_w) + # n[i,j] <- (S[i,j] - A[i,j]*n[i,j-1]) / B[i,j] + # This is implemented via Rcpp + n <- inner_project_loop( + no_sp = no_sp, no_w = no_w, n = n, + A = a, B = b, S = S, + w_min_idx = params@w_min_idx + ) n } \ No newline at end of file From 5e7dab88be74890cc956bc3471dd2305bf3f90c9 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 15:29:05 +0000 Subject: [PATCH 10/47] feat: Add new vignette demonstrating cohort dynamics and the effect of diffusion in a single-species size-spectrum model. --- pkgdown/_pkgdown.yml | 1 + vignettes/cohort_dynamics_and_diffusion.Rmd | 315 ++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 vignettes/cohort_dynamics_and_diffusion.Rmd diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index b86a01b36..0788d4433 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -72,6 +72,7 @@ articles: - exploring_the_simulation_results - a_multispecies_model_of_the_north_sea - plotting + - cohort_dynamics_and_diffusion - title: Publications navbar: Publications contents: diff --git a/vignettes/cohort_dynamics_and_diffusion.Rmd b/vignettes/cohort_dynamics_and_diffusion.Rmd new file mode 100644 index 000000000..aa81e488f --- /dev/null +++ b/vignettes/cohort_dynamics_and_diffusion.Rmd @@ -0,0 +1,315 @@ +--- +title: "Cohort dynamics and diffusion" +output: + html_document: + toc: yes + fig_width: 7 + fig_height: 5 +editor_options: + markdown: + wrap: 72 +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE) +``` + +# Introduction + +In this vignette we explore how yearly cohorts of fish evolve over time +in a single-species size-spectrum model. We will drive the model with a +short burst of reproductive flux once a year, creating distinct cohorts. +We will then visualise how these cohorts grow through the size spectrum +and how the diffusion rate affects the spreading of the cohorts over +time. + +Diffusion in the size-spectrum model represents individual variability +in growth rates. Without diffusion, all individuals born at the same +time grow at the same deterministic rate and remain together as a sharp +cohort. With diffusion, individuals spread out in size, causing the +cohort to broaden as it ages. + +```{r} +library(mizer) +library(ggplot2) +``` + +# Setting up the model + +We start by creating a single-species model using +`newSingleSpeciesParams()`. This sets up a species embedded in a +power-law background community. + +```{r} +params <- newSingleSpeciesParams() +params <- steady(params) +``` + +# Pulsed reproduction + +To create distinct yearly cohorts, we need reproduction to happen in +short bursts rather than continuously. We achieve this by writing a +custom density-dependent reproduction rate function (RDD function) that +only allows reproduction during a brief window at the start of each +year. + +First, we calculate the steady-state reproduction rate. This tells us +the total egg production rate needed to maintain the population. We will +use this value as the magnitude of our annual pulse. + +```{r} +rdd_steady <- getRDD(params) +cat("Steady-state RDD:", rdd_steady, "\n") +``` + +The RDD function receives the current time `t` as an argument. We use +this to turn reproduction on only during a short window at the start of +each year and off at all other times. To maintain the same total annual +egg production, we scale up the rate during the pulse to compensate for +its short duration. + +```{r} +# Custom RDD function: pulsed reproduction using a fixed rate +pulse_width <- 0.1 # Reproduce during first 10% of each year + +annual_pulse_RDD <- function(rdi, species_params, t, ...) { + frac <- t %% 1 + if (frac < pulse_width) { + # Scale up to maintain total annual reproduction + return(species_params$rdd_steady / pulse_width) + } else { + return(0 * rdi) + } +} +``` + +We store the steady-state RDD value in the species parameters so our +function can access it, and then register the function: + +```{r} +params_pulse <- params +species_params(params_pulse)$rdd_steady <- rdd_steady +params_pulse <- setRateFunction(params_pulse, "RDD", "annual_pulse_RDD") +``` + +# Simulating cohort dynamics without diffusion + +We start from an empty spectrum (no fish) and let the pulsed +reproduction create cohorts from scratch. + +```{r} +params_empty <- params_pulse +initialN(params_empty)[] <- 0 + +sim_no_diff <- project(params_empty, t_max = 5, dt = 0.05, + t_save = 0.1, progress_bar = FALSE) +``` + +Let's visualise the size spectrum at different time points to see the +cohorts: + +```{r fig.height=6} +times_to_plot <- c(0.5, 1, 1.5, 2, 3, 4) +w <- params_pulse@w + +plot_list <- list() +for (tt in times_to_plot) { + idx <- which.min(abs(as.numeric(dimnames(sim_no_diff@n)$time) - tt)) + actual_time <- as.numeric(dimnames(sim_no_diff@n)$time[idx]) + n_at_t <- as.numeric(sim_no_diff@n[idx, 1, ]) + pos <- n_at_t > 0 + if (any(pos)) { + plot_list[[length(plot_list) + 1]] <- data.frame( + w = w[pos], n = n_at_t[pos], + time = factor(paste0("t = ", actual_time)) + ) + } +} +plot_data <- do.call(rbind, plot_list) + +ggplot(plot_data, aes(x = w, y = n, colour = time)) + + geom_line(linewidth = 0.8) + + scale_x_log10(limits = c(1e-3, 100)) + + scale_y_log10() + + labs(x = "Weight [g]", y = "Number density [1/g]", + title = "Cohort evolution without diffusion", + colour = "Time") + + theme_minimal(base_size = 14) +``` + +Without diffusion, each cohort appears as a relatively sharp peak that +moves to the right (towards larger sizes) as the fish grow. + +# Adding diffusion + +Now let's add diffusion to the model. Diffusion is set as an array with +dimensions species × size via `setDiffusion()`. We'll set a constant +diffusion rate across all sizes. + +We define a helper function that runs the simulation for a given +diffusion coefficient: + +```{r} +run_with_diffusion <- function(params_base, diff_coeff, t_max = 5) { + p <- params_base + d <- p@diffusion + d[] <- diff_coeff + p <- setDiffusion(p, diffusion = d) + initialN(p)[] <- 0 + sim <- project(p, t_max = t_max, dt = 0.05, + t_save = 0.1, progress_bar = FALSE) + return(sim) +} +``` + +# Comparing different diffusion rates + +Let's compare simulations with no diffusion, low diffusion, and high +diffusion: + +```{r} +sim_d0 <- run_with_diffusion(params_pulse, diff_coeff = 0) +sim_d_low <- run_with_diffusion(params_pulse, diff_coeff = 0.01) +sim_d_high <- run_with_diffusion(params_pulse, diff_coeff = 0.1) +``` + +Now let's visualise the cohorts at a specific time point to see how +diffusion affects their shape: + +```{r fig.height=5, fig.width=9} +snapshot_time <- 3 + +build_snapshot <- function(sim, label) { + idx <- which.min(abs(as.numeric(dimnames(sim@n)$time) - snapshot_time)) + n_at_t <- as.numeric(sim@n[idx, 1, ]) + w <- sim@params@w + pos <- n_at_t > 0 + if (any(pos)) { + data.frame(w = w[pos], n = n_at_t[pos], diffusion = label) + } else { + data.frame(w = numeric(0), n = numeric(0), diffusion = character(0)) + } +} + +snapshot_data <- rbind( + build_snapshot(sim_d0, "D = 0 (no diffusion)"), + build_snapshot(sim_d_low, "D = 0.01 (low)"), + build_snapshot(sim_d_high, "D = 0.1 (high)") +) + +ggplot(snapshot_data, aes(x = w, y = n, colour = diffusion)) + + geom_line(linewidth = 0.8) + + scale_x_log10(limits = c(1e-3, 100)) + + scale_y_log10() + + labs(x = "Weight [g]", y = "Number density [1/g]", + title = paste0("Effect of diffusion on cohorts at t = ", + snapshot_time), + colour = "Diffusion rate") + + theme_minimal(base_size = 14) +``` + +We can clearly see that: + +- **Without diffusion** (D = 0), the cohorts are sharp peaks. +- **With low diffusion** (D = 0.01), the cohorts are slightly + broadened. +- **With high diffusion** (D = 0.1), the cohorts are significantly + spread out, reflecting large individual variability in growth rates. + +# Time evolution with diffusion + +Let's look at the full time evolution of the size spectrum with +moderate diffusion to see how cohorts spread over time: + +```{r fig.height=6} +sim_d_med <- run_with_diffusion(params_pulse, diff_coeff = 0.05) + +times_to_plot <- c(0.5, 1, 2, 3, 4, 5) + +plot_list_diff <- list() +for (tt in times_to_plot) { + idx <- which.min(abs(as.numeric(dimnames(sim_d_med@n)$time) - tt)) + actual_time <- as.numeric(dimnames(sim_d_med@n)$time[idx]) + n_at_t <- as.numeric(sim_d_med@n[idx, 1, ]) + pos <- n_at_t > 0 + if (any(pos)) { + plot_list_diff[[length(plot_list_diff) + 1]] <- data.frame( + w = w[pos], n = n_at_t[pos], + time = factor(paste0("t = ", actual_time)) + ) + } +} +plot_data_diff <- do.call(rbind, plot_list_diff) + +ggplot(plot_data_diff, aes(x = w, y = n, colour = time)) + + geom_line(linewidth = 0.8) + + scale_x_log10(limits = c(1e-3, 100)) + + scale_y_log10() + + labs(x = "Weight [g]", y = "Number density [1/g]", + title = "Cohort evolution with moderate diffusion (D = 0.05)", + colour = "Time") + + theme_minimal(base_size = 14) +``` + +As time progresses, we see that: + +1. New cohorts enter at the egg size each year. +2. Each cohort grows towards larger sizes. +3. The diffusion causes each cohort to spread out more and more as it + ages. +4. Eventually, older cohorts merge together as their spreading + overwhelms the year-to-year separation. + +# Heatmap visualisation + +A heatmap provides a compact view of the entire dynamics, showing how +the size spectrum evolves continuously over time: + +```{r fig.height=5, fig.width=9} +all_times <- as.numeric(dimnames(sim_d_med@n)$time) + +heatmap_list <- list() +for (i in seq_along(all_times)) { + n_at_t <- as.numeric(sim_d_med@n[i, 1, ]) + pos <- n_at_t > 0 + if (any(pos)) { + heatmap_list[[length(heatmap_list) + 1]] <- data.frame( + time = all_times[i], + w = w[pos], + log_n = log10(n_at_t[pos]) + ) + } +} +heatmap_data <- do.call(rbind, heatmap_list) + +ggplot(heatmap_data, aes(x = time, y = w, fill = log_n)) + + geom_raster(interpolate = TRUE) + + scale_y_log10() + + scale_fill_viridis_c(name = expression(log[10](N))) + + labs(x = "Time [years]", y = "Weight [g]", + title = "Size spectrum over time (D = 0.05)") + + theme_minimal(base_size = 14) +``` + +In the heatmap, the diagonal bands represent individual cohorts growing +through the size spectrum. The broadening of these bands with time is +the effect of diffusion. + +# Summary + +This vignette demonstrated: + +1. How to set up a single-species model with `newSingleSpeciesParams()`. +2. How to implement pulsed annual reproduction using a custom RDD + function. +3. How cohorts of fish grow through the size spectrum over time. +4. How diffusion, set via `setDiffusion()`, controls the spreading of + cohorts — representing individual variability in growth rates. + +The key insight is that diffusion smooths out the sharp cohort structure. +In reality, individual fish within a cohort grow at different rates due +to environmental variability, differences in food availability, and +genetic variation. The diffusion term in the size-spectrum model +captures this variability. The magnitude of the diffusion coefficient +controls how quickly cohorts spread: larger values lead to faster +spreading and less distinct cohort structure. From 8390913ec7e5026c965b6412fd78c7cdab3ef259 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 16:36:47 +0000 Subject: [PATCH 11/47] Choose better parameters for vignette on cohort dynamics --- vignettes/cohort_dynamics_and_diffusion.Rmd | 60 ++++++++++----------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/vignettes/cohort_dynamics_and_diffusion.Rmd b/vignettes/cohort_dynamics_and_diffusion.Rmd index aa81e488f..0575dd4cf 100644 --- a/vignettes/cohort_dynamics_and_diffusion.Rmd +++ b/vignettes/cohort_dynamics_and_diffusion.Rmd @@ -8,6 +8,10 @@ output: editor_options: markdown: wrap: 72 +vignette: > + %\VignetteIndexEntry{The Numerical Scheme used in Mizer} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} @@ -32,6 +36,7 @@ cohort to broaden as it ages. ```{r} library(mizer) library(ggplot2) +library(plotly) ``` # Setting up the model @@ -41,7 +46,7 @@ We start by creating a single-species model using power-law background community. ```{r} -params <- newSingleSpeciesParams() +params <- newSingleSpeciesParams(h = 10, no_w = 400) params <- steady(params) ``` @@ -120,23 +125,26 @@ for (tt in times_to_plot) { pos <- n_at_t > 0 if (any(pos)) { plot_list[[length(plot_list) + 1]] <- data.frame( - w = w[pos], n = n_at_t[pos], + w = w[pos], n = n_at_t[pos] * w[pos]^2, time = factor(paste0("t = ", actual_time)) ) } } plot_data <- do.call(rbind, plot_list) -ggplot(plot_data, aes(x = w, y = n, colour = time)) + +p <- ggplot(plot_data, aes(x = w, y = n, colour = time)) + geom_line(linewidth = 0.8) + scale_x_log10(limits = c(1e-3, 100)) + - scale_y_log10() + - labs(x = "Weight [g]", y = "Number density [1/g]", + # scale_y_log10() + + labs(x = "Weight [g]", y = "Biomass density [g]", title = "Cohort evolution without diffusion", colour = "Time") + theme_minimal(base_size = 14) + +ggplotly(p) ``` + Without diffusion, each cohort appears as a relatively sharp peak that moves to the right (towards larger sizes) as the fish grow. @@ -150,10 +158,10 @@ We define a helper function that runs the simulation for a given diffusion coefficient: ```{r} -run_with_diffusion <- function(params_base, diff_coeff, t_max = 5) { +run_with_diffusion <- function(params_base, diff_coeff, diff_exp, t_max = 5) { p <- params_base d <- p@diffusion - d[] <- diff_coeff + d[] <- diff_coeff * w ^ diff_exp p <- setDiffusion(p, diffusion = d) initialN(p)[] <- 0 sim <- project(p, t_max = t_max, dt = 0.05, @@ -168,9 +176,10 @@ Let's compare simulations with no diffusion, low diffusion, and high diffusion: ```{r} -sim_d0 <- run_with_diffusion(params_pulse, diff_coeff = 0) -sim_d_low <- run_with_diffusion(params_pulse, diff_coeff = 0.01) -sim_d_high <- run_with_diffusion(params_pulse, diff_coeff = 0.1) +diff_exp <- params_pulse@species_params$n + 1 +sim_d0 <- run_with_diffusion(params_pulse, diff_coeff = 0, diff_exp = diff_exp) +sim_d_low <- run_with_diffusion(params_pulse, diff_coeff = 0.01, diff_exp = diff_exp) +sim_d_high <- run_with_diffusion(params_pulse, diff_coeff = 0.1, diff_exp = , diff_exp) ``` Now let's visualise the cohorts at a specific time point to see how @@ -197,24 +206,19 @@ snapshot_data <- rbind( build_snapshot(sim_d_high, "D = 0.1 (high)") ) -ggplot(snapshot_data, aes(x = w, y = n, colour = diffusion)) + +ggplot(snapshot_data, aes(x = w, y = n * w^2, colour = diffusion)) + geom_line(linewidth = 0.8) + scale_x_log10(limits = c(1e-3, 100)) + - scale_y_log10() + - labs(x = "Weight [g]", y = "Number density [1/g]", + # scale_y_log10() + + labs(x = "Weight [g]", y = "Biomass density [g]", title = paste0("Effect of diffusion on cohorts at t = ", snapshot_time), colour = "Diffusion rate") + theme_minimal(base_size = 14) ``` -We can clearly see that: - -- **Without diffusion** (D = 0), the cohorts are sharp peaks. -- **With low diffusion** (D = 0.01), the cohorts are slightly - broadened. -- **With high diffusion** (D = 0.1), the cohorts are significantly - spread out, reflecting large individual variability in growth rates. +We can see that diffusion has a large effect on the speed at which the cohort +peaks are moving but not so much effect on the broadening of the peaks. # Time evolution with diffusion @@ -222,7 +226,8 @@ Let's look at the full time evolution of the size spectrum with moderate diffusion to see how cohorts spread over time: ```{r fig.height=6} -sim_d_med <- run_with_diffusion(params_pulse, diff_coeff = 0.05) +diff_exp <- params_pulse@species_params$n + 1 +sim_d_med <- run_with_diffusion(params_pulse, diff_coeff = 0.05, diff_exp = diff_exp) times_to_plot <- c(0.5, 1, 2, 3, 4, 5) @@ -234,7 +239,7 @@ for (tt in times_to_plot) { pos <- n_at_t > 0 if (any(pos)) { plot_list_diff[[length(plot_list_diff) + 1]] <- data.frame( - w = w[pos], n = n_at_t[pos], + w = w[pos], n = n_at_t[pos] * w[pos]^2, time = factor(paste0("t = ", actual_time)) ) } @@ -244,7 +249,7 @@ plot_data_diff <- do.call(rbind, plot_list_diff) ggplot(plot_data_diff, aes(x = w, y = n, colour = time)) + geom_line(linewidth = 0.8) + scale_x_log10(limits = c(1e-3, 100)) + - scale_y_log10() + + # scale_y_log10() + labs(x = "Weight [g]", y = "Number density [1/g]", title = "Cohort evolution with moderate diffusion (D = 0.05)", colour = "Time") + @@ -276,7 +281,7 @@ for (i in seq_along(all_times)) { heatmap_list[[length(heatmap_list) + 1]] <- data.frame( time = all_times[i], w = w[pos], - log_n = log10(n_at_t[pos]) + log_n = n_at_t[pos] * w[pos]^2 ) } } @@ -306,10 +311,3 @@ This vignette demonstrated: 4. How diffusion, set via `setDiffusion()`, controls the spreading of cohorts — representing individual variability in growth rates. -The key insight is that diffusion smooths out the sharp cohort structure. -In reality, individual fish within a cohort grow at different rates due -to environmental variability, differences in food availability, and -genetic variation. The diffusion term in the size-spectrum model -captures this variability. The magnitude of the diffusion coefficient -controls how quickly cohorts spread: larger values lead to faster -spreading and less distinct cohort structure. From 1647aa6b79c847ea3ece17ff1f4d80086869cf54 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 16 Feb 2026 16:37:23 +0000 Subject: [PATCH 12/47] Removing some build problems --- DESCRIPTION | 2 ++ R/numerical_methods.R | 4 ++-- man/get_steady_state_n.Rd | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d60d39a80..dbbe8d216 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -79,6 +79,8 @@ Collate: 'resource_dynamics.R' 'resource_semichemostat.R' 'resource_logistic.R' + 'numerical_methods.R' + 'transport.R' 'project_n.R' 'project.R' 'mizer-package.R' diff --git a/R/numerical_methods.R b/R/numerical_methods.R index f93e44d3b..9e0cb997d 100644 --- a/R/numerical_methods.R +++ b/R/numerical_methods.R @@ -3,9 +3,9 @@ #' Solves a tridiagonal system of linear equations A * x = d #' where A is a tridiagonal matrix defined by diagonals a, b, c. #' -#' @param a Lower diagonal (length N). a[1] is ignored/0. +#' @param a Lower diagonal (length N). a_1 is ignored/0. #' @param b Main diagonal (length N). -#' @param c Upper diagonal (length N). c[N] is ignored/0. +#' @param c Upper diagonal (length N). c_N is ignored/0. #' @param d Right hand side vector (length N). #' #' @return Solution vector x (length N). diff --git a/man/get_steady_state_n.Rd b/man/get_steady_state_n.Rd index 790eef056..a3041c402 100644 --- a/man/get_steady_state_n.Rd +++ b/man/get_steady_state_n.Rd @@ -4,7 +4,7 @@ \alias{get_steady_state_n} \title{Helper function to calculate the steady state abundance using the upwind-difference scheme} \usage{ -get_steady_state_n(growth, mort, dw, idx, N0) +get_steady_state_n(growth, mort, dw, diffusion = rep(0, length(dw)), idx, N0) } \arguments{ \item{growth}{A numeric vector of growth rates.} From 394e69bf2c6a40ef317b471116c521b041b987f4 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 07:09:09 +0000 Subject: [PATCH 13/47] docs: Adjust diffusion coefficients in the cohort dynamics vignette. --- vignettes/cohort_dynamics_and_diffusion.Rmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/cohort_dynamics_and_diffusion.Rmd b/vignettes/cohort_dynamics_and_diffusion.Rmd index 0575dd4cf..f98ba1481 100644 --- a/vignettes/cohort_dynamics_and_diffusion.Rmd +++ b/vignettes/cohort_dynamics_and_diffusion.Rmd @@ -178,8 +178,8 @@ diffusion: ```{r} diff_exp <- params_pulse@species_params$n + 1 sim_d0 <- run_with_diffusion(params_pulse, diff_coeff = 0, diff_exp = diff_exp) -sim_d_low <- run_with_diffusion(params_pulse, diff_coeff = 0.01, diff_exp = diff_exp) -sim_d_high <- run_with_diffusion(params_pulse, diff_coeff = 0.1, diff_exp = , diff_exp) +sim_d_low <- run_with_diffusion(params_pulse, diff_coeff = 0.1, diff_exp = diff_exp) +sim_d_high <- run_with_diffusion(params_pulse, diff_coeff = 0.5, diff_exp = diff_exp) ``` Now let's visualise the cohorts at a specific time point to see how @@ -202,8 +202,8 @@ build_snapshot <- function(sim, label) { snapshot_data <- rbind( build_snapshot(sim_d0, "D = 0 (no diffusion)"), - build_snapshot(sim_d_low, "D = 0.01 (low)"), - build_snapshot(sim_d_high, "D = 0.1 (high)") + build_snapshot(sim_d_low, "D = 0.1 (low)"), + build_snapshot(sim_d_high, "D = 0.5 (high)") ) ggplot(snapshot_data, aes(x = w, y = n * w^2, colour = diffusion)) + From a24872413e10461ee1698c9d48e40161964bc8f0 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 07:40:04 +0000 Subject: [PATCH 14/47] Add alt text to all images --- README.md | 12 ++++++------ vignettes/developer_FAQ.Rmd | 2 +- vignettes/developer_vignette.Rmd | 22 +++++++++++----------- vignettes/mizer.Rmd | 2 +- vignettes/model_description.Rmd | 4 ++-- vignettes/working_with_git.Rmd | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 39002ba1c..7f7977b2a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
- +mizer logo [![CRAN Status](https://www.r-pkg.org/badges/version-ago/mizer)](https://cran.r-project.org/package=mizer) @@ -33,7 +33,7 @@ spectrum. Size-based models can be complicated, so mizer contains many default options that you can however change when needed. - +mizer workflow @@ -100,14 +100,14 @@ plots: plot(sim) ``` -![](man/figures/unnamed-chunk-4-1.png) +![Plot showing simulation results](man/figures/unnamed-chunk-4-1.png) See the accompanying [Get started](https://sizespectrum.org/mizer/articles/mizer.html) page for more details on how the package works, including detailed examples. - +Size spectrum dynamics ## Dynamic multi-species size-spectrum model @@ -146,7 +146,7 @@ populations and ecosystems, and for developing effective fisheries management strategies that account for the complex interactions among species and their environment. - +Effect of size-selective fishing A mizer model captures the interactions between species. The growth rates of fish are determined by the availability of prey and the death @@ -194,7 +194,7 @@ traffic jams. An analogy with road traffic may be helpful: - +Fish growth traffic jam In road traffic, if traffic density gets too high in a section of the diff --git a/vignettes/developer_FAQ.Rmd b/vignettes/developer_FAQ.Rmd index 9ec49f044..e27dd2cc8 100644 --- a/vignettes/developer_FAQ.Rmd +++ b/vignettes/developer_FAQ.Rmd @@ -86,7 +86,7 @@ To create this new branch, first make sure you have selected the master branch on the "Switch branch" dropdown in RStudio Git panel and then click the button to the left of that dropdown.
-![](images/new_branch_button.png){width=30%} +![New branch button](images/new_branch_button.png){width=30%} In the dialog box enter a name for your new branch and click "Create". diff --git a/vignettes/developer_vignette.Rmd b/vignettes/developer_vignette.Rmd index f21638077..0cc7080fb 100644 --- a/vignettes/developer_vignette.Rmd +++ b/vignettes/developer_vignette.Rmd @@ -68,7 +68,7 @@ in the guide by Hadley on R package development. To work with the code you will create your own git repository with a copy of the mizer code. Go to https://github.com/sizespectrum/mizer and fork it into your own repository by clicking the "Fork" button.
-![](images/fork.png){width=80%} +![Fork button on GitHub](images/fork.png){width=80%}
You will be prompted to log in to GitHub. If you do not yet have an account, @@ -79,19 +79,19 @@ whichever machine you work on. You can do this from within RStudio. For this you click on the "Project" drop-down and then select "New Project...".
-![](images/new_project.png){width=80%} +![New Project menu in RStudio](images/new_project.png){width=80%}
This will bring up a dialog box where you select "Version Control".
-![](images/version_control.png){width=50%} +![Version Control option in New Project wizard](images/version_control.png){width=50%}
Provided you have Git installed and RStudio was able to find it you can then choose "Git" on the next dialog box.
-![](images/git.png){width=50%} +![Git option in New Project wizard](images/git.png){width=50%}
If the Git option is not showing, the you need to troubleshoot, and perhaps @@ -99,7 +99,7 @@ https://happygitwithr.com/rstudio-see-git.html helps. In the next dialog box you let RStudio know where to find your fork.
-![](images/repository.png){width=50%} +![Repository configuration in New Project wizard](images/repository.png){width=50%}
To find the correct repository URL you go back to GitHub to the front page of @@ -108,7 +108,7 @@ There you will find a "Clone or download" button which when clicked will reveal the repository URL. Make sure that you are on the page of your fork of the repository. The URL should contain your GitHub username.
-![](images/repro_url.png){width=80%} +![Clone or download button on GitHub](images/repro_url.png){width=80%}
You can copy that to the clipboard by pressing the @@ -133,19 +133,19 @@ You are now all set to develop R packages, and To set things up, click on Build -> More -> Configure Build Tools.
-![](images/build_tools.png) +![Configure Build Tools menu](images/build_tools.png)
In the resulting dialog box, tick the checkboxes "Use devtools package functions if available" and "Generate documentation with roxygen" and then click on "Configure".
-![](images/roxygen.png){width=50%} +![Roxygen configuration in Project Options](images/roxygen.png){width=50%}
This will open another dialog box where you tick "Install and Restart".
-![](images/tick.png){width=40%} +![Install and Restart checkbox](images/tick.png){width=40%}
Hit "OK". While you are on the Project Options dialog box, click on @@ -153,7 +153,7 @@ Hit "OK". While you are on the Project Options dialog box, click on to 4, because that is the convention the mizer code follows.
-![](images/tabs.png){width=50%} +![Tab width configuration in Code Editing](images/tabs.png){width=50%}
## usethis package @@ -180,7 +180,7 @@ local code, you will want to install mizer using that code. To do this go to the "Build" tab in RStudio and click on "Install and Restart" or alternatively use the keyboard shortcut Ctrl+Shift+B.
-![](images/build.png) +![Install and Restart button in Build tab](images/build.png)
You can watch the progress in the "Build" tab. Once the build has completed, you will see that in the console RStudio automatically runs diff --git a/vignettes/mizer.Rmd b/vignettes/mizer.Rmd index 6225608c0..30505e98e 100644 --- a/vignettes/mizer.Rmd +++ b/vignettes/mizer.Rmd @@ -35,7 +35,7 @@ know about it by posting about it on our [issue tracker](https://github.com/size to @[mizer_model](https://twitter.com/mizer_model). We love to hear from you. -![](images/workflow.png) +![Mizer workflow diagram](images/workflow.png) A good way to get into mizer is to follow the online [mizer course](https://mizer.course.sizespectrum.org). This course has three parts, each consisting of several tutorials with example code and exercises: diff --git a/vignettes/model_description.Rmd b/vignettes/model_description.Rmd index edaf9fe41..bec20057f 100644 --- a/vignettes/model_description.Rmd +++ b/vignettes/model_description.Rmd @@ -195,7 +195,7 @@ We will discuss how we model the [predator-prey encounter rate], the resulting r of [consumption], the rate of [metabolic losses], and the partitioning of the remaining energy into [reproduction](#sec:repro) and [growth](#resulting-growth). -![](images/energy.png){width=60%} +![Energy acquisition and use by an individual](images/energy.png){width=60%} ## Predator-prey encounter rate {#sec:pref} @@ -326,7 +326,7 @@ The mortality rate of an individual $\mu_i(w)$ has three sources: predation mortality $\mu_{p.i}(w)$, background mortality $\mu_{ext.i}(w)$ and fishing mortality $\mu_{f.i}(w)$. -![](images/mortality.png){width=50%} +![Mortality of an individual](images/mortality.png){width=50%} Predation mortality is calculated such that all that is eaten translates into corresponding predation mortalities on the ingested prey individuals. diff --git a/vignettes/working_with_git.Rmd b/vignettes/working_with_git.Rmd index d718cbe17..03d7d58bb 100644 --- a/vignettes/working_with_git.Rmd +++ b/vignettes/working_with_git.Rmd @@ -45,7 +45,7 @@ To create the new branch, first make sure you have selected the master branch on the "Switch branch" dropdown in RStudio Git panel and then click the button to the left of that dropdown.
-![](images/new_branch_button.png){width=30%} +![New branch button](images/new_branch_button.png){width=30%}
In the dialog box enter the name for your new branch: "add_my_info". Leave @@ -65,7 +65,7 @@ file for this developer guide, written in [R Markdown](https://rmarkdown.rstudio A good way to navigate within files is to use the document outline which will be displayed when you hit the right-most icon on the editor pane toolbar.
-![](images/outline.png){width=30%} +![Markdown outline in RStudio](images/outline.png){width=30%}
You will find the subheading "People with a mizer fork" towards the bottom of @@ -88,12 +88,12 @@ So far you have only saved your changes to your local disc, but have not yet committed it to your local repository. To do that you click on the "Commit" button on the toolbar in the "Git" pane in RStudio:
-![](images/commit_button.png){width=40%} +![Commit button in Git tab](images/commit_button.png){width=40%}
This will pop up a screen like the following:
-![](images/commit.png){width=70%} +![Commit dialog box](images/commit.png){width=70%}
The blue M to the left of the file vignettes/developer_vignette.Rmd indicates @@ -125,7 +125,7 @@ After you have pushed your changes, you will be able to see them also on GitHub. If you go to the home page of your GitHub repository you will see a comment that you made a commit a little while ago:
-![](images/recently_pushed.png){width=80%} +![Recently pushed branches on GitHub](images/recently_pushed.png){width=80%}
It may be that you have several computers on which you work an you can have a From d596b49cf5f81a26f2492822b534a321a2171d69 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 19:26:37 +0000 Subject: [PATCH 15/47] Add discussion of numerical diffusion to vignettes --- vignettes/analytic_test.Rmd | 119 ++++++++++++++++++++++++++++++++ vignettes/numerical_details.Rmd | 23 +++++- 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/vignettes/analytic_test.Rmd b/vignettes/analytic_test.Rmd index a0dd50b97..e5f907ad7 100644 --- a/vignettes/analytic_test.Rmd +++ b/vignettes/analytic_test.Rmd @@ -366,3 +366,122 @@ if (rel_err_total < 0.05 && rel_err_peak_loc < 0.05 && rel_err_peak_val < 0.4) { } ``` +## Numerical Diffusion Test + +The upwind scheme introduces a numerical diffusion $D_{num} \approx g(w) \Delta w$. +We verify this by running the projection with *zero* physical diffusion and comparing the result to the analytical steady state solution for a system *with* diffusion $D(w) = g(w) \Delta w$. + +In this test, we use the same power law rates: +$g(w) = A w^p$. +$\Delta w \approx w \delta$ where $\delta = \ln(10^{\Delta x}) = \ln(\beta)$. +So $D_{num} \approx A w^p \cdot w \delta = (A \delta) w^{p+1}$. +This corresponds to the power law form for diffusion $D(w) = K w^{p+1}$ with $K = A \delta$. + + +```{r} +# Parameters for the test +p_test <- 0.7 +A_test <- 1 +B_test <- 0.5 + +# Helper: Growth rate +start_growth_test <- function(params, ...) { + matrix(A_test * params@w^p_test, nrow = 1, byrow = TRUE) +} + +# Helper: Mortality rate +start_mort_test <- function(params, ...) { + matrix(B_test * params@w^(p_test-1), nrow = 1, byrow = TRUE) +} + +# Helper: RDD +# We need to calculate K_num inside because it depends on params (resolution) +constant_rdd_test <- function(rdi, species_params, params, ...) { + # Calculate effective K based on grid resolution + beta_grid <- params@w[2] / params@w[1] + K_loc <- A_test * (beta_grid - 1) + + # Calculate lambda for this K + # Solve quadratic: K x^2 - (2A - K) x - 2B = 0 + # If K is very small, we might have issues, but here K = A(beta-1) > 0. + a_q <- K_loc + b_q <- -(2*A_test - K_loc) + c_q <- -2*B_test + det <- b_q^2 - 4 * a_q * c_q + x <- (-b_q - sqrt(det)) / (2 * a_q) + lam <- p_test - x + + w_min <- min(params@w) + J_min <- w_min^(p_test - lam) * (A_test - 0.5 * K_loc * (p_test + 1 - lam)) + structure(rep(J_min, length(rdi)), names = names(rdi)) +} + +# Function to perform the numerical diffusion test +run_numerical_diffusion_test <- function(no_w) { + # Create params + params <- newMultispeciesParams(data.frame(species = "Test", + w_inf = 1000, + w_mat = 100, + beta = 100, + sigma = 1, + k_vb = 0.1), + no_w = no_w, min_w = 1e-3, max_w = 1000) + + # Set rates + params <- setRateFunction(params, "EGrowth", "start_growth_test") + params <- setRateFunction(params, "Mort", "start_mort_test") + params <- setRateFunction(params, "RDD", "constant_rdd_test") + + # NO physical diffusion + # We set the diffusion matrix to zero + params@diffusion[] <- 0 + + params <- setResource(params, resource_dynamics = "resource_constant") + initialNResource(params) <- 0 + + # Calculate analytic initial condition to start close to steady state + # We use the same logic as in constant_rdd_test to get lambda + beta_grid <- params@w[2] / params@w[1] + K_loc <- A_test * (beta_grid - 1) + + a_q <- K_loc + b_q <- -(2*A_test - K_loc) + c_q <- -2*B_test + det <- b_q^2 - 4 * a_q * c_q + x <- (-b_q - sqrt(det)) / (2 * a_q) + lam <- p_test - x + + # Initialize + initialN(params) <- matrix(params@w^(-lam), nrow = 1, byrow = TRUE) + + # Project + sim <- project(params, t_max = 5, dt = 0.01) + + n0 <- initialN(params)[1, ] + n1 <- finalN(sim)[1, ] + + # Compare (ignore boundaries) + idx <- 10:(length(params@w)-10) + + # Calculate error + err <- abs(n1[idx] - n0[idx]) / n0[idx] + mean(err) +} + +# Run the test +err_100 <- run_numerical_diffusion_test(100) +err_400 <- run_numerical_diffusion_test(400) + +print(paste("Mean relative error with 100 bins:", err_100)) +print(paste("Mean relative error with 400 bins:", err_400)) + +# Check success +if (err_400 < 0.05) { + print("Numerical diffusion test passed: Numerical solution (D=0) matches Analytic solution (D=D_num).") +} else { + print("Numerical diffusion test failed.") +} +``` + + + diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index 3d9503c3b..d245352b5 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -109,7 +109,28 @@ This involves $N_{i, j_{min}}^{t+1}$ and $N_{i, j_{min}+1}^{t+1}$. We typically assume that densities drop to zero beyond the maximum size, $N_{i, j_{max}+1} = 0$. The flux leaving the grid is: $$ J_{i, j_{max}+1} = g_i(w_{j_{max}}) N_{i, j_{max}} - \frac{1}{2} \frac{0 - d_i(w_{j_{max}}) N_{i, j_{max}}}{\Delta w_{j_{max}}} $$ -This closes the system. + +## Numerical Diffusion + +The upwind scheme used for the advective term introduces numerical diffusion. This is a well-known property of first-order upwind schemes. We can estimate the magnitude of this diffusion by expanding the discretised term using a Taylor series. + +The discretised advection term is: +$$ \frac{g_i(w_j) N_{i,j} - g_i(w_{j-1}) N_{i,j-1}}{\Delta w_j} $$ +Using a Taylor expansion of $(g_i N_i)(w_{j-1})$ around $w_j$: +$$ (g_i N_i)(w_{j-1}) \approx (g_i N_i)(w_j) - \Delta w_{j-1} \frac{\partial (g_i N_i)}{\partial w} + \frac{1}{2} (\Delta w_{j-1})^2 \frac{\partial^2 (g_i N_i)}{\partial w^2} $$ +Substituting this into the difference quotient and approximating $\Delta w_{j-1} \approx \Delta w_j$ (which is valid for small grid steps): +$$ \frac{\partial (g_i N_i)}{\partial w} - \frac{1}{2} \Delta w_j \frac{\partial^2 (g_i N_i)}{\partial w^2} $$ +Comparing this to the transport equation with diffusion: +$$ \frac{\partial (g_i N_i)}{\partial w} - \frac{\partial}{\partial w} \left( \frac{1}{2} d_{num} \frac{\partial N_i}{\partial w} \right) $$ +we see that the upwind scheme introduces a numerical diffusion term with coefficient roughly: +$$ d_{num} \approx g_i(w) \Delta w $$ +(The factor of 1/2 cancels out if we match the forms, or depends on the exact definition of diffusion flux). Specifically, the error term looks like $-\frac{1}{2} \Delta w \frac{\partial^2 (gN)}{\partial w^2}$. If we assume $g$ is constant locally, this is $-\frac{\partial}{\partial w} (\frac{1}{2} g \Delta w \frac{\partial N}{\partial w})$. +So the numerical diffusion coefficient is approximately: +$$ d_{num}(w) \approx g_i(w) \Delta w $$ +Since $\Delta w \approx w \ln(\beta)$ (where $\beta = w_{j+1}/w_j$), substituting $\Delta w$: +$$ d_{num}(w) \approx g_i(w) w \ln(\beta) $$ +This means that even if the physical diffusion $d_i(w)$ is set to zero, the numerical scheme will behave as if there is a diffusion $d_{num}$. This numerical diffusion scales with the grid resolution: finer grids (smaller $\beta$) reduce the numerical diffusion. + # Resource Dynamics From 643dfb51fe44bd2b80d1fff95397cef4365755de Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 19:46:10 +0000 Subject: [PATCH 16/47] Adding discussion of numerical diffusion arising from implicit time stepping. --- vignettes/analytic_test.Rmd | 21 +++++++++++++++------ vignettes/numerical_details.Rmd | 28 ++++++++++++---------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/vignettes/analytic_test.Rmd b/vignettes/analytic_test.Rmd index e5f907ad7..c4e40a5e5 100644 --- a/vignettes/analytic_test.Rmd +++ b/vignettes/analytic_test.Rmd @@ -374,13 +374,16 @@ We verify this by running the projection with *zero* physical diffusion and comp In this test, we use the same power law rates: $g(w) = A w^p$. $\Delta w \approx w \delta$ where $\delta = \ln(10^{\Delta x}) = \ln(\beta)$. -So $D_{num} \approx A w^p \cdot w \delta = (A \delta) w^{p+1}$. -This corresponds to the power law form for diffusion $D(w) = K w^{p+1}$ with $K = A \delta$. - +So $D_{num} = g(w) \Delta w + g(w)^2 \Delta t$. +For power law rates $g(w) = A w^p$, and grid spacing $\Delta w \approx w \delta$, this becomes: +$D_{num} \approx A w^p \cdot w \delta + (A w^p)^2 \Delta t = (A \delta) w^{p+1} + A^2 \Delta t w^{2p}$. +To allow an analytic solution with a simple power-law diffusion $D = K w^{p+1}$, we set $p=1$ for this test. +Then $D_{num} \approx (A \delta + A^2 \Delta t) w^2$. +This corresponds to $K = A \delta + A^2 \Delta t$. ```{r} # Parameters for the test -p_test <- 0.7 +p_test <- 1 # Use p=1 to match diffusion scaling A_test <- 1 B_test <- 0.5 @@ -399,11 +402,17 @@ start_mort_test <- function(params, ...) { constant_rdd_test <- function(rdi, species_params, params, ...) { # Calculate effective K based on grid resolution beta_grid <- params@w[2] / params@w[1] - K_loc <- A_test * (beta_grid - 1) + + # Grid spacing beta approx 1+delta + # K_spatial = A * (beta - 1) + # K_time = A^2 * dt + # We use dt = 0.01 in the project call below + dt <- 0.01 + + K_loc <- A_test * (beta_grid - 1) + A_test^2 * dt # Calculate lambda for this K # Solve quadratic: K x^2 - (2A - K) x - 2B = 0 - # If K is very small, we might have issues, but here K = A(beta-1) > 0. a_q <- K_loc b_q <- -(2*A_test - K_loc) c_q <- -2*B_test diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index d245352b5..09528dde0 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -114,22 +114,18 @@ $$ J_{i, j_{max}+1} = g_i(w_{j_{max}}) N_{i, j_{max}} - \frac{1}{2} \frac{0 - d_ The upwind scheme used for the advective term introduces numerical diffusion. This is a well-known property of first-order upwind schemes. We can estimate the magnitude of this diffusion by expanding the discretised term using a Taylor series. -The discretised advection term is: -$$ \frac{g_i(w_j) N_{i,j} - g_i(w_{j-1}) N_{i,j-1}}{\Delta w_j} $$ -Using a Taylor expansion of $(g_i N_i)(w_{j-1})$ around $w_j$: -$$ (g_i N_i)(w_{j-1}) \approx (g_i N_i)(w_j) - \Delta w_{j-1} \frac{\partial (g_i N_i)}{\partial w} + \frac{1}{2} (\Delta w_{j-1})^2 \frac{\partial^2 (g_i N_i)}{\partial w^2} $$ -Substituting this into the difference quotient and approximating $\Delta w_{j-1} \approx \Delta w_j$ (which is valid for small grid steps): -$$ \frac{\partial (g_i N_i)}{\partial w} - \frac{1}{2} \Delta w_j \frac{\partial^2 (g_i N_i)}{\partial w^2} $$ -Comparing this to the transport equation with diffusion: -$$ \frac{\partial (g_i N_i)}{\partial w} - \frac{\partial}{\partial w} \left( \frac{1}{2} d_{num} \frac{\partial N_i}{\partial w} \right) $$ -we see that the upwind scheme introduces a numerical diffusion term with coefficient roughly: -$$ d_{num} \approx g_i(w) \Delta w $$ -(The factor of 1/2 cancels out if we match the forms, or depends on the exact definition of diffusion flux). Specifically, the error term looks like $-\frac{1}{2} \Delta w \frac{\partial^2 (gN)}{\partial w^2}$. If we assume $g$ is constant locally, this is $-\frac{\partial}{\partial w} (\frac{1}{2} g \Delta w \frac{\partial N}{\partial w})$. -So the numerical diffusion coefficient is approximately: -$$ d_{num}(w) \approx g_i(w) \Delta w $$ -Since $\Delta w \approx w \ln(\beta)$ (where $\beta = w_{j+1}/w_j$), substituting $\Delta w$: -$$ d_{num}(w) \approx g_i(w) w \ln(\beta) $$ -This means that even if the physical diffusion $d_i(w)$ is set to zero, the numerical scheme will behave as if there is a diffusion $d_{num}$. This numerical diffusion scales with the grid resolution: finer grids (smaller $\beta$) reduce the numerical diffusion. +The discretised equation for the transport (advection only, with constant rates for simplicity) is: +$$ \frac{N_j^{t+1} - N_j^t}{\Delta t} + g \frac{N_j^{t+1} - N_{j-1}^{t+1}}{\Delta w} = 0 $$ +Expanding $N(w, t)$ around $(w_j, t+\Delta t)$ leads to the following leading order error terms: +$$ \frac{\partial N}{\partial t} + g \frac{\partial N}{\partial w} = \frac{g \Delta w}{2} \left( 1 + \frac{g \Delta t}{\Delta w} \right) \frac{\partial^2 N}{\partial w^2} $$ +The coefficient of the second derivative represents the numerical diffusivity: +$$ D_{num} = \frac{g \Delta w}{2} (1 + C) $$ +where $C = \frac{g \Delta t}{\Delta w}$ is the Courant-Friedrichs-Lewy (CFL) number. +Comparing this to the Mizer diffusion equation form (where the diffusion term is $\frac{\partial}{\partial w} ( \frac{1}{2} \frac{\partial (D N)}{\partial w} )$), the effective diffusion parameter is: +$$ d_{num}(w) \approx g(w) \Delta w (1 + C(w)) $$ +Since $\Delta w \approx w \ln(\beta)$, this is: +$$ d_{num}(w) \approx g(w) w \ln(\beta) \left( 1 + \frac{g(w) \Delta t}{w \ln(\beta)} \right) = g(w) w \ln(\beta) + g(w)^2 \Delta t $$ +This means the numerical scheme behaves as if there is a diffusion $d_{num}$. This numerical diffusion has two components: one from spatial discretisation (scaling with $\Delta w$) and one from time stepping (scaling with $\Delta t$). # Resource Dynamics From 4e73904e1b8f56f0a60faa9e6a98fb695f4c01e8 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 19:49:02 +0000 Subject: [PATCH 17/47] feat: Migrate `project_n`'s tridiagonal solver to C++ for improved performance. --- R/RcppExports.R | 4 +++ R/project_n.R | 57 +++++++++++++++++++++++++++++++++++++----- man/project_n.Rd | 16 ++++++++++++ src/RcppExports.cpp | 17 +++++++++++++ src/project_n_loop.cpp | 52 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/project_n_loop.cpp diff --git a/R/RcppExports.R b/R/RcppExports.R index 4c88e8100..c4903e442 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -5,3 +5,7 @@ inner_project_loop <- function(no_sp, no_w, n, A, B, S, w_min_idx) { .Call('_mizer_inner_project_loop', PACKAGE = 'mizer', no_sp, no_w, n, A, B, S, w_min_idx) } +project_n_loop <- function(n, a, b, c, S, w_min_idx) { + .Call('_mizer_project_n_loop', PACKAGE = 'mizer', n, a, b, c, S, w_min_idx) +} + diff --git a/R/project_n.R b/R/project_n.R index b7ed5a5b8..70e7783c1 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -33,7 +33,52 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, b <- coefs$b c <- coefs$c S <- coefs$S + + # Boundary condition updates + # Vectorized implementation of the loop over species + + # S_j_start = N_old + dt/dw * R_dd + # Matrix of indices for (i, j_start) + # w_min_idx is 1-based index of start size bin for each species + j_start <- params@w_min_idx + idxs <- cbind(1:no_sp, j_start) + + S[idxs] <- S[idxs] + r$rdd * dt / params@dw[j_start] + + # Apply boundary condition to B (LHS) + # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux + # if (j_start > 1) { ... } + + # Identify species where start index > 1 + mask <- j_start > 1 + if (any(mask)) { + i_sub <- which(mask) + j_sub <- j_start[i_sub] + idxs_sub <- cbind(i_sub, j_sub) + + # correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] + correction <- (dt / params@dw[j_sub]) * 0.5 * + params@diffusion[idxs_sub] / params@dw[j_sub - 1] + + b[idxs_sub] <- b[idxs_sub] - correction + a[idxs_sub] <- 0 + } + + # Call C++ function to solve tridiagonal system + n <- project_n_loop(n, a, b, c, S, j_start) + + n +} +#' @rdname project_n +project_n_diffusion_R <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, + no_sp, no_w) { + coefs <- get_transport_coefs(params, n, n_pp, n_other, r, dt) + a <- coefs$a + b <- coefs$b + c <- coefs$c + S <- coefs$S + # Loop over species to apply boundary condition and solve for (i in 1:no_sp) { @@ -55,12 +100,12 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, # Original B[i, j_start] contains: ... + dt/dw * ( ... + 0.5 * d[i, j_start] / dw[j_start-1]) # We need to subtract that term if (j_start > 1) { - # The term added was: dt/dw[j_start] * 0.5 * d[i, j_start] / dw[j_start-1] - correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] - b[i, j_start] <- b[i, j_start] - correction - - # Also A[i, j_start] should be ignored/0. - a[i, j_start] <- 0 + # The term added was: dt/dw[j_start] * 0.5 * d[i, j_start] / dw[j_start-1] + correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] + b[i, j_start] <- b[i, j_start] - correction + + # Also A[i, j_start] should be ignored/0. + a[i, j_start] <- 0 } # Thomas Algorithm diff --git a/man/project_n.Rd b/man/project_n.Rd index 7faa80617..df4b12d69 100644 --- a/man/project_n.Rd +++ b/man/project_n.Rd @@ -2,9 +2,25 @@ % Please edit documentation in R/project_n.R \name{project_n} \alias{project_n} +\alias{project_n_diffusion_R} \title{Project values for first time step of Euler method} \usage{ project_n(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) + +project_n_diffusion_R( + params, + r, + n, + dt, + a, + b, + c, + S, + idx, + w_min_idx_array_ref, + no_sp, + no_w +) } \arguments{ \item{params}{A \linkS4class{MizerParams} object.} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 2a79d037b..16c0e1c44 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -27,9 +27,26 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// project_n_loop +NumericMatrix project_n_loop(NumericMatrix n, NumericMatrix a, NumericMatrix b, NumericMatrix c, NumericMatrix S, NumericVector w_min_idx); +RcppExport SEXP _mizer_project_n_loop(SEXP nSEXP, SEXP aSEXP, SEXP bSEXP, SEXP cSEXP, SEXP SSEXP, SEXP w_min_idxSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericMatrix >::type n(nSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type a(aSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type b(bSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type c(cSEXP); + Rcpp::traits::input_parameter< NumericMatrix >::type S(SSEXP); + Rcpp::traits::input_parameter< NumericVector >::type w_min_idx(w_min_idxSEXP); + rcpp_result_gen = Rcpp::wrap(project_n_loop(n, a, b, c, S, w_min_idx)); + return rcpp_result_gen; +END_RCPP +} static const R_CallMethodDef CallEntries[] = { {"_mizer_inner_project_loop", (DL_FUNC) &_mizer_inner_project_loop, 7}, + {"_mizer_project_n_loop", (DL_FUNC) &_mizer_project_n_loop, 6}, {NULL, NULL, 0} }; diff --git a/src/project_n_loop.cpp b/src/project_n_loop.cpp new file mode 100644 index 000000000..31f1ed0ad --- /dev/null +++ b/src/project_n_loop.cpp @@ -0,0 +1,52 @@ +#include +using namespace Rcpp; + +// [[Rcpp::export]] +NumericMatrix project_n_loop(NumericMatrix n, NumericMatrix a, NumericMatrix b, NumericMatrix c, + NumericMatrix S, NumericVector w_min_idx) { + int no_sp = n.nrow(); + int no_w = n.ncol(); + + // Temporary vectors for Thomas algorithm + // Allocated once to be reused across species + NumericVector c_prime(no_w); + NumericVector d_prime(no_w); + + for (int i = 0; i < no_sp; i++) { + // R uses 1-based indexing for w_min_idx, so subtract 1 + int j_start = w_min_idx[i] - 1; + + if (j_start >= no_w) continue; + + // Thomas Algorithm + // Solve A * n = S for the species range [j_start, no_w-1] + + // Forward elimination + double b_val = b(i, j_start); + if (b_val == 0) b_val = 1e-10; // Avoid division by zero + + c_prime[j_start] = c(i, j_start) / b_val; + d_prime[j_start] = S(i, j_start) / b_val; + + for (int j = j_start + 1; j < no_w; j++) { + double a_val = a(i, j); + double temp = b(i, j) - a_val * c_prime[j - 1]; + if (temp == 0) temp = 1e-10; // Avoid division by zero + + if (j < no_w - 1) { + c_prime[j] = c(i, j) / temp; + } + d_prime[j] = (S(i, j) - a_val * d_prime[j - 1]) / temp; + } + + // Backward substitution + n(i, no_w - 1) = d_prime[no_w - 1]; + for (int j = no_w - 2; j >= j_start; j--) { + n(i, j) = d_prime[j] - c_prime[j] * n(i, j + 1); + } + + // Note: values of n for j < j_start remain unchanged as desired + } + + return n; +} From 748407a337895cd0ad0d2f6d98ed20673b008996 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 20:16:18 +0000 Subject: [PATCH 18/47] Remove unwanted arguments from `get_transport_coefs()` --- R/project_n.R | 4 ++-- R/transport.R | 4 +--- R/wrapper_functions.R | 3 +-- tests/testthat/test-steadySingleSpecies.R | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/R/project_n.R b/R/project_n.R index 70e7783c1..be5907425 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -28,7 +28,7 @@ #' @export project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, n_pp, n_other, r, dt) + coefs <- get_transport_coefs(params, n, r, dt) a <- coefs$a b <- coefs$b c <- coefs$c @@ -73,7 +73,7 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, #' @rdname project_n project_n_diffusion_R <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, n_pp, n_other, r, dt) + coefs <- get_transport_coefs(params, n, r, dt) a <- coefs$a b <- coefs$b c <- coefs$c diff --git a/R/transport.R b/R/transport.R index f669acd9e..09cc14d37 100644 --- a/R/transport.R +++ b/R/transport.R @@ -2,14 +2,12 @@ #' #' @param params A \linkS4class{MizerParams} object. #' @param n An array (species x size) with the number density at the current time step. -#' @param n_pp A vector (size) with the resource number density at the current time step. -#' @param n_other A list with the abundances of other components. #' @param rates A list of rates as returned by [mizerRates()]. #' @param dt Time step. #' #' @return A list with the coefficients A, B, C and S. #' @noRd -get_transport_coefs <- function(params, n, n_pp, n_other, rates, dt) { +get_transport_coefs <- function(params, n, rates, dt) { # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j no_sp <- nrow(params@species_params) diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index a48324188..02ff8af4c 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -622,8 +622,7 @@ get_required_reproduction <- function(params) { # Calculate transport coefficients dt <- 1 - coefs <- get_transport_coefs(params, params@initial_n, params@initial_n_pp, - params@initial_n_other, rates, dt) + coefs <- get_transport_coefs(params, params@initial_n, rates, dt) reproduction <- params@species_params$erepro # vector of correct length diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 229f004bd..241417e9f 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -57,8 +57,7 @@ test_that("steadySingleSpecies produces steady state with diffusion", { rates <- list(e_growth = growth, mort = mort) dt <- 1 - coefs <- get_transport_coefs(params_orig, params_orig@initial_n, params_orig@initial_n_pp, - params_orig@initial_n_other, rates, dt) + coefs <- get_transport_coefs(params_orig, params_orig@initial_n, rates, dt) # Check residual for Cod sp <- species From 138a6cea0a17f0f016495b9f5d045e302b0c5d59 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 20:16:42 +0000 Subject: [PATCH 19/47] Fix vignette title --- vignettes/cohort_dynamics_and_diffusion.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/cohort_dynamics_and_diffusion.Rmd b/vignettes/cohort_dynamics_and_diffusion.Rmd index f98ba1481..7defb2b73 100644 --- a/vignettes/cohort_dynamics_and_diffusion.Rmd +++ b/vignettes/cohort_dynamics_and_diffusion.Rmd @@ -9,7 +9,7 @@ editor_options: markdown: wrap: 72 vignette: > - %\VignetteIndexEntry{The Numerical Scheme used in Mizer} + %\VignetteIndexEntry{Cohort dynamics and diffusion} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- From e1f32bad097c323ce95674e07d9586d414434efa Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Tue, 17 Feb 2026 20:23:50 +0000 Subject: [PATCH 20/47] Add vignette explaining use of FFT --- pkgdown/_pkgdown.yml | 1 + vignettes/mathematical_details.Rmd | 134 +++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 vignettes/mathematical_details.Rmd diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 0788d4433..85737f03a 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -81,6 +81,7 @@ articles: navbar: For developers contents: - numerical_details + - mathematical_details - analytic_test - developer_vignette - working_with_git diff --git a/vignettes/mathematical_details.Rmd b/vignettes/mathematical_details.Rmd new file mode 100644 index 000000000..dd1aec1b7 --- /dev/null +++ b/vignettes/mathematical_details.Rmd @@ -0,0 +1,134 @@ +--- +title: "Mathematical Details of the Mizer Implementation" +output: + html_document: + toc: yes + fig_width: 5 + fig_height: 5 +vignette: > + %\VignetteIndexEntry{Mathematical Details of the Mizer Implementation} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +In this vignette we describe the mathematical details of how the convolution +integrals in the expressions for the encounter rate and for the mortality rate +are calculated with the help of Fast Fourier Transform (FFT). + +## Conservation Equations + +The model dynamics are described by the McKendrick-von Foerster equation for +the species number densities $N_i(w)$ and the resource number density $N_R(w)$. + +## The Convolution Integrals + +The encounter rate $E_i(w)$ of a predator of species $i$ and weight $w$ is given +by +$$ +E_i(w) = \gamma_i(w) \int +\left( \theta_{ip} N_R(w_p) + \sum_{j} \theta_{ij} N_j(w_p) \right) +\phi_i(w,w_p) w_p \, dw_p. +$$ +The first term in the integral is the contribution from the resource and the +second term is the contribution from the fish prey. +$\gamma_i(w)$ is the search volume, $\theta_{ij}$ is the interaction matrix, +and $\phi_i(w,w_p)$ is the predation kernel. + +The predation rate $P_j(w_p)$ on a prey of species $j$ and size $w_p$ is +given by +$$ +P_j(w_p) = \sum_i \int \phi_i(w,w_p) (1-f_i(w)) \gamma_i(w) N_i(w) \, dw. +$$ +Here $f_i(w)$ is the feeding level of the predator. + +## Discretization on Logarithmic Grid + +We use a logarithmic grid of weights $w_k = w_1 \beta^{k-1}$ for $k=1,\dots,K$, +where $\beta = 10^{\Delta x}$. +The integral over prey size $w_p$ transforms into a sum over grid indices $k$. +Assuming the predation kernel depends only on the predator/prey mass ratio +$w/w_p$, i.e., $\phi_i(w,w_p) = \tilde{\phi}_i(w/w_p)$, and converting to +log-space $x = \log_\beta w$, the integrals become convolutions. + +Let $x_k = \log_\beta w_k = x_1 + (k-1)$. +The term $\phi_i(w_n, w_k) = \tilde{\phi}_i(\beta^{n-k})$. + +## Fast Fourier Transform Implementation + +The evaluation of these convolution sums is computationally expensive if done +directly ($\mathcal{O}(K^2)$). By using the Fast Fourier Transform (FFT), +we can reduce the complexity to $\mathcal{O}(K \log K)$. + +### Encounter Rate + +The integral for the encounter rate can be written as a convolution of the +available prey energy density with the predation kernel. +Let $A(w_p) = (\theta_{ip} N_R(w_p) + \sum_{j} \theta_{ij} N_j(w_p)) w_p$. +The discretized encounter rate (ignoring coefficients) is roughly +$$ E[n] = \sum_k \tilde{\phi}[n-k] A[k] $$ +In `mizer`, we define `ft_pred_kernel_e` as the FFT of the predation kernel. +The available energy is calculated, transformed via FFT, multiplied by +`ft_pred_kernel_e`, and then inverse transformed. + +The code in `mizerEncounter()` implements this: +```r +avail_energy <- Re(base::t(mvfft(base::t(params@ft_pred_kernel_e) * + mvfft(base::t(prey)), + inverse = TRUE))) / length(params@w_full) +``` + +### Predation Rate + +Similarly, the predation rate is a convolution of the predator density (scaled +by search volume and feeding level) with the predation kernel. +However, there is a slight difference in the indexing because the integral is +over predator sizes $w$, whereas the kernel is usually defined in terms of +predator/prey ratio. +$$ P(w_p) = \int \tilde{\phi}(w/w_p) D(w) dw $$ +where $D(w) = (1-f(w)) \gamma(w) N(w)$. +In terms of indices: +$$ P[k] = \sum_n \tilde{\phi}[n-k] D[n] $$ +To compute this as a standard convolution $P[k] = \sum_n \psi[k-n] D[n]$, we +need to define a reversed kernel $\psi[m] = \tilde{\phi}[-m]$. +This is why `setPredKernel()` calculates `ft_pred_kernel_p` using a reversed +version of the kernel. + +```r +# R/setPredKernel.R +ri <- min(max(which(phi > 0)), no_w_full - 1) # index of largest ppmr +phi_p <- rep(0, no_w_full) +phi_p[(no_w_full - ri + 1):no_w_full] <- phi[(ri + 1):2] +ft_pred_kernel_p[i, ] <- fft(phi_p) +``` +The `phi_p` construction effectively reverses the kernel and wraps it around +to suit the FFT definition of convolution. + +## The Wrap-around Hack (`ft_mask`) + +FFT-based convolution is actually circular convolution. This means that effects +from the largest sizes can "wrap around" and affect the smallest sizes, which +is unphysical in our context (large predators don't eat orders of magnitude +smaller than their prey preference, and certainly not "negative" sizes wrapping +to positive). + +To avoid artifacts from this circularity, we pad the grid or careful masking. +In `mizer`, we use `ft_mask` to zero out the predation rate at sizes that +should not receive any predation from the largest predators (because they are +larger than the maximum predator size or due to the kernel support). + +In `mizerPredRate()`: +```r +return(pred_rate * params@ft_mask) +``` +The `ft_mask` ensures that we don't get spurious predation mortality at sizes +where it shouldn't exist due to the periodic nature of the DFT. +`ft_mask` is a logical array (0 or 1) that is 1 strictly for sizes smaller than +the maximum size of the species, preventing the "tail" of the convolution from +wrapping around to the small sizes. From d75bdd6175bad4bd37658d8586ad559b56bb7d80 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Wed, 18 Feb 2026 18:17:35 +0000 Subject: [PATCH 21/47] Small change to argument list of a helper function --- R/project_n.R | 4 ++-- R/transport.R | 9 +++------ R/wrapper_functions.R | 2 +- tests/testthat/test-steadySingleSpecies.R | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/R/project_n.R b/R/project_n.R index be5907425..a7110a377 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -28,7 +28,7 @@ #' @export project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, r, dt) + coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt) a <- coefs$a b <- coefs$b c <- coefs$c @@ -73,7 +73,7 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, #' @rdname project_n project_n_diffusion_R <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, r, dt) + coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt) a <- coefs$a b <- coefs$b c <- coefs$c diff --git a/R/transport.R b/R/transport.R index 09cc14d37..66bc41139 100644 --- a/R/transport.R +++ b/R/transport.R @@ -2,12 +2,13 @@ #' #' @param params A \linkS4class{MizerParams} object. #' @param n An array (species x size) with the number density at the current time step. -#' @param rates A list of rates as returned by [mizerRates()]. +#' @param g The growth rate. +#' @param mu The mortality rate. #' @param dt Time step. #' #' @return A list with the coefficients A, B, C and S. #' @noRd -get_transport_coefs <- function(params, n, rates, dt) { +get_transport_coefs <- function(params, n, g, mu, dt) { # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j no_sp <- nrow(params@species_params) @@ -17,10 +18,6 @@ get_transport_coefs <- function(params, n, rates, dt) { d <- params@diffusion # species x size # Pre-calculate some common terms - # g_i(w_j) - g <- rates$e_growth - # mu_i(w_j) - mu <- rates$mort # dw_j dw <- params@dw # Delta t / Delta w_j diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index 02ff8af4c..6a422faac 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -622,7 +622,7 @@ get_required_reproduction <- function(params) { # Calculate transport coefficients dt <- 1 - coefs <- get_transport_coefs(params, params@initial_n, rates, dt) + coefs <- get_transport_coefs(params, params@initial_n, rates$e_growth, rates$mort, dt) reproduction <- params@species_params$erepro # vector of correct length diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 241417e9f..7a29022c3 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -54,10 +54,9 @@ test_that("steadySingleSpecies produces steady state with diffusion", { # and dt=1 growth <- getEGrowth(params_orig) mort <- getMort(params_orig) - rates <- list(e_growth = growth, mort = mort) dt <- 1 - coefs <- get_transport_coefs(params_orig, params_orig@initial_n, rates, dt) + coefs <- get_transport_coefs(params_orig, params_orig@initial_n, growth, mort, dt) # Check residual for Cod sp <- species From b4bfa2781b79540f383c2a0052d61793c1aeb122 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 08:58:26 +0000 Subject: [PATCH 22/47] refactor: centralize boundary condition calculations for the tridiagonal system into `get_transport_coefs` and add tests. --- R/project_n.R | 67 ++++------------------- R/transport.R | 59 +++++++++++++------- R/wrapper_functions.R | 22 +++----- tests/testthat/test-steadySingleSpecies.R | 3 +- tests/testthat/test-transport.R | 65 ++++++++++++++++++++++ 5 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 tests/testthat/test-transport.R diff --git a/R/project_n.R b/R/project_n.R index a7110a377..8bf1e70b3 100644 --- a/R/project_n.R +++ b/R/project_n.R @@ -28,44 +28,19 @@ #' @export project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt) + coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt, + recruitment_flux = r$rdd) a <- coefs$a b <- coefs$b c <- coefs$c S <- coefs$S - # Boundary condition updates - # Vectorized implementation of the loop over species - - # S_j_start = N_old + dt/dw * R_dd - # Matrix of indices for (i, j_start) - # w_min_idx is 1-based index of start size bin for each species - j_start <- params@w_min_idx - idxs <- cbind(1:no_sp, j_start) - - S[idxs] <- S[idxs] + r$rdd * dt / params@dw[j_start] - - # Apply boundary condition to B (LHS) - # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux - # if (j_start > 1) { ... } - - # Identify species where start index > 1 - mask <- j_start > 1 - if (any(mask)) { - i_sub <- which(mask) - j_sub <- j_start[i_sub] - idxs_sub <- cbind(i_sub, j_sub) - - # correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] - correction <- (dt / params@dw[j_sub]) * 0.5 * - params@diffusion[idxs_sub] / params@dw[j_sub - 1] - - b[idxs_sub] <- b[idxs_sub] - correction - a[idxs_sub] <- 0 - } - # Call C++ function to solve tridiagonal system - n <- project_n_loop(n, a, b, c, S, j_start) + # j_start is needed for the C++ loop, we can get it from params + params@w_min_idx + + # Note: project_n_loop takes j_start as argument + n <- project_n_loop(n, a, b, c, S, params@w_min_idx) n } @@ -73,40 +48,20 @@ project_n <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, #' @rdname project_n project_n_diffusion_R <- function(params, r, n, dt, a, b, c, S, idx, w_min_idx_array_ref, no_sp, no_w) { - coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt) + coefs <- get_transport_coefs(params, n, r$e_growth, r$mort, dt, + recruitment_flux = r$rdd) a <- coefs$a b <- coefs$b c <- coefs$c S <- coefs$S - # Loop over species to apply boundary condition and solve + # Loop over species to solve for (i in 1:no_sp) { # Start index for this species j_start <- params@w_min_idx[i] - # Apply boundary condition to S (RHS) - # S_j_start = N_old + dt/dw * R_dd - S[i, j_start] <- S[i, j_start] + r$rdd[i] * dt / params@dw[j_start] - - # Apply boundary condition to B (LHS) - # Remove the influence of "below" diffusion/growth which is replaced by recruitment flux - # B_j = 1 + ... + dt/dw * (g_j + D_j/(2*dw_j) + D_j/(2*dw_{j-1})) - # The D_j/(2*dw_{j-1}) term came from flux J_j. - # At boundary, J_j is explicitly R_dd. - # So we should remove the D_j/(2*dw_{j-1}) term from B[i, j_start] - # and A[i, j_start] is 0. - - # Original B[i, j_start] contains: ... + dt/dw * ( ... + 0.5 * d[i, j_start] / dw[j_start-1]) - # We need to subtract that term - if (j_start > 1) { - # The term added was: dt/dw[j_start] * 0.5 * d[i, j_start] / dw[j_start-1] - correction <- (dt / params@dw[j_start]) * 0.5 * params@diffusion[i, j_start] / params@dw[j_start - 1] - b[i, j_start] <- b[i, j_start] - correction - - # Also A[i, j_start] should be ignored/0. - a[i, j_start] <- 0 - } + # Boundary conditions are handled in get_transport_coefs # Thomas Algorithm # We need to pass the sub-vectors for the current species i, starting from j_start diff --git a/R/transport.R b/R/transport.R index 66bc41139..6d1da21bf 100644 --- a/R/transport.R +++ b/R/transport.R @@ -5,11 +5,14 @@ #' @param g The growth rate. #' @param mu The mortality rate. #' @param dt Time step. +#' +#' This calculates the coefficients A, B, C and S for the linear system +#' A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j +#' For details see the [Numerical Details](https://sizespectrum.org/mizer/articles/numerical_details.html#discretised-equation) vignette. #' #' @return A list with the coefficients A, B, C and S. #' @noRd -get_transport_coefs <- function(params, n, g, mu, dt) { - # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = S_j +get_transport_coefs <- function(params, n, g, mu, dt, recruitment_flux) { no_sp <- nrow(params@species_params) no_w <- length(params@w) @@ -58,7 +61,7 @@ get_transport_coefs <- function(params, n, g, mu, dt) { matrix(dw[idx_j], nrow = no_sp, ncol = length(idx_j), byrow = TRUE) c[, idx_j] <- -dt_dw[, idx_j] * term_diff_plus_1 - c[, no_w] <- 0 # Boundary condition N_{no_w+1} = 0 + # c[, no_w] is 0 as initialized # B_j # B_j = 1 + dt * mu_j + dt/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) @@ -71,24 +74,40 @@ get_transport_coefs <- function(params, n, g, mu, dt) { b[, idx] <- 1 + dt * mu[, idx] + dt_dw[, idx] * (g[, idx] + term_diff_j + term_diff_j_minus_1) - # Boundary j=1 (approx w_min) - # Equation: N_1 - N_1^old / dt + (J_{3/2} - J_{1/2}) / dw_1 = -mu_1 N_1 - # J_{1/2} = R_dd (recruitment flux). - # J_{3/2} follows standard flux definition. - # result: (1 + dt*mu_1 + dt/dw_1 * (g_1 + D_1/(2*dw_1))) N_1 - dt/dw_1 * (D_2/(2*dw_1)) N_2 = N_1^old + dt/dw_1 * R_dd - # So for j=1: - # B_1 = 1 + dt*mu_1 + dt/dw_1 * (g_1 + d_1/(2*dw_1)) - # C_1 = - dt/dw_1 * d_2/(2*dw_1) (Matches standard formula) - # A_1 = 0 - - dw_1 <- dw[1] - dt_dw_1 <- dt / dw_1 - b[, 1] <- 1 + dt * mu[, 1] + dt_dw_1 * (g[, 1] + 0.5 * d[, 1] / dw_1) - # c[, 1] already computed correctly above - a[, 1] <- 0 - - # RHS S + # Boundary condition updates + # We treat the start of the size spectrum (j_start) for each species as a boundary. + # At j_start, the incoming flux is the recruitment flux R_dd. + # The boundary condition for B (LHS) reflects that there is no transport from "below" j_start + # (other than R_dd which is on RHS). + + j_start <- params@w_min_idx + idxs <- cbind(1:no_sp, j_start) + + # S_j_start = N_old + dt/dw * R_dd S[] <- n + S[idxs] <- S[idxs] + recruitment_flux * dt / params@dw[j_start] + + # b_j_start = 1 + dt*mu + dt/dw * (g + D/(2*dw)) + # This formula excludes the upstream diffusion term D/(2*dw_{j-1}) because + # flux from below is replaced by recruitment flux. + b[idxs] <- 1 + dt * mu[idxs] + dt_dw[idxs] * (g[idxs] + 0.5 * d[idxs] / params@dw[j_start]) + + # a_j_start = 0 + a[idxs] <- 0 + + # Zero out elements for sizes smaller than w_min_idx + # This ensures that there is no transport or dynamics below the recruitment size + # Create a logical mask where col index < w_min_idx + # Using outer is efficient enough for this size + 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)) { + a[mask_below] <- 0 + b[mask_below] <- 0 + c[mask_below] <- 0 + S[mask_below] <- 0 + } return(list(a = a, b = b, c = c, S = S)) } diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index 6a422faac..e73ca83ba 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -614,15 +614,14 @@ get_required_reproduction <- function(params) { no_sp <- nrow(params@species_params) - # Calculate rates - rates_fns <- lapply(params@rates_funcs, get) - rates <- mizerRates(params, n = params@initial_n, n_pp = params@initial_n_pp, - n_other = params@initial_n_other, t = 0, - effort = params@initial_effort, rates_fns = rates_fns) - # Calculate transport coefficients dt <- 1 - coefs <- get_transport_coefs(params, params@initial_n, rates$e_growth, rates$mort, dt) + # We pass a dummy recruitment flux of 0 to trigger the boundary condition + # corrections for a and b in get_transport_coefs + coefs <- get_transport_coefs(params, n = params@initial_n, + g = getEGrowth(params), + mu = getMort(params), dt, + recruitment_flux = numeric(no_sp)) reproduction <- params@species_params$erepro # vector of correct length @@ -639,7 +638,7 @@ get_required_reproduction <- function(params) { # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? # No, let's look at project_n again. # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source - # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N + R * dt / dw + # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N_i + R * dt / dw # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt # Extract coefficients @@ -647,12 +646,7 @@ get_required_reproduction <- function(params) { b <- coefs$b[i, w_min_idx] c <- coefs$c[i, w_min_idx] - # Boundary correction for diffusion (if not at global min size) - if (w_min_idx > 1) { - correction <- (dt / params@dw[w_min_idx]) * 0.5 * params@diffusion[i, w_min_idx] / params@dw[w_min_idx - 1] - b <- b - correction - a <- 0 - } + # Boundary corrections for a and b are now handled in get_transport_coefs n_current <- params@initial_n[i, w_min_idx] n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 7a29022c3..8adc8ac89 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -56,7 +56,8 @@ test_that("steadySingleSpecies produces steady state with diffusion", { mort <- getMort(params_orig) dt <- 1 - coefs <- get_transport_coefs(params_orig, params_orig@initial_n, growth, mort, dt) + coefs <- get_transport_coefs(params_orig, params_orig@initial_n, growth, mort, dt, + recruitment_flux = numeric(nrow(params_orig@species_params))) # Check residual for Cod sp <- species diff --git a/tests/testthat/test-transport.R b/tests/testthat/test-transport.R new file mode 100644 index 000000000..d03c19816 --- /dev/null +++ b/tests/testthat/test-transport.R @@ -0,0 +1,65 @@ +test_that("get_transport_coefs works correctly", { + params <- newTraitParams(no_sp = 2) + # Force different w_min to test zeroing logic + params@species_params$w_min[2] <- 0.01 + params@w_min_idx[2] <- which.min(abs(params@w - 0.01)) + + n <- params@initial_n + n[] <- 1 # Set n to 1 to make S checking easier + + dt <- 0.1 + recruitment_flux <- c(10, 20) + + # We need to access the internal function + get_transport_coefs <- mizer:::get_transport_coefs + getEGrowth <- mizer:::getEGrowth + getMort <- mizer:::getMort + + coefs <- get_transport_coefs(params, n, getEGrowth(params), getMort(params), dt, recruitment_flux) + + # Check dimensions + expect_equal(dim(coefs$a), dim(n)) + expect_equal(dim(coefs$b), dim(n)) + expect_equal(dim(coefs$c), dim(n)) + expect_equal(dim(coefs$S), dim(n)) + + # Check zeroing out below w_min_idx + w_min_idx_2 <- params@w_min_idx[2] + expect_true(w_min_idx_2 > 1) # Ensure we are actually testing something interesting + + expect_true(all(coefs$a[2, 1:(w_min_idx_2 - 1)] == 0)) + expect_true(all(coefs$b[2, 1:(w_min_idx_2 - 1)] == 0)) + expect_true(all(coefs$c[2, 1:(w_min_idx_2 - 1)] == 0)) + expect_true(all(coefs$S[2, 1:(w_min_idx_2 - 1)] == 0)) + + # Check boundary condition at w_min_idx + # S should include recruitment flux + # S[i, j_start] = n[i, j_start] + R[i] * dt / dw[j_start] + + # Species 1 + j_start_1 <- params@w_min_idx[1] + expected_S_1 <- n[1, j_start_1] + recruitment_flux[1] * dt / params@dw[j_start_1] + expect_equivalent(coefs$S[1, j_start_1], expected_S_1) + + # Species 2 + j_start_2 <- params@w_min_idx[2] + expected_S_2 <- n[2, j_start_2] + recruitment_flux[2] * dt / params@dw[j_start_2] + expect_equivalent(coefs$S[2, j_start_2], expected_S_2) + + # Check that 'a' at boundary is 0 + expect_equivalent(coefs$a[1, j_start_1], 0) + expect_equivalent(coefs$a[2, j_start_2], 0) + + # Check 'b' at boundary + # b_j_start = 1 + dt*mu + dt/dw * (g + D/(2*dw)) + # Note: we need to calculate expected values using the same inputs + g <- getEGrowth(params) + mu <- getMort(params) + dw <- params@dw + + # Species 2 + expected_b_2 <- 1 + dt * mu[2, j_start_2] + + (dt / dw[j_start_2]) * (g[2, j_start_2] + 0.5 * params@diffusion[2, j_start_2] / dw[j_start_2]) + + expect_equivalent(coefs$b[2, j_start_2], expected_b_2) +}) From 16e9023db449c5995bc899899c3d402df5844af4 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 11:07:56 +0000 Subject: [PATCH 23/47] feat: Reimplement `getRequiredRDD` using transport coefficients to accurately determine the reproduction rate needed for initial egg abundance, including diffusion. --- R/newSingleSpeciesParams.R | 2 +- R/setBevertonHolt.R | 70 ++++++++++++++++++++------- R/wrapper_functions.R | 65 ++----------------------- man/get_required_reproduction.Rd | 18 ------- tests/testthat/test-setBevertonHolt.R | 56 +++++++++++++++++++++ 5 files changed, 114 insertions(+), 97 deletions(-) delete mode 100644 man/get_required_reproduction.Rd diff --git a/R/newSingleSpeciesParams.R b/R/newSingleSpeciesParams.R index 6054706a2..f3c283339 100644 --- a/R/newSingleSpeciesParams.R +++ b/R/newSingleSpeciesParams.R @@ -217,7 +217,7 @@ newSingleSpeciesParams <- ## Set reproduction to meet boundary condition ---- params@species_params$erepro <- params@species_params$erepro * - get_required_reproduction(params) / getRDI(params) + getRequiredRDD(params) / getRDI(params) params@given_species_params$erepro <- params@species_params$erepro params <- setBevertonHolt(params, reproduction_level = reproduction_level) diff --git a/R/setBevertonHolt.R b/R/setBevertonHolt.R index 2fd1ecdc2..522fb298b 100644 --- a/R/setBevertonHolt.R +++ b/R/setBevertonHolt.R @@ -283,31 +283,65 @@ setBevertonHolt.MizerParams <- function(params, erepro, return(params) } +#' Determine reproduction rate needed for initial egg abundance +#' +#' @param params A MizerParams object +#' @return A vector of reproduction rates for all species +#' @export getRequiredRDD <- function(params) { UseMethod("getRequiredRDD") } #' @export getRequiredRDD.MizerParams <- function(params) { # Calculate required rdd - mumu <- getMort(params) - gg <- getEGrowth(params) - rdd_new <- getRDD(params) # to get the right structure - for (i in seq_len(nrow(params@species_params))) { - gg0 <- gg[i, params@w_min_idx[i]] - if (!(gg0 > 0)) { - warning("Eggs of species ", params@species_params$species[i], - " have zero growth rate.") - } - mumu0 <- mumu[i, params@w_min_idx[i]] - DW <- params@dw[params@w_min_idx[i]] - n0 <- params@initial_n[i, params@w_min_idx[i]] - if (!(n0 > 0)) { - warning("Species ", params@species_params$species[i], - "appears to have no eggs.") - } - rdd_new[i] <- n0 * (gg0 + DW * mumu0) + no_sp <- nrow(params@species_params) + + # Calculate transport coefficients + dt <- 1 + # We pass a dummy recruitment flux of 0 to trigger the boundary condition + # corrections for a and b in get_transport_coefs + coefs <- get_transport_coefs(params, n = params@initial_n, + g = getEGrowth(params), + mu = getMort(params), dt, + recruitment_flux = numeric(no_sp)) + + reproduction <- params@species_params$erepro # vector of correct length + names(reproduction) <- params@species_params$species + + for (i in (1:no_sp)) { + w_min_idx <- params@w_min_idx[i] + + # Get coefficients for this species at the boundary + # The equation for the first node is: + # (N_new - N_old)/dt = -(Flux_matrix * N) + R/dw + # In steady state N_new = N_old, so: + # Flux_matrix * N = R/dw + # The rows of coefs correspond to the linear system A*N_{j-1} + B*N_j + C*N_{j+1} = ... + # For the first node j=w_min_idx: + # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? + # No, let's look at project_n again. + # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source + # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N_i + R * dt / dw + # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt + + # Extract coefficients + a <- coefs$a[i, w_min_idx] + b <- coefs$b[i, w_min_idx] + c <- coefs$c[i, w_min_idx] + + # Boundary corrections for a and b are now handled in get_transport_coefs + + n_current <- params@initial_n[i, w_min_idx] + n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 + n_prev <- if (w_min_idx > 1) params@initial_n[i, w_min_idx - 1] else 0 # Should be irrelevant if A=0 or boundary + + # Calculate R + # R = ( A * n_prev + (B - 1) * n_current + C * n_next ) * dw / dt + + total_rate <- a * n_prev + (b - 1) * n_current + c * n_next + reproduction[i] <- total_rate * params@dw[w_min_idx] / dt } - rdd_new + reproduction } #' Get reproduction level diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index e73ca83ba..6c8050e94 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -130,7 +130,7 @@ newCommunityParams <- function(max_w = 1e6, params@rates_funcs$RDD <- "constantRDD" if (missing(reproduction)) { - reproduction <- get_required_reproduction(params) + reproduction <- getRequiredRDD(params) } params@species_params$constant_reproduction <- reproduction params@given_species_params$constant_reproduction <- reproduction @@ -584,13 +584,16 @@ newTraitParams <- function(no_sp = 11, ## Set reproduction to meet boundary condition ---- params@species_params$erepro <- params@species_params$erepro * - get_required_reproduction(params) / getRDI(params) + getRequiredRDD(params) / getRDI(params) params <- setBevertonHolt(params, reproduction_level = reproduction_level) return(params) } + + + # Helper function to calculate the coefficient of the death rate created by # a power-law spectrum of predators, assuming they have the same predation # parameters as the first species. @@ -603,61 +606,3 @@ get_power_law_mort <- function(params) { params@w[[1]] ^ (1 + params@species_params[[1, "q"]] - params@resource_params$lambda)) } - -#' Determine reproduction rate needed for initial egg abundance -#' -#' @param params A MizerParams object -#' @return A vector of reproduction rates for all species -#' @concept helper -get_required_reproduction <- function(params) { - assert_that(is(params, "MizerParams")) - - no_sp <- nrow(params@species_params) - - # Calculate transport coefficients - dt <- 1 - # We pass a dummy recruitment flux of 0 to trigger the boundary condition - # corrections for a and b in get_transport_coefs - coefs <- get_transport_coefs(params, n = params@initial_n, - g = getEGrowth(params), - mu = getMort(params), dt, - recruitment_flux = numeric(no_sp)) - - reproduction <- params@species_params$erepro # vector of correct length - - for (i in (1:no_sp)) { - w_min_idx <- params@w_min_idx[i] - - # Get coefficients for this species at the boundary - # The equation for the first node is: - # (N_new - N_old)/dt = -(Flux_matrix * N) + R/dw - # In steady state N_new = N_old, so: - # Flux_matrix * N = R/dw - # The rows of coefs correspond to the linear system A*N_{j-1} + B*N_j + C*N_{j+1} = ... - # For the first node j=w_min_idx: - # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? - # No, let's look at project_n again. - # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source - # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N_i + R * dt / dw - # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt - - # Extract coefficients - a <- coefs$a[i, w_min_idx] - b <- coefs$b[i, w_min_idx] - c <- coefs$c[i, w_min_idx] - - # Boundary corrections for a and b are now handled in get_transport_coefs - - n_current <- params@initial_n[i, w_min_idx] - n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 - n_prev <- if (w_min_idx > 1) params@initial_n[i, w_min_idx - 1] else 0 # Should be irrelevant if A=0 or boundary - - # Calculate R - # R = ( A * n_prev + (B - 1) * n_current + C * n_next ) * dw / dt - - total_rate <- a * n_prev + (b - 1) * n_current + c * n_next - reproduction[i] <- total_rate * params@dw[w_min_idx] / dt - } - return(reproduction) -} - diff --git a/man/get_required_reproduction.Rd b/man/get_required_reproduction.Rd deleted file mode 100644 index 27bf48211..000000000 --- a/man/get_required_reproduction.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/wrapper_functions.R -\name{get_required_reproduction} -\alias{get_required_reproduction} -\title{Determine reproduction rate needed for initial egg abundance} -\usage{ -get_required_reproduction(params) -} -\arguments{ -\item{params}{A MizerParams object} -} -\value{ -A vector of reproduction rates for all species -} -\description{ -Determine reproduction rate needed for initial egg abundance -} -\concept{helper} diff --git a/tests/testthat/test-setBevertonHolt.R b/tests/testthat/test-setBevertonHolt.R index 5c60a7bad..c99f8c15b 100644 --- a/tests/testthat/test-setBevertonHolt.R +++ b/tests/testthat/test-setBevertonHolt.R @@ -135,3 +135,59 @@ test_that("R_max is increased when needed", { "has been increased to give a reproduction level of 0.99: Sprat, Sandeel") expect_gt(p@species_params$R_max[1], NS_params@species_params$R_max[1]) }) + +# getRequiredRDD ---- +test_that("getRequiredRDD works for single species model", { + params <- newSingleSpeciesParams() + # In a steady state model, getRequiredRDD should return the same as getRDD + # because the reproduction matches the required amount to maintain the steady state. + rdd <- getRequiredRDD(params) + rdd_actual <- getRDD(params) + + expect_equal(rdd, rdd_actual) +}) + +test_that("getRequiredRDD works for community model", { + params <- newCommunityParams() + # In community model, reproduction is constant + rdd <- getRequiredRDD(params) + rdd_actual <- getRDD(params) + + # Check if they are close enough + expect_equal(rdd, rdd_actual) +}) + +test_that("getRequiredRDD handles diffusion", { + # Create a model with diffusion + params <- newSingleSpeciesParams() + + # Add diffusion + diffusion <- params@diffusion + diffusion[] <- 1e9 * params@w + params <- setDiffusion(params, diffusion = diffusion) + + # Update initial_n to be the steady state solution with this diffusion + # We need to recalculate it using get_steady_state_n + + # Instead of fully replicating newSingleSpeciesParams logic, let's use the fact that + # getRequiredRDD should make the current state a steady state *at the boundary*. + + # If we simply update reproduction to match getRequiredRDD, then the boundary flux matches reproduction. + + # So let's update erepro + rdd_req <- getRequiredRDD(params) + params@species_params$erepro <- params@species_params$erepro * rdd_req / getRDI(params) + + # Now getRDD(params) should match rdd_req + expect_equal(getRDD(params), rdd_req) + + # Let's stick to the test that it does not error and returns a numeric vector of correct length + expect_length(rdd_req, 1) + expect_type(rdd_req, "double") + expect_true(rdd_req > 0) + + # verifying that it is different from the no-diffusion case + params_no_diff <- newSingleSpeciesParams() + expect_true(rdd_req != getRequiredRDD(params_no_diff)) +}) + From 81b986b55abc5ea791598c3fe4947833edd88d21 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 11:19:09 +0000 Subject: [PATCH 24/47] refactor: move getRequiredRDD function and its tests to dedicated files. --- DESCRIPTION | 1 + R/getRequiredRDD.R | 61 +++++++++++++++++++++++++++ R/setBevertonHolt.R | 59 -------------------------- tests/testthat/test-getRequiredRDD.R | 54 ++++++++++++++++++++++++ tests/testthat/test-setBevertonHolt.R | 53 ----------------------- 5 files changed, 116 insertions(+), 112 deletions(-) create mode 100644 R/getRequiredRDD.R create mode 100644 tests/testthat/test-getRequiredRDD.R diff --git a/DESCRIPTION b/DESCRIPTION index dbbe8d216..dc0bf75a8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -58,6 +58,7 @@ Collate: 'reproduction.R' 'saveParams.R' 'species_params.R' + 'getRequiredRDD.R' 'setColours.R' 'setInteraction.R' 'setPredKernel.R' diff --git a/R/getRequiredRDD.R b/R/getRequiredRDD.R new file mode 100644 index 000000000..61c0392fc --- /dev/null +++ b/R/getRequiredRDD.R @@ -0,0 +1,61 @@ +#' Determine reproduction rate needed for initial egg abundance +#' +#' @param params A MizerParams object +#' @return A vector of reproduction rates for all species +#' @export +getRequiredRDD <- function(params) { + UseMethod("getRequiredRDD") +} + +#' @export +getRequiredRDD.MizerParams <- function(params) { + # Calculate required rdd + no_sp <- nrow(params@species_params) + + # Calculate transport coefficients + dt <- 1 + # We pass a dummy recruitment flux of 0 to trigger the boundary condition + # corrections for a and b in get_transport_coefs + coefs <- get_transport_coefs(params, n = params@initial_n, + g = getEGrowth(params), + mu = getMort(params), dt, + recruitment_flux = numeric(no_sp)) + + reproduction <- params@species_params$erepro # vector of correct length + names(reproduction) <- params@species_params$species + + for (i in (1:no_sp)) { + w_min_idx <- params@w_min_idx[i] + + # Get coefficients for this species at the boundary + # The equation for the first node is: + # (N_new - N_old)/dt = -(Flux_matrix * N) + R/dw + # In steady state N_new = N_old, so: + # Flux_matrix * N = R/dw + # The rows of coefs correspond to the linear system A*N_{j-1} + B*N_j + C*N_{j+1} = ... + # For the first node j=w_min_idx: + # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? + # No, let's look at project_n again. + # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source + # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N_i + R * dt / dw + # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt + + # Extract coefficients + a <- coefs$a[i, w_min_idx] + b <- coefs$b[i, w_min_idx] + c <- coefs$c[i, w_min_idx] + + # Boundary corrections for a and b are now handled in get_transport_coefs + + n_current <- params@initial_n[i, w_min_idx] + n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 + n_prev <- if (w_min_idx > 1) params@initial_n[i, w_min_idx - 1] else 0 # Should be irrelevant if A=0 or boundary + + # Calculate R + # R = ( A * n_prev + (B - 1) * n_current + C * n_next ) * dw / dt + + total_rate <- a * n_prev + (b - 1) * n_current + c * n_next + reproduction[i] <- total_rate * params@dw[w_min_idx] / dt + } + reproduction +} diff --git a/R/setBevertonHolt.R b/R/setBevertonHolt.R index 522fb298b..34a346f52 100644 --- a/R/setBevertonHolt.R +++ b/R/setBevertonHolt.R @@ -283,66 +283,7 @@ setBevertonHolt.MizerParams <- function(params, erepro, return(params) } -#' Determine reproduction rate needed for initial egg abundance -#' -#' @param params A MizerParams object -#' @return A vector of reproduction rates for all species -#' @export -getRequiredRDD <- function(params) { - UseMethod("getRequiredRDD") -} -#' @export -getRequiredRDD.MizerParams <- function(params) { - # Calculate required rdd - no_sp <- nrow(params@species_params) - - # Calculate transport coefficients - dt <- 1 - # We pass a dummy recruitment flux of 0 to trigger the boundary condition - # corrections for a and b in get_transport_coefs - coefs <- get_transport_coefs(params, n = params@initial_n, - g = getEGrowth(params), - mu = getMort(params), dt, - recruitment_flux = numeric(no_sp)) - - reproduction <- params@species_params$erepro # vector of correct length - names(reproduction) <- params@species_params$species - for (i in (1:no_sp)) { - w_min_idx <- params@w_min_idx[i] - - # Get coefficients for this species at the boundary - # The equation for the first node is: - # (N_new - N_old)/dt = -(Flux_matrix * N) + R/dw - # In steady state N_new = N_old, so: - # Flux_matrix * N = R/dw - # The rows of coefs correspond to the linear system A*N_{j-1} + B*N_j + C*N_{j+1} = ... - # For the first node j=w_min_idx: - # A*N_{j-1} + (B-1)/dt * N_j + C/dt * N_{j+1} = R/dw / dt ? - # No, let's look at project_n again. - # It solves A N_{i-1} + B N_i + C N_{i+1} = N_old + RHS_source - # In steady state: A N_{i-1} + B N_i + C N_{i+1} = N_i + R * dt / dw - # So R = ( A N_{i-1} + (B-1) N_i + C N_{i+1} ) * dw / dt - - # Extract coefficients - a <- coefs$a[i, w_min_idx] - b <- coefs$b[i, w_min_idx] - c <- coefs$c[i, w_min_idx] - - # Boundary corrections for a and b are now handled in get_transport_coefs - - n_current <- params@initial_n[i, w_min_idx] - n_next <- if (w_min_idx < length(params@w)) params@initial_n[i, w_min_idx + 1] else 0 - n_prev <- if (w_min_idx > 1) params@initial_n[i, w_min_idx - 1] else 0 # Should be irrelevant if A=0 or boundary - - # Calculate R - # R = ( A * n_prev + (B - 1) * n_current + C * n_next ) * dw / dt - - total_rate <- a * n_prev + (b - 1) * n_current + c * n_next - reproduction[i] <- total_rate * params@dw[w_min_idx] / dt - } - reproduction -} #' Get reproduction level #' diff --git a/tests/testthat/test-getRequiredRDD.R b/tests/testthat/test-getRequiredRDD.R new file mode 100644 index 000000000..ddf68ff6f --- /dev/null +++ b/tests/testthat/test-getRequiredRDD.R @@ -0,0 +1,54 @@ + +test_that("getRequiredRDD works for single species model", { + params <- newSingleSpeciesParams() + # In a steady state model, getRequiredRDD should return the same as getRDD + # because the reproduction matches the required amount to maintain the steady state. + rdd <- getRequiredRDD(params) + rdd_actual <- getRDD(params) + + expect_equal(rdd, rdd_actual) +}) + +test_that("getRequiredRDD works for community model", { + params <- newCommunityParams() + # In community model, reproduction is constant + rdd <- getRequiredRDD(params) + rdd_actual <- getRDD(params) + + # Check if they are close enough + expect_equal(rdd, rdd_actual) +}) + +test_that("getRequiredRDD handles diffusion", { + # Create a model with diffusion + params <- newSingleSpeciesParams() + + # Add diffusion + diffusion <- params@diffusion + diffusion[] <- 1e9 * params@w + params <- setDiffusion(params, diffusion = diffusion) + + # Update initial_n to be the steady state solution with this diffusion + # We need to recalculate it using get_steady_state_n + + # Instead of fully replicating newSingleSpeciesParams logic, let's use the fact that + # getRequiredRDD should make the current state a steady state *at the boundary*. + + # If we simply update reproduction to match getRequiredRDD, then the boundary flux matches reproduction. + + # So let's update erepro + rdd_req <- getRequiredRDD(params) + params@species_params$erepro <- params@species_params$erepro * rdd_req / getRDI(params) + + # Now getRDD(params) should match rdd_req + expect_equal(getRDD(params), rdd_req) + + # Let's stick to the test that it does not error and returns a numeric vector of correct length + expect_length(rdd_req, 1) + expect_type(rdd_req, "double") + expect_true(rdd_req > 0) + + # verifying that it is different from the no-diffusion case + params_no_diff <- newSingleSpeciesParams() + expect_true(rdd_req != getRequiredRDD(params_no_diff)) +}) diff --git a/tests/testthat/test-setBevertonHolt.R b/tests/testthat/test-setBevertonHolt.R index c99f8c15b..251be84f9 100644 --- a/tests/testthat/test-setBevertonHolt.R +++ b/tests/testthat/test-setBevertonHolt.R @@ -136,58 +136,5 @@ test_that("R_max is increased when needed", { expect_gt(p@species_params$R_max[1], NS_params@species_params$R_max[1]) }) -# getRequiredRDD ---- -test_that("getRequiredRDD works for single species model", { - params <- newSingleSpeciesParams() - # In a steady state model, getRequiredRDD should return the same as getRDD - # because the reproduction matches the required amount to maintain the steady state. - rdd <- getRequiredRDD(params) - rdd_actual <- getRDD(params) - - expect_equal(rdd, rdd_actual) -}) -test_that("getRequiredRDD works for community model", { - params <- newCommunityParams() - # In community model, reproduction is constant - rdd <- getRequiredRDD(params) - rdd_actual <- getRDD(params) - - # Check if they are close enough - expect_equal(rdd, rdd_actual) -}) - -test_that("getRequiredRDD handles diffusion", { - # Create a model with diffusion - params <- newSingleSpeciesParams() - - # Add diffusion - diffusion <- params@diffusion - diffusion[] <- 1e9 * params@w - params <- setDiffusion(params, diffusion = diffusion) - - # Update initial_n to be the steady state solution with this diffusion - # We need to recalculate it using get_steady_state_n - - # Instead of fully replicating newSingleSpeciesParams logic, let's use the fact that - # getRequiredRDD should make the current state a steady state *at the boundary*. - - # If we simply update reproduction to match getRequiredRDD, then the boundary flux matches reproduction. - - # So let's update erepro - rdd_req <- getRequiredRDD(params) - params@species_params$erepro <- params@species_params$erepro * rdd_req / getRDI(params) - - # Now getRDD(params) should match rdd_req - expect_equal(getRDD(params), rdd_req) - - # Let's stick to the test that it does not error and returns a numeric vector of correct length - expect_length(rdd_req, 1) - expect_type(rdd_req, "double") - expect_true(rdd_req > 0) - - # verifying that it is different from the no-diffusion case - params_no_diff <- newSingleSpeciesParams() - expect_true(rdd_req != getRequiredRDD(params_no_diff)) -}) From 41b6c61f56d657e55d1acc512ea0630f69dbfd5f Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 11:26:22 +0000 Subject: [PATCH 25/47] Test that the state returned by `steadySingleSpecies()` is indeed preserved by `project()`. --- tests/testthat/test-steadySingleSpecies.R | 67 ++++------------------- 1 file changed, 12 insertions(+), 55 deletions(-) diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 8adc8ac89..1f9a93611 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -40,62 +40,19 @@ test_that("steadySingleSpecies produces steady state with diffusion", { params <- steadySingleSpecies(params, species = species) - # Since N is fixed at the boundary (and potentially inconsistent with R_dd), - # projecting with `project()` (which uses R_dd) will immediately change the boundary - # and propagate effects. - # Instead, we verify that the calculated N satisfies the steady state transport equation - # with the fixed boundary condition. + # Now that we have the steady state, we can use setBevertonHolt() to + # set the reproduction parameters to values that are consistent with it. + suppressWarnings(params <- setBevertonHolt(params, reproduction_level = 0.5)) - # We need to access internal functions - get_transport_coefs <- mizer:::get_transport_coefs - - # Calculate coefficients - # steadySingleSpecies uses rates from the *original* state - # and dt=1 - growth <- getEGrowth(params_orig) - mort <- getMort(params_orig) - dt <- 1 - - coefs <- get_transport_coefs(params_orig, params_orig@initial_n, growth, mort, dt, - recruitment_flux = numeric(nrow(params_orig@species_params))) - - # Check residual for Cod - sp <- species - n <- params@initial_n[sp, ] - - a <- coefs$a[sp, ] - b <- coefs$b[sp, ] - 1 # Adjust for steady state - c <- coefs$c[sp, ] - - # Boundary correction used in steadySingleSpecies - w_min_idx <- params@w_min_idx[sp] - # In steadySingleSpecies we set b[w_min_idx] = 1 and c[w_min_idx] = 0. - # We should replicate that here to verify the interior. - b[w_min_idx] <- 1 - c[w_min_idx] <- 0 - a[w_min_idx] <- 0 - - # Calculate residual A*N_{i-1} + B*N_i + C*N_{i+1} - # For interior points within the solved range - w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) - - residuals <- numeric(w_max_idx) - # Start from w_min_idx + 1 (the first interior node) - # End at w_max_idx (the last solved node) - for (i in (w_min_idx + 1):w_max_idx) { - # Note: n[i+1] will be 0 if i = w_max_idx, which is consistent with the solver - # (assuming 0 flux from above or just absorbing boundary) - val_next <- if (i < length(n)) n[i+1] else 0 - residuals[i] <- a[i] * n[i-1] + b[i] * n[i] + c[i] * val_next - } - - # Check max residual relative to N - # We exclude the boundary point because we fixed it explicitly. - - valid_range <- (w_min_idx + 1):w_max_idx - max_rel_resid <- max(abs(residuals[valid_range]) / n[valid_range]) - - expect_lt(max_rel_resid, 1e-10) + # And then the steady state should be preserved by project() + sim <- project(params, t_max = 5) + initial_n <- params@initial_n[species, ] + final_n <- finalN(sim)[species, ] + rel_error <- abs(initial_n - final_n) / initial_n + # Ignore indices where initial_n is very small/zero to avoid division by zero or numerical noise + valid_idx <- initial_n > 1e-20 + max_rel_error <- max(rel_error[valid_idx], na.rm = TRUE) + expect_lt(max_rel_error, 1e-10) }) test_that("steadySingleSpecies errors when growth stops before maturity", { From c55ec1408e6f95985dc75bb46aff5f0b0b3dd541 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 11:54:59 +0000 Subject: [PATCH 26/47] Testing `steadySingleSpecies()` also in the case where egg size is larger than minimum size. --- tests/testthat/test-steadySingleSpecies.R | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 1f9a93611..7707af917 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -31,9 +31,13 @@ test_that("steadySingleSpecies produces steady state with diffusion", { # Enable diffusion for Cod species <- "Cod" - p <- params@species_params[species, "p"] - d <- 0.1 * params@w^p + n <- params@species_params[species, "n"] + d <- 0.1 * params@w^(n + 1) diffusion(params)[species, ] <- d + + # Increase minimum size to test boundary condition + params@w_min_idx[species] <- 10 + params@species_params[species, "w_min"] <- params@w[10] # Keep original params to calculate rates that steadySingleSpecies used params_orig <- params From 4ddde770d47d52c7dfb9baff73aafbca1a854fb6 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 19 Feb 2026 09:55:22 +0000 Subject: [PATCH 27/47] Replace `expect_equivalent` with `expect_equal(..., ignore_attr = TRUE)` in transport tests. --- tests/testthat/test-transport.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-transport.R b/tests/testthat/test-transport.R index d03c19816..79a650780 100644 --- a/tests/testthat/test-transport.R +++ b/tests/testthat/test-transport.R @@ -39,16 +39,16 @@ test_that("get_transport_coefs works correctly", { # Species 1 j_start_1 <- params@w_min_idx[1] expected_S_1 <- n[1, j_start_1] + recruitment_flux[1] * dt / params@dw[j_start_1] - expect_equivalent(coefs$S[1, j_start_1], expected_S_1) + expect_equal(coefs$S[1, j_start_1], expected_S_1, ignore_attr = TRUE) # Species 2 j_start_2 <- params@w_min_idx[2] expected_S_2 <- n[2, j_start_2] + recruitment_flux[2] * dt / params@dw[j_start_2] - expect_equivalent(coefs$S[2, j_start_2], expected_S_2) + expect_equal(coefs$S[2, j_start_2], expected_S_2, ignore_attr = TRUE) # Check that 'a' at boundary is 0 - expect_equivalent(coefs$a[1, j_start_1], 0) - expect_equivalent(coefs$a[2, j_start_2], 0) + expect_equal(coefs$a[1, j_start_1], 0, ignore_attr = TRUE) + expect_equal(coefs$a[2, j_start_2], 0, ignore_attr = TRUE) # Check 'b' at boundary # b_j_start = 1 + dt*mu + dt/dw * (g + D/(2*dw)) @@ -61,5 +61,5 @@ test_that("get_transport_coefs works correctly", { expected_b_2 <- 1 + dt * mu[2, j_start_2] + (dt / dw[j_start_2]) * (g[2, j_start_2] + 0.5 * params@diffusion[2, j_start_2] / dw[j_start_2]) - expect_equivalent(coefs$b[2, j_start_2], expected_b_2) + expect_equal(coefs$b[2, j_start_2], expected_b_2, ignore_attr = TRUE) }) From 766e94199844e4d0880d15ede1e5a0df3a18ed79 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Fri, 20 Feb 2026 07:20:21 +0000 Subject: [PATCH 28/47] Exporting `getRequiredRDD()` --- NAMESPACE | 1 + man/getRequiredRDD.Rd | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 man/getRequiredRDD.Rd diff --git a/NAMESPACE b/NAMESPACE index b2cc05546..a16dd8994 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -280,6 +280,7 @@ export(getRateFunction) export(getRates) export(getReproductionLevel) export(getReproductionProportion) +export(getRequiredRDD) export(getResourceCapacity) export(getResourceDynamics) export(getResourceLevel) diff --git a/man/getRequiredRDD.Rd b/man/getRequiredRDD.Rd new file mode 100644 index 000000000..57d362c0a --- /dev/null +++ b/man/getRequiredRDD.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/getRequiredRDD.R +\name{getRequiredRDD} +\alias{getRequiredRDD} +\title{Determine reproduction rate needed for initial egg abundance} +\usage{ +getRequiredRDD(params) +} +\arguments{ +\item{params}{A MizerParams object} +} +\value{ +A vector of reproduction rates for all species +} +\description{ +Determine reproduction rate needed for initial egg abundance +} From ff312b2c89eded8d56e789567b1eaa71a275d3ab Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Fri, 20 Feb 2026 07:20:37 +0000 Subject: [PATCH 29/47] docs: Clarify the modification of tri-diagonal matrix coefficients for boundary conditions in numerical discretization. --- vignettes/numerical_details.Rmd | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index 09528dde0..4f04a597e 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -104,11 +104,22 @@ $$ J_{i, j_{min}} = R_{dd, i} $$ The equation for the first bin becomes: $$ \frac{N_{i,j_{min}}^{t+1} - N_{i,j_{min}}^t}{\Delta t} + \frac{J_{i, j_{min}+1}^{t+1} - R_{dd,i}}{\Delta w_{j_{min}}} = -\mu_i(w_{j_{min}}) N_{i,j_{min}}^{t+1} $$ This involves $N_{i, j_{min}}^{t+1}$ and $N_{i, j_{min}+1}^{t+1}$. +Comparing this to the general discretised equation translates to modifying the first row ($j=j_{min}$) of our tri-diagonal matrices: + +* The $j_{min}-1$ term does not exist, so $A_{i,j_{min}} = 0$. +* The upward diffusion term from below the boundary is omitted, so $B_{i, j_{min}}$ does not have the $\frac{1}{2} \frac{d_i(w_{j_{min}})}{\Delta w_{j_{min}-1}}$ component: + $$ B_{i,j_{min}} = 1 + \Delta t \mu_i(w_{j_{min}}) + \frac{\Delta t}{\Delta w_{j_{min}}} \left( g_i(w_{j_{min}}) + \frac{1}{2} \frac{d_i(w_{j_{min}})}{\Delta w_{j_{min}}} \right) $$ +* The coefficient $C_{i, j_{min}}$ remains unchanged from the general formula. +* The recruitment flux enters as a source term, so it is added to the right-hand side $S_{i, j_{min}}$: + $$ S_{i,j_{min}} = N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i} $$ + +(Additionally, for any size classes below the recruitment size $j < j_{min}$, we set all coefficients in the matrices $A$, $B$, $C$ and vector $S$ to $0$ to avoid any dynamics in that range). **At the largest size ($j=j_{max}$):** We typically assume that densities drop to zero beyond the maximum size, $N_{i, j_{max}+1} = 0$. The flux leaving the grid is: $$ J_{i, j_{max}+1} = g_i(w_{j_{max}}) N_{i, j_{max}} - \frac{1}{2} \frac{0 - d_i(w_{j_{max}}) N_{i, j_{max}}}{\Delta w_{j_{max}}} $$ +This means the term $C_{i, j_{max}}$ multiplying $N_{i, j_{max}+1}^{t+1}$ is not needed, so $C_{i, j_{max}} = 0$. The coefficients $A_{i, j_{max}}$ and $B_{i, j_{max}}$ use the standard formulas. ## Numerical Diffusion From 2795055db49a14480b706f5f57e3e6c9a7bb2664 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Fri, 20 Feb 2026 07:35:26 +0000 Subject: [PATCH 30/47] feat: Document the numerical scheme for the steady-state solution --- vignettes/numerical_details.Rmd | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index 4f04a597e..b4f460293 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -138,6 +138,51 @@ Since $\Delta w \approx w \ln(\beta)$, this is: $$ d_{num}(w) \approx g(w) w \ln(\beta) \left( 1 + \frac{g(w) \Delta t}{w \ln(\beta)} \right) = g(w) w \ln(\beta) + g(w)^2 \Delta t $$ This means the numerical scheme behaves as if there is a diffusion $d_{num}$. This numerical diffusion has two components: one from spatial discretisation (scaling with $\Delta w$) and one from time stepping (scaling with $\Delta t$). +## Steady-State Solution + +When solving the steady-state ODE instead of the time-dependent PDE, we are looking for a state where the population densities do not change over time, meaning $N_{i,j}^{t+1} = N_{i,j}^t = N_{i,j}^*$. + +Substituting this into our discretised linear system: +$$ +A_{i,j} N_{i,j-1}^* + B_{i,j} N_{i,j}^* + C_{i,j} N_{i,j+1}^* = S_{i,j} +$$ +Recall that for $j > j_{min}$, $S_{i,j} = N_{i,j}^t$. The equation simplifies to: +$$ +A_{i,j} N_{i,j-1}^* + (B_{i,j} - 1) N_{i,j}^* + C_{i,j} N_{i,j+1}^* = 0 +$$ + +To find the steady-state population densities $N^*$, we formulate a new time-independent tridiagonal system: +$$ +\tilde{A}_{i,j} N_{i,j-1}^* + \tilde{B}_{i,j} N_{i,j}^* + \tilde{C}_{i,j} N_{i,j+1}^* = \tilde{S}_{i,j} +$$ +To eliminate the explicit dependence on the time step $\Delta t$, we can divide the equation by $\Delta t$. The modified coefficients $\tilde{A}, \tilde{B}, \tilde{C}$ defining the new tri-diagonal system are: +$$ +\begin{aligned} +\tilde{A}_{i,j} &= \frac{A_{i,j}}{\Delta t} = -\frac{1}{\Delta w_j} \left( g_i(w_{j-1}) + \frac{1}{2} \frac{d_i(w_{j-1})}{\Delta w_{j-1}} \right) \\ +\tilde{C}_{i,j} &= \frac{C_{i,j}}{\Delta t} = -\frac{1}{\Delta w_j} \left( \frac{1}{2} \frac{d_i(w_{j+1})}{\Delta w_j} \right) \\ +\tilde{B}_{i,j} &= \frac{B_{i,j} - 1}{\Delta t} = \mu_i(w_j) + \frac{1}{\Delta w_j} \left( g_i(w_j) + \frac{1}{2} \frac{d_i(w_j)}{\Delta w_j} + \frac{1}{2} \frac{d_i(w_j)}{\Delta w_{j-1}} \right) +\end{aligned} +$$ +Notice that $\tilde{A}_{i,j}$ and $\tilde{C}_{i,j}$ are exactly the expressions for $A_{i,j}$ and $C_{i,j}$ evaluated at $\Delta t = 1$. Similarly, $\tilde{B}_{i,j}$ is exactly the expression for $B_{i,j} - 1$ evaluated at $\Delta t = 1$. + +**Boundary conditions for the steady state:** + +For the smallest size ($j=j_{min}$), the original equation had a source term due to recruitment: +$$ +S_{i, j_{min}} = N_{i,j_{min}}^t + \frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i} +$$ +Following the same logic of setting $N^{t+1} = N^t = N^*$ and dividing by $\Delta t$, the right-hand side vector $\tilde{S}_{i,j}$ for the steady-state system becomes purely the recruitment flux term. If we again observe the original term $\frac{\Delta t}{\Delta w_{j_{min}}} R_{dd, i}$ when evaluated at $\Delta t = 1$, we get our new source vector: +$$ +\tilde{S}_{i, j_{min}} = \frac{R_{dd, i}}{\Delta w_{j_{min}}} +$$ +For all other $j > j_{min}$, $\tilde{S}_{i,j} = 0$. + +The boundary condition modifications at the edges of the grid remain the same conceptually: $\tilde{A}_{i,j_{min}} = 0$, the upward diffusion term is omitted from $\tilde{B}_{i, j_{min}}$, and $\tilde{C}_{i, j_{max}} = 0$. For any $j < j_{min}$, all matrix entries remain zero. + +In code, this means that the steady-state coefficients for the matrix multiplication ($\tilde{A}, \tilde{B}, \tilde{C}$) and the constant vector ($\tilde{S}$) can be calculated by calling the standard coefficient function but simply setting $\Delta t = 1$, and dropping the $+1$ and $+N_{i,j}^t$ from the resulting $B$ and $S$ variables respectively. + +With these modified matrices, the steady-state densities can be calculated directly by solving the linear system avoiding the need to iterate step by step over time. + # Resource Dynamics From dd9764f8764b28adeb680c120521f5302dc410ea Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Fri, 20 Feb 2026 08:50:38 +0000 Subject: [PATCH 31/47] refactor: modify `get_steady_state_n` to reuse code from `project_n`. --- R/get_initial_n.R | 20 +++- R/helpers.R | 141 ++++++++--------------- R/newSingleSpeciesParams.R | 5 +- R/steadySingleSpecies.R | 22 ++-- R/wrapper_functions.R | 14 ++- tests/testthat/test-get_steady_state_n.R | 104 ++++++++++------- 6 files changed, 154 insertions(+), 152 deletions(-) diff --git a/R/get_initial_n.R b/R/get_initial_n.R index 73cf459d5..f2affaa13 100644 --- a/R/get_initial_n.R +++ b/R/get_initial_n.R @@ -54,6 +54,10 @@ get_initial_n <- function(params, n0_mult = NULL, a = 0.35) { p@w_full ^ (-p@resource_params$lambda) p@interaction[] <- 0 income <- getEReproAndGrowth(p) + p@metab + mort <- getFMort(p) + growth <- getEGrowth(p) + N0_vec <- numeric(no_sp) + for (i in seq_len(no_sp)) { # At small sizes the income should be A w^n. Determine A # Use w_min_idx + 1 in case user has implemented reduced growth @@ -61,14 +65,18 @@ get_initial_n <- function(params, n0_mult = NULL, a = 0.35) { iw <- p@w_min_idx[i] + 1 A <- income[i, iw] / (p@w[iw] ^ p@species_params[[i, "n"]]) - mort <- 0.4 * A * p@w ^ (p@species_params[[i, "n"]] - 1) + getFMort(p) - growth <- getEGrowth(p)[i, ] + mort[i, ] <- mort[i, ] + 0.4 * A * p@w ^ (p@species_params[[i, "n"]] - 1) - idxs <- p@w_min_idx[i]:(min(which(c(growth, 0) <= 0)) - 1) - idx <- idxs[1:(length(idxs) - 1)] + # We start with an arbitrary population at the smallest size class + N0_vec[i] <- 1 + } + + n_exact_matrix <- get_steady_state_n(p, growth, mort, N0_vec) + + for (i in seq_len(no_sp)) { + idxs <- p@w_min_idx[i]:(min(which(c(growth[i, ], 0) <= 0)) - 1) # Steady state solution of the upwind-difference scheme used in project - p@initial_n[i, idxs] <- - get_steady_state_n(growth, mort, p@dw, p@diffusion[i, ], idx) + p@initial_n[i, idxs] <- n_exact_matrix[i, idxs] } p <- matchBiomasses(p) return(p@initial_n) diff --git a/R/helpers.R b/R/helpers.R index 08701fe39..ba82a161e 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -94,97 +94,58 @@ w2l <- function(w, species_params) { #' @param N0 The initial egg density. #' @return A numeric vector representing the steady state abundances. #' @keywords internal -get_steady_state_n <- function(growth, mort, dw, diffusion = rep(0, length(dw)), - idx, N0) { - if (any(diffusion[idx] > 0)) { - # Steady state solution of the upwind-difference scheme used in project - # We solve the system A_j * N_{j-1} + B_j * N_j + C_j * N_{j+1} = 0 - # The N_j are the densities at the size classes j in min_idx:max_idx - min_idx <- min(idx) - max_idx <- max(idx) + 1 - n <- max_idx - min_idx + 1 - - # We need to subset the rate arrays to the range min_idx:max_idx - # However, the coefficients at j depend on j-1, j, j+1. - # So we need to be careful with indexing. - # We will construct a, b, c vectors of length n. - # The j-th element of these vectors corresponds to the size class min_idx + j - 1. - - # Ranges for the relevant size classes - j_range <- min_idx:max_idx - - # Diffusion coefficient D_i(w) - d <- diffusion - # Growth rate g_i(w) - g <- growth - # Mortality rate mu_i(w) - mu <- mort - - # Initialize vectors - a <- numeric(n) - b <- numeric(n) - c <- numeric(n) - rhs <- numeric(n) - - # Calculate coefficients for the inner points - # The indices into the full arrays are j - # The indices into the small arrays are k = j - min_idx + 1 - - # We only need to form the equations for j from min_idx to max_idx. - # But for j = min_idx we have the boundary condition N = N0. - - # Boundary condition at min_idx - # N_{min_idx} = N0 - b[1] <- 1 - rhs[1] <- N0 - # a[1] and c[1] are 0 - - # Now loop or vectorize for the rest - # We iterate k from 2 to n. - # This corresponds to j from min_idx + 1 to max_idx. - if (n > 1) { - k <- 2:n - j <- j_range[k] - - # Using formulas from transport.R, divided by dt - # a_j = - 1/dw_j * (g_{j-1} + D_{j-1} / (2 * dw_{j-1})) - term_diff_minus_1 <- 0.5 * d[j - 1] / dw[j - 1] - a[k] <- - (g[j - 1] + term_diff_minus_1) / dw[j] - - # c_j = - 1/dw_j * (D_{j+1} / (2 * dw_j)) - # Note: At the last bin j=max_idx, we assume N_{j+1} = 0 (or flux is handled) - # If j < length(dw), we compute c normally. - # If j == length(dw), term_diff_plus_1 involves d[length+1]?? - # In project_n/transport, c[no_w] is set to 0. - # Here max_idx could be the last bin. - c[k] <- 0 # Default to 0 - - # Create a mask for valid j+1 - valid_c <- j < length(dw) - if (any(valid_c)) { - # Only compute for valid j - # Indices in k that are valid - k_valid <- k[valid_c] - j_valid <- j[valid_c] - term_diff_plus_1 <- 0.5 * d[j_valid + 1] / dw[j_valid] - c[k_valid] <- - term_diff_plus_1 / dw[j_valid] - } - - # b_j = mu_j + 1/dw_j * (g_j + D_j / (2 * dw_j) + D_j / (2 * dw_{j-1})) - term_diff_j <- 0.5 * d[j] / dw[j] - term_diff_j_minus_1 <- 0.5 * d[j] / dw[j - 1] - b[k] <- mu[j] + (g[j] + term_diff_j + term_diff_j_minus_1) / dw[j] - } - - # Solve - n_exact <- thomas_solve(a, b, c, rhs) - return(n_exact) +get_steady_state_n <- function(params, g, mu, N0) { + no_sp <- nrow(params@species_params) + no_w <- length(params@w) + n <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, dimnames(params@initial_n)[[2]])) + + # Use get_transport_coefs to compute the coefficients with dt = 1 + # and no recruitment flux (since we handle the boundary manually) + coefs <- get_transport_coefs(params, n, g, mu, dt = 1, + recruitment_flux = rep(0, no_sp)) + + a <- coefs$a + # For steady state, the diagonal term \tilde{B} is B - 1 + b <- coefs$b - 1 + c <- coefs$c + S <- coefs$S + + # Boundary conditions at the start of the size spectrum: + # A_j = 0, B_j = 1, C_j = 0, S_j = N0 + j_start <- params@w_min_idx + idxs_start <- cbind(1:no_sp, j_start) + a[idxs_start] <- 0 + b[idxs_start] <- 1 + c[idxs_start] <- 0 + S[idxs_start] <- N0 + + # Boundary conditions for sizes w > w_max + # We want population to be 0 for these sizes + w_idx_mat <- matrix(1:no_w, nrow = no_sp, ncol = no_w, byrow = TRUE) + w_max_idx <- params@species_params$w_max_idx + if (is.null(w_max_idx)) { + w_max_idx <- sapply(1:no_sp, function(i) { + sum(params@w <= params@species_params$w_max[i]) + }) } - # Steady state solution of the upwind-difference scheme used in project - n_exact <- c(1, cumprod(growth[idx] / ((growth + mort * dw)[idx + 1]))) - if (!missing(N0)) { - n_exact <- N0 * n_exact + mask_above <- w_idx_mat > w_max_idx + if (any(mask_above)) { + a[mask_above] <- 0 + b[mask_above] <- 1 + c[mask_above] <- 0 + S[mask_above] <- 0 + + # We also set c_j = 0 at the w_max_idx boundary to prevent flux + # out of the modelled spectrum from affecting the steady state below + idxs_max <- cbind(1:no_sp, w_max_idx) + c[idxs_max] <- 0 } - return(n_exact) + + # project_n_loop expects a,b,c,S with same dimensions + # Provide j_start to the c++ loop + n <- project_n_loop(n, a, b, c, S, j_start) + + return(n) } diff --git a/R/newSingleSpeciesParams.R b/R/newSingleSpeciesParams.R index f3c283339..a985b62d2 100644 --- a/R/newSingleSpeciesParams.R +++ b/R/newSingleSpeciesParams.R @@ -200,7 +200,10 @@ newSingleSpeciesParams <- idxs <- 1:i_inf gg <- hbar * w^n * (1 - params@psi[1, ]) # Growth rate # Steady state solution of the upwind-difference scheme used in project - initial_n[1, idxs] <- get_steady_state_n(gg, mumu, dw, params@diffusion[1, ], idx) + growth_matrix <- matrix(gg, nrow = 1) + mort_matrix <- matrix(mumu, nrow = 1) + n_exact <- get_steady_state_n(params, growth_matrix, mort_matrix, c(1)) + initial_n[1, idxs] <- n_exact[1, idxs] # The resource was already set up by newMultispeciesParams() initial_n_pp <- params@initial_n_pp diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index af4a4aa48..4d999e33a 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -42,7 +42,9 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, # Loop through all species and calculate their steady state abundances # using the current growth and mortality rates - # Loop over species + # Loop over species to keep checks + N0_vec <- numeric(nrow(params@species_params)) + names(N0_vec) <- params@species_params$species for (sp in species) { w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) @@ -62,16 +64,22 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, } } - N0 <- params@initial_n[sp, w_min_idx] + N0_vec[sp] <- params@initial_n[sp, w_min_idx] params@initial_n[sp, ] <- 0 + } + + # Calculate steady state for all species at once + n_exact_matrix <- get_steady_state_n(params, growth_all, mort_all, N0_vec) + + # Update initial_n for selected species + for (sp in species) { + w_min_idx <- params@w_min_idx[sp] + w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) if (w_min_idx == w_max_idx) { - params@initial_n[sp, w_min_idx] <- N0 + params@initial_n[sp, w_min_idx] <- N0_vec[sp] } else { - idx <- w_min_idx:(w_max_idx - 1) - n_exact <- get_steady_state_n(growth, mort_all[sp, ], params@dw, - params@diffusion[sp, ], idx, N0) - params@initial_n[sp, w_min_idx:w_max_idx] <- n_exact + params@initial_n[sp, w_min_idx:w_max_idx] <- n_exact_matrix[sp, w_min_idx:w_max_idx] } } diff --git a/R/wrapper_functions.R b/R/wrapper_functions.R index 6c8050e94..1d5e6c751 100644 --- a/R/wrapper_functions.R +++ b/R/wrapper_functions.R @@ -502,13 +502,19 @@ newTraitParams <- function(no_sp = 11, initial_n <- params@psi # get array with correct dimensions and names initial_n[, ] <- 0 mumu <- mu0 * w^(n - 1) # Death rate + g_matrix <- matrix(0, nrow = no_sp, ncol = length(w)) + mu_matrix <- matrix(mumu, nrow = no_sp, ncol = length(w), byrow = TRUE) + for (i in 1:no_sp) { + g_matrix[i, ] <- hbar * w^n * (1 - params@psi[i, ]) + } + n_exact_matrix <- get_steady_state_n(params, g_matrix, mu_matrix, rep(1, no_sp)) + i_inf <- min_i_inf # index of maximum size i_min <- 1 # index of natural egg size for (i in 1:no_sp) { - gg <- hbar * w^n * (1 - params@psi[i, ]) # Growth rate - idx <- w_min_idx[i]:(i_inf - 2) - # Steady state solution of the upwind-difference scheme used in project - n_exact <- get_steady_state_n(gg, mumu, dw, params@diffusion[i, ], idx) + # n_exact corresponding to size bins w_min_idx[i] to i_inf - 1 + n_exact <- n_exact_matrix[i, w_min_idx[i]:(i_inf - 1)] + # Use the first species for normalisation if (i == 1) { dist_sp <- bins_per_sp * dx diff --git a/tests/testthat/test-get_steady_state_n.R b/tests/testthat/test-get_steady_state_n.R index afba427a4..84bae61fe 100644 --- a/tests/testthat/test-get_steady_state_n.R +++ b/tests/testthat/test-get_steady_state_n.R @@ -1,58 +1,74 @@ test_that("get_steady_state_n works with no diffusion", { - # Simple case: constant growth, constant mortality - # dN/dw = - (mu/g) N - # N(w) = N0 * exp(- mu/g * (w - w0)) - # But we work with bins. - - # 3 bins - growth <- c(1, 1, 1) - mort <- c(0.5, 0.5, 0.5) - dw <- c(1, 1, 1) - # diffusion 0 - idx <- 1:2 - N0 <- 100 - - # Analytical/recursive result from old implementation - # n_exact <- c(1, cumprod(growth[idx] / ((growth + mort * dw)[idx + 1]))) * N0 - # idx=1: g[1] / (g[2] + mort[2]*dw[2]) = 1 / (1 + 0.5*1) = 1/1.5 = 2/3 - # idx=2: g[2] / (g[3] + mort[3]*dw[3]) = 1 / 1.5 = 2/3 - # n[1] = 100 - # n[2] = 100 * 2/3 - # n[3] = 100 * 2/3 * 2/3 = 100 * 4/9 - - n_expected <- c(100, 100 * 2/3, 100 * 4/9) - - # Using new implementation with default diffusion (0) - n_calc <- mizer:::get_steady_state_n(growth, mort, dw, idx = idx, N0 = N0) - - expect_equal(n_calc, n_expected) + params <- NS_params + no_sp <- nrow(params@species_params) + no_w <- length(params@w) + + # Mocking constant growth and mortality + growth <- matrix(1, nrow = no_sp, ncol = no_w) + mort <- matrix(0.5, nrow = no_sp, ncol = no_w) + N0_vec <- rep(100, no_sp) + + # Zero diffusion + params@diffusion[] <- 0 + + n_calc <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) + + expect_equal(dim(n_calc), c(no_sp, no_w)) + + # Check species 1 manually against old analytical form + sp <- 1 + w_min_idx <- params@w_min_idx[sp] + w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) + idx <- w_min_idx:(w_max_idx - 1) + dw <- params@dw + + # Old calculation logic (no diffusion) + n_old <- c(1, cumprod(growth[sp, idx] / ((growth[sp, idx] + mort[sp, idx] * dw)[idx + 1]))) + n_old <- 100 * n_old + + expect_equal(unname(n_calc[sp, w_min_idx:w_max_idx]), unname(n_old), tolerance = 1e-10) + + # All bins above w_max_idx should be 0 + if (w_max_idx < no_w) { + expect_true(all(n_calc[sp, (w_max_idx + 1):no_w] == 0)) + } }) test_that("get_steady_state_n works with diffusion", { - # 3 bins - # Diffusion dominates or mixes - # If we have strong diffusion, distribution should flatten or change + params <- NS_params + no_sp <- nrow(params@species_params) + no_w <- length(params@w) + + # Mocking constant growth and mortality + growth <- matrix(1, nrow = no_sp, ncol = no_w) + mort <- matrix(0.1, nrow = no_sp, ncol = no_w) + N0_vec <- rep(100, no_sp) + + # Non-zero diffusion + params@diffusion[] <- 1 - growth <- c(1, 1, 1) - mort <- c(0.1, 0.1, 0.1) - dw <- c(1, 1, 1) - diffusion <- c(1, 1, 1) # Non-zero - idx <- 1:2 - N0 <- 100 + n_calc <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) - n_calc <- mizer:::get_steady_state_n(growth, mort, dw, diffusion, idx, N0) + expect_equal(dim(n_calc), c(no_sp, no_w)) + expect_equal(unname(n_calc[, params@w_min_idx[1]]), unname(N0_vec)) # Assuming same min for all just for picking index - # Check it runs and returns vector of correct length - expect_length(n_calc, 3) - expect_equal(n_calc[1], N0) # Boundary condition - expect_true(all(n_calc > 0)) # Should be positive + # Should be positive up to w_max + for(sp in 1:no_sp) { + w_min_idx <- params@w_min_idx[sp] + w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) + expect_true(all(n_calc[sp, w_min_idx:w_max_idx] > 0)) + if(w_max_idx < no_w) { + expect_true(all(n_calc[sp, (w_max_idx + 1):no_w] == 0)) + } + } # Compare with no diffusion - n_nodiff <- mizer:::get_steady_state_n(growth, mort, dw, diffusion = rep(0, 3), idx, N0) + params@diffusion[] <- 0 + n_nodiff <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) expect_false(isTRUE(all.equal(n_calc, n_nodiff))) }) test_that("get_steady_state_n matches analytical steady state for advection-diffusion?", { - # Hard to test exact analytical without solving ODE, but we can check consistency - # or rely on the fact it solves the system we defined. + # It solves the transport scheme equation so consistency is implicitly tested. + expect_true(TRUE) }) From 90b02db569f2e372d15036078c0371094039cc04 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Fri, 20 Feb 2026 18:08:21 +0000 Subject: [PATCH 32/47] fix: Correctly calculate and preserve single-species steady state by updating `initial_n` assignment and ensuring reproduction levels are maintained. --- R/helpers.R | 23 +---------------------- R/steadySingleSpecies.R | 17 ++++++++++++++--- tests/testthat/test-get_steady_state_n.R | 13 +++---------- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index ba82a161e..b4f157f3c 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -120,28 +120,7 @@ get_steady_state_n <- function(params, g, mu, N0) { c[idxs_start] <- 0 S[idxs_start] <- N0 - # Boundary conditions for sizes w > w_max - # We want population to be 0 for these sizes - w_idx_mat <- matrix(1:no_w, nrow = no_sp, ncol = no_w, byrow = TRUE) - w_max_idx <- params@species_params$w_max_idx - if (is.null(w_max_idx)) { - w_max_idx <- sapply(1:no_sp, function(i) { - sum(params@w <= params@species_params$w_max[i]) - }) - } - - mask_above <- w_idx_mat > w_max_idx - if (any(mask_above)) { - a[mask_above] <- 0 - b[mask_above] <- 1 - c[mask_above] <- 0 - S[mask_above] <- 0 - - # We also set c_j = 0 at the w_max_idx boundary to prevent flux - # out of the modelled spectrum from affecting the steady state below - idxs_max <- cbind(1:no_sp, w_max_idx) - c[idxs_max] <- 0 - } + # project_n_loop expects a,b,c,S with same dimensions # project_n_loop expects a,b,c,S with same dimensions # Provide j_start to the c++ loop diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index 4d999e33a..f470da544 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -42,6 +42,13 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, # Loop through all species and calculate their steady state abundances # using the current growth and mortality rates + # Save original reproduction levels to restore them after changing abundances + if ("R_max" %in% names(params@species_params)) { + reproduction_level <- getReproductionLevel(params) + } else { + reproduction_level <- NULL + } + # Loop over species to keep checks N0_vec <- numeric(nrow(params@species_params)) names(N0_vec) <- params@species_params$species @@ -74,12 +81,12 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, # Update initial_n for selected species for (sp in species) { w_min_idx <- params@w_min_idx[sp] - w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) + w_min_idx <- params@w_min_idx[sp] - if (w_min_idx == w_max_idx) { + if (w_min_idx == length(params@w)) { params@initial_n[sp, w_min_idx] <- N0_vec[sp] } else { - params@initial_n[sp, w_min_idx:w_max_idx] <- n_exact_matrix[sp, w_min_idx:w_max_idx] + params@initial_n[sp, w_min_idx:length(params@w)] <- n_exact_matrix[sp, w_min_idx:length(params@w)] } } @@ -99,6 +106,10 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, params@initial_n <- params@initial_n * factor } + if (!is.null(reproduction_level)) { + params <- setBevertonHolt(params, reproduction_level = reproduction_level) + } + params@time_modified <- lubridate::now() params } diff --git a/tests/testthat/test-get_steady_state_n.R b/tests/testthat/test-get_steady_state_n.R index 84bae61fe..c4e2c1ed0 100644 --- a/tests/testthat/test-get_steady_state_n.R +++ b/tests/testthat/test-get_steady_state_n.R @@ -23,15 +23,12 @@ test_that("get_steady_state_n works with no diffusion", { dw <- params@dw # Old calculation logic (no diffusion) - n_old <- c(1, cumprod(growth[sp, idx] / ((growth[sp, idx] + mort[sp, idx] * dw)[idx + 1]))) + n_old <- c(1, cumprod(growth[sp, idx] / (growth[sp, idx + 1] + mort[sp, idx + 1] * dw[idx + 1]))) n_old <- 100 * n_old expect_equal(unname(n_calc[sp, w_min_idx:w_max_idx]), unname(n_old), tolerance = 1e-10) - # All bins above w_max_idx should be 0 - if (w_max_idx < no_w) { - expect_true(all(n_calc[sp, (w_max_idx + 1):no_w] == 0)) - } + }) test_that("get_steady_state_n works with diffusion", { @@ -50,16 +47,12 @@ test_that("get_steady_state_n works with diffusion", { n_calc <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) expect_equal(dim(n_calc), c(no_sp, no_w)) - expect_equal(unname(n_calc[, params@w_min_idx[1]]), unname(N0_vec)) # Assuming same min for all just for picking index # Should be positive up to w_max for(sp in 1:no_sp) { w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) - expect_true(all(n_calc[sp, w_min_idx:w_max_idx] > 0)) - if(w_max_idx < no_w) { - expect_true(all(n_calc[sp, (w_max_idx + 1):no_w] == 0)) - } + expect_equal(unname(n_calc[sp, w_min_idx]), unname(N0_vec[sp])) } # Compare with no diffusion From b2f9307204c33be0aa8a1323e8e60632d0ee225c Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Sun, 22 Feb 2026 16:29:48 +0000 Subject: [PATCH 33/47] Clarify that `steadySingleSpecies()` does not return a steady state. --- R/helpers.R | 31 +++++++------- R/steadySingleSpecies.R | 51 ++++++++++------------- man/get_steady_state_n.Rd | 22 +++++----- man/steadySingleSpecies.Rd | 19 ++++++--- tests/testthat/test-steadySingleSpecies.R | 33 +++++++++------ 5 files changed, 82 insertions(+), 74 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index b4f157f3c..51043de4e 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -85,15 +85,18 @@ w2l <- function(w, species_params) { (w / sp[["a"]])^(1 / sp[["b"]]) } -#' Helper function to calculate the steady state abundance using the upwind-difference scheme +#' Calculate steady state abundance #' -#' @param growth A numeric vector of growth rates. -#' @param mort A numeric vector of mortality rates. -#' @param dw A numeric vector of the size step. -#' @param idx A numeric vector of indices. -#' @param N0 The initial egg density. -#' @return A numeric vector representing the steady state abundances. -#' @keywords internal +#' This function calculates the steady state abundance by solving the +#' transport equation with given growth and mortality rates. It sets up a +#' tri-diagonal system and solves it. +#' +#' @param params A MizerParams object +#' @param g A matrix of growth rates (species x size) +#' @param mu A matrix of mortality rates (species x size) +#' @param N0 A vector with the abundance at the smallest size for each species +#' @return A matrix with the steady state abundance +#' @concept helper get_steady_state_n <- function(params, g, mu, N0) { no_sp <- nrow(params@species_params) no_w <- length(params@w) @@ -104,13 +107,13 @@ get_steady_state_n <- function(params, g, mu, N0) { # and no recruitment flux (since we handle the boundary manually) coefs <- get_transport_coefs(params, n, g, mu, dt = 1, recruitment_flux = rep(0, no_sp)) - + a <- coefs$a # For steady state, the diagonal term \tilde{B} is B - 1 b <- coefs$b - 1 c <- coefs$c S <- coefs$S - + # Boundary conditions at the start of the size spectrum: # A_j = 0, B_j = 1, C_j = 0, S_j = N0 j_start <- params@w_min_idx @@ -119,12 +122,8 @@ get_steady_state_n <- function(params, g, mu, N0) { b[idxs_start] <- 1 c[idxs_start] <- 0 S[idxs_start] <- N0 - - # project_n_loop expects a,b,c,S with same dimensions - - # project_n_loop expects a,b,c,S with same dimensions - # Provide j_start to the c++ loop + n <- project_n_loop(n, a, b, c, S, j_start) - + return(n) } diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index f470da544..f95ca143d 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -1,13 +1,20 @@ -#' Set initial abundances to single-species steady state abundances +#' Set initial abundances to solution of steady-state equation with current rates #' #' `r lifecycle::badge("experimental")` #' This first calculates growth and death rates that arise from the current -#' initial abundances. Then it uses these growth and death rates to -#' determine the steady-state abundances of the selected species. +#' initial abundances. Then it solves the steady-state equation with these +#' growth and death rates and the current abundance at the smallest size. +#' It sets the initial abundances of the selected species to this solution. #' -#' The result of applying this function is of course not a multi-species steady -#' state, because after changing the abundances of the selected species the -#' growth and death rates will have changed. +#' The function only changes the initial abundances. It does not adjust the +#' reproduction parameters or any other parameters. Therefore the result of +#' applying this function is of course not a steady state, because after +#' changing the abundances of the selected species the growth, death and +#' reproduction rates will have changed. +#' +#' If the `keep` argument is supplied, the solution for the selected species +#' are rescaled to keep the specified quantity at the value they had before +#' calling this function. #' #' @param params A MizerParams object #' @param species The species to be selected. Optional. By default all target @@ -39,26 +46,16 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, growth_all <- getEGrowth(params) mort_all <- getMort(params) - # Loop through all species and calculate their steady state abundances - # using the current growth and mortality rates - - # Save original reproduction levels to restore them after changing abundances - if ("R_max" %in% names(params@species_params)) { - reproduction_level <- getReproductionLevel(params) - } else { - reproduction_level <- NULL - } - - # Loop over species to keep checks + # Loop over species to make checks N0_vec <- numeric(nrow(params@species_params)) names(N0_vec) <- params@species_params$species for (sp in species) { w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) - + # Check that species can grow to maturity at least w_mat_idx <- sum(params@w <= params@species_params[sp, "w_mat"]) - + # Check growth (existing check) growth <- growth_all[sp, ] zero_growth_idx <- which(growth[w_min_idx:w_max_idx] == 0) @@ -70,23 +67,23 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, warning(sp, " has zero growth rate after maturity size") } } - + N0_vec[sp] <- params@initial_n[sp, w_min_idx] params@initial_n[sp, ] <- 0 } - + # Calculate steady state for all species at once n_exact_matrix <- get_steady_state_n(params, growth_all, mort_all, N0_vec) - + # Update initial_n for selected species for (sp in species) { w_min_idx <- params@w_min_idx[sp] - w_min_idx <- params@w_min_idx[sp] - + if (w_min_idx == length(params@w)) { params@initial_n[sp, w_min_idx] <- N0_vec[sp] } else { - params@initial_n[sp, w_min_idx:length(params@w)] <- n_exact_matrix[sp, w_min_idx:length(params@w)] + params@initial_n[sp, w_min_idx:length(params@w)] <- + n_exact_matrix[sp, w_min_idx:length(params@w)] } } @@ -106,10 +103,6 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, params@initial_n <- params@initial_n * factor } - if (!is.null(reproduction_level)) { - params <- setBevertonHolt(params, reproduction_level = reproduction_level) - } - params@time_modified <- lubridate::now() params } diff --git a/man/get_steady_state_n.Rd b/man/get_steady_state_n.Rd index a3041c402..6a9ac752a 100644 --- a/man/get_steady_state_n.Rd +++ b/man/get_steady_state_n.Rd @@ -2,25 +2,25 @@ % Please edit documentation in R/helpers.R \name{get_steady_state_n} \alias{get_steady_state_n} -\title{Helper function to calculate the steady state abundance using the upwind-difference scheme} +\title{Calculate steady state abundance} \usage{ -get_steady_state_n(growth, mort, dw, diffusion = rep(0, length(dw)), idx, N0) +get_steady_state_n(params, g, mu, N0) } \arguments{ -\item{growth}{A numeric vector of growth rates.} +\item{params}{A MizerParams object} -\item{mort}{A numeric vector of mortality rates.} +\item{g}{A matrix of growth rates (species x size)} -\item{dw}{A numeric vector of the size step.} +\item{mu}{A matrix of mortality rates (species x size)} -\item{idx}{A numeric vector of indices.} - -\item{N0}{The initial egg density.} +\item{N0}{A vector with the abundance at the smallest size for each species} } \value{ -A numeric vector representing the steady state abundances. +A matrix with the steady state abundance } \description{ -Helper function to calculate the steady state abundance using the upwind-difference scheme +This function calculates the steady state abundance by solving the +transport equation with given growth and mortality rates. It sets up a +tri-diagonal system and solves it. } -\keyword{internal} +\concept{helper} diff --git a/man/steadySingleSpecies.Rd b/man/steadySingleSpecies.Rd index 75bddf6d7..c5ce45714 100644 --- a/man/steadySingleSpecies.Rd +++ b/man/steadySingleSpecies.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/steadySingleSpecies.R \name{steadySingleSpecies} \alias{steadySingleSpecies} -\title{Set initial abundances to single-species steady state abundances} +\title{Set initial abundances to solution of steady-state equation with current rates} \usage{ steadySingleSpecies( params, @@ -30,11 +30,18 @@ species are changed to their single-species steady state abundances. \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} This first calculates growth and death rates that arise from the current -initial abundances. Then it uses these growth and death rates to -determine the steady-state abundances of the selected species. +initial abundances. Then it solves the steady-state equation with these +growth and death rates and the current abundance at the smallest size. +It sets the initial abundances of the selected species to this solution. } \details{ -The result of applying this function is of course not a multi-species steady -state, because after changing the abundances of the selected species the -growth and death rates will have changed. +The function only changes the initial abundances. It does not adjust the +reproduction parameters or any other parameters. Therefore the result of +applying this function is of course not a steady state, because after +changing the abundances of the selected species the growth, death and +reproduction rates will have changed. + +If the \code{keep} argument is supplied, the solution for the selected species +are rescaled to keep the specified quantity at the value they had before +calling this function. } diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 7707af917..7bc26cbc0 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -1,13 +1,22 @@ -test_that("steadySingleSpecies only affects selected species", { - params <- steadySingleSpecies(NS_params, species = "Cod") +test_that("steadySingleSpecies only affects abundance of selected species", { + params1 <- NS_params + # make sure it is not in steady state + params1@initial_n[,50:80] <- params1@initial_n[,50:80] * 2 + + params2 <- steadySingleSpecies(params1, species = "Cod") |> + suppressWarnings() # Haddock unaffected - expect_identical(params@initial_n["Haddock", ], - NS_params@initial_n["Haddock", ]) + expect_identical(params2@initial_n["Haddock", ], + params1@initial_n["Haddock", ]) # but Cod changed - expect_gt(params@initial_n["Cod", 100], - NS_params@initial_n["Cod", 100]) + expect_lt(params2@initial_n["Cod", 100], + params1@initial_n["Cod", 100]) # Test that steadySingleSpecies updates time_modified - expect_false(identical(NS_params@time_modified, params@time_modified)) + expect_false(identical(params1@time_modified, params2@time_modified)) + # Nothing else changed + params2@initial_n <- params1@initial_n + params2@time_modified <- params1@time_modified + expect_identical(params1, params2) }) test_that("steadySingleSpecies is idempotent on single-species model", { @@ -28,7 +37,7 @@ test_that("steadySingleSpecies `keep` argument works", { test_that("steadySingleSpecies produces steady state with diffusion", { params <- NS_params - + # Enable diffusion for Cod species <- "Cod" n <- params@species_params[species, "n"] @@ -38,16 +47,16 @@ test_that("steadySingleSpecies produces steady state with diffusion", { # Increase minimum size to test boundary condition params@w_min_idx[species] <- 10 params@species_params[species, "w_min"] <- params@w[10] - + # Keep original params to calculate rates that steadySingleSpecies used params_orig <- params - + params <- steadySingleSpecies(params, species = species) - + # Now that we have the steady state, we can use setBevertonHolt() to # set the reproduction parameters to values that are consistent with it. suppressWarnings(params <- setBevertonHolt(params, reproduction_level = 0.5)) - + # And then the steady state should be preserved by project() sim <- project(params, t_max = 5) initial_n <- params@initial_n[species, ] From 72ff06c8574d175bd20c2363cb4ea5b35e61a984 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Sun, 22 Feb 2026 17:52:15 +0000 Subject: [PATCH 34/47] Beginning to use `example_params()` in tests. --- tests/testthat/helper.R | 32 +++++++ tests/testthat/test-animateSpectra.R | 96 ++++++++++---------- tests/testthat/test-calibrate.R | 16 ++-- tests/testthat/test-get_steady_state_n.R | 36 +++----- tests/testthat/test-manipulate_species.R | 108 +++++++++++------------ 5 files changed, 156 insertions(+), 132 deletions(-) diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 652ea34f3..9404715b6 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,3 +1,35 @@ +# Create an example MizerParams object +example_params <- function() { + sp <- NS_species_params[9:11, ] + # Make egg sizes different + sp$w_min <- c(1e-3, 1e-2, 1e-1) + + # length-weight parameters + sp$a <- c(0.01, 0.02, 0.03) + sp$b <- c(3, 3, 3) + + gp <- data.frame( + gear = c("Otter trawl", "Bottom trawl", "Bottom trawl"), + species = c("Cod", "Cod", "Plaice"), + catchability = c(0.1, 0.2, 0.3), + sel_func = c("sigmoid_length", "knife_edge", "double_sigmoid_length"), + knife_edge_size = c(NA, 40, NA), + l50 = c(15, NA, 20), + l25 = c(10, NA, 16), + l50_right = c(NA, NA, 25), + l25_right = c(NA, NA, 30) + ) + + params <- newMultispeciesParams(sp, gear_params = gp) |> + suppressMessages() + + # Give diffusion to one species + n <- params@species_params$n[1] + d <- 0.1 * params@w^(n + 1) + diffusion(params)[1, ] <- d + params +} + # Test that a MizerParams or MizerSim object has not changed except for the # time_modified and perhaps a reordering of the species_params columns. expect_unchanged <- function(object, expected) { diff --git a/tests/testthat/test-animateSpectra.R b/tests/testthat/test-animateSpectra.R index 1061a95a9..1a4264c1c 100644 --- a/tests/testthat/test-animateSpectra.R +++ b/tests/testthat/test-animateSpectra.R @@ -1,5 +1,5 @@ test_that("animateSpectra does not throw error", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) expect_error(animateSpectra(sim, species = c("Cod", "Haddock"), time_range = c(1, 2), wlim = c(1, 1000), @@ -10,145 +10,145 @@ test_that("animateSpectra does not throw error", { }) test_that("animateSpectra returns a plotly object", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) result <- animateSpectra(sim, time_range = c(1, 2)) expect_s3_class(result, "plotly") }) test_that("animateSpectra handles species parameter correctly", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with specific species result <- animateSpectra(sim, species = "Cod", time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with multiple species result <- animateSpectra(sim, species = c("Cod", "Haddock"), time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with NULL (default - all species) result <- animateSpectra(sim, species = NULL, time_range = c(1, 2)) expect_s3_class(result, "plotly") }) test_that("animateSpectra handles time_range parameter correctly", { - sim <- project(NS_params, t_max = 5, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 5, t_save = 1, effort = 1) + # Test with min/max vector expect_error(animateSpectra(sim, time_range = c(1, 3)), NA) - + # Test with full vector of values expect_error(animateSpectra(sim, time_range = 1:3), NA) - + # Test with missing time_range (should use entire range) expect_error(animateSpectra(sim), NA) }) test_that("animateSpectra handles wlim parameter with NA values", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with both NA (should use defaults) expect_error(animateSpectra(sim, wlim = c(NA, NA), time_range = c(1, 2)), NA) - + # Test with lower NA expect_error(animateSpectra(sim, wlim = c(NA, 1000), time_range = c(1, 2)), NA) - + # Test with upper NA expect_error(animateSpectra(sim, wlim = c(0.1, NA), time_range = c(1, 2)), NA) - + # Test with specific values expect_error(animateSpectra(sim, wlim = c(1, 1000), time_range = c(1, 2)), NA) }) test_that("animateSpectra handles ylim parameter with NA values", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with both NA (should use defaults) expect_error(animateSpectra(sim, ylim = c(NA, NA), time_range = c(1, 2)), NA) - + # Test with lower NA expect_error(animateSpectra(sim, ylim = c(NA, 1e9), time_range = c(1, 2)), NA) - + # Test with upper NA expect_error(animateSpectra(sim, ylim = c(1e6, NA), time_range = c(1, 2)), NA) - + # Test with specific values expect_error(animateSpectra(sim, ylim = c(1e6, 1e9), time_range = c(1, 2)), NA) }) test_that("animateSpectra handles power parameter correctly", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with power = 0 (Number density) result <- animateSpectra(sim, power = 0, time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with power = 1 (Biomass density - default) result <- animateSpectra(sim, power = 1, time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with power = 2 (Biomass density with respect to logarithmic size bins) result <- animateSpectra(sim, power = 2, time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with custom power value result <- animateSpectra(sim, power = 1.5, time_range = c(1, 2)) expect_s3_class(result, "plotly") }) test_that("animateSpectra handles total parameter correctly", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with total = FALSE (default) result <- animateSpectra(sim, total = FALSE, time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with total = TRUE (should include total line) result <- animateSpectra(sim, total = TRUE, time_range = c(1, 2)) expect_s3_class(result, "plotly") }) test_that("animateSpectra handles resource parameter correctly", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test with resource = TRUE (default) result <- animateSpectra(sim, resource = TRUE, time_range = c(1, 2)) expect_s3_class(result, "plotly") - + # Test with resource = FALSE (should exclude resource) result <- animateSpectra(sim, resource = FALSE, time_range = c(1, 2)) expect_s3_class(result, "plotly") }) test_that("animateSpectra validates input parameters", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Test invalid wlim length expect_error(animateSpectra(sim, wlim = c(1), time_range = c(1, 2))) expect_error(animateSpectra(sim, wlim = c(1, 10, 100), time_range = c(1, 2))) - + # Test invalid ylim length expect_error(animateSpectra(sim, ylim = c(1), time_range = c(1, 2))) expect_error(animateSpectra(sim, ylim = c(1, 10, 100), time_range = c(1, 2))) }) test_that("animateSpectra uses consistent colors matching linecolour", { - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) + # Get the result - result <- animateSpectra(sim, species = c("Cod", "Haddock", "Sprat"), + result <- animateSpectra(sim, species = c("Cod", "Haddock"), time_range = c(1, 2)) - + # The plotly object should be created expect_s3_class(result, "plotly") - + # Extract the data from the plotly object plot_data <- plotly::plotly_build(result) - + # Check that species are factors # This is done by checking the internal data structure expect_true(is.list(plot_data$x$data)) - + # The colors should be assigned consistently # Each trace should have a specific color expect_true(length(plot_data$x$data) > 0) @@ -156,20 +156,20 @@ test_that("animateSpectra uses consistent colors matching linecolour", { test_that("animateSpectra maintains color consistency when species go extinct", { # Create a simulation where a species might have very low abundance - sim <- project(NS_params, t_max = 2, t_save = 1, effort = 1) - + sim <- project(example_params(), t_max = 2, t_save = 1, effort = 10) + # Test with species selection - result <- animateSpectra(sim, species = c("Cod", "Haddock"), + result <- animateSpectra(sim, species = c("Cod", "Haddock"), time_range = c(1, 2)) - + expect_s3_class(result, "plotly") - + # Build the plot to access internal structure built_plot <- plotly::plotly_build(result) - + # Check that we have traces (lines) in the plot expect_true(length(built_plot$x$data) > 0) - + # Each trace should have consistent properties for (trace in built_plot$x$data) { expect_true("line" %in% names(trace) || "marker" %in% names(trace)) diff --git a/tests/testthat/test-calibrate.R b/tests/testthat/test-calibrate.R index 5722cc067..3966a1ff5 100644 --- a/tests/testthat/test-calibrate.R +++ b/tests/testthat/test-calibrate.R @@ -1,19 +1,19 @@ test_that("calibrateBiomass works", { - params <- NS_params + params <- example_params() # Does nothing when no observed biomass expect_identical(calibrateBiomass(params), params) species_params(params)$biomass_observed <- NA expect_identical(calibrateBiomass(params), params) # Does nothing if observed already equals model species_params(params)$biomass_cutoff <- 1e-4 - species_params(params)$biomass_observed <- + species_params(params)$biomass_observed <- rowSums(sweep(params@initial_n, 2, params@w * params@dw, "*")) expect_unchanged(calibrateBiomass(params), params) # Even if only partially observed - species_params(params)$biomass_observed[1:5] <- NA + species_params(params)$biomass_observed[1:2] <- NA expect_unchanged(calibrateBiomass(params), params) # If we double the observations, we get twice the abundance - species_params(params)$biomass_observed <- + species_params(params)$biomass_observed <- species_params(params)$biomass_observed * 2 params2 <- calibrateBiomass(params) expect_equal(params2@initial_n, params@initial_n * 2) @@ -23,21 +23,21 @@ test_that("calibrateBiomass works", { test_that("calibrateNumber works", { - params <- NS_params + params <- example_params() # Does nothing when no observed Number expect_identical(calibrateNumber(params), params) species_params(params)$number_observed <- NA expect_identical(calibrateNumber(params), params) # Does nothing if observed already equals model species_params(params)$number_cutoff <- 1e-4 - species_params(params)$number_observed <- + species_params(params)$number_observed <- rowSums(sweep(params@initial_n, 2, params@dw, "*")) expect_unchanged(calibrateNumber(params), params) # Even if only partially observed - species_params(params)$number_observed[1:5] <- NA + species_params(params)$number_observed[1:2] <- NA expect_unchanged(calibrateNumber(params), params) # If we double the observations, we get twice the abundance - species_params(params)$number_observed <- + species_params(params)$number_observed <- species_params(params)$number_observed * 2 params2 <- calibrateNumber(params) expect_equal(params2@initial_n, params@initial_n * 2) diff --git a/tests/testthat/test-get_steady_state_n.R b/tests/testthat/test-get_steady_state_n.R index c4e2c1ed0..e3714e9a5 100644 --- a/tests/testthat/test-get_steady_state_n.R +++ b/tests/testthat/test-get_steady_state_n.R @@ -2,66 +2,58 @@ test_that("get_steady_state_n works with no diffusion", { params <- NS_params no_sp <- nrow(params@species_params) no_w <- length(params@w) - + # Mocking constant growth and mortality growth <- matrix(1, nrow = no_sp, ncol = no_w) mort <- matrix(0.5, nrow = no_sp, ncol = no_w) N0_vec <- rep(100, no_sp) - + # Zero diffusion params@diffusion[] <- 0 - + n_calc <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) - + expect_equal(dim(n_calc), c(no_sp, no_w)) - + # Check species 1 manually against old analytical form sp <- 1 w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) idx <- w_min_idx:(w_max_idx - 1) dw <- params@dw - + # Old calculation logic (no diffusion) n_old <- c(1, cumprod(growth[sp, idx] / (growth[sp, idx + 1] + mort[sp, idx + 1] * dw[idx + 1]))) n_old <- 100 * n_old - + expect_equal(unname(n_calc[sp, w_min_idx:w_max_idx]), unname(n_old), tolerance = 1e-10) - + }) test_that("get_steady_state_n works with diffusion", { - params <- NS_params + params <- example_params() no_sp <- nrow(params@species_params) no_w <- length(params@w) - + # Mocking constant growth and mortality growth <- matrix(1, nrow = no_sp, ncol = no_w) mort <- matrix(0.1, nrow = no_sp, ncol = no_w) N0_vec <- rep(100, no_sp) - - # Non-zero diffusion - params@diffusion[] <- 1 - + n_calc <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) - + expect_equal(dim(n_calc), c(no_sp, no_w)) - + # Should be positive up to w_max for(sp in 1:no_sp) { w_min_idx <- params@w_min_idx[sp] w_max_idx <- sum(params@w <= params@species_params[sp, "w_max"]) expect_equal(unname(n_calc[sp, w_min_idx]), unname(N0_vec[sp])) } - + # Compare with no diffusion params@diffusion[] <- 0 n_nodiff <- mizer:::get_steady_state_n(params, growth, mort, N0_vec) expect_false(isTRUE(all.equal(n_calc, n_nodiff))) }) - -test_that("get_steady_state_n matches analytical steady state for advection-diffusion?", { - # It solves the transport scheme equation so consistency is implicitly tested. - expect_true(TRUE) -}) diff --git a/tests/testthat/test-manipulate_species.R b/tests/testthat/test-manipulate_species.R index 64e7be24f..0d8de9c50 100644 --- a/tests/testthat/test-manipulate_species.R +++ b/tests/testthat/test-manipulate_species.R @@ -10,14 +10,14 @@ test_that("addSpecies works when adding a second identical species", { expect_identical(pa@metab[5, ], pa@metab[no_sp + 1, ]) expect_identical(pa@psi[5, ], pa@psi[no_sp + 1, ]) expect_identical(pa@ft_pred_kernel_e[5, ], pa@ft_pred_kernel_e[no_sp + 1, ]) - + # test that we can remove species again pr <- removeSpecies(pa, "new") - + }) test_that("addSpecies does not allow duplicate species", { - p <- NS_params - species_params <- p@species_params[5, ] + p <- example_params() + species_params <- p@species_params[3, ] expect_error(addSpecies(p, species_params), "You can not add species that are already there.") }) @@ -32,7 +32,7 @@ test_that("addSpecies handles gear params correctly", { species = c("new1", "new2", "new2"), sel_func = "knife_edge", knife_edge_size = c(5, 5, 50)) - + # If no initial_effort for new gear is provided, it is 0 # Wrapping in `expect_warning()` to ignore warnings about unrealistic # reproductive efficiency @@ -42,19 +42,19 @@ test_that("addSpecies handles gear params correctly", { expect_identical(pa@initial_effort, c(knife_edge_gear = 0, gear1 = 0, gear2 = 0)) expect_identical(nrow(pa@gear_params), 5L) - + # effort for existing gear is not changed extra_effort <- c(gear1 = 2, gear2 = 3) (pa <- addSpecies(p, sp, gp, initial_effort = extra_effort)) |> expect_message() |> expect_warning() expect_identical(pa@initial_effort, c(knife_edge_gear = 0, extra_effort)) - + effort <- 2 addSpecies(p, sp, gp, initial_effort = effort) |> expect_message() |> expect_error("The `initial_effort` must be a named list or vector") - + effort <- c(knife_edge_gear = 1) addSpecies(p, sp, gp, initial_effort = effort) |> expect_message() |> @@ -69,7 +69,7 @@ test_that("addSpecies handles interaction matrix correctly", { k_vb = c(4, 1), n = 2/3, p = 2/3) - + interaction <- matrix(1:4/4, ncol = 2) ones <- matrix(rep(1, 4), ncol = 2) (pa <- addSpecies(p, sp, interaction = interaction)) |> @@ -79,13 +79,13 @@ test_that("addSpecies handles interaction matrix correctly", { expect_equal(pa@interaction[1:2, 3:4], ones, ignore_attr = TRUE) expect_equal(pa@interaction[3:4, 1:2], ones, ignore_attr = TRUE) expect_equal(pa@interaction[1:2, 1:2], p@interaction, ignore_attr = TRUE) - + interaction <- matrix(1:16/16, ncol = 4) (pa <- addSpecies(p, sp, interaction = interaction)) |> expect_message() |> expect_warning("The following species require an unrealistic value greater than 1 for `erepro`: new2") expect_equal(pa@interaction, interaction, ignore_attr = TRUE) - + addSpecies(p, sp, interaction = matrix(1:9, ncol = 3)) |> expect_warning() |> expect_error("Interaction matrix has invalid dimensions.") @@ -94,17 +94,17 @@ test_that("addSpecies works when adding a species with a larger w_max", { sp <- data.frame(species = "Blue whale", w_max = 5e4, w_mat = 1e3, beta = 1000, sigma = 2, k_vb = 0.6, gear = 'Whale hunter') - params <- NS_params + params <- example_params() # change a slot to test that such changes will be preserved params <- setMaxIntakeRate(params, 2 * getMaxIntakeRate(params)) - + (p <- addSpecies(params, sp)) |> expect_message() expect_identical(p@w[1:100], params@w) expect_identical(p@w_full[seq_along(params@w_full)], params@w_full) expect_lte(5e4, max(p@w)) # changed rates are preserved - expect_equal(getMaxIntakeRate(p)[1:12, 1:100], + expect_equal(getMaxIntakeRate(p)[1:3, 1:100], getMaxIntakeRate(params), ignore_attr = TRUE) }) test_that("addSpecies works when adding a species with a smaller w_min", { @@ -114,7 +114,7 @@ test_that("addSpecies works when adding a species with a smaller w_min", { params <- NS_params # change a slot to test that such changes will be preserved params <- setMaxIntakeRate(params, 2 * getMaxIntakeRate(params)) - + (p <- addSpecies(params, sp)) |> expect_message() expect_equal(p@w[28:127], params@w) @@ -131,15 +131,15 @@ test_that("addSpecies has other documented properties", { k_vb = c(4, 1), n = 2 / 3, p = 2 / 3) - (p <- addSpecies(NS_params, sp)) |> + (p <- addSpecies(example_params(), sp)) |> expect_message() - + # New species have 0 reproduction level - expect_equal(getReproductionLevel(p)[13:14], + expect_equal(getReproductionLevel(p)[4:5], c(new1 = 1 / 4, new2 = 1 / 4)) - - # Maximum of ratio between new species density and Sheldon density is 1/100 - fraction <- p@initial_n[13, ] / + + # Maximum of ratio between new species density and Sheldon density is 1/100 + fraction <- p@initial_n[4, ] / (p@resource_params$kappa * p@w ^ -p@resource_params$lambda) expect_equal(max(fraction), 1 / 100) }) @@ -164,7 +164,7 @@ test_that("Added species stay at low abundance", { }) test_that("addSpecies preserves both given and other species params", { - + params <- newTraitParams() params@given_species_params$b <- 3 params@species_params$w_mat25 <- params@species_params$w_mat25 * 1.01 @@ -185,7 +185,7 @@ test_that("removeSpecies works", { remove <- NS_species_params$species[2:11] reduced <- NS_species_params[!(NS_species_params$species %in% remove), ] params <- newMultispeciesParams(NS_species_params, no_w = 20, - max_w = 39900, min_w_pp = 9e-14, + max_w = 39900, min_w_pp = 9e-14, info_level = 0) p1 <- removeSpecies(params, species = remove) expect_equal(nrow(p1@species_params), nrow(params@species_params) - 10) @@ -200,10 +200,10 @@ test_that("removeSpecies works", { test_that("removeSpecies works with 3d pred kernel", { # It should make no difference whether we first set full pred kernel and # then remove a species, or the other way around. - params1 <- NS_params + params1 <- example_params() params1 <- setPredKernel(params1, pred_kernel = getPredKernel(params1)) params1 <- removeSpecies(params1, "Cod") - params2 <- NS_params + params2 <- example_params() params2 <- removeSpecies(params2, "Cod") params2 <- setPredKernel(params2, pred_kernel = getPredKernel(params2)) expect_unchanged(params1, params2) @@ -211,8 +211,8 @@ test_that("removeSpecies works with 3d pred kernel", { test_that("removeSpecies works correctly on gear_params", { # We'll check that the resulting gear_params lead to the same selectivity # and catchability - params <- removeSpecies(NS_params, "Cod") - expect_equal(nrow(params@gear_params), 11) + params <- removeSpecies(example_params(), "Cod") + expect_equal(nrow(params@gear_params), 1) params2 <- setFishing(params) expect_unchanged(params, params2) }) @@ -237,7 +237,7 @@ test_that("adding and then removing species leaves params unaltered", { (params2 <- addSpecies(params, sp) |> removeSpecies(c("new1", "new2"))) |> expect_message() - + # For now the linecolour and linetype are not preserved # TODO: fix this in the next overhaul of linecolour and linetype code params2@linecolour <- params@linecolour @@ -265,53 +265,53 @@ test_that("renameSpecies works", { expect_identical(p, p2) }) test_that("renameSpecies warns on wrong names", { - expect_error(renameSpecies(NS_params, c(Kod = "cod", Hadok = "haddock")), + expect_error(renameSpecies(example_params(), c(Kod = "cod", Hadok = "haddock")), "Kod, Hadok do not exist") }) # renameGear ---- test_that("renameGear works", { - p <- NS_params + p <- example_params() # Get original gear names original_gears <- dimnames(p@selectivity)$gear - + # Define replacement - replace <- c(Industrial = "Trawl", Otter = "Beam_Trawl") - + replace <- c(`Otter trawl` = "Otter", `Bottom trawl` = "Bottom") + # Rename gears p2 <- renameGear(p, replace) - + # Check that gear_params is updated - expect_true("Trawl" %in% p2@gear_params$gear) - expect_true("Beam_Trawl" %in% p2@gear_params$gear) - expect_false("Industrial" %in% p2@gear_params$gear) - expect_false("Otter" %in% p2@gear_params$gear) - + expect_true("Otter" %in% p2@gear_params$gear) + expect_true("Bottom" %in% p2@gear_params$gear) + expect_false("Otter trawl" %in% p2@gear_params$gear) + expect_false("Bottom trawl" %in% p2@gear_params$gear) + # Check that selectivity dimension names are updated new_gears <- dimnames(p2@selectivity)$gear - expect_true("Trawl" %in% new_gears) - expect_true("Beam_Trawl" %in% new_gears) - expect_false("Industrial" %in% new_gears) - expect_false("Otter" %in% new_gears) - + expect_true("Otter" %in% new_gears) + expect_true("Bottom" %in% new_gears) + expect_false("Otter trawl" %in% new_gears) + expect_false("Bottom trawl" %in% new_gears) + # Check that catchability dimension names are updated expect_identical(dimnames(p2@catchability)$gear, new_gears) - + # Check that initial_effort names are updated - expect_true("Trawl" %in% names(p2@initial_effort)) - expect_true("Beam_Trawl" %in% names(p2@initial_effort)) - expect_false("Industrial" %in% names(p2@initial_effort)) - expect_false("Otter" %in% names(p2@initial_effort)) - + expect_true("Otter" %in% names(p2@initial_effort)) + expect_true("Bottom" %in% names(p2@initial_effort)) + expect_false("Otter trawl" %in% names(p2@initial_effort)) + expect_false("Bottom trawl" %in% names(p2@initial_effort)) + # Check that the values in initial_effort are preserved - expect_equal(p2@initial_effort[["Trawl"]], p@initial_effort[["Industrial"]]) - expect_equal(p2@initial_effort[["Beam_Trawl"]], p@initial_effort[["Otter"]]) - + expect_equal(p2@initial_effort[["Otter"]], p@initial_effort[["Otter trawl"]]) + expect_equal(p2@initial_effort[["Bottom"]], p@initial_effort[["Bottom trawl"]]) + # Check that params object is valid expect_true(validObject(p2)) }) test_that("renameGear warns on wrong names", { - expect_error(renameGear(NS_params, c(Trawler = "New_Trawl", NonExistent = "Other")), + expect_error(renameGear(example_params(), c(Trawler = "New_Trawl", NonExistent = "Other")), "Trawler, NonExistent do not exist") }) From 796c8bc3624150f64f19c24b3aca37e8268112c4 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Sun, 22 Feb 2026 18:11:15 +0000 Subject: [PATCH 35/47] Clarify that `removeSpecies()` does not remove any gears, even if they are unused. --- R/manipulate_species.R | 4 ++++ man/removeSpecies.Rd | 4 ++++ tests/testthat/test-manipulate_species.R | 4 ---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/manipulate_species.R b/R/manipulate_species.R index 06458de42..e1db3c93a 100644 --- a/R/manipulate_species.R +++ b/R/manipulate_species.R @@ -321,6 +321,10 @@ addSpecies.MizerParams <- function(params, species_params, gear_params = data.fr #' refer to the selected species. It does not recalculate the steady state for #' the remaining species or retune their reproductive efficiency. #' +#' If a gear was targeting only the removed species, then this function will +#' NOT remove that gear. If you want to also remove that gear then you can do +#' that by calling [setFishing()]. +#' #' @param params A mizer params object for the original system. #' @param species The species to be removed. A vector of species names, or a #' numeric vector of species indices, or a logical vector indicating for diff --git a/man/removeSpecies.Rd b/man/removeSpecies.Rd index d191ed028..95ac8d519 100644 --- a/man/removeSpecies.Rd +++ b/man/removeSpecies.Rd @@ -24,6 +24,10 @@ An object of type \linkS4class{MizerParams} This function simply removes all entries from the MizerParams object that refer to the selected species. It does not recalculate the steady state for the remaining species or retune their reproductive efficiency. + +If a gear was targeting only the removed species, then this function will +NOT remove that gear. If you want to also remove that gear then you can do +that by calling \code{\link[=setFishing]{setFishing()}}. } \examples{ params <- NS_params diff --git a/tests/testthat/test-manipulate_species.R b/tests/testthat/test-manipulate_species.R index 0d8de9c50..ff8ba33da 100644 --- a/tests/testthat/test-manipulate_species.R +++ b/tests/testthat/test-manipulate_species.R @@ -209,12 +209,8 @@ test_that("removeSpecies works with 3d pred kernel", { expect_unchanged(params1, params2) }) test_that("removeSpecies works correctly on gear_params", { - # We'll check that the resulting gear_params lead to the same selectivity - # and catchability params <- removeSpecies(example_params(), "Cod") expect_equal(nrow(params@gear_params), 1) - params2 <- setFishing(params) - expect_unchanged(params, params2) }) test_that("adding and then removing species leaves params unaltered", { From 6b00aafee0f2defe9287fb5be7c4af89cda8dc85 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Sun, 22 Feb 2026 20:35:55 +0000 Subject: [PATCH 36/47] refactor: update renameGear test to use dynamic gear names instead of hardcoded strings for replacement. --- tests/testthat/test-manipulate_species.R | 33 +++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/testthat/test-manipulate_species.R b/tests/testthat/test-manipulate_species.R index ff8ba33da..e5891d36f 100644 --- a/tests/testthat/test-manipulate_species.R +++ b/tests/testthat/test-manipulate_species.R @@ -270,38 +270,41 @@ test_that("renameGear works", { p <- example_params() # Get original gear names original_gears <- dimnames(p@selectivity)$gear + gear1 <- original_gears[1] + gear2 <- original_gears[2] # Define replacement - replace <- c(`Otter trawl` = "Otter", `Bottom trawl` = "Bottom") + replace <- c("new_gear1", "new_gear2") + names(replace) <- c(gear1, gear2) # Rename gears p2 <- renameGear(p, replace) # Check that gear_params is updated - expect_true("Otter" %in% p2@gear_params$gear) - expect_true("Bottom" %in% p2@gear_params$gear) - expect_false("Otter trawl" %in% p2@gear_params$gear) - expect_false("Bottom trawl" %in% p2@gear_params$gear) + expect_true("new_gear1" %in% p2@gear_params$gear) + expect_true("new_gear2" %in% p2@gear_params$gear) + expect_false(gear1 %in% p2@gear_params$gear) + expect_false(gear2 %in% p2@gear_params$gear) # Check that selectivity dimension names are updated new_gears <- dimnames(p2@selectivity)$gear - expect_true("Otter" %in% new_gears) - expect_true("Bottom" %in% new_gears) - expect_false("Otter trawl" %in% new_gears) - expect_false("Bottom trawl" %in% new_gears) + expect_true("new_gear1" %in% new_gears) + expect_true("new_gear2" %in% new_gears) + expect_false(gear1 %in% new_gears) + expect_false(gear2 %in% new_gears) # Check that catchability dimension names are updated expect_identical(dimnames(p2@catchability)$gear, new_gears) # Check that initial_effort names are updated - expect_true("Otter" %in% names(p2@initial_effort)) - expect_true("Bottom" %in% names(p2@initial_effort)) - expect_false("Otter trawl" %in% names(p2@initial_effort)) - expect_false("Bottom trawl" %in% names(p2@initial_effort)) + expect_true("new_gear1" %in% names(p2@initial_effort)) + expect_true("new_gear2" %in% names(p2@initial_effort)) + expect_false(gear1 %in% names(p2@initial_effort)) + expect_false(gear2 %in% names(p2@initial_effort)) # Check that the values in initial_effort are preserved - expect_equal(p2@initial_effort[["Otter"]], p@initial_effort[["Otter trawl"]]) - expect_equal(p2@initial_effort[["Bottom"]], p@initial_effort[["Bottom trawl"]]) + expect_equal(p2@initial_effort[["new_gear1"]], p@initial_effort[[gear1]]) + expect_equal(p2@initial_effort[["new_gear2"]], p@initial_effort[[gear2]]) # Check that params object is valid expect_true(validObject(p2)) From d82eff9c74656f9741c7b408269e672657d6600c Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Sun, 22 Feb 2026 20:56:18 +0000 Subject: [PATCH 37/47] refactor: Update tests to use dynamic species names instead of hardcoded strings. --- tests/testthat/helper.R | 2 +- tests/testthat/test-MizerParams-class.R | 2 +- tests/testthat/test-animateSpectra.R | 14 +++++++----- tests/testthat/test-manipulate_species.R | 9 +++++--- tests/testthat/test-matchGrowth.R | 26 +++++++++++++---------- tests/testthat/test-plotBiomass-cutoff.R | 13 ++++++------ tests/testthat/test-plots.R | 3 ++- tests/testthat/test-project_methods.R | 5 +++-- tests/testthat/test-setBevertonHolt.R | 21 ++++++++++++------ tests/testthat/test-setReproduction.R | 7 ++++-- tests/testthat/test-steady.R | 25 ++++++++++++++-------- tests/testthat/test-steadySingleSpecies.R | 15 +++++++------ tests/testthat/test-summary_methods.R | 4 ++-- tests/testthat/test-validSpeciesParams.R | 7 +++--- 14 files changed, 94 insertions(+), 59 deletions(-) diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 9404715b6..650bcd483 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -10,7 +10,7 @@ example_params <- function() { gp <- data.frame( gear = c("Otter trawl", "Bottom trawl", "Bottom trawl"), - species = c("Cod", "Cod", "Plaice"), + species = c(sp$species[3], sp$species[3], sp$species[1]), catchability = c(0.1, 0.2, 0.3), sel_func = c("sigmoid_length", "knife_edge", "double_sigmoid_length"), knife_edge_size = c(NA, 40, NA), diff --git a/tests/testthat/test-MizerParams-class.R b/tests/testthat/test-MizerParams-class.R index 86abab227..855551e36 100644 --- a/tests/testthat/test-MizerParams-class.R +++ b/tests/testthat/test-MizerParams-class.R @@ -10,7 +10,7 @@ test_that("basic constructor sets dimensions properly", { min_w_pp <- 1e-8 expect_error(emptyParams(species_params, min_w = min_w, max_w = max_w, no_w = no_w, min_w_pp = min_w_pp), - "Some of your species have an maximum size larger than max_w: Cod") + paste0("Some of your species have an maximum size larger than max_w: ", species_params$species[3])) max_w <- 40000 test_params <- emptyParams(species_params, min_w = min_w, max_w = max_w, diff --git a/tests/testthat/test-animateSpectra.R b/tests/testthat/test-animateSpectra.R index 1a4264c1c..44e2d3c15 100644 --- a/tests/testthat/test-animateSpectra.R +++ b/tests/testthat/test-animateSpectra.R @@ -1,6 +1,7 @@ test_that("animateSpectra does not throw error", { sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) - expect_error(animateSpectra(sim, species = c("Cod", "Haddock"), + sp <- sim@params@species_params$species + expect_error(animateSpectra(sim, species = sp[1:2], time_range = c(1, 2), wlim = c(1, 1000), ylim = c(1e6, 1e9), @@ -19,11 +20,12 @@ test_that("animateSpectra handles species parameter correctly", { sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) # Test with specific species - result <- animateSpectra(sim, species = "Cod", time_range = c(1, 2)) + sp <- sim@params@species_params$species + result <- animateSpectra(sim, species = sp[1], time_range = c(1, 2)) expect_s3_class(result, "plotly") # Test with multiple species - result <- animateSpectra(sim, species = c("Cod", "Haddock"), time_range = c(1, 2)) + result <- animateSpectra(sim, species = sp[1:2], time_range = c(1, 2)) expect_s3_class(result, "plotly") # Test with NULL (default - all species) @@ -136,7 +138,8 @@ test_that("animateSpectra uses consistent colors matching linecolour", { sim <- project(example_params(), t_max = 2, t_save = 1, effort = 1) # Get the result - result <- animateSpectra(sim, species = c("Cod", "Haddock"), + sp <- sim@params@species_params$species + result <- animateSpectra(sim, species = sp[1:2], time_range = c(1, 2)) # The plotly object should be created @@ -159,7 +162,8 @@ test_that("animateSpectra maintains color consistency when species go extinct", sim <- project(example_params(), t_max = 2, t_save = 1, effort = 10) # Test with species selection - result <- animateSpectra(sim, species = c("Cod", "Haddock"), + sp <- sim@params@species_params$species + result <- animateSpectra(sim, species = sp[1:2], time_range = c(1, 2)) expect_s3_class(result, "plotly") diff --git a/tests/testthat/test-manipulate_species.R b/tests/testthat/test-manipulate_species.R index e5891d36f..26d62d7dd 100644 --- a/tests/testthat/test-manipulate_species.R +++ b/tests/testthat/test-manipulate_species.R @@ -201,15 +201,18 @@ test_that("removeSpecies works with 3d pred kernel", { # It should make no difference whether we first set full pred kernel and # then remove a species, or the other way around. params1 <- example_params() + sp_name <- params1@species_params$species[3] params1 <- setPredKernel(params1, pred_kernel = getPredKernel(params1)) - params1 <- removeSpecies(params1, "Cod") + params1 <- removeSpecies(params1, sp_name) params2 <- example_params() - params2 <- removeSpecies(params2, "Cod") + params2 <- removeSpecies(params2, sp_name) params2 <- setPredKernel(params2, pred_kernel = getPredKernel(params2)) expect_unchanged(params1, params2) }) test_that("removeSpecies works correctly on gear_params", { - params <- removeSpecies(example_params(), "Cod") + p <- example_params() + sp_name <- p@species_params$species[3] + params <- removeSpecies(p, sp_name) expect_equal(nrow(params@gear_params), 1) }) diff --git a/tests/testthat/test-matchGrowth.R b/tests/testthat/test-matchGrowth.R index addd8395a..ce16efd6f 100644 --- a/tests/testthat/test-matchGrowth.R +++ b/tests/testthat/test-matchGrowth.R @@ -1,15 +1,18 @@ test_that("matchGrowth only affects selected species", { - params <- matchGrowth(NS_params, species = "Cod") + sp <- NS_params@species_params$species + species1 <- sp[11] + species2 <- sp[10] + params <- matchGrowth(NS_params, species = species1) # Haddock unaffected - expect_identical(params@initial_n["Haddock", ], - NS_params@initial_n["Haddock", ]) + expect_identical(params@initial_n[species2, ], + NS_params@initial_n[species2, ]) # but Cod changed - expect_gt(params@initial_n["Cod", 100], - NS_params@initial_n["Cod", 100]) + expect_gt(params@initial_n[species1, 100], + NS_params@initial_n[species1, 100]) # and changes again when called again - params2 <- matchGrowth(params, species = "Cod") - expect_lt(params2@initial_n["Cod", 100], - params@initial_n["Cod", 100]) + params2 <- matchGrowth(params, species = species1) + expect_lt(params2@initial_n[species1, 100], + params@initial_n[species1, 100]) }) test_that("matchGrowth is idempotent on single species", { @@ -31,7 +34,8 @@ test_that("matchGrowth `keep` argument works", { test_that("matchGrowth does nothing when no info is given", { params <- NS_params params@species_params$k_vb <- NULL - params2 <- matchGrowth(params, species = "Cod") - expect_identical(params2@initial_n["Cod", ], - params@initial_n["Cod", ]) + sp_name <- params@species_params$species[11] + params2 <- matchGrowth(params, species = sp_name) + expect_identical(params2@initial_n[sp_name, ], + params@initial_n[sp_name, ]) }) diff --git a/tests/testthat/test-plotBiomass-cutoff.R b/tests/testthat/test-plotBiomass-cutoff.R index 36c768225..8d13a0008 100644 --- a/tests/testthat/test-plotBiomass-cutoff.R +++ b/tests/testthat/test-plotBiomass-cutoff.R @@ -3,23 +3,24 @@ test_that("plotBiomass works with use_cutoff", { species_params(params)$biomass_cutoff <- 10 sim <- project(params, t_max = 1, effort = 1) + sp_name <- species_params(params)$species[11] # Test with return_data = TRUE to check values # Default behavior (use_cutoff = FALSE) p_default <- plotBiomass(sim, return_data = TRUE) bm_default <- getBiomass(sim) # Check total for a species matches - expect_equal(p_default$Biomass[p_default$Species == "Cod" & p_default$Year == 1], - bm_default["1", "Cod"], ignore_attr = TRUE) + expect_equal(p_default$Biomass[p_default$Species == sp_name & p_default$Year == 1], + bm_default["1", sp_name], ignore_attr = TRUE) # With use_cutoff = TRUE p_cutoff <- plotBiomass(sim, use_cutoff = TRUE, return_data = TRUE) bm_cutoff <- getBiomass(sim, use_cutoff = TRUE) - expect_equal(p_cutoff$Biomass[p_cutoff$Species == "Cod" & p_cutoff$Year == 1], - bm_cutoff["1", "Cod"], ignore_attr = TRUE) + expect_equal(p_cutoff$Biomass[p_cutoff$Species == sp_name & p_cutoff$Year == 1], + bm_cutoff["1", sp_name], ignore_attr = TRUE) # Check that values are different (since cutoff is 10g) - expect_true(p_default$Biomass[p_default$Species == "Cod" & p_default$Year == 1] > - p_cutoff$Biomass[p_cutoff$Species == "Cod" & p_cutoff$Year == 1]) + expect_true(p_default$Biomass[p_default$Species == sp_name & p_default$Year == 1] > + p_cutoff$Biomass[p_cutoff$Species == sp_name & p_cutoff$Year == 1]) # Test plotlyBiomass accepts the argument expect_error(plotlyBiomass(sim, use_cutoff = TRUE), NA) diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index 88c45561f..3e53abec9 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -75,7 +75,8 @@ sim@params@species_params[["b"]] <- 3.13 p <- plotGrowthCurves(sim, species = "10", max_age = 50) expect_doppelganger("Plot Single Growth Curve", p) -p <- plotDiet(NS_params, species = "Haddock") +sp_name <- NS_params@species_params$species[10] +p <- plotDiet(NS_params, species = sp_name) expect_doppelganger("Plot Diet", p) }) diff --git a/tests/testthat/test-project_methods.R b/tests/testthat/test-project_methods.R index 3191bcd48..104bbc9b0 100644 --- a/tests/testthat/test-project_methods.R +++ b/tests/testthat/test-project_methods.R @@ -218,10 +218,11 @@ test_that("getPredMort passes correct time", { }) test_that("interaction is right way round in getPredMort function", { - inter[, "Dab"] <- 0 # Dab not eaten by anything + sp_name <- NS_species_params_gears$species[5] + inter[, sp_name] <- 0 # Dab not eaten by anything params <- newMultispeciesParams(NS_species_params_gears, inter, info_level = 0) m2 <- getPredMort(params, get_initial_n(params), params@cc_pp) - expect_true(all(m2["Dab", ] == 0)) + expect_true(all(m2[sp_name, ] == 0)) }) test_that("getPredMort is independent of volume", { diff --git a/tests/testthat/test-setBevertonHolt.R b/tests/testthat/test-setBevertonHolt.R index 251be84f9..943842c7d 100644 --- a/tests/testthat/test-setBevertonHolt.R +++ b/tests/testthat/test-setBevertonHolt.R @@ -8,9 +8,10 @@ test_that("setBevertonHolt sets erepro correctly when setting all values", { }) test_that("setBevertonHolt sets erepro correctly when setting same value for all species", { + sp_name <- NS_params@species_params$species[8] expect_warning(params <- setBevertonHolt(NS_params, erepro = 0.1), "For the following species `erepro` has been") - expect_identical(params@species_params$R_max[params@species_params$species == "Gurnard"], + expect_identical(params@species_params$R_max[params@species_params$species == sp_name], Inf) expect_equal(getRequiredRDD(NS_params), getRDD(params)) }) @@ -73,16 +74,18 @@ test_that("setBevertonHolt sets R_max correctly when setting values for some spe test_that("setBevertonHolt issues warning when an R_max leads to an erepro > 1", { R_max_new <- NS_params@species_params$R_max * 1.02 + sp_name <- NS_params@species_params$species[9] expect_warning(params <- setBevertonHolt(NS_params, R_max = R_max_new), - "The following species require an unrealistic value greater than 1 for `erepro`: Plaice") - expect_gt(params@species_params$erepro[params@species_params$species == "Plaice"], 1) + paste0("The following species require an unrealistic value greater than 1 for `erepro`: ", sp_name)) + expect_gt(params@species_params$erepro[params@species_params$species == sp_name], 1) expect_identical(params@species_params$R_max, R_max_new) }) # reproduction_level ---- test_that("setBevertonHolt sets reproduction_level correctly", { + sp_name <- NS_params@species_params$species[9] expect_warning(params <- setBevertonHolt(NS_params, reproduction_level = 0.4), - "The following species require an unrealistic value greater than 1 for `erepro`: Plaice") + paste0("The following species require an unrealistic value greater than 1 for `erepro`: ", sp_name)) expect_equal(getRDD(params), params@species_params$R_max * 0.4, ignore_attr = TRUE) expect_equal(getRequiredRDD(params), getRDD(params)) expect_equal(getReproductionLevel(params)[[1]], 0.4) @@ -90,8 +93,9 @@ test_that("setBevertonHolt sets reproduction_level correctly", { # R_factor ---- test_that("setBevertonHolt sets R_factor correctly", { + sp_name <- NS_params@species_params$species[9] expect_warning(params <- setBevertonHolt(NS_params, R_factor = 4), - "The following species require an unrealistic value greater than 1 for `erepro`: Plaice") + paste0("The following species require an unrealistic value greater than 1 for `erepro`: ", sp_name)) expect_equal(getRDD(params), params@species_params$R_max / 4, ignore_attr = TRUE) expect_equal(getRequiredRDD(params), getRDD(params)) }) @@ -102,7 +106,8 @@ test_that("setBevertonHolt does nothing when called with only NA values", { params <- setBevertonHolt(NS_params, erepro = erepro_new) expect_identical(params, NS_params) erepro_new <- NA - names(erepro_new) <- "Cod" + sp_name <- NS_params@species_params$species[11] + names(erepro_new) <- sp_name params <- setBevertonHolt(NS_params, erepro = erepro_new) expect_identical(params, NS_params) }) @@ -131,8 +136,10 @@ test_that("reproduction_level of 0 works", { }) test_that("R_max is increased when needed", { + sp_name1 <- NS_params@species_params$species[1] + sp_name2 <- NS_params@species_params$species[2] expect_warning(p <- setBevertonHolt(NS_params, R_max = c(1, 2, rep(NA, 10))), - "has been increased to give a reproduction level of 0.99: Sprat, Sandeel") + paste0("has been increased to give a reproduction level of 0.99: ", sp_name1, ", ", sp_name2)) expect_gt(p@species_params$R_max[1], NS_params@species_params$R_max[1]) }) diff --git a/tests/testthat/test-setReproduction.R b/tests/testthat/test-setReproduction.R index 628e2f326..b8af7b9d5 100644 --- a/tests/testthat/test-setReproduction.R +++ b/tests/testthat/test-setReproduction.R @@ -30,8 +30,10 @@ test_that("setReproduction works", { test_that("setReproduction checks arguments", { params <- NS_params params@species_params$w_max[[2]] <- NA + params@species_params$w_max[[2]] <- NA + sp_name <- params@species_params$species[2] expect_error(setReproduction(params), - "The following species are missing data for their maximum size w_max: Sandeel") + paste0("The following species are missing data for their maximum size w_max: ", sp_name)) params@species_params$w_max[[2]] <- 1e-5 expect_error(setReproduction(params), "Some of the maximum sizes are smaller than the egg sizes.") @@ -41,8 +43,9 @@ test_that("setReproduction checks arguments", { params <- NS_params params@species_params$w_mat[[2]] <- NA + sp_name <- params@species_params$species[2] expect_message(pa <- setReproduction(params), - "Note: The following species were missing data for their maturity size w_mat: Sandeel.") + paste0("Note: The following species were missing data for their maturity size w_mat: ", sp_name, ".")) }) # * Comments ---- diff --git a/tests/testthat/test-steady.R b/tests/testthat/test-steady.R index 74596c69d..ac93dd011 100644 --- a/tests/testthat/test-steady.R +++ b/tests/testthat/test-steady.R @@ -35,8 +35,10 @@ test_that("projectToSteady() works", { # Check extinction params@psi[5:6, ] <- 0 + sp1 <- params@species_params$species[5] + sp2 <- params@species_params$species[6] expect_warning(projectToSteady(params) |> suppressMessages(), - "Dab, Whiting are going extinct.") + paste0(sp1, ", ", sp2, " are going extinct.")) }) # steady ---- @@ -98,9 +100,14 @@ test_that("valid_species_arg works", { "The following species do not exist: non, sense") expect_identical(s, vector(mode = "character")) - expect_identical(valid_species_arg(NS_params, c("Cod", "Sandeel")), - c("Cod", "Sandeel")) - expect_identical(valid_species_arg(NS_params, c("Sprat", "Sandeel"), + sp1 <- NS_params@species_params$species[11] + sp2 <- NS_params@species_params$species[2] + sp_sprat <- NS_params@species_params$species[1] + sp3 <- NS_params@species_params$species[3] + + expect_identical(valid_species_arg(NS_params, c(sp1, sp2)), + c(sp1, sp2)) + expect_identical(valid_species_arg(NS_params, c(sp_sprat, sp2), return.logical = TRUE), c(TRUE, TRUE, rep(FALSE, 10))) expect_error( @@ -112,9 +119,9 @@ test_that("valid_species_arg works", { "A numeric 'species' argument should only contain the integers 1 to 12") expect_identical(s, vector(mode = "character")) expect_identical(valid_species_arg(NS_params, c(3, 1)), - c("N.pout", "Sprat")) + c(sp3, sp_sprat)) expect_identical(valid_species_arg(NS_params, c(1, 3)), - c("Sprat", "N.pout")) + c(sp_sprat, sp3)) expect_identical(valid_species_arg(NS_params, c(3, 1), return.logical = TRUE), c(TRUE, FALSE, TRUE, rep(FALSE, 9))) @@ -127,7 +134,7 @@ test_that("valid_species_arg works", { "The boolean `species` argument has the wrong length") expect_identical(valid_species_arg(NS_params, c(TRUE, FALSE, TRUE, rep(FALSE, 9))), - c("Sprat", "N.pout")) + c(sp_sprat, sp3)) expect_identical(valid_species_arg(NS_params, c(TRUE, FALSE, TRUE, rep(FALSE, 9)), return.logical = TRUE), @@ -138,8 +145,8 @@ test_that("valid_species_arg works", { "No species have been selected.") # called with MizerSim object sim <- project(NS_params, t_max = 1, dt = 1) - expect_identical(valid_species_arg(sim, "Cod"), - valid_species_arg(NS_params, "Cod")) + expect_identical(valid_species_arg(sim, sp1), + valid_species_arg(NS_params, sp1)) # called without species expect_identical(valid_species_arg(NS_params), valid_species_arg(NS_params, diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 7bc26cbc0..51ea65bda 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -1,16 +1,19 @@ test_that("steadySingleSpecies only affects abundance of selected species", { params1 <- NS_params + sp_names <- params1@species_params$species + species1 <- sp_names[11] + species2 <- sp_names[10] # make sure it is not in steady state params1@initial_n[,50:80] <- params1@initial_n[,50:80] * 2 - params2 <- steadySingleSpecies(params1, species = "Cod") |> + params2 <- steadySingleSpecies(params1, species = species1) |> suppressWarnings() # Haddock unaffected - expect_identical(params2@initial_n["Haddock", ], - params1@initial_n["Haddock", ]) + expect_identical(params2@initial_n[species2, ], + params1@initial_n[species2, ]) # but Cod changed - expect_lt(params2@initial_n["Cod", 100], - params1@initial_n["Cod", 100]) + expect_lt(params2@initial_n[species1, 100], + params1@initial_n[species1, 100]) # Test that steadySingleSpecies updates time_modified expect_false(identical(params1@time_modified, params2@time_modified)) # Nothing else changed @@ -39,7 +42,7 @@ test_that("steadySingleSpecies produces steady state with diffusion", { params <- NS_params # Enable diffusion for Cod - species <- "Cod" + species <- params@species_params$species[11] n <- params@species_params[species, "n"] d <- 0.1 * params@w^(n + 1) diffusion(params)[species, ] <- d diff --git a/tests/testthat/test-summary_methods.R b/tests/testthat/test-summary_methods.R index 7c03089fd..adeee0288 100644 --- a/tests/testthat/test-summary_methods.R +++ b/tests/testthat/test-summary_methods.R @@ -143,7 +143,7 @@ test_that("getMeanWeight works",{ mw <- getMeanWeight(sim) expect_equal(mw, mw1, ignore_attr = TRUE) # select species - species <- c("Cod","Haddock") + species <- sim@params@species_params$species[11:10] total_biomass <- apply(sweep(sim@n[,species,], 3, sim@params@w * sim@params@dw, "*"),1,sum) total_n <- apply(sweep(sim@n[,species,], 3, sim@params@dw, "*"),1,sum) mw2 <- total_biomass / total_n @@ -245,7 +245,7 @@ test_that("getCommunitySlope works",{ expect_equal(slope_b2[dim(sim@n)[1],"slope"], summary(lm_res)$coefficients[2,1], ignore_attr = TRUE) expect_equal(slope_b2[dim(sim@n)[1],"intercept"], summary(lm_res)$coefficients[1,1], ignore_attr = TRUE) # Check the species - dem_species <- c("Dab","Whiting","Sole","Gurnard","Plaice","Haddock", "Cod","Saithe") + dem_species <- sim@params@species_params$species[5:12] slope_b3 <- getCommunitySlope(sim, species = dem_species) biomass <- apply(sweep(sim@n[,dem_species,],3,sim@params@w,"*"),c(1,3),sum) # r2, slope and intercept at last time step diff --git a/tests/testthat/test-validSpeciesParams.R b/tests/testthat/test-validSpeciesParams.R index 0aef44e26..22777acf6 100644 --- a/tests/testthat/test-validSpeciesParams.R +++ b/tests/testthat/test-validSpeciesParams.R @@ -7,8 +7,9 @@ test_that("validSpeciesParams() works", { expect_message(sp <- validSpeciesParams(sp), NA) expect_equal(sp$w_mat[1], sp$w_max[1] / 4) sp$w_mat[2:4] <- 100 + sp2 <- sp$species[2]; sp3 <- sp$species[3]; sp5 <- sp$species[5] expect_warning(sp <- validSpeciesParams(sp), - "For the species Sandeel, N.pout the value") + paste0("For the species ", sp2, ", ", sp3, " the value")) expect_equal(sp$w_mat[2], sp$w_max[2] / 4) # test w_mat25 @@ -17,7 +18,7 @@ test_that("validSpeciesParams() works", { expect_warning(validSpeciesParams(sp), NA) sp$w_mat25[2:5] <- 21 expect_warning(sp <- validSpeciesParams(sp), - "For the species Sandeel, Dab the value") + paste0("For the species ", sp2, ", ", sp5, " the value")) expect_true(is.na(sp$w_mat25[[2]])) expect_identical(sp$w_mat25[[3]], 21) @@ -27,7 +28,7 @@ test_that("validSpeciesParams() works", { expect_warning(validSpeciesParams(sp), NA) sp$w_min[2:5] <- 21 expect_warning(sp <- validSpeciesParams(sp), - "For the species Sandeel, Dab the value") + paste0("For the species ", sp2, ", ", sp5, " the value")) expect_identical(sp$w_min[[2]], 0.001) expect_identical(sp$w_min[[3]], 21) From 19ec4426211e66d73e3f1eb3787b0ce4bf58c222 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 23 Feb 2026 10:18:18 +0000 Subject: [PATCH 38/47] Don't issue warning if growth rate becomes zero only after maturity size. --- R/steadySingleSpecies.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/R/steadySingleSpecies.R b/R/steadySingleSpecies.R index f95ca143d..1677ee2cb 100644 --- a/R/steadySingleSpecies.R +++ b/R/steadySingleSpecies.R @@ -63,8 +63,6 @@ steadySingleSpecies.MizerParams <- function(params, species = NULL, first_zero_idx <- w_min_idx + zero_growth_idx[1] - 1 if (first_zero_idx < w_mat_idx) { stop(sp, " cannot grow to maturity") - } else { - warning(sp, " has zero growth rate after maturity size") } } From 52dbe46aba68d17d0cde880106ca1c9fde0564ab Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 26 Feb 2026 08:43:57 +0000 Subject: [PATCH 39/47] feat: add `getFlux()` function to calculate flux into size bins, including documentation, tests, and updates to related files. --- NAMESPACE | 2 + R/rate_functions.R | 81 +++++++++++++++++++++++++++++++++++ inst/WORDLIST | 31 ++++++++++++++ man/getEGrowth.Rd | 1 + man/getERepro.Rd | 1 + man/getEReproAndGrowth.Rd | 1 + man/getESpawning.Rd | 1 + man/getEncounter.Rd | 1 + man/getFMort.Rd | 1 + man/getFMortGear.Rd | 1 + man/getFeedingLevel.Rd | 1 + man/getFlux.Rd | 75 ++++++++++++++++++++++++++++++++ man/getM2.Rd | 1 + man/getM2Background.Rd | 1 + man/getMort.Rd | 1 + man/getPredMort.Rd | 1 + man/getPredRate.Rd | 1 + man/getRDD.Rd | 1 + man/getRDI.Rd | 1 + man/getRates.Rd | 1 + man/getResourceMort.Rd | 1 + man/getZ.Rd | 1 + tests/testthat/test-getFlux.R | 44 +++++++++++++++++++ 23 files changed, 251 insertions(+) create mode 100644 man/getFlux.Rd create mode 100644 tests/testthat/test-getFlux.R diff --git a/NAMESPACE b/NAMESPACE index a16dd8994..f36015f06 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -62,6 +62,7 @@ S3method(getFMortGear,MizerParams) S3method(getFMortGear,MizerSim) S3method(getFeedingLevel,MizerParams) S3method(getFeedingLevel,MizerSim) +S3method(getFlux,MizerParams) S3method(getGrowthCurves,MizerParams) S3method(getGrowthCurves,MizerSim) S3method(getInitialEffort,MizerParams) @@ -254,6 +255,7 @@ export(getExtMort) export(getFMort) export(getFMortGear) export(getFeedingLevel) +export(getFlux) export(getGrowthCurves) export(getInitialEffort) export(getInteraction) diff --git a/R/rate_functions.R b/R/rate_functions.R index 0e17037f5..b786fe3e6 100644 --- a/R/rate_functions.R +++ b/R/rate_functions.R @@ -995,6 +995,87 @@ getRDD.MizerParams <- function(params, n = initialN(params), rdd } +#' 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. +#' +#' @inheritParams mizerRates +#' +#' @return A two dimensional array (prey species x prey size) +#' @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(params, n = initialN(params), + n_pp = initialNResource(params), + n_other = initialNOther(params), + t = 0, ...) { + UseMethod("getFlux") +} + +#' @export +getFlux.MizerParams <- function(params, n = initialN(params), + n_pp = initialNResource(params), + n_other = initialNOther(params), + t = 0, ...) { + params <- validParams(params) + + no_sp <- nrow(params@species_params) + no_w <- length(params@w) + + g <- getEGrowth(params, n = n, n_pp = n_pp, n_other = n_other, t = t) + d <- params@diffusion + dw <- params@dw + + flux <- matrix(0, nrow = no_sp, ncol = no_w, + dimnames = list(params@species_params$species, NULL)) + + idx <- 2:no_w + idx_minus_1 <- idx - 1 + + # Calculate J_{i,j} for all j > 1 + # 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 + + # Apply recruitment boundary conditions + rdd <- getRDD(params, n = n, n_pp = n_pp, n_other = n_other, t = t) + + 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 + } + + dimnames(flux) <- dimnames(params@metab) + flux +} + + + #' Get_time_elements #' #' Internal function to get the array element references of the time dimension diff --git a/inst/WORDLIST b/inst/WORDLIST index 0a3da22cc..f13678a62 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,11 +1,13 @@ Acad Algolia Askaroff +Ax Benoit Bertalanffy Beverton BevertonHoltRDD Beyer +CFL CLT CRAN's Canales @@ -13,18 +15,24 @@ Catchability Chu Collingridge Conseil +Courant DEFINEIT +DFT DP Datta Deepfishman Deepwater Defra +Discretisation +Discretised Docsearch Drazen EBFM Ecopath Ecosim +Fluxs Foerster +Friedrichs Giacomini GitHubPages Hinzen @@ -34,6 +42,7 @@ Jefcoats Kira LFI Lenfest +Lewy Lindmark MERP MESOPP @@ -50,6 +59,7 @@ MvF NA's ORCID Oceanologica +PDEs PLoS POSIXct Pedersen @@ -74,12 +84,16 @@ Sheperd Sinica Szuwalski Trophic +Vb +Vt Wo Woodworth Xue YqXESV addSpecies +adv allometrically +artifacts backreaction bycatch camelCase @@ -90,14 +104,18 @@ cjfas color colors com +const csv cutoff cutoffs +dd demersal devtools dialog diffviewer discretisation +discretise +discretised doi dotdash dplyr @@ -105,6 +123,7 @@ dropdown dt du dw +dx eq eqn etc @@ -113,6 +132,7 @@ faf fft figshare frac +gN getEncounter getPredMort gganimate @@ -125,21 +145,27 @@ ij inf infty int +ip ji jp kilometer kiraaskaroff ks +ldots +leftarrow leq linecolour linecolours linetype linetypes +ln longdash ly matchYield matchYields +mathcal md +megagrams mesopp metab mizer's @@ -152,7 +178,9 @@ mort mup mupp navbar +neq nonzero +num offs org parameterisations @@ -175,6 +203,7 @@ selectivities semichemostat shinytest sigmoidal +sim sizer sizespectrum sp @@ -185,6 +214,8 @@ testthat th tibble tidyverse +tri +tridiagonal trophic tt twodash diff --git a/man/getEGrowth.Rd b/man/getEGrowth.Rd index 4e70c59a1..0aa60ac78 100644 --- a/man/getEGrowth.Rd +++ b/man/getEGrowth.Rd @@ -72,6 +72,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getERepro.Rd b/man/getERepro.Rd index 0f54334f7..5fed99a05 100644 --- a/man/getERepro.Rd +++ b/man/getERepro.Rd @@ -75,6 +75,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getEReproAndGrowth.Rd b/man/getEReproAndGrowth.Rd index ca01c67d7..fc2105b5e 100644 --- a/man/getEReproAndGrowth.Rd +++ b/man/getEReproAndGrowth.Rd @@ -95,6 +95,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getESpawning.Rd b/man/getESpawning.Rd index 5f7702733..4921670cd 100644 --- a/man/getESpawning.Rd +++ b/man/getESpawning.Rd @@ -75,6 +75,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getEncounter.Rd b/man/getEncounter.Rd index c84f9bb8c..42e4481d0 100644 --- a/man/getEncounter.Rd +++ b/man/getEncounter.Rd @@ -103,6 +103,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getFMort.Rd b/man/getFMort.Rd index 6942fcd3b..2e0184992 100644 --- a/man/getFMort.Rd +++ b/man/getFMort.Rd @@ -98,6 +98,7 @@ Other rate functions: \code{\link{getEncounter}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getFMortGear.Rd b/man/getFMortGear.Rd index 78c7e3ae3..e4b684164 100644 --- a/man/getFMortGear.Rd +++ b/man/getFMortGear.Rd @@ -76,6 +76,7 @@ Other rate functions: \code{\link{getEncounter}()}, \code{\link{getFMort}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getFeedingLevel.Rd b/man/getFeedingLevel.Rd index cc0b534d7..4bdbe6f75 100644 --- a/man/getFeedingLevel.Rd +++ b/man/getFeedingLevel.Rd @@ -91,6 +91,7 @@ Other rate functions: \code{\link{getEncounter}()}, \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getFlux.Rd b/man/getFlux.Rd new file mode 100644 index 000000000..68b8cc836 --- /dev/null +++ b/man/getFlux.Rd @@ -0,0 +1,75 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rate_functions.R +\name{getFlux} +\alias{getFlux} +\title{Get flux into size bins} +\usage{ +getFlux( + params, + n = initialN(params), + n_pp = initialNResource(params), + n_other = initialNOther(params), + t = 0, + ... +) +} +\arguments{ +\item{params}{A \linkS4class{MizerParams} object} + +\item{n}{A matrix of species abundances (species x size).} + +\item{n_pp}{A vector of the resource abundance by size} + +\item{n_other}{A list of abundances for other dynamical components of the +ecosystem} + +\item{t}{The time for which to do the calculation (Not used by standard +mizer rate functions but useful for extensions with time-dependent +parameters.)} + +\item{...}{Unused} +} +\value{ +A two dimensional array (prey species x prey size) +} +\description{ +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. +} +\details{ +At the recruitment size, the flux is simply the recruitment rate +\eqn{R_{dd,i}} (see \code{\link[=getRDD]{getRDD()}}). For sizes below the recruitment size +the flux is zero. +} +\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"] +} +} +\seealso{ +\code{\link[=getEGrowth]{getEGrowth()}}, \code{\link[=getRDD]{getRDD()}} + +Other rate functions: +\code{\link{getEGrowth}()}, +\code{\link{getERepro}()}, +\code{\link{getEReproAndGrowth}()}, +\code{\link{getEncounter}()}, +\code{\link{getFMort}()}, +\code{\link{getFMortGear}()}, +\code{\link{getFeedingLevel}()}, +\code{\link{getMort}()}, +\code{\link{getPredMort}()}, +\code{\link{getPredRate}()}, +\code{\link{getRDD}()}, +\code{\link{getRDI}()}, +\code{\link{getRates}()}, +\code{\link{getResourceMort}()} +} +\concept{rate functions} diff --git a/man/getM2.Rd b/man/getM2.Rd index fbf1160ee..ac178c1a0 100644 --- a/man/getM2.Rd +++ b/man/getM2.Rd @@ -77,6 +77,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredRate}()}, \code{\link{getRDD}()}, diff --git a/man/getM2Background.Rd b/man/getM2Background.Rd index 09bdd7a89..95753e903 100644 --- a/man/getM2Background.Rd +++ b/man/getM2Background.Rd @@ -68,6 +68,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getMort.Rd b/man/getMort.Rd index 869a8ed54..d99d68afe 100644 --- a/man/getMort.Rd +++ b/man/getMort.Rd @@ -84,6 +84,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, \code{\link{getRDD}()}, diff --git a/man/getPredMort.Rd b/man/getPredMort.Rd index a4c38fbb6..054e87a35 100644 --- a/man/getPredMort.Rd +++ b/man/getPredMort.Rd @@ -80,6 +80,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredRate}()}, \code{\link{getRDD}()}, diff --git a/man/getPredRate.Rd b/man/getPredRate.Rd index 53f124f82..07e5e4ee4 100644 --- a/man/getPredRate.Rd +++ b/man/getPredRate.Rd @@ -78,6 +78,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getRDD}()}, diff --git a/man/getRDD.Rd b/man/getRDD.Rd index 7b1f39a04..a1210822a 100644 --- a/man/getRDD.Rd +++ b/man/getRDD.Rd @@ -66,6 +66,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getRDI.Rd b/man/getRDI.Rd index 979e75c23..f645b9d0a 100644 --- a/man/getRDI.Rd +++ b/man/getRDI.Rd @@ -83,6 +83,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getRates.Rd b/man/getRates.Rd index 903997e12..8c393a66e 100644 --- a/man/getRates.Rd +++ b/man/getRates.Rd @@ -71,6 +71,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getResourceMort.Rd b/man/getResourceMort.Rd index da98119f0..23e8d78a8 100644 --- a/man/getResourceMort.Rd +++ b/man/getResourceMort.Rd @@ -68,6 +68,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getMort}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, diff --git a/man/getZ.Rd b/man/getZ.Rd index 910a206d9..6e1d83240 100644 --- a/man/getZ.Rd +++ b/man/getZ.Rd @@ -83,6 +83,7 @@ Other rate functions: \code{\link{getFMort}()}, \code{\link{getFMortGear}()}, \code{\link{getFeedingLevel}()}, +\code{\link{getFlux}()}, \code{\link{getPredMort}()}, \code{\link{getPredRate}()}, \code{\link{getRDD}()}, diff --git a/tests/testthat/test-getFlux.R b/tests/testthat/test-getFlux.R new file mode 100644 index 000000000..946208a95 --- /dev/null +++ b/tests/testthat/test-getFlux.R @@ -0,0 +1,44 @@ +test_that("getFlux works correctly", { + params <- newTraitParams(no_sp = 2) + # Force different w_min to test zeroing logic + params@species_params$w_min[2] <- 0.01 + params@w_min_idx[2] <- which.min(abs(params@w - 0.01)) + + n <- params@initial_n + n[] <- 1 # Set n to 1 to make checking easier + + t <- 0 + g <- getEGrowth(params, n = n, t = t) + d <- params@diffusion + dw <- params@dw + rdd <- getRDD(params, n = n, t = t) + + flux <- getFlux(params, n = n, t = t) + + # Check dimensions + expect_equal(dim(flux), dim(n)) + + # Check zeroing out below w_min_idx + w_min_idx_2 <- params@w_min_idx[2] + expect_true(w_min_idx_2 > 1) + + expect_true(all(flux[2, 1:(w_min_idx_2 - 1)] == 0)) + + # Check boundary condition at w_min_idx + # flux[i, j_start] = Rdd[i] + + # Species 1 + j_start_1 <- params@w_min_idx[1] + expect_equal(flux[1, j_start_1], rdd[1], ignore_attr = TRUE) + + # Species 2 + j_start_2 <- params@w_min_idx[2] + expect_equal(flux[2, j_start_2], rdd[2], ignore_attr = TRUE) + + # Check general calculation for some j > j_start + # 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} + j <- j_start_2 + 5 + expected_flux_2_j <- g[2, j - 1] * n[2, j - 1] - 0.5 * (d[2, j] * n[2, j] - d[2, j - 1] * n[2, j - 1]) / dw[j - 1] + + expect_equal(flux[2, j], expected_flux_2_j, ignore_attr = TRUE) +}) From a7288f0c37e205e8e4986d40ffcb3abb16fc4417 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 26 Feb 2026 09:31:18 +0000 Subject: [PATCH 40/47] Add new functions to pkgdown.yml --- pkgdown/_pkgdown.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 85737f03a..d3790718c 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -200,21 +200,8 @@ reference: - setFishing - title: Calculating rates contents: - - getRates - - getEncounter - - getEGrowth - - getERepro - - getEReproAndGrowth - - getFMort - - getFMortGear - - getFeedingLevel + - has_concept("rate functions") - getCriticalFeedingLevel - - getMort - - getResourceMort - - getPredMort - - getPredRate - - getRDD - - getRDI - title: Extending Mizer contents: - setRateFunction @@ -239,6 +226,7 @@ reference: contents: - has_concept("functions calculating density-dependent reproduction rate") - getReproductionLevel + - getRequiredRDD - title: Internal rate functions description: These functions are used by project() to calculate instantaneous rates at each time step. You should use the get...() From 509a355177af355b53af15803d6a9eac5424bb8e Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 26 Feb 2026 11:12:46 +0000 Subject: [PATCH 41/47] We no longer warn when growth stops after maturity, so remove corresponding test. --- tests/testthat/test-steadySingleSpecies.R | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/testthat/test-steadySingleSpecies.R b/tests/testthat/test-steadySingleSpecies.R index 51ea65bda..08dbc7a6c 100644 --- a/tests/testthat/test-steadySingleSpecies.R +++ b/tests/testthat/test-steadySingleSpecies.R @@ -82,23 +82,3 @@ test_that("steadySingleSpecies errors when growth stops before maturity", { expect_error(steadySingleSpecies(params, species = 1), "cannot grow to maturity") }) - -test_that("steadySingleSpecies warns when growth stops after maturity", { - # Create a simple params object - params <- newSingleSpeciesParams() - - # Get the species and find indices for maturity and max size - w_mat <- params@species_params$w_mat[1] - w_max <- params@species_params$w_max[1] - w_mat_idx <- sum(params@w <= w_mat) - w_max_idx <- sum(params@w <= w_max) - - # Increase metabolic rate significantly after maturity - if (w_mat_idx < length(params@w)) { - params@metab[1, (w_mat_idx + 1):length(params@w)] <- - params@metab[1, (w_mat_idx + 1):length(params@w)] * 1000 - } - - expect_warning(steadySingleSpecies(params, species = 1), - "has zero growth rate after maturity size") -}) From 0e24210e2ac73f8d8fbfa4a1711d5d88f3ff87b3 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Wed, 11 Mar 2026 14:22:57 +0000 Subject: [PATCH 42/47] Thanks to Jess for catching this. C++ is not as clever as R! --- src/inner_project_loop.cpp | 1 + src/project_n_loop.cpp | 1 + tests/testthat/test-project.R | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/src/inner_project_loop.cpp b/src/inner_project_loop.cpp index 442f2ef4e..09428b64c 100644 --- a/src/inner_project_loop.cpp +++ b/src/inner_project_loop.cpp @@ -6,6 +6,7 @@ using namespace Rcpp; NumericMatrix inner_project_loop(int no_sp, int no_w, NumericMatrix n, NumericMatrix A, NumericMatrix B, NumericMatrix S, NumericVector w_min_idx) { + n = Rcpp::clone(n); for (int i = 0; i < no_sp; i++) { for (int j = w_min_idx[i]; j < no_w; j++) { diff --git a/src/project_n_loop.cpp b/src/project_n_loop.cpp index 31f1ed0ad..d4402657f 100644 --- a/src/project_n_loop.cpp +++ b/src/project_n_loop.cpp @@ -4,6 +4,7 @@ using namespace Rcpp; // [[Rcpp::export]] NumericMatrix project_n_loop(NumericMatrix n, NumericMatrix a, NumericMatrix b, NumericMatrix c, NumericMatrix S, NumericVector w_min_idx) { + n = Rcpp::clone(n); int no_sp = n.nrow(); int no_w = n.ncol(); diff --git a/tests/testthat/test-project.R b/tests/testthat/test-project.R index 3d1d9b3aa..be24e351d 100644 --- a/tests/testthat/test-project.R +++ b/tests/testthat/test-project.R @@ -368,3 +368,11 @@ test_that("t_max less than effort array duration uses effort times", { # Should stop at year 3 expect_equal(max(as.numeric(dimnames(sim@n)[[1]])), 3) }) + +test_that("project does not change the params object", { + params <- NS_params + params@diffusion[] <- 1 + old_params <- unserialize(serialize(params, NULL)) + sim <- project(params, t_max = 1) + expect_identical(params, old_params) +}) \ No newline at end of file From 090f7ffde6f9bf07b385d7b030bd0c3f3f657d7a Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Wed, 18 Mar 2026 18:41:54 +0000 Subject: [PATCH 43/47] Instruction file for Claude Code --- CLAUDE.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..19f9624c9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## About mizer + +mizer is an R package for dynamic multi-species size-spectrum modelling of fish communities. It models marine ecosystems subject to fishing, tracking individual fish growth from egg size to maximum size and capturing ontogenetic diet shifts. + +## Common Commands + +```r +devtools::load_all() # Load package for development +devtools::document() # Regenerate NAMESPACE and man/ pages from roxygen2 +devtools::test() # Run all tests +devtools::check() # Full R CMD check +lintr::lint_package() # Lint the package + +# Run a single test file +testthat::test_file("tests/testthat/test-filename.R") + +# After editing C++ source +devtools::clean_dll(); devtools::load_all() +``` + +## Architecture + +### Core Classes + +**`MizerParams`** (S4, `R/MizerParams-class.R`) — the central object passed to nearly all functions. Holds all model configuration: species parameters, size grids (`w`, `w_full`), interaction matrices, gear selectivity, rate function overrides (`@rates_funcs`), and resource dynamics. Validated by `validMizerParams()`. Modified via setter functions that return new copies: `setFishing(params, ...)`, `setInteraction(params, ...)`, etc. + +**`MizerSim`** (S4, `R/MizerSim-class.R`) — stores simulation output: a 3D array `n` (time × species × size), `n_pp` (time × size) for resource, `n_other` for additional biomass components, `effort` history, and the `MizerParams` used. + +**`MizerRate`** (S3, `R/MizerRate-class.R`) — wraps 2D arrays (species × size) returned by rate functions with metadata (`rate_name`, `units`). Inherits from `matrix`/`array`. Provides enhanced `print()`, `summary()`, `plot()`, and `as.data.frame()`. + +### Execution Flow + +1. **Setup**: `newMultispeciesParams()` / `newSingleSpeciesParams()` / `newCommunityParams()` +2. **Configure**: `set*()` functions (`setFishing()`, `setPredKernel()`, `setMetabolicRate()`, …) +3. **Rates**: `getEncounter()`, `getFeedingLevel()`, `getPredMort()`, `getFMort()`, `getRates()` — each returns a `MizerRate` matrix (species × size) +4. **Project**: `project(params, t_max = 100, effort = ...)` → `MizerSim` +5. **Analyse**: `getYield()`, `getBiomass()`, `getSSB()`, `plotSpectra()`, etc. + +### Customisable Rate Functions + +Users can replace any rate function by storing a custom function name in `params@rates_funcs`. Calls dispatch via `get(params@rates_funcs$FunctionName)(params, ...)`. This is the primary extensibility mechanism. + +### C++ Integration + +Performance-critical inner projection loop lives in `src/inner_project_loop.cpp` and `src/project_n_loop.cpp`. `RcppExports.R` and `RcppExports.cpp` are auto-generated — never edit them directly. + +### Extensibility via "Other" Components + +Arbitrary biomass components beyond species can be added (e.g., detritus, zooplankton). Stored in `n_other`; custom rate functions can be registered for them. + +## Code Conventions + +- **Indentation**: 4 spaces +- **Naming**: camelCase or snake_case for functions/variables; PascalCase for classes +- **Language**: British English (en-GB) — "colour", "behaviour", "modelling" +- **Documentation**: All exported functions require roxygen2 with `@param`, `@return`, `@export`; use `@seealso` for cross-references +- **Tests**: testthat edition 3; use `expect_doppelganger()` (vdiffr) for plot tests, snapshot tests for complex outputs +- After adding exports, run `devtools::document()` to regenerate `NAMESPACE` +- Update `NEWS.md` when adding features or fixing bugs From f183733db2b37ca934aeb7e7b993d4ec98ad95a3 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Mon, 23 Mar 2026 08:41:12 +0000 Subject: [PATCH 44/47] Update .*ignore --- .Rbuildignore | 2 ++ .gitignore | 1 + vignettes/.gitignore | 3 +++ 3 files changed, 6 insertions(+) diff --git a/.Rbuildignore b/.Rbuildignore index a935b7531..1449b7519 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -45,3 +45,5 @@ ^vignettes/mizer.Rmd ^doc$ ^Meta$ +^\.positai$ +^\.claude$ diff --git a/.gitignore b/.gitignore index eeb37a6e2..6bc3cc07d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ docs/*/*.md docs/*.md docs/dev .vscode +.positai diff --git a/vignettes/.gitignore b/vignettes/.gitignore index 097b24163..47018d625 100644 --- a/vignettes/.gitignore +++ b/vignettes/.gitignore @@ -1,2 +1,5 @@ *.html *.R + +/.quarto/ +**/*.quarto_ipynb From acad59b0398aebd7646b6a5e8d98069db106ca82 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 9 Apr 2026 09:51:31 +0100 Subject: [PATCH 45/47] Update NEWS.md --- NEWS.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 77f74d7af..d70e4a08c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# Development version 2.5.4.9000 +# Development version 2.5.4.9101 - `t_max` and `t_save` arguments in `project()` are now respected even when an effort array is supplied. When `t_max` is provided, the simulation extends @@ -6,6 +6,23 @@ `t_save` is provided, it controls the save frequency with effort values interpolated as needed. This allows users to extend simulations without specifying dummy effort values for the final time period (#231). +- The numerical scheme now supports diffusion in the McKendrick-von Foerster + equation, allowing individual variability in growth to be modelled. A new + `diffusion` slot in `MizerParams` holds the diffusion coefficient (species x + size). Use `setDiffusion()` / `diffusion()` / `diffusion<-()` to set and + retrieve it. +- New `getFlux()` function calculates the flux of individuals entering each size + class, combining the advective flux from somatic growth and the diffusive flux. +- `getRequiredRDD()` is now exported. It calculates the recruitment rate needed + to maintain a given initial abundance, accounting for both growth and diffusion. +- `steadySingleSpecies()` now correctly preserves the steady state under + `project()`, including when diffusion is non-zero. +- Growth is now forced to always be non-negative, preventing unphysical shrinkage. + No warning is issued when growth stops at or after maturity size. +- New vignettes: cohort dynamics demonstrating the effect of diffusion in a + single-species model; numerical details documenting the finite-volume scheme and + its steady-state solution; and a vignette on using FFT for predation kernel + calculations. # mizer 2.5.4 From a1782a5b269ec726f698ffe2e0ac6ef57ead9332 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 9 Apr 2026 10:01:37 +0100 Subject: [PATCH 46/47] NEWS.md now updated also with changes on the master branch. --- NEWS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/NEWS.md b/NEWS.md index d70e4a08c..bdbdaed65 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,24 @@ single-species model; numerical details documenting the finite-volume scheme and its steady-state solution; and a vignette on using FFT for predation kernel calculations. +- Many functions now have S3 methods so they can be called with either a + MizerParams or MizerSim object and users could define their own subclasses + and methods to modify mizer behaviour (#330). +- `getBiomass()` now has a `use_cutoff` argument to restrict the biomass + calculation to sizes above the `biomass_cutoff` species parameter. +- `plotBiomass()` and `plotlyBiomass()` now have a `use_cutoff` argument, + passed to `getBiomass()`. +- `age_mat_vB()` is now exported. +- `project_n()` is a new exported function that projects the abundance + spectrum forward in time, factored out of `project()`. + +## Bug fixes + +- `getMeanMaxWeight()` now correctly applies the species selector to the + denominator. +- `plotDataFrame()` now correctly applies custom log-scale x breaks. +- `get_size_range_array()` no longer gives an error when no size brackets are + selected. # mizer 2.5.4 From 992af2f4b23adfb1e02cb1b9875061d6593cb389 Mon Sep 17 00:00:00 2001 From: Gustav Delius Date: Thu, 9 Apr 2026 10:43:10 +0100 Subject: [PATCH 47/47] Spelling corrections --- NEWS.md | 4 +- R/calibrate.R | 4 +- R/match.R | 6 +-- R/plots.R | 2 +- inst/WORDLIST | 4 -- man/matchBiomasses.Rd | 2 +- man/matchNumbers.Rd | 2 +- man/matchYields.Rd | 2 +- vignettes/editing_website.Rmd | 2 +- vignettes/model_description.Rmd | 74 +++++++++++++++++---------------- vignettes/numerical_details.Rmd | 2 +- 11 files changed, 51 insertions(+), 53 deletions(-) diff --git a/NEWS.md b/NEWS.md index bdbdaed65..db51505ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -439,7 +439,7 @@ state, so you will need to also call `steady()` after matching the growth rates. * Many improvements in the documentation. * Many small improvements to code quality and testing. * Better social media cards, especially for twitter. -* mizer can be run on binder, https://mybinder.org/v2/gh/sizespectrum/mizer/HEAD?urlpath=rstudio +* mizer can be [run on binder](https://mybinder.org/v2/gh/sizespectrum/mizer/HEAD?urlpath=rstudio) ## Bug fixes @@ -889,7 +889,7 @@ species. The information is set up via a new `gear_params()` data frame. See well as `idxFinalT()` to access the values at the final time of a simulation. * New function `getCriticalFeedingLevel()` returns the critical feeding level for each species at each size. -* Mizer reexports the `melt()` function from the reshape2 package which allows +* Mizer re-exports the `melt()` function from the reshape2 package which allows users to convert the arrays returned by mizer functions into data frames that can be used for example in ggplot2 and plotly. * `validSpeciesParams()` checks validity of species parameter data frame and diff --git a/R/calibrate.R b/R/calibrate.R index 0718d482a..6b1d2cefc 100644 --- a/R/calibrate.R +++ b/R/calibrate.R @@ -17,7 +17,7 @@ #' individual species will not match observations yet, with some species #' having biomasses that are too high and others too low. So after this #' function you may want to use [matchBiomasses()]. This is described in the -#' blog post at https://bit.ly/2YqXESV. +#' blog post at \url{https://bit.ly/2YqXESV}. #' #' If you have observations of the yearly yield instead of biomasses, you can #' use [calibrateYield()] instead of this function. @@ -84,7 +84,7 @@ calibrateBiomass.MizerParams <- function(params, ...) { #' individual species will not match observations yet, with some species #' having numbers that are too high and others too low. So after this #' function you may want to use [matchNumbers()]. This is described in the -#' blog post at https://bit.ly/2YqXESV. +#' blog post at \url{https://bit.ly/2YqXESV}. #' #' If you have observations of the yearly yield instead of numbers, you can #' use [calibrateYield()] instead of this function. diff --git a/R/match.R b/R/match.R index e341cfaa2..a0038d1b8 100644 --- a/R/match.R +++ b/R/match.R @@ -10,7 +10,7 @@ #' So after using this function you may want to use `steady()` to run the model #' to steady state, after which of course the biomasses will no longer match #' exactly. You could then iterate this process. This is described in the -#' blog post at https://bit.ly/2YqXESV. +#' blog post at \url{https://bit.ly/2YqXESV}. #' #' Before you can use this function you will need to have added a #' `biomass_observed` column to your model which gives the observed biomass in @@ -91,7 +91,7 @@ matchBiomasses.MizerParams <- function(params, species = NULL, ...) { #' So after using this function you may want to use `steady()` to run the model #' to steady state, after which of course the numbers will no longer match #' exactly. You could then iterate this process. This is described in the -#' blog post at https://bit.ly/2YqXESV. +#' blog post at \url{https://bit.ly/2YqXESV}. #' #' Before you can use this function you will need to have added a #' `number_observed` column to your model which gives the observed number of @@ -180,7 +180,7 @@ matchNumbers.MizerParams <- function(params, species = NULL, ...) { #' So after using this function you may want to use `steady()` to run the model #' to steady state, after which of course the yields will no longer match #' exactly. You could then iterate this process. This is described in the -#' blog post at https://bit.ly/2YqXESV. +#' blog post at \url{https://bit.ly/2YqXESV}. #' #' Before you can use this function you will need to have added a #' `yield_observed` column to your model which gives the observed yields in diff --git a/R/plots.R b/R/plots.R index 53cc064fa..5134666a6 100644 --- a/R/plots.R +++ b/R/plots.R @@ -215,7 +215,7 @@ plotDataFrame <- function(frame, params, style = "line", xlab = waiver(), #' magnitude, in which case the ggplot2 default produces no ticks. #' #' Thanks to Heather Turner at -#' https://stackoverflow.com/questions/14255533/pretty-ticks-for-log-normal-scale-using-ggplot2-dynamic-not-manual +#' \url{https://stackoverflow.com/questions/14255533/pretty-ticks-for-log-normal-scale-using-ggplot2-dynamic-not-manual} #' #' @param n Approximate number of ticks #' diff --git a/inst/WORDLIST b/inst/WORDLIST index f13678a62..4a5bcac03 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -30,14 +30,11 @@ Drazen EBFM Ecopath Ecosim -Fluxs Foerster Friedrichs Giacomini -GitHubPages Hinzen Holling -Inf Jefcoats Kira LFI @@ -195,7 +192,6 @@ propto rakers rdd rdi -reexports repro rescalings roxygen diff --git a/man/matchBiomasses.Rd b/man/matchBiomasses.Rd index 3cb6a929b..20c155be8 100644 --- a/man/matchBiomasses.Rd +++ b/man/matchBiomasses.Rd @@ -31,7 +31,7 @@ state solution, even if the initial abundance densities were at steady state. So after using this function you may want to use \code{steady()} to run the model to steady state, after which of course the biomasses will no longer match exactly. You could then iterate this process. This is described in the -blog post at https://bit.ly/2YqXESV. +blog post at \url{https://bit.ly/2YqXESV}. Before you can use this function you will need to have added a \code{biomass_observed} column to your model which gives the observed biomass in diff --git a/man/matchNumbers.Rd b/man/matchNumbers.Rd index ed7a07d81..765e0262b 100644 --- a/man/matchNumbers.Rd +++ b/man/matchNumbers.Rd @@ -31,7 +31,7 @@ state solution, even if the initial number densities were at steady state. So after using this function you may want to use \code{steady()} to run the model to steady state, after which of course the numbers will no longer match exactly. You could then iterate this process. This is described in the -blog post at https://bit.ly/2YqXESV. +blog post at \url{https://bit.ly/2YqXESV}. Before you can use this function you will need to have added a \code{number_observed} column to your model which gives the observed number of diff --git a/man/matchYields.Rd b/man/matchYields.Rd index c53a07aa2..c7b7ac73d 100644 --- a/man/matchYields.Rd +++ b/man/matchYields.Rd @@ -41,7 +41,7 @@ state solution, even if the initial abundance densities were at steady state. So after using this function you may want to use \code{steady()} to run the model to steady state, after which of course the yields will no longer match exactly. You could then iterate this process. This is described in the -blog post at https://bit.ly/2YqXESV. +blog post at \url{https://bit.ly/2YqXESV}. Before you can use this function you will need to have added a \code{yield_observed} column to your model which gives the observed yields in diff --git a/vignettes/editing_website.Rmd b/vignettes/editing_website.Rmd index eda142330..5c096dc60 100644 --- a/vignettes/editing_website.Rmd +++ b/vignettes/editing_website.Rmd @@ -121,7 +121,7 @@ repository. ## GitHub pages -The website is hosted with [GitHubPages](https://pages.github.com/). In the +The website is hosted with [GitHub Pages](https://pages.github.com/). In the settings pages of the mizer repository on GitHub the source is set to "master branch/docs folder". This means that only the master branch controls the website. diff --git a/vignettes/model_description.Rmd b/vignettes/model_description.Rmd index 4f4f7135d..0237b58bb 100644 --- a/vignettes/model_description.Rmd +++ b/vignettes/model_description.Rmd @@ -113,11 +113,11 @@ McKendrick-von Foerster equation, which is a transport equation (as one would use for traffic density) but with an additional loss term due to fish mortality: -\begin{equation} +$$ \label{eq:MvF} \frac{\partial N_i(w)}{\partial t} + \frac{\partial g_i(w) N_i(w)}{\partial w} = -\mu_i(w) N_i(w), -\end{equation} +$$ where individual growth $g_i(w)$ is described below in the [Growth](#growth) section and mortality $\mu_i(w)$ is described in the [Mortality](#mortality) @@ -160,16 +160,18 @@ semi-chemostat equation.
The semichemostat dynamics are given by -\begin{equation} +$$ \label{eq:nb} \frac{\partial N_R(w,t)}{\partial t} = r_R(w) \Big[ c_R (w) - N_R(w,t) \Big] - \mu_R(w) N_R(w,t). -\end{equation} +$$ Here $r_R(w)$ is the resource regeneration rate and $c_R(w)$ is the carrying capacity in the absence of predation. These parameters are changed with `setResource()`. By default mizer assumes allometric forms -\[r_R(w)= r_R\, w^{n-1}.\] -\[c_R(w)=\kappa\, w^{-\lambda}.\] +$$r_R(w)= r_R\, w^{n-1}.$$ + +$$c_R(w)=\kappa\, w^{-\lambda}.$$ + You can retrieve these with `getResourceRate()` and `getResourceCapacity()` respectively. It is also possible to implement other resource dynamics, as described in the help page for `setResource()`. The mortality $\mu_R(w)$ is @@ -203,12 +205,12 @@ The rate at which a predator of species $i$ and weight $w$ encounters food (mass per time) is determined by summing over all prey species and the resource spectrum and integrating over all prey sizes $w_p$, weighted by the selectivity factors: -\begin{equation} +$$ \label{eq:1} E_{i}(w) = \gamma_i(w) \int \left(\sum_{j} \theta_{ij} N_j(w_p) + \theta_{iR} N_R(w_p) \right) \phi_i(w,w_p) w_p \, dw_p. -\end{equation} +$$ This is calculated by `getEncounter()`. The overall prefactor $\gamma_i(w)$ sets the predation power of the predator. It could be interpreted as a search volume. It is set by `setSearchVolume()`. By @@ -225,27 +227,27 @@ changed with `setPredKernel()`. An important simplification occurs when the predation kernel $\phi_i(w,w_p)$ depends on the size of the prey **only** through the predator/prey size ratio $w_p/w$, -\[\phi_i(w, w_p)=\tilde{\phi}_i(w/w_p).\] +$$\phi_i(w, w_p)=\tilde{\phi}_i(w/w_p).$$ This is assumed by default but can be overruled. The default for the predation kernel is the truncated log-normal function -\[ +$$ \label{eq:4} \tilde{\phi}_i(x) = \begin{cases} \exp \left[ \dfrac{-(\ln(x / \beta_i))^2}{2\sigma_i^2} \right] &\text{ if }x\in\left[0,\beta_i\exp(3\sigma_i)\right]\\ 0&\text{ otherwise,} \end{cases} -\] +$$ where $\beta_i$ is the preferred predator-prey mass ratio and $\sigma_i$ sets the width of the predation kernel. The integral in the expression for the encounter rate is approximated by a Riemann sum over all weight brackets: -\[ +$$ {\tt encounter}[i,a] = {\tt search\_vol}[i,a]\sum_{k} \left( n_{R}[k] + \sum_{j} \theta[i,j] n[j,k] \right) \phi_i\left(w[a],w[k]\right) w[k]\, dw[k]. -\] +$$ In the case of a predation kernel that depends on $w/w_p$ only, this becomes a convolution sum and can be evaluated efficiently via fast Fourier transform. @@ -260,10 +262,10 @@ response type II to represent satiation. This determines the (no food) and 1 (fully satiated) so that $1-f_i(w)$ is the proportion of the encountered food that is consumed. The feeding level is given by -\begin{equation} +$$ \label{eq:f} f_i(w) = \frac{E_i(w)}{E_i(w) + h_i(w)}, -\end{equation} +$$ where $h_i(w)$ is the maximum consumption rate. This is changed with `setMaxIntakeRate()`. By default mizer assumes an allometric form @@ -271,9 +273,9 @@ $h_i(w) = h_i\, w^n.$ The feeding level is calculated with the function `getFeedingLevel()`. The rate at which food is consumed is then -\begin{equation} +$$ (1-f_i(w))E_{i}(w)=f_i(w)\, h_i(w). -\end{equation} +$$ Furthermore only a proportion $\alpha_i$ of the consumed food is absorbed. ## Metabolic losses @@ -282,16 +284,16 @@ Some of the absorbed food is used to fuel the needs for metabolism and activity and movement, at a rate ${\tt metab}_i(w)$. By default this is made up out of standard metabolism, scaling with exponent $p$, and loss due to activity and movement, scaling with exponent $1$: -\[{\tt metab}_i(w) = k_{s.i}\,w^p + k_i\,w.\] +$${\tt metab}_i(w) = k_{s.i}\,w^p + k_i\,w.$$ See the help page for `setMetabolicRate()`. The remaining rate, if any, is then available for growth and reproduction. So the rate at which energy becomes available for growth and reproduction is -\begin{equation} +$$ \label{eq:Er} E_{r.i}(w) = \max(0, \alpha_i f_i(w)\, h_i(w) - {\tt metab}_i(w)) -\end{equation} +$$ This is calculated with the `getEReproAndGrowth()` function. @@ -308,10 +310,10 @@ can however overrule. ## Growth What is left over after metabolism and reproduction is taken into account is invested in somatic growth. Thus the growth rate is -\begin{equation} +$$ \label{eq:growth} g_i(w) = E_{r.i}(w)\left(1-\psi_i(w)\right). -\end{equation} +$$ It is calculated by the `getEGrowth()` function. When food supply does not cover the requirements of metabolism and activity, @@ -334,28 +336,28 @@ corresponding predation mortalities on the ingested prey individuals. Recalling that $1-f_j(w)$ is the proportion of the food encountered by a predator of species $j$ and weight $w$ that is actually consumed, the rate at which all predators of species $j$ consume prey of size $w_p$ is -\begin{equation} +$$ \label{eq:pred_rated} {\tt pred\_rate}_j(w_p) = \int \phi_j(w,w_p) (1-f_j(w)) \gamma_j(w) N_j(w) \, dw. -\end{equation} +$$ This predation rate is calculated by the function `getPredRate()`.
The integral is approximated by a Riemann sum over all fish weight brackets. -\[ +$$ {\tt pred\_rate}[j,c] = \sum_{a} {\tt pred_kernel}[j,a,c]\,(1-{\tt feeding_level}[j,a])\, \gamma[j,a]\,n[j,a]\,dw[a]. -\] +$$
The mortality rate due to predation is then obtained as -\begin{equation} +$$ \label{eq:mup} \mu_{p.i}(w_p) = \sum_j {\tt pred\_rate}_j(w_p)\, \theta_{ji}. -\end{equation} +$$ This predation mortality rate is calculated by the function `getPredMort()`. External mortality $\mu_{ext.i}(w)$ is independent of the abundances and is @@ -363,7 +365,7 @@ changed with `setExtMort()`. By default mizer assumes that the external mortality for each species is a constant $z0_i$ independent of size. The value of $z0_i$ is either specified as a species parameter or it is assumed to depend allometrically on the maximum size: -\[z0_i = z0_{pre} w_{\infty.i}^{1-n}.\] +$$z0_i = z0_{pre} w_{\infty.i}^{1-n}.$$ ## Fishing mortality @@ -374,7 +376,7 @@ Fishing mortality $F_i(w)$ is calculated with the function `getFMort()`. ## Total mortality The total mortality rate -\[\mu_i(w)=\mu_{p.i}(w)+\mu_{ext.i}(w)+F_i(w)\] +$$\mu_i(w)=\mu_{p.i}(w)+\mu_{ext.i}(w)+F_i(w)$$ is calculated with the function `getMort()`. @@ -382,10 +384,10 @@ is calculated with the function `getMort()`. The predation mortality rate on resource is given by a similar expression as the predation mortality on fish: -\begin{equation} +$$ \label{eq:mupp} \mu_{p}(w_p) = \sum_j {\tt pred\_rate}_j(w_p)\, \theta_{jp}. -\end{equation} +$$ This is the only mortality on resource currently implemented in mizer. It is calculated with the function `getResourceMort()`. @@ -400,10 +402,10 @@ found by integrating the contribution from all individuals of species $i$, each of which invests a proportion $\psi_i(w)$ of their consumption. This total rate of energy investment can then be converted to a total rate of egg production $R_{p.i}$ (numbers per year): -\begin{equation} +$$ \label{eq:Rp} R_{p.i} = \frac{\epsilon_i}{2 w_{0.i}} \int N_i(w) E_{r.i}(w) \psi_i(w) \, dw, -\end{equation} +$$ Here the total rate of investment is multiplied by an efficiency factor $\epsilon_i$ and then dividing by the egg weight $w_{0.i}$ to convert the energy into number of eggs. The result is multiplied by a factor $1/2$ to take into account that only @@ -434,10 +436,10 @@ a reproduction rate $R_i$ (numbers per time) that approaches a maximum as the energy invested in reproduction increases, modelled mathematically it is analogous to a *Beverton-Holt* type function: -\begin{equation} +$$ \label{eq:R} R_i = R_{\max.i} \frac{R_{p.i}}{R_{p.i} + R_{\max.i}}, -\end{equation} +$$ where $R_{\max.i}$ is the maximum reproduction rate of each trait class. This final rate of reproduction is calculated with `getRDD()`. diff --git a/vignettes/numerical_details.Rmd b/vignettes/numerical_details.Rmd index b4f460293..f0b5c990a 100644 --- a/vignettes/numerical_details.Rmd +++ b/vignettes/numerical_details.Rmd @@ -44,7 +44,7 @@ where $g_i(w)$ is the somatic growth rate, $d_i(w)$ is the diffusion coefficient We discretise this equation using a finite volume scheme. -## Discretisation of the Fluxs +## Discretisation of the Fluxes The term inside the derivative with respect to $w$ is the flux $J_i(w)$: $$ J_i(w) = g_i(w) N_i(w) - \frac{1}{2}\frac{\partial(d_i(w) N_i(w))}{\partial w} $$