The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Temperature Sensitivity and Climate Change Scenarios

Hans Ttito

2026-05-04

Overview

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:

  1. Quantify how final weight responds to temperature shifts (sensitivity analysis)
  2. Project growth outcomes under +1 °C, +2 °C, and +4 °C warming scenarios
  3. Identify thermal performance windows using the sensitivity plot
  4. Compare sensitivity across two ecologically contrasting species

1. Base model — juvenile Chinook salmon

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)))
#> Baseline temperature range : 4.1 – 13.0 °C

2. Manual sensitivity analysis across temperature offsets

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)")
)
p-value required to achieve 40 g across temperature offsets
Temp. offset (°C) Estimated p Final weight (g)
-3 0.5100 40
-2 0.4705 40
-1 0.4381 40
0 0.4125 40
1 0.3936 40
2 0.3821 40
3 0.3794 40
4 0.3887 40
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()
Estimated p-value as a function of temperature offset.
Estimated p-value as a function of temperature offset.

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.


3. Built-in sensitivity plot

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")
#> === GROWTH SENSITIVITY ANALYSIS ===
#> Mode: SEQUENTIAL
#> Temperature range: 8 - 18 °C ( 6 values)
#> P-value range: 0.3 - 1 ( 8 values)
#> Total combinations: 48 
#> 
#> Processing temperature: 8 °C
#> Processing temperature: 10 °C
#> Processing temperature: 12 °C
#> Processing temperature: 14 °C
#> Processing temperature: 16 °C
#> Processing temperature: 18 °C
#> 
#> === ANALYSIS COMPLETE ===
#> Successful simulations: 48 / 48 ( 100 %)
#> Maximum growth rate: 1.768 %/d
#> Optimal conditions: 14 °C, 100 % feeding (p = 1.00 )
Built-in temperature sensitivity plot. Each point shows the p required to achieve the target weight at a given temperature offset.
Built-in temperature sensitivity plot. Each point shows the p required to achieve the target weight at a given temperature offset.

4. Climate change scenarios — projected growth

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)")
)
Projected outcomes at p = 0.65 under four temperature scenarios
Scenario Δ T (°C) Final weight (g) Total consumption (g)
Baseline 0 111.1 264.1
+1 °C 1 125.8 306.8
+2 °C 2 137.4 343.6
+4 °C 4 138.3 362.4
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")
Projected final weight under climate change scenarios (p fixed at 0.65).
Projected final weight under climate change scenarios (p fixed at 0.65).

5. Thermal performance window

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)))
#> Feasible temperature range for 40 g in 120 days : 2 – 19 °C
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()
Thermal performance window: p required to achieve 40 g over 120 days at constant temperatures. The shaded region shows the feasible window (p ≤ 1).
Thermal performance window: p required to achieve 40 g over 120 days at constant temperatures. The shaded region shows the feasible window (p ≤ 1).

6. Management implications

These analyses have direct relevance for fish management under climate change:


References

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

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.