Multispecies models rely on either estimated or input parameters governing predator consumption rates to estimate predation mortality on consumed species.
Stomach weights are needed as input to the length-based multispecies model hydra. At present each species has a stomach weight for each size bin that is repeated for each year. We have total consumed weight for each predator age class at each timestep in the detailed diet atlantis output, so would need to map age classes to length bins, sum consumed weight by length bin and divide by total numbers in that length bin to get stomach weight.
What we have saved so far is a diet comp in proportion that has had
bias and observation error added to proportions. However, we need the
stomach weight portion of the equation, which has the survey
timing/area/efficiency/selectivity applied to true consumed weight. The
hydra input is mean daily stomach weight in grams. From the Atlantis
manual, “DetailedDietCheck.txt returns the total consumed biomass since
the last output given for each cell (box and layer) of each age group of
each functional group.” So daily per capita consumed biomass is this
output, summed over prey for total consumption, divided by the numbers
from the same survey design, divided by the output step
omlist_ss$runpar$outputstep
.
The function below should return per-capita consumption by age class
using the detailedDietCheck.txt
output. This can then be
converted to lengthbin as necessary.
om_cons<- function(config = configfile,
dietfile = file_diet,
usersurvey = usersurvey_file,
omlist_ss,
n_reps = n_reps,
save = TRUE){
source(config)
#Load functional groups
fgs <- atlantisom::load_fgs(dir=d.name,
file_fgs = functional.groups.file)
# load or read in saved detailed diet
if(!file.exists(file.path(d.name,
paste0(scenario.name, "detaileddiet.rds")))){
detaileddiet <- load_detailed_diet_comp(dir = d.name,
file_diet,
fgs = fgs)
if(save){
saveRDS(detaileddiet, file.path(d.name, paste0(scenario.name, "detaileddiet.rds")))
}
} else {
detaileddiet <- readRDS(file.path(d.name,
paste0(scenario.name, "detaileddiet.rds")))
}
#one script for dimension parameters to be used in multiple functions
source("config/omdimensions.R", local = TRUE)
survObsStomWt <- list()
for (s in usersurvey)
{
source(s, local = TRUE)
# survtime doesn't match units of time.days in detaileddiet
survtime <- survey_sample_full*omlist_ss$runpar$outputstep
# apply survey design to detailed diet
survey_cons <- create_survey_diet(dat = detaileddiet,
time = survtime,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex)
# get numbers at ageclass for same survey design
# note different time units!
survey_N <- atlantisom::create_survey(dat = omlist_ss$truenums_ss,
time = survey_sample_full,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex.agecl)
# convert survey N times to cons times in days
survey_N$time <- survey_N$time*omlist_ss$runpar$outputstep
# get rid of polygon
survey_totN <- survey_N %>%
group_by(species, agecl, time) %>%
summarise(totN = sum(atoutput)) %>%
ungroup()
# sum over prey to get total consumption in t, divide by N and timestep, convert to g
survey_totcons <- survey_cons %>%
group_by(species, agecl, time) %>%
summarise(totcons = sum(atoutput)) %>%
ungroup() %>%
left_join(survey_totN) %>%
mutate(percap_cons = totcons/totN) %>%
#mutate(daily_percap_g = percap_cons/omlist_ss$runpar$outputstep*1000000)
mutate(daily_percap_g = percap_cons*1000000) #try assuming cons is snapshot not cumulative since last timestep
#save survey consumption, takes a long time to generate with lots of reps/species
#if(save){
saveRDS(survey_cons, file.path(d.name, paste0(scenario.name, "_",
survey.name, "surveycons.rds")))
#}
survObsStomWt[[survey.name]] <- survey_cons
}
}
The configuration files for NOBA are in posiedon-dev/config and see also ms-keyrun project repo illustrating full simulated multispecies modeling data. The NOBA model output was producing fairly low per capita consumption using the code above, so we will compare that with what we get from the CC model to see if we have an output file problem across models or if this is a characteristic of the NOBA model.
source(here("config/NOBA2Config.R"))
# this hardcodes a d.name in a different folder because I don't want multiple copies
if(!dir.exists(d.name)){
d.name <- "/Users/sarah.gaichas/Documents/0_Data/ms-keyrun/simulated-data/atlantisoutput/NOBA_march_2020"
}
# apply surveys to true total consumption (snipped from atlantosom om_diet.R and om_comps.R)
detaileddiet <- readRDS(file.path(d.name, paste0(scenario.name, "detaileddiet.rds")))
usersurvey <- c(here("config/mssurvey_spring.R"),
here("config/mssurvey_fall.R"))
omlist_ss <- readRDS(file.path(d.name, paste0(scenario.name, "omlist_ss.rds")))
source(here("config/omdimensions.R"))
source(usersurvey[2])
# survtime doesn't match units of time.days in detaileddiet
survtime <- survey_sample_full*omlist_ss$runpar$outputstep
# apply survey design to detailed diet
survey_cons <- create_survey_diet(dat = detaileddiet,
time = survtime,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex)
# get numbers at ageclass for same survey design
# note different time units!
survey_N <- atlantisom::create_survey(dat = omlist_ss$truenums_ss,
time = survey_sample_full,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex.agecl)
# convert survey N times to cons times in days
survey_N$time <- survey_N$time*omlist_ss$runpar$outputstep
# get rid of polygon
survey_totN <- survey_N %>%
group_by(species, agecl, time) %>%
summarise(totN = sum(atoutput)) %>%
ungroup()
# sum over prey to get total consumption in t, divide by N and timestep, convert to g
survey_totcons <- survey_cons %>%
group_by(species, agecl, time) %>%
summarise(totcons = sum(atoutput)) %>%
ungroup() %>%
left_join(survey_totN) %>%
mutate(percap_cons = totcons/totN) %>%
#mutate(daily_percap_g = percap_cons/omlist_ss$runpar$outputstep*1000000)
mutate(daily_percap_g = percap_cons*1000000) #try assuming cons is snapshot not cumulative since last timestep
For example, NOBA cod daily per capita intake is tens of grams for older age classes, but is 3-4x lower than expected:
DT::datatable(as.data.frame(survey_totcons%>%filter(species=="North_atl_cod")),
rownames = FALSE,
options = list(pageLength = 25,
order = list(list(0, 'asc')))
)
This is a short run of the CCA model implemented in the latest
codebase, so it can produce full age structured output. First we need to
process the detailed diet file, and also get the output of
run_truth
for numbers. At present it seems the CC model is
also producing very low per capita consumpton. This suggests that
interpretation of the DetailedDietCheck.txt output is incorrect? It
doesn’t seem like it is tons consumed at by the population of predators
at agecl.
source(here("config/CCConfig_constest.R"))
file_diet <- paste0(scenario.name,"DetailedDietCheck.txt")
save <- TRUE
#Load functional groups
fgs <- atlantisom::load_fgs(dir=d.name,
file_fgs = functional.groups.file)
# load or read in saved detailed diet
if(!file.exists(file.path(d.name,
paste0(scenario.name, "detaileddiet.rds")))){
detaileddiet <- load_detailed_diet_comp(dir = d.name,
file_diet,
fgs = fgs)
if(save){
saveRDS(detaileddiet, file.path(d.name, paste0(scenario.name, "detaileddiet.rds")))
}
} else {
detaileddiet <- readRDS(file.path(d.name,
paste0(scenario.name, "detaileddiet.rds")))
}
# Age structured single species subset
sppsubset <- fgs %>%
filter(Name %in% c("Yelloweye_rockfish",
"Darkblotched_rockfish",
"Bocaccio_rockfish",
"Pacific_Ocean_Perch",
"Arrowtooth_flounder",
"Petrale_sole",
"Jack_mackerel",
"Pacific_sardine",
"Anchovy",
"Herring",
"Spiny_dogfish")
)
# Get true data and species subset
CCAtestom <- om_init(here("config/CCConfig_constest.R"))
CCAtestom_ms <- om_species(sppsubset$Name, CCAtestom)
omlist_ss <- CCAtestom_ms
# fall census survey
source(here("config/omdimensions.R"))
source(here("config/mssurvey_CCtest.R"))
# survtime doesn't match units of time.days in detaileddiet
survtime <- survey_sample_full*omlist_ss$runpar$outputstep
# apply survey design to detailed diet
survey_cons <- create_survey_diet(dat = detaileddiet,
time = survtime,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex)
# get numbers at ageclass for same survey design
# note different time units!
survey_N <- atlantisom::create_survey(dat = omlist_ss$truenums_ss,
time = survey_sample_full,
species = survspp,
boxes = survboxes,
effic = surveffic,
selex = survselex.agecl)
# convert survey N times to cons times in days
survey_N$time <- survey_N$time*omlist_ss$runpar$outputstep
# get rid of polygon
survey_totN <- survey_N %>%
group_by(species, agecl, time) %>%
summarise(totN = sum(atoutput)) %>%
ungroup()
# sum over prey to get total consumption in t, divide by N and timestep, convert to g
survey_totcons <- survey_cons %>%
group_by(species, agecl, time) %>%
summarise(totcons = sum(atoutput)) %>%
ungroup() %>%
left_join(survey_totN) %>%
mutate(percap_cons = totcons/totN) %>%
#mutate(daily_percap_g = percap_cons/omlist_ss$runpar$outputstep*1000000)
mutate(daily_percap_g = percap_cons*1000000) #try assuming cons is snapshot not cumulative since last timestep
For example, CCA arrowtooth flounder should have a daily per capita intake in at least tens of grams for older age classes:
knitr::kable(as.data.frame(survey_totcons%>%filter(species=="Arrowtooth_flounder")))
species | agecl | time | totcons | totN | percap_cons | daily_percap_g |
---|---|---|---|---|---|---|
Arrowtooth_flounder | 2 | 365 | 0.0000006 | 26203309.8 | 0e+00 | 0.0000000 |
Arrowtooth_flounder | 2 | 730 | 0.0000020 | 26312266.5 | 0e+00 | 0.0000001 |
Arrowtooth_flounder | 2 | 1095 | 0.0000019 | 24872346.5 | 0e+00 | 0.0000001 |
Arrowtooth_flounder | 2 | 1460 | 0.0000012 | 15855594.8 | 0e+00 | 0.0000001 |
Arrowtooth_flounder | 3 | 365 | 0.2361153 | 15708545.3 | 0e+00 | 0.0150310 |
Arrowtooth_flounder | 3 | 730 | 0.5687898 | 16401427.4 | 0e+00 | 0.0346793 |
Arrowtooth_flounder | 3 | 1095 | 0.5574132 | 16718155.0 | 0e+00 | 0.0333418 |
Arrowtooth_flounder | 3 | 1460 | 0.6081520 | 18053962.5 | 0e+00 | 0.0336852 |
Arrowtooth_flounder | 4 | 365 | 0.1738271 | 9417156.4 | 0e+00 | 0.0184586 |
Arrowtooth_flounder | 4 | 730 | 0.4186986 | 9832692.3 | 0e+00 | 0.0425823 |
Arrowtooth_flounder | 4 | 1095 | 0.4103810 | 10022737.4 | 0e+00 | 0.0409450 |
Arrowtooth_flounder | 4 | 1460 | 0.4477898 | 10823708.4 | 0e+00 | 0.0413712 |
Arrowtooth_flounder | 5 | 365 | 0.1167518 | 5645442.6 | 0e+00 | 0.0206807 |
Arrowtooth_flounder | 5 | 730 | 0.2812394 | 5894574.2 | 0e+00 | 0.0477116 |
Arrowtooth_flounder | 5 | 1095 | 0.2756387 | 6008552.9 | 0e+00 | 0.0458744 |
Arrowtooth_flounder | 5 | 1460 | 0.3008103 | 6488936.0 | 0e+00 | 0.0463574 |
Arrowtooth_flounder | 6 | 365 | 0.0747134 | 3384354.4 | 0e+00 | 0.0220761 |
Arrowtooth_flounder | 6 | 730 | 0.1799696 | 3533708.3 | 1e-07 | 0.0509294 |
Arrowtooth_flounder | 6 | 1095 | 0.1763902 | 3602074.4 | 0e+00 | 0.0489691 |
Arrowtooth_flounder | 6 | 1460 | 0.1925054 | 3890156.2 | 0e+00 | 0.0494853 |
Arrowtooth_flounder | 7 | 365 | 0.0465334 | 2028868.1 | 0e+00 | 0.0229356 |
Arrowtooth_flounder | 7 | 730 | 0.1120871 | 2118401.4 | 1e-07 | 0.0529112 |
Arrowtooth_flounder | 7 | 1095 | 0.1098614 | 2159432.4 | 1e-07 | 0.0508751 |
Arrowtooth_flounder | 7 | 1460 | 0.1198996 | 2332138.0 | 1e-07 | 0.0514119 |
Arrowtooth_flounder | 8 | 365 | 0.0285327 | 1216278.2 | 0e+00 | 0.0234590 |
Arrowtooth_flounder | 8 | 730 | 0.0687274 | 1269951.3 | 1e-07 | 0.0541181 |
Arrowtooth_flounder | 8 | 1095 | 0.0673626 | 1294548.2 | 1e-07 | 0.0520356 |
Arrowtooth_flounder | 8 | 1460 | 0.0735204 | 1398093.4 | 1e-07 | 0.0525862 |
Arrowtooth_flounder | 9 | 365 | 0.0173360 | 729137.8 | 0e+00 | 0.0237760 |
Arrowtooth_flounder | 9 | 730 | 0.0417577 | 761316.8 | 1e-07 | 0.0548494 |
Arrowtooth_flounder | 9 | 1095 | 0.0409296 | 776064.4 | 1e-07 | 0.0527399 |
Arrowtooth_flounder | 9 | 1460 | 0.0446700 | 838137.6 | 1e-07 | 0.0532967 |
Arrowtooth_flounder | 10 | 365 | 0.0104766 | 437107.4 | 0e+00 | 0.0239680 |
Arrowtooth_flounder | 10 | 730 | 0.0252349 | 456395.7 | 1e-07 | 0.0552917 |
Arrowtooth_flounder | 10 | 1095 | 0.0247339 | 465234.7 | 1e-07 | 0.0531644 |
Arrowtooth_flounder | 10 | 1460 | 0.0269944 | 502449.0 | 1e-07 | 0.0537256 |
An existing atlantisom
function
calc_pred_diet()
was tested
and found to produce consumption estimates orders of magnitude
higher than those derived from
detailedDietCheck.txt
.
Here we develop new functions that don’t apply global diet composition to consumption but rather just get total consumption from PROD.nc. The challenge is figuring out what the units are of “Eat” and “Grazing” and how to appropriately expand them to the population level (water volume or numbers in a polygon).
First we need to get “Eat” and “Grazing” from PROD.nc and “Vol” from the .nc file. Here is our model setup from the config file. Lets look at NOBA_sacc_38, which is being used for mskeyrun simulated dataset generation. THIS OUTPUT IS NOT COMPARABLE TO NOBA_March_2020 above.
# WARNING!
# hardcoding because making copies of this in each repo is too cumbersome
# this currently lives in mskeyrun to make the simulated datasets with no climate
# change as appropriate!
dir <- "/Users/sarah.gaichas/Documents/0_Data/ms-keyrun/simulated-data/atlantisoutput/NOBA_sacc_38"
file_fgs <- "nordic_groups_v04.csv"
file_biolprm <- "nordic_biol_incl_harv_v_011_1skg.prm"
file_bgm <- "Nordic02.bgm"
file_init <- "nordic_biol_v23.nc"
file_runprm <- "nordic_run_v01.xml"
scenario <- "nordic_runresults_01"
select_groups <- mskeyrun::simFocalSpecies$Name # 11 mskeyrun fish species
verbose <- FALSE
This code is part of atlantisom::run_truth()
. With our
selected species from mskeyrun
, only eat
will
be created. grazing
applies to non-age structured
groups.
If this works for fish, test for inverts too
# Read in the functional groups csv since that is used by many functions
fgs <- load_fgs(dir = dir, file_fgs = file_fgs)
# Read in the biomass pools
bps <- load_bps(dir = dir, fgs = file_fgs, file_init = file_init)
# Read in the biological parameters
biol <- load_biolprm(dir = dir, file_biolprm = file_biolprm)
# Read in the run parameters
runprm <- load_runprm(dir = dir, file_runprm = file_runprm)
nc_out <- paste0(scenario, ".nc")
nc_prod <- paste0(scenario, "PROD.nc")
# Get the boundary boxes
allboxes <- load_box(dir = dir, file_bgm = file_bgm)
boxes <- get_boundary(allboxes)
eat <- load_nc(dir = dir,
file_nc = nc_prod,
bps = bps,
fgs = fgs,
select_groups = select_groups,
select_variable = "Eat",
check_acronyms = TRUE,
bboxes = boxes)
## [1] "Read /Users/sarah.gaichas/Documents/0_Data/ms-keyrun/simulated-data/atlantisoutput/NOBA_sacc_38/nordic_runresults_01PROD.nc successfully"
if(verbose) message("Eaten read in.")
grazing <- load_nc(dir = dir,
file_nc = nc_prod,
bps = bps,
fgs = fgs,
select_groups = select_groups,
select_variable = "Grazing",
check_acronyms = TRUE,
bboxes = boxes)
## [1] "Read /Users/sarah.gaichas/Documents/0_Data/ms-keyrun/simulated-data/atlantisoutput/NOBA_sacc_38/nordic_runresults_01PROD.nc successfully"
if(verbose) message("Grazing read in.")
vol <- load_nc_physics(dir = dir,
file_nc = nc_out,
physic_variables = "volume",
aggregate_layers = FALSE,
bboxes = boxes)
if(verbose) message("Volume read in.")
Now try the new function atlantisom::calc_pred_cons()
which uses the volume of all water column layers to expand the atoutput
(assumed to be mg N per cubic m at that timestep) to bio_eaten in
tons.
This is the function code
#' Calculate eaten total biomass in tonnes for each functional group.
#'
#' Function to calculate the eaten total biomass in tonnes:
#' "consumption" for each functional group
#' per time, polygon, and ageclass.
#'
#' @param eat A \code{data.frame} containing the consumed biomass in mg N for
#' each functional group, time, ageclass, and polygon.
#' The \code{data.frame} must originate from \code{\link{load_nc}}
#' using \code{select_variable = "Eat"}.
#' @param grazing A \code{data.frame} containing the consumed biomass in mg N for
#' each functional group, time, ageclass, and polygon.
#' The \code{data.frame} must originate from \code{\link{load_nc}}
#' using \code{select_variable = "Grazing"}.
#' @param vol A \code{data.frame} containing the volume for
#' each time, polygon, and layer.
#' The \code{data.frame} must originate from \code{\link{load_nc_physics}}
#' using \code{physic_variables = "volume"}.
#' @template biolprm
#' @return A code{data.frame} in tidy format with the following columns:
#' species, agecl, time, polygon, atoutput (bio_eaten)
#'
#' @family calc functions
#' @importFrom magrittr %>%
#' @export
#' @author Alexander Keth, Sarah Gaichas
#'
#' @examples
#' d <- system.file("extdata", "SETAS_Example", package = "atlantisom")
#' fgs <- load_fgs(d, "Functional_groups.csv")
#' bps <- load_bps(dir = d, fgs = "Functional_groups.csv",
#' file_init = "Initial_condition.nc")
#' runprm <- load_runprm(d, "Run_settings.xml")
#' boxes <- get_boundary(load_box(dir = d, file_bgm = "Geography.bgm"))
#' groups <- load_fgs(dir = d, "Functional_groups.csv")
#' groups <- groups[groups$IsTurnedOn > 0, "Name"]
#' eat <- load_nc(dir = d, file_nc = "outputsPROD.nc",
#' bps = bps, fgs = fgs, select_groups = groups,
#' select_variable = "Eat", check_acronyms = TRUE,
#' bboxes = boxes)
#' grazing <- load_nc(dir = d, file_nc = "outputsPROD.nc",
#' bps = bps, fgs = fgs, select_groups = groups,
#' select_variable = "Grazing", check_acronyms = TRUE,
#' bboxes = boxes)
#' vol <- load_nc_physics(dir = d, file_nc = "outputs.nc",
#' physic_variables = "volume", aggregate_layers = FALSE,
#' bboxes = boxes)
#' biolprm <- load_biolprm(dir = d,
#' file_biolprm = "Biology.prm")
#' runprm <- load_runprm(dir = d, file_runprm = "Run_settings.xml")
#' calcs <- calc_pred_cons(eat = eat, grazing = grazing,
#' vol = vol, biolprm = biolprm, runprm = runprm)
#' rm(calcs)
#'
calc_pred_cons <- function(eat, grazing, vol, biolprm, runprm){
# Conversion factor from mg N to tonnes wet-weight
bio_conv <- biolprm$redfieldcn * biolprm$kgw2d / 1000000000
# Eat and grazing are per species, agecl, polygon, and time.
# Therefore, we need to aggregate the vol over layers.
# SKG: The main question is the UNITS of eat and grazing.
# manual says this is either mg/m3/day or mg/day/individ
# calculations here assume mg/m3/day so multiplying by box vol
# is this m3 assuming the entire box volume or only where groups feed?
# do we need to match species to layers to get sppropriate volume?
# VERTnight_XXX and VERTday_XXX in biol.prm
# all layers? even sediment? try filtering out layer > 7 to remove sediment volume
# WARNING: ensure that layer 7 (processed via atlantisom) isn't unique to NOBA
vol <- dplyr::filter(vol, layer<7)
vol <- aggregate(atoutput ~ polygon + time, data = vol, sum)
colnames(vol)[colnames(vol) == "atoutput"] <- "vol"
# Combine eat and grazing! Calculate eaten biomass
biomass_eaten <- rbind(eat, grazing)
biomass_eaten <- merge(biomass_eaten, vol,
by = c("time", "polygon"))
# todo: previously AK noted that the bio_eaten values were too high
# the new values need to be checked to see if they give realistic
# results
# removed layer 7 in volume which should expand mg N to only water?
# total consumption and per capita from this look ok
# snapshot so should be daily value?
biomass_eaten$bio_eaten <- with(biomass_eaten,
atoutput * vol * bio_conv)
return(biomass_eaten)
}
This is the current result
biomass_eaten <- calc_pred_cons(eat = eat, grazing = grazing, vol = vol, biolprm = biol, runprm = runprm)
codtotcons <- biomass_eaten %>%
dplyr::filter(species=="North_atl_cod") %>%
dplyr::group_by(time, agecl) %>%
dplyr::summarise(totcons = sum(bio_eaten))
Plotting a cod example total consumption by age class
ggplot2::ggplot(codtotcons, aes(x=time, y=totcons)) + geom_line() + facet_wrap(~agecl, scales="free")