--- title: "Generating an Rsim ensemble with Ecosense" author: "George A. Whitehouse" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Generating an Rsim ensemble with Ecosense} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) library(Rpath); library(data.table); library(viridis) #knitr::opts_knit$set(root.dir = rprojroot::find_rstudio_root_file()) ``` Rpath is an R implementation of the food web modeling program Ecopath with Ecosim (EwE; Christensen and Pauly 1992^[Christensen V, Pauly D (1992) Ecopath II-a software for balancing steady-state ecosystem models and calculating network characteristics. Ecol Model 61:169-185. doi:10.1016/0304-3800(92)90016-8], Walters et al. 1997^[Walters C, Christensen V, Pauly D (1997) Structuring dynamic models of exploited ecosystems from trophic mass-balance assessments. Rev Fish Biol Fish 7:139-172. doi:10.1023/a:1018479526149]). For full documentation of the Rpath package see Lucey et al. (2020^[Lucey SM, Gaichas SK, Aydin KY (2020) Conducting reproducible ecosystem modeling using the open source mass balance model Rpath. Ecol Model 427:11. doi:10.1016/j.ecolmodel.2020.109057]). Ecosense is a Monte Carlo approach to generating an ensemble of plausible ecosystem parameter sets from a single Rpath model for use with `rsim.run`, the time-dynamic counterpart to Rpath. For complete documentation of Ecosense see Whitehouse and Aydin (2020^[Whitehouse GA, Aydin KY (2020) Assessing the sensitivity of three Alaska marine food webs to perturbations: an example of Ecosim simulations using Rpath. Ecol Model 429:16. doi:10.1016/j.ecolmodel.2020.109074]). To use this vignette it is recommended you are familiar with Rpath and its basic operations. ## Ecosense This vignette includes the example Rpath models from Whitehouse and Aydin (2020) for the eastern Bering Sea (EBS), eastern Chukchi Sea (ECS), and Gulf of Alaska (GOA) marine food webs, and uses the EBS model in an example. Each of these models has 50–54 functional groups, including one fishery, one primary producer, two detrital compartments, and there are no stanzas. To use Ecosense you need an `rpath.params` object and the corresponding `rsim.scenario` object. First, load the unbalanced models, balance them, and setup the rsim scenario objects. ```{r loadbalanacemodels} # load the unbalanced models # load("data/Ecosense.EBS.rda") # load("data/Ecosense.ECS.rda") # load("data/Ecosense.GOA.rda") # balance the models EBS_bal <- rpath(Ecosense.EBS) ECS_bal <- rpath(Ecosense.ECS) GOA_bal <- rpath(Ecosense.GOA) # create rsim scenario objects EBS_scene <- rsim.scenario(EBS_bal, Ecosense.EBS, years=1:100) ECS_scene <- rsim.scenario(ECS_bal, Ecosense.ECS, years=1:100) GOA_scene <- rsim.scenario(GOA_bal, Ecosense.GOA, years=1:100) # # source ecosense.R # source("R/ecosense.R", local = knitr::knit_global()) # ls() ``` ### Setting up Ecosense Full sets of Ecosim parameter sets are drawn from distributions centered on the original Rpath parameter estimates. The width of the distribution for each parameter is defined in the data pedigree of the `rpath.params` object. The drawn parameters include biomass, P/B, Q/B, diet composition, and natural mortality (i.e., M zero). Additionally, vulnerability and handling time from the predator prey functional response can be drawn over a specified range (lower/upper bounds in log space-1) for each individual predator-prey interaction. A single parameter set can be generated with a call to `rsim.sense`. The object returned by `rsim.sense` is equivalent to the list of parameters in an `rsim.scenario` object (e.g., scenario_object$params). ```{r genmod, echo=TRUE} # One set of Ecosim parameters for the EBS model rsim.sense(EBS_scene,Ecosense.EBS,Vvary = c(0,0), Dvary = c(0,0)) ``` Each Ecosim parameter set drawn with Ecosense can be subject to an initial simulation, also known as a burn-in, to eliminate unstable parameter sets. This instability usually results from incompatible draws of parameter sets that either lead to uncontrolled population growth or population crash. Such as predator consumption and production that exceeds the production of prey. The length of the burn-in period can be set in the original `rsim.scenario` object. ```{r init, echo=TRUE} # Setting the burn-in period in the EBS scenario object to 50 years. EBS_scene$params$BURN_YEARS <- 50 ``` During burn-in, if the biomass of a functional group exceeds 1,000x its starting biomass or decreases to less than 1/1,000 its starting biomass, the ecosystem parameter set is rejected and not retained for further analysis. A 50 year burn-in period is generally sufficient to eliminate most of the unstable configurations. ### Generating an ensemble of Ecosim parameter sets To generate an ensemble of parameter sets you can repeat the call to `rsim.sense` in a loop. First, determine the number of parameter sets you wish to generate and create lists to store those parameter sets. Set up another vector to keep track of which generated parameter sets were rejected or retained. ```{r ensemble, echo=TRUE} NUM_RUNS <- 1000 # how many ecosystem parameter sets to generate parlist<-as.list(rep(NA,NUM_RUNS)) # create lists to store the generated parameters kept<-rep(NA,NUM_RUNS) # object to keep track of kept systems set.seed(666) # Optional, set seed so output can be replicated ``` In this example we will generate 1,000 parameter sets (NUM_RUNS) for the eastern Bering Sea food web model. parlist[[i]] is the object that will store the ith generated parameter set. Optionally, you can use `set.seed` to replicate these results. The following loop generates an ensemble of Ecosim parameters for the EBS food web model, assigns the generated base biomass (B_BaseRef) to the starting biomass in the `rsim.scenario` object, sets the burn years to 50, runs a simulation with `rsim.run`, and rejects or retains parameter sets based on the boundaries described above. ```{r generatorloop, echo=TRUE, results='hide'} for (i in 1:NUM_RUNS){ EBSsense <- EBS_scene # scenario object # INSERT SENSE ROUTINE BELOW parlist[[i]]<- EBS_scene$params # Base ecosim params parlist[[i]]<- rsim.sense(EBS_scene,Ecosense.EBS,Vvary = c(-4.5,4.5), Dvary = c(0,0)) # Replace the base params with Ecosense params EBSsense$start_state$Biomass <- parlist[[i]]$B_BaseRef # Apply the Ecosense starting biomass parlist[[i]]$BURN_YEARS <- 50 # Set Burn Years to 50 EBSsense$params <- parlist[[i]] # replace base params with the Ecosense generated params EBStest <- rsim.run(EBSsense, method="AB") # Run rsim with the generated system failList <- which(is.na(EBStest$end_state$Biomass)) {if (length(failList)>0) {cat(i,": fail in year ",EBStest$crash_year,": ",EBStest$params$spname[failList],"\n"); kept[i]<-F; flush.console()} else {cat(i,": success!\n"); kept[i]<-T; flush.console()}} # output for the console parlist[[i]]$BURN_YEARS <- 1 } ``` This loop produces output in the console noting whether the generated parameter set was a success or failure, the year the ecosystem "crashed", and which group(s) were responsible for the crash. Here are the results for the first two generated parameter sets: 1 : fail in year 1 : Squids\ 2 : fail in year 7 : Salmon returning\ The first two ecosystems both failed, the first in year one and the second in year seven. Squids crashed in the first ecosystem and Salmon returning in the second. The 52nd generated parameter set was the first to survive the 50 year burn-in: 52 : success!\ To determine which of the 1,000 generated systems survived burn-in, how many survived burn-in and what the rejection rate was: ```{r status, echo=TRUE} KEPT <- which(kept==T); KEPT # the number associated with the kept system nkept <- length(KEPT); nkept # how many were kept 1-(nkept/NUM_RUNS) # rejection rate ``` The numbers in KEPT can be used to access the retained parameter sets in parlist[[i]]. In this example, 43 parameter sets were retained and the rejection rate for generated parameter sets was 95.7%. All of the retained Rsim parameter sets can be run through a simulation with another for loop. This loop subjects each of the 43 retained parameter sets in this example to a 100 year simulation without any additional perturbations, the first 50 years of which is the burn-in period. ```{r simstuff, echo=TRUE, results='hide'} ecos <- as.list(rep(NA,length(KEPT))) # lists for simulated ecosystems k <- 0 # counter for simulated ecosystems for (i in KEPT) { EBS_scene$start_state$Biomass <- parlist[[i]]$B_BaseRef # set the starting Biomass to the generated values EBSsense <- EBS_scene # set up the scenario object EBSsense$params <- parlist[[i]] # set the params in the scenario object equal to the generated params. EBSsense$BURN_YEARS <- -1 # no burn-in period k <- k + 1 # set the number for the simulated ecosystem ecos[[k]] <- rsim.run(EBSsense,method='AB') # run rsim.run on the generated system print(c("Ecosystem no.",k,"out of",nkept)) # progress output to console } ``` Because biomass can differ greatly between the generated parameter sets, relative biomass can be used for plotting. This loop calculates the relative biomass of each group in each system, relative to their starting biomass, as drawn by Ecosense. ```{r relbio, echo=TRUE} relB_ecos <- as.list(rep(NA,length(KEPT))) # list to output relative biomass k <- 0 for (i in 1:nkept) { spname <- colnames(ecos[[i]]$out_Biomass[,2:ncol(ecos[[i]]$out_Biomass)]) biomass <- ecos[[i]]$out_Biomass[, spname] n <- ncol(biomass) start.bio <- biomass[1, ] # the drawn starting biomass start.bio[which(start.bio == 0)] <- 1 rel.bio <- matrix(NA, dim(biomass)[1], dim(biomass)[2]) for(isp in 1:n) rel.bio[, isp] <- biomass[, isp] / start.bio[isp] # biomass relative to biomass at t=1 colnames(rel.bio) <- spname k <- k + 1 relB_ecos[[k]] <- rel.bio } ``` Here's an example plot of walleye pollock from all the retained Rsim parameter sets. First setup a matrix to store the pollock trajectories from the simulations with the retained parameter sets. ```{r walleye, echo=TRUE} this_species <- "Walleye pollock" plot_mat <- matrix(nrow=1200, ncol=nkept) # matrix of pollock trajectories from all generated systems for (i in 1:nkept) { plot_mat[,i] <- relB_ecos[[i]][,this_species] } ``` Then plot all the trajectories: ```{r plottraj, echo=TRUE} plot_col <- viridis(nkept) #layout(matrix(c(1,1,1,1,1,1,2,2), nrow = 1, ncol = 8, byrow = TRUE)) plot(1:1200, relB_ecos[[1]][,this_species], type='n', xlab="Months", ylab="Relative biomass", ylim=c(min(plot_mat),max(plot_mat)), main=this_species) # one line for pollock in each of the generated systems for (i in 1:nkept) { lines(1:1200, relB_ecos[[i]][,this_species], lwd=2, col=plot_col[i]) } # distribution of pollock relative biomasses boxplot(plot_mat[1200,], ylim=c(min(plot_mat),max(plot_mat)), yaxt='n') axis(side=2, at=c(seq(0,150,50)), tick=TRUE, labels=F) ``` Each line in the left panel is the relative biomass trajectory of walleye pollock over the 100 year simulation with the retained Rsim parameter sets. ### Run the kept systems with a perturbation When working with an ensemble such as this, we are often interested in doing the same perturbation across all ensemble members to describe a range of potential outcomes. In this example, we'll increase the fishing mortality on walleye pollock using the `adjust.fishing` function. The first 50 years of the simulation are the burn-in period. So, the perturbation is put in place from year 51 to 100, and in this example we increase walleye pollock fishing mortality by 2X. ```{r run, echo=TRUE, results='hide'} ecos_sp <- as.list(rep(NA,length(KEPT))) # lists for simulated ecosystems k <- 0 # counter for simulated ecosystems for (i in KEPT) { EBS_scene$start_state$Biomass <- parlist[[i]]$B_BaseRef # set the starting Biomass to the generated values EBSsense <- EBS_scene # set up the scenario object EBSsense <- adjust.fishing(EBSsense, "ForcedFRate", group=this_species, sim.year=51:100, value=2) # perturb pollock FRate EBSsense$params <- parlist[[i]] # set the params in the scenario object equal to the generated params. EBSsense$BURN_YEARS <- -1 # no burn-in period k <- k + 1 # set the number for the simulated ecosystem ecos_sp[[k]] <- rsim.run(EBSsense,method='AB') # run rsim.run on the generated system print(c("Ecosystem no.", k, "out of", nkept)) # progress output to console } ``` To visualize the results, we use relative biomass again but this time relative to biomass at the end of the burn-in period (year 50). That is the point in time where the perturbation began and the point from which we want to measure any response or change. ```{r viz, echo=TRUE} relB_ecos_sp <- as.list(rep(NA,length(KEPT))) k <- 0 for (i in 1:nkept) { spname <- colnames(ecos_sp[[i]]$out_Biomass[,2:ncol(ecos_sp[[i]]$out_Biomass)]) biomass <- ecos_sp[[i]]$out_Biomass[, spname] n <- ncol(biomass) start.bio <- biomass[600, ] # end of burn-in start.bio[which(start.bio == 0)] <- 1 rel.bio <- matrix(NA, dim(biomass)[1], dim(biomass)[2]) for(isp in 1:n) rel.bio[, isp] <- biomass[, isp] / start.bio[isp] # biomass relative to the end of burn-in biomass colnames(rel.bio) <- spname k <- k + 1 relB_ecos_sp[[k]] <- rel.bio } ``` Plot the trajectories for walleye pollock from this perturbation. Note, the x-axis starts at the begining of the perturbation (January of year 51). ```{r walleyeplot, echo=TRUE} plot_mat_sp <- matrix(nrow=1200, ncol=nkept) for (i in 1:nkept) { plot_mat_sp[,i] <- relB_ecos_sp[[i]][,this_species] } #layout(matrix(c(1,1,1,1,1,1,2,2), nrow = 1, ncol = 8, byrow = TRUE)) plot(601:1200, relB_ecos_sp[[1]][601:1200,this_species], type='n', xlab="Months", ylab="Relative biomass", ylim=c(min(plot_mat_sp[601:1200,]),max(plot_mat_sp[601:1200,])), main=this_species) for (i in 1:nkept) { lines(601:1200, relB_ecos_sp[[i]][601:1200,this_species], lwd=2, col=plot_col[i]) } boxplot(plot_mat_sp[1200,], ylim=c(min(plot_mat_sp[601:1200,]),max(plot_mat_sp[601:1200,]))) ``` Biomass for walleye pollock decreased in all 43 ecosystems in response to the perturbation. Walleye pollock are a nodal species in the eastern Bering Sea food web and such a perturbation as in this example might illicit a response from many functional groups across the food web. Here is an example boxplot of the distribution of biomass outcomes for all the living functional groups relative to their biomass at the start of the perturbation. ```{r perturb, echo=TRUE} sp_perturb_out <- matrix(nrow=nkept, ncol=(EBS_bal$NUM_LIVING+EBS_bal$NUM_DEAD)) for(i in 1:nkept){ sp_perturb_out[i,] <- relB_ecos_sp[[i]][1200,] } colnames(sp_perturb_out) <- colnames(relB_ecos_sp[[1]]) par(mfrow=c(1,1), mar=c(9,3,0.5,0.5)) boxplot(sp_perturb_out, outline=FALSE, las=2, cex.axis=0.6) abline(h=1, lty=2) ```