knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5, message = FALSE, warning = FALSE ) library(fb4package)
Temperature is the primary environmental driver in fish bioenergetics.
Because metabolic rates scale non-linearly with temperature, even modest
warming can have disproportionate effects on growth and consumption.
This vignette uses fb4package to:
We use 120-day summer conditions as the baseline.
# Species parameters (Stewart & Ibarra 1991) sp_params_chinook <- list( consumption = list( CEQ = 2, CA = 0.303, CB = -0.275, CQ = 3.0, CTO = 15.0, CTM = 20.0, CTL = 24.0, CK1 = 0.1, CK4 = 0.13 ), respiration = list( REQ = 1, RA = 0.00264, RB = -0.217, RQ = 0.06818, RTO = 0.0234, RTM = 0.0, RTL = 25.0, RK1 = 1.0, RK4 = 0.13, RK5 = 0.0 ), activity = list(ACT = 1.0, BACT = 0.0), sda = list(SDA = 0.172), egestion = list(EGEQ = 1, FA = 0.212, FB = -0.222, FG = 0.631), excretion = list(EXEQ = 1, UA = 0.0314, UB = 0.58, UG = -0.299), predator = list( PREDEDEQ = 2, Alpha1 = 4500, Beta1 = 6.0, Alpha2 = 5500, Beta2 = 2.0, Cutoff = 250 ) ) days <- 1:120 # Baseline temperature: cool Pacific NW summer temp_base <- data.frame( Day = days, Temperature = round(7 + 6 * sin(pi * (days - 20) / 120), 2) ) # Diet diet_props <- data.frame(Day = days, Alewife = 0.65, Shrimp = 0.35) prey_energy <- data.frame(Day = days, Alewife = 4900, Shrimp = 3200) # Build baseline object make_bio <- function(temp_df, sp_params, initial_weight = 5) { bio <- Bioenergetic( species_params = sp_params, species_info = list(scientific_name = "Oncorhynchus tshawytscha", common_name = "Chinook salmon", life_stage = "juvenile"), environmental_data = list(temperature = temp_df), diet_data = list( proportions = diet_props, prey_names = c("Alewife", "Shrimp"), energies = prey_energy ), simulation_settings = list(initial_weight = initial_weight, duration = 120) ) bio } bio_base <- make_bio(temp_base, sp_params_chinook) cat(sprintf("Baseline temperature range : %.1f – %.1f °C\n", min(temp_base$Temperature), max(temp_base$Temperature)))
We run binary search at each temperature offset and record the p required to achieve 40 g after 120 days (our target growth endpoint).
offsets <- c(-3, -2, -1, 0, 1, 2, 3, 4) target_wt <- 40 results_sens <- lapply(offsets, function(delta) { temp_shifted <- temp_base temp_shifted$Temperature <- pmax(1, temp_base$Temperature + delta) bio_s <- make_bio(temp_shifted, sp_params_chinook) res <- tryCatch( run_fb4(bio_s, fit_to = "Weight", fit_value = target_wt, strategy = "binary_search", verbose = FALSE), error = function(e) NULL ) if (is.null(res)) { return(data.frame(offset = delta, p_value = NA, final_weight = NA)) } data.frame( offset = delta, p_value = round(res$summary$p_value, 4), final_weight = round(res$summary$final_weight, 1) ) }) sens_df <- do.call(rbind, results_sens) knitr::kable( sens_df, caption = paste0("p-value required to achieve ", target_wt, " g across temperature offsets"), col.names = c("Temp. offset (°C)", "Estimated p", "Final weight (g)") )
plot(sens_df$offset, sens_df$p_value, type = "b", pch = 19, lwd = 2, col = "steelblue", xlab = "Temperature offset (°C)", ylab = "Estimated p-value", main = "Feeding level required to achieve target weight") abline(v = 0, lty = 2, col = "gray50") grid()
Interpretation: At warmer temperatures, Chinook must feed at a higher proportion of maximum ration to achieve the same growth target, because respiration costs increase faster than maximum consumption near the thermal optimum. Above ~+3 °C, the required p approaches or exceeds 1, indicating that growth to 40 g may become impossible at those temperatures.
plot(bio, type = "sensitivity") runs the same analysis internally and
produces a formatted figure directly.
plot(bio_base, type = "sensitivity", temp_offsets = c(-3, -2, -1, 0, 1, 2, 3, 4), fit_to = "Weight", fit_value = 40, colors = "blue")
Rather than fixing a target weight, here we fix p = 0.65 (estimated from field data) and ask: how does final weight change under warming scenarios?
p_fixed <- 0.65 scenarios <- c("Baseline" = 0, "+1 °C" = 1, "+2 °C" = 2, "+4 °C" = 4) scenario_results <- lapply(names(scenarios), function(sc) { delta <- scenarios[[sc]] temp_s <- temp_base temp_s$Temperature <- pmax(1, temp_base$Temperature + delta) bio_s <- make_bio(temp_s, sp_params_chinook) res <- run_fb4(bio_s, fit_to = "p_value", fit_value = p_fixed, strategy = "direct_p_value", verbose = FALSE) if (is.null(res)) return(NULL) data.frame( Scenario = sc, Delta_T = delta, Final_wt_g = round(res$summary$final_weight, 1), Consumption = round(res$summary$total_consumption_g, 1) ) }) scenario_df <- do.call(rbind, scenario_results) knitr::kable( scenario_df, caption = paste0("Projected outcomes at p = ", p_fixed, " under four temperature scenarios"), col.names = c("Scenario", "Δ T (°C)", "Final weight (g)", "Total consumption (g)") )
bar_cols <- c("steelblue", "gold", "darkorange", "firebrick") bp <- barplot(scenario_df$Final_wt_g, names.arg = scenario_df$Scenario, col = bar_cols, ylab = "Final weight (g)", main = paste("Growth at p =", p_fixed, "under warming scenarios"), ylim = c(0, max(scenario_df$Final_wt_g) * 1.25), border = NA) text(bp, scenario_df$Final_wt_g + 0.5, labels = paste0(scenario_df$Final_wt_g, " g"), cex = 0.85, font = 2) grid(nx = NA, ny = NULL, col = "gray85")
The metabolism–consumption balance defines a thermal performance window: the temperature range where the fish can maintain positive growth (p < 1). We can derive it by finding the temperature at which p = 1 for our target weight.
# Sweep a fine temperature grid and record required p temp_grid <- seq(2, 24, by = 1) p_at_target <- sapply(temp_grid, function(t_const) { temp_const <- data.frame(Day = days, Temperature = t_const) bio_t <- make_bio(temp_const, sp_params_chinook) tryCatch({ res <- run_fb4(bio_t, fit_to = "Weight", fit_value = target_wt, strategy = "binary_search", verbose = FALSE) res$summary$p_value }, error = function(e) NA_real_) }) perf_df <- data.frame(Temperature = temp_grid, p_required = p_at_target) feasible <- perf_df[!is.na(perf_df$p_required) & perf_df$p_required <= 1, ] cat(sprintf("Feasible temperature range for %.0f g in %d days : %.0f – %.0f °C\n", target_wt, max(days), min(feasible$Temperature), max(feasible$Temperature)))
plot(perf_df$Temperature, perf_df$p_required, type = "l", lwd = 2, col = "steelblue", xlab = "Constant water temperature (°C)", ylab = "p required to achieve 40 g", main = "Thermal performance window — juvenile Chinook", ylim = c(0, max(perf_df$p_required, na.rm = TRUE) * 1.1)) # Shade feasible zone polygon(c(min(feasible$Temperature), feasible$Temperature, max(feasible$Temperature)), c(0, feasible$p_required, 0), col = adjustcolor("steelblue", alpha.f = 0.2), border = NA) abline(h = 1, lty = 2, col = "firebrick", lwd = 1.5) text(max(temp_grid) - 2, 1.02, "p = 1 (maximum ration)", col = "firebrick", cex = 0.8) grid()
These analyses have direct relevance for fish management under climate change:
Deslauriers D, Chipps SR, Breck JE, Rice JA, Madenjian CP (2017). Fish Bioenergetics 4.0: An R-Based Modeling Application. Fisheries 42(11):586–596. https://doi.org/10.1080/03632415.2017.1377558
Kitchell JF, Stewart DJ, Weininger D (1977). Applications of a bioenergetics model to yellow perch (Perca flavescens) and walleye (Stizostedion vitreum vitreum). Journal of the Fisheries Research Board of Canada 34(10):1922–1935. https://doi.org/10.1139/f77-258
Parmesan C, Yohe G (2003). A globally coherent fingerprint of climate change impacts across natural systems. Nature 421:37–42. https://doi.org/10.1038/nature01286
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.