Introduction

What is MSE?

Management Strategy Evaluation (MSE) is a simulation framework that tests management strategies/procedures before they are applied to a real fishery.

The core idea: Build a virtual population, extract imperfect fishery data from it, apply different management rules and compare which ones perform best, even under such uncertainties.

The MSE Closed-Loop

MSE works as a feedback loop:

  1. Operating Model (OM) — simulates the “true” population dynamics
  2. Observation Model — generates data with errors (what scientists actually collect)
  3. Management Procedure (MP) — takes the noisy data and recommends a TAC
  4. Implementation — applies the TAC (may include implementation error)
  5. Repeat — the population responds to fishing, new data are generated, and the cycle continues

Key point: It is not a matter of “Which MP gives the right answer?” MSE tells us“Which MP performs best DESPITE uncertainty and errors?”

Learning Objectives

By the end of this practical, you will be able to:

  1. Build an Operating Model and explore its parameters
  2. Understand what data Management Procedures are
  3. Understand how different MPs work (empirical vs benchmark)
  4. Run closed-loop MSE simulations
  5. Calculate and interpret Performance Metrics
  6. Visualize trade-offs (yield vs stock status)
  7. Test robustness to uncertainty in biological parameters

Part 1: Setup

Load Packages

# Install if needed:
# install.packages("openMSE")
# install.packages(c("ggplot2", "dplyr", "tidyr"))

library(openMSE)
library(ggplot2)
library(dplyr)
library(tidyr)

# check versions (just in case...)
cat("openMSE version:", as.character(packageVersion("openMSE")), "\n")
## openMSE version: 1.0.1
cat("MSEtool version:", as.character(packageVersion("MSEtool")), "\n")
## MSEtool version: 3.7.5

Part 2: Building an Operating Model

What is an OM?

An Operating Model has 4 components:

  • Stock: life history (growth, mortality, recruitment, current depletion)
  • Fleet: fishing history (selectivity, effort trends)
  • Obs: observation errors (how noisy are our data?)
  • Imp: implementation errors (is the TAC actually respected?)

You can explore the built-in components that are already available in openMSE:

avail("Stock")
##  [1] "Albacore"          "Blue_shark"        "Bluefin_tuna"     
##  [4] "Bluefin_tuna_WAtl" "Butterfish"        "Herring"          
##  [7] "Mackerel"          "Porgy"             "Rockfish"         
## [10] "Snapper"           "Sole"              "Toothfish"
avail("Fleet")
##  [1] "DecE_Dom"              "DecE_HDom"             "DecE_NDom"            
##  [4] "FlatE_Dom"             "FlatE_HDom"            "FlatE_NDom"           
##  [7] "Generic_DecE"          "Generic_FlatE"         "Generic_Fleet"        
## [10] "Generic_IncE"          "IncE_HDom"             "IncE_NDom"            
## [13] "Low_Effort_Non_Target" "Target_All_Fish"       "Targeting_Small_Fish"

Assemble the OM

We use Blue shark, a relatively slow-growing and low-productivity species (even though in the higher productivity spectrum within pelagic sharks).

OM <- new("OM",
          Stock = Blue_shark,
          Fleet = Generic_Fleet,
          Obs   = Generic_Obs,
          Imp   = Perfect_Imp)

# Simulation parameters
OM@nsim     <- 48    # Number of stochastic simulations (use >200 for a real project)
OM@proyears <- 30    # Project 30 years into the future (OK for a longer lived shark)
OM@interval <- 3     # MPs update advice every 3 years (relatively common for management preferences)

Explore OM Parameters

om_summary <- data.frame(
  Parameter = c("Species/Stock", "Maximum age", "Natural mortality (M)",
                "Asymptotic length (Linf)", "Growth coefficient (K)",
                "Steepness (h)", "Current depletion (SSB/SSB0)",
                "Historical years"),
  Value = c(OM@Name,
            paste(OM@maxage, "years"),
            paste(OM@M[1], "–", OM@M[2]),
            paste(OM@Linf[1], "–", OM@Linf[2], "cm"),
            paste(OM@K[1], "–", OM@K[2]),
            paste(OM@h[1], "–", OM@h[2]),
            paste(OM@D[1], "–", OM@D[2]),
            paste(OM@nyears, "years"))
)

knitr::kable(om_summary, col.names = c("Parameter", "Value / Range"),
             caption = "Operating Model Summary")
Operating Model Summary
Parameter Value / Range
Species/Stock Stock:Blue shark Fleet:Generic_Fleet Obs model:Generic_Obs Imp model:Perfect_Imp
Maximum age 32 years
Natural mortality (M) 0.15 – 0.25
Asymptotic length (Linf) 195 – 205 cm
Growth coefficient (K) 0.18 – 0.24
Steepness (h) 0.4 – 0.9
Current depletion (SSB/SSB0) 0.05 – 0.6
Historical years 50 years

Question: Why are parameters given as ranges (e.g., M = 0.15–0.25) rather than single values?

Simulate the Historical Period

Simulate() runs the OM through the historical period, generating 48 stochastic realisations of the population history.

Hist <- Simulate(OM)

Reference Points

Look at some values from one of the simulations

cat("\n=== Reference Points (simulation 1) ===\n")
## 
## === Reference Points (simulation 1) ===
cat("MSY:", round(Hist@Ref$ByYear$MSY[1, OM@nyears]), "tonnes\n")
## MSY: 1578 tonnes
cat("FMSY:", round(Hist@Ref$ByYear$FMSY[1, OM@nyears], 3), "\n")
## FMSY: 0.091
cat("BMSY:", round(Hist@Ref$ByYear$BMSY[1, OM@nyears]), "tonnes\n")
## BMSY: 23775 tonnes
cat("SSBMSY:", round(Hist@Ref$ByYear$SSBMSY[1, OM@nyears]), "tonnes\n")
## SSBMSY: 16973 tonnes

Historical Trajectories

These plots show the “truth” across all 48 simulations. The spread represents our uncertainty about biology.

# SBiomass is [nsim × nyears × areas] — sum across areas
ssb <- apply(Hist@TSdata$SBiomass, c(1, 2), sum)
yrs <- 1:OM@nyears

matplot(yrs, t(ssb), type = "l", col = rgb(0, 0, 0.5, 0.15), lty = 1,
        xlab = "Year", ylab = "Spawning Biomass",
        main = "Historical Spawning Biomass (all simulations)")
lines(yrs, colMeans(ssb), col = "red", lwd = 2)
legend("topright", "Mean", col = "red", lwd = 2, bty = "n")

# F/FMSY trajectories
Fmort    <- Hist@TSdata$Find
fmsy_vec <- Hist@Ref$ByYear$FMSY[, OM@nyears]
F_ratio  <- Fmort / fmsy_vec

matplot(yrs, t(F_ratio), type = "l", col = rgb(0.5, 0, 0, 0.15), lty = 1,
        xlab = "Year", ylab = expression(F/F[MSY]),
        main = "Historical F/FMSY (all simulations)")
lines(yrs, colMeans(F_ratio, na.rm = TRUE), col = "red", lwd = 2)
abline(h = 1, lty = 2, col = "grey40")
legend("topright", c("Mean", "F = FMSY"), col = c("red", "grey40"),
       lwd = c(2, 1), lty = c(1, 2), bty = "n")

# SB/SBMSY trajectories
ssbmsy_vec <- Hist@Ref$ByYear$SSBMSY[, OM@nyears]
SB_ratio   <- ssb / ssbmsy_vec

matplot(yrs, t(SB_ratio), type = "l", col = rgb(0, 0.4, 0, 0.15), lty = 1,
        xlab = "Year", ylab = expression(SSB/SSB[MSY]),
        main = "Historical SSB/SSBMSY (all simulations)")
lines(yrs, colMeans(SB_ratio, na.rm = TRUE), col = "red", lwd = 2)
abline(h = 1, lty = 2, col = "grey40")
legend("topright", c("Mean", "SSB = SSBMSY"), col = c("red", "grey40"),
       lwd = c(2, 1), lty = c(1, 2), bty = "n")

Question: Looking at the SB/SBMSY plot: are there many simulations where the stock currently overfished (below 1)? Why do different simulations show different trajectories?


Part 3: Exploring the Data Object

What Does the MP Actually See?

Important: the MP does NOT see the truth. It sees data WITH observation error. The Data object represents what an MP would receive.

Data <- Hist@Data

Truth vs What the MP Sees

In this plot, the red line is what’s really happening (OM truth). The green dashed line is what the MP sees and works with (i.e., noisy data). There will be some disagreement, meaning the MP will make suboptimal decision. And that gets back into the OM later during the feedback-loop process.

ssb_sim1 <- apply(Hist@TSdata$SBiomass[1, , ], 1, sum)
ssb_scaled <- ssb_sim1 / max(ssb_sim1, na.rm = TRUE)

plot(Data@Year, ssb_scaled, type = "l", lwd = 3, col = "red",
     xlab = "Year", ylab = "Scaled value (0–1)",
     main = "Truth (OM) vs What the MP Sees (Data)", ylim = c(0, 1.2))
lines(Data@Year, index_scaled, lwd = 2, col = "darkgreen", lty = 2)
legend("topright", c("True SSB (OM)", "Observed Index (with error)"),
       col = c("red", "darkgreen"), lwd = c(3, 2), lty = c(1, 2), bty = "n")


Part 4: Management Procedures

What is an MP?

A Management Procedure is a function that:

  • Input: Data object (catch, index, life history information)
  • Output: Management recommendation (usually a TAC)

Management Procedures We Will Test

MP Type Logic (one sentence)
FMSYref Reference benchmark Fish at exactly FMSY with perfect knowledge
AvC Empirical (no feedback) TAC = mean historical catch (never adjusts)
Iratio Empirical (level-based) Adjust TAC by ratio of recent to previous index
Islope1 Empirical (trend-based) Adjust TAC based on the slope of the index
MyMP Custom (precautionary) Cut fast (−20%), grow slow (+10%) based on index

Different MPs use different logic. None of them “knows” the truth.

MP 1: FMSYref — Perfect Information Benchmark

This is NOT a real MP. It uses the true FMSY from the OM (information that in a real fishery we’d never have). Its purpose is to show the best possible outcome. No real MP can beat it.

# FMSYref can't be tested standalone (it needs OM internals from Project)
# But we can approximate its TAC: FMSY × current vulnerable biomass
fmsy_sim1 <- Hist@Ref$ByYear$FMSY[1, OM@nyears]
vb_sim1   <- sum(Hist@TSdata$VBiomass[1, OM@nyears, ])
TAC_fref  <- round(fmsy_sim1 * vb_sim1)

cat("FMSYref Logic: Fish at exactly FMSY (uses true value from OM)\n")
## FMSYref Logic: Fish at exactly FMSY (uses true value from OM)
cat("Approximate TAC year1:", TAC_fref, "tonnes\n")
## Approximate TAC year1: 3854 tonnes

MP 2: AvC — Average Catch

The simplest possible MP: TAC = mean of all historical catches. It completely ignores the abundance index. No feedback at all.

This is what happens when we want to maintain status-quo: “just keep catches at their historical average and do nothing else about it”.

Rec_AvC <- AvC(x = 1, Data = Data, reps = 1)

cat("AvC Logic: TAC = mean of all historical catches\n")
## AvC Logic: TAC = mean of all historical catches
cat("Recommended TAC year1:", round(Rec_AvC@TAC), "tonnes\n")
## Recommended TAC year1: 2264 tonnes

MP 3: Iratio — Index Ratio

An empirical MP that responds to the index level:

\[\text{TAC}_{\text{new}} = \text{TAC}_{\text{previous}} \times \frac{\text{recent index}}{\text{previous index}}\]

If the recent index is higher than before → increase TAC. If lower → decrease TAC.

It asks: “Where IS the stock?”

Rec_Ir <- Iratio(x = 1, Data = Data, reps = 1)

cat("Iratio Logic: TAC adjusted by ratio of recent to previous index\n")
## Iratio Logic: TAC adjusted by ratio of recent to previous index
cat("Recommended TAC year1:", round(Rec_Ir@TAC), "tonnes\n")
## Recommended TAC year1: 4453 tonnes

MP 4: Islope1 — Index Slope

An empirical MP that responds to the index trend. It estimates the slope of the index over recent years and adjusts TAC accordingly.

  • Slope > 0 (rising) → allow TAC increase
  • Slope < 0 (falling) → force TAC decrease
  • Slope ≈ 0 (stable) → keep TAC roughly constant

It asks: “Where is the stock GOING?”

Rec_Is <- Islope1(x = 1, Data = Data, reps = 1)

cat("Islope1 Logic: TAC adjusted based on slope (trend) of recent index\n")
## Islope1 Logic: TAC adjusted based on slope (trend) of recent index
cat("Recommended TAC year1:", round(Rec_Is@TAC), "tonnes\n")
## Recommended TAC year1: 4036 tonnes

Question: What is the key difference between Iratio and Islope1? Can you think of a situation where one would give good advice but the other would not?

MP 5: MyMP — Precautionary Ratchet

This is our custom MP. The core idea: “Be precautionary: reduce TAC fast when needed, increase slowly when possible”

  • Start from last year’s catch
  • Compare the recent index (last 3 years) to the previous period (3 years before)
  • If index is rising → allow a cautious +10% TAC increase
  • If index is falling → force a more aggressive −20% TAC decrease

The asymmetry is the precautionary principle we are implementing in this casen: stock collapses are hard to reverse biologically (especially for sharks), so we cut more aggressively when things look bad but increase more cautiously when things look better.

MyMP <- function(x, Data, reps = 1, plot = FALSE) {
  
  Rec <- new("Rec")
  ny  <- length(Data@Year)
  
  # Step 1: Start from last year's catch
  base_TAC <- Data@MPrec[x]
  
  # Step 2: Compare recent index (last 3 yrs) to previous (3 yrs before)
  idx_recent   <- mean(Data@Ind[x, (ny-2):ny], na.rm = TRUE)
  idx_previous <- mean(Data@Ind[x, (ny-5):(ny-3)], na.rm = TRUE)
  
  # Step 3: Apply asymmetric HCR in TAC changes
  if (!is.na(idx_recent) && !is.na(idx_previous) && idx_previous > 0) {
    if (idx_recent >= idx_previous) {
      Rec@TAC <- base_TAC * 1.10   # Index rising  → cautious +10%
    } else {
      Rec@TAC <- base_TAC * 0.80   # Index falling → aggressive -20%
    }
  } else {
    Rec@TAC <- base_TAC             # No change in data → keep TAC unchanged
  }
  
  return(Rec)
}
class(MyMP) <- "MP"

# Test it
Rec_My <- MyMP(x = 1, Data = Data, reps = 1)
cat("MyMP Logic: Cut fast (-20%), grow slow (+10%) based on index direction\n")
## MyMP Logic: Cut fast (-20%), grow slow (+10%) based on index direction
cat("Recommended TAC year1:", round(Rec_My@TAC), "tonnes\n")
## Recommended TAC year1: 3316 tonnes

Compare Year 1 Recommendations

All MPs look at the same data but give different advice. Why? Because they use different rules on how they are using the data.

TAC_compare <- data.frame(
  MP  = c("FMSYref*", "AvC", "Iratio", "Islope1", "MyMP"),
  TAC = c(TAC_fref, round(Rec_AvC@TAC), round(Rec_Ir@TAC),
          round(Rec_Is@TAC), round(Rec_My@TAC))
)
print(TAC_compare)
##         MP  TAC
## 1 FMSYref* 3854
## 2      AvC 2264
## 3   Iratio 4453
## 4  Islope1 4036
## 5     MyMP 3316
barplot(TAC_compare$TAC, names.arg = TAC_compare$MP,
        col = c("#E07A5F", "#1C7293", "#81B29A", "#F2CC8F", "#6C4F82"),
        main = "Year 1 TAC Recommendations by MP",
        ylab = "TAC (tonnes)", las = 2)

Question: Which MP recommends the highest TAC in year 1? And the lowest? Is the “perfect knowledge” benchmark (FMSYref) the highest? Why not?

Think about it this way: “Which MP would a fisher prefer in Year 1? But which MP would a fisher prefer in Year 30? Let’s run the MSE and find out.”


Part 5: Running the MSE

Closed-Loop Simulation

Now we run the full MSE: 48 simulations × 30 years × 5 MPs.

MPs <- c("FMSYref", "AvC", "Iratio", "Islope1", "MyMP")

MP_colours <- c("FMSYref" = "#E07A5F", "AvC" = "#1C7293",
                "Iratio" = "#81B29A", "Islope1" = "#F2CC8F",
                "MyMP" = "#6C4F82")
MSE <- Project(Hist, MPs = MPs)
cat("MSE complete:", MSE@nsim, "simulations ×", length(MPs), "MPs ×", MSE@proyears, "years\n")

Quick Results

mean_catch <- round(apply(MSE@Catch, 2, mean, na.rm = TRUE))
names(mean_catch) <- MPs
# Mean Projected Catch (tonnes/year)
print(mean_catch)
## FMSYref     AvC  Iratio Islope1    MyMP 
##    2739    1990    1807    1952    1671
final_BBmsy <- round(apply(MSE@SB_SBMSY[, , OM@proyears], 2, mean, na.rm = TRUE), 2)
names(final_BBmsy) <- MPs
# Mean SB/SBMSY at final projected year (year 30)
print(final_BBmsy)
## FMSYref     AvC  Iratio Islope1    MyMP 
##    1.01    0.93    1.47    1.06    1.64

Year 1 TAC vs Long-Term Reality

Now we can start to answer some questions: Does what the MP “promised” in Year 1 hold up over 30 years?

comparison <- data.frame(
  MP              = MPs,
  Year1_TAC       = c(TAC_fref, round(Rec_AvC@TAC), round(Rec_Ir@TAC),
                      round(Rec_Is@TAC), round(Rec_My@TAC)),
  Mean_Catch_30yr = mean_catch,
  Change_pct      = round((mean_catch / c(TAC_fref, round(Rec_AvC@TAC),
                    round(Rec_Ir@TAC), round(Rec_Is@TAC),
                    round(Rec_My@TAC)) - 1) * 100)
)

# Year 1 TAC vs 30-Year Mean Catch
print(comparison, row.names = FALSE)
##       MP Year1_TAC Mean_Catch_30yr Change_pct
##  FMSYref      3854            2739        -29
##      AvC      2264            1990        -12
##   Iratio      4453            1807        -59
##  Islope1      4036            1952        -52
##     MyMP      3316            1671        -50
bar_data <- rbind(comparison$Year1_TAC, comparison$Mean_Catch_30yr)
colnames(bar_data) <- MPs

barplot(bar_data, beside = TRUE, col = c("grey70", "steelblue"),
        main = "Year 1 TAC vs 30-Year Mean Catch",
        ylab = "Catch (tonnes)", las = 2)
legend("topright", c("Year 1 TAC", "30-yr Mean Catch"),
       fill = c("grey70", "steelblue"), bty = "n")

Note: Look at the Change_pct column. A negative value means the MP “promised” more than it could deliver. Which MP has the biggest gap? Which is most sustainable from the start?

We have been looking at the catches (yield) only. Now let’s also looks at the trade-offs with the other management objectices (e.g., status, safety)


Part 6: Performance Metrics

What are Performance Metrics?

PMs quantify how well each MP achieves management objectives. Each PM answers a specific question:

PM Question Management Objective
P100 P(SB > SBMSY)? Stock status (green zone)
P50 P(SB > 0.5 × SBMSY)? Safety (above limit)
LTY Long-term yield relative to MSY? Yield
AAVY How variable is catch year-to-year? Stability

Helper Function

Load a helper function to extract Performance Metric values:

source("functions_MSE_helper.R")
pm_green <- P100(MSE)
pm_safe  <- P50(MSE)
pm_yield <- LTY(MSE)
pm_var   <- AAVY(MSE)

PM_table <- data.frame(
  MP         = MPs,
  P_green    = round(extract_pm(pm_green), 2),
  P_safe     = round(extract_pm(pm_safe), 2),
  LT_Yield   = round(extract_pm(pm_yield), 2),
  Catch_Var  = round(extract_pm(pm_var), 2)
)

# Performance Metrics Summary
# P_green:   P(SB > SBMSY) over last 10 projection years
# P_safe:    P(SB > 0.5 × SBMSY):  above the limit ref point
# LT_Yield:  long-term catch / reference yield (higher = more TAC/captures)
# Catch_Var: avg annual variation in catch (lower = more stable)
print(PM_table, row.names = FALSE)
##       MP P_green P_safe LT_Yield Catch_Var
##  FMSYref    0.50   0.93     0.98      0.23
##      AvC    0.46   0.64     0.57      0.79
##   Iratio    0.54   0.75     0.46      0.90
##  Islope1    0.46   0.72     0.66      0.88
##     MyMP    0.60   0.81     0.44      0.96

Question: Which MP has the highest P(SB > SBMSY)? Which has the most stable catches?


Part 7: Trade-Off Visualisation

Built-in MSE Plots

Tplot(MSE)

##              MP PNOF  LTY P100  P50  P10 Satisificed
## FMSYref FMSYref 0.70 0.98 0.50 0.93 1.00       FALSE
## AvC         AvC 0.45 0.57 0.46 0.64 0.75       FALSE
## Iratio   Iratio 0.64 0.46 0.54 0.75 0.91       FALSE
## Islope1 Islope1 0.49 0.66 0.46 0.72 0.83       FALSE
## MyMP       MyMP 0.72 0.44 0.60 0.81 0.94       FALSE

Note: The “Satisficed” column uses strict default thresholds. If ALL are not met simultaneously, it shows FALSE. Focus on the individual metric values instead.

Kplot(MSE)


Part 8: Robustness Testing

Why Test Robustness?

So far, all results assume our OM is correct. But what if we’re wrong about the biology? A good MP should perform reasonably well even under wrong assumptions.

We test two “what-if” scenarios for a shark:

  1. Lower M (−50%): the species is less productive than we thought
  2. Lower steepness: recruitment is less resilient than we thought

Scenario 1: Lower Natural Mortality

OM_lowM <- OM
OM_lowM@M <- OM@M * 0.5

# Robustness Scenario: Lower M (−50%)
cat("Baseline M:", OM@M[1], "–", OM@M[2], "\n")
## Baseline M: 0.15 – 0.25
cat("Lower M:   ", OM_lowM@M[1], "–", OM_lowM@M[2], "\n")
## Lower M:    0.075 – 0.125
Hist_lowM <- Simulate(OM_lowM)
MSE_lowM  <- Project(Hist_lowM, MPs = MPs)
cat("low M robustness test complete:", MSE_lowM@nsim, "simulations ×", length(MPs), "MPs ×", MSE_lowM@proyears, "years\n")

Scenario 2: Lower Steepness

OM_lowh <- OM
OM_lowh@h <- c(0.2, 0.4)

# Robustness Scenario: Lower Steepness
cat("Baseline h:", OM@h[1], "–", OM@h[2], "\n")
## Baseline h: 0.4 – 0.9
cat("Scenario h:", OM_lowh@h[1], "–", OM_lowh@h[2], "\n")
## Scenario h: 0.2 – 0.4
Hist_lowh <- Simulate(OM_lowh)
MSE_lowh  <- Project(Hist_lowh, MPs = MPs)
cat("low h test complete:", MSE_lowh@nsim, "simulations ×", length(MPs), "MPs ×", MSE_lowh@proyears, "years\n")

Compare across scenarios

p100_base <- extract_pm(P100(MSE))
p100_lowM <- extract_pm(P100(MSE_lowM))
p100_lowh <- extract_pm(P100(MSE_lowh))

p50_base <- extract_pm(P50(MSE))
p50_lowM <- extract_pm(P50(MSE_lowM))
p50_lowh <- extract_pm(P50(MSE_lowh))

robust_table <- data.frame(
  MP             = MPs,
  P_green_Base   = round(p100_base, 2),
  P_green_LowM   = round(p100_lowM, 2),
  P_green_LowH   = round(p100_lowh, 2),
  P_safe_Base    = round(p50_base, 2),
  P_safe_LowM    = round(p50_lowM, 2),
  P_safe_LowH    = round(p50_lowh, 2)
)

# Robustness: P(SB > SBMSY) across scenarios
print(robust_table %>% select(MP, starts_with("P_green")), row.names = FALSE)
##       MP P_green_Base P_green_LowM P_green_LowH
##  FMSYref         0.50         0.50         0.37
##      AvC         0.46         0.41         0.28
##   Iratio         0.54         0.46         0.30
##  Islope1         0.46         0.41         0.29
##     MyMP         0.60         0.51         0.33
# Robustness: P(SB > 0.5*SBMSY) across scenarios
print(robust_table %>% select(MP, starts_with("P_safe")), row.names = FALSE)
##       MP P_safe_Base P_safe_LowM P_safe_LowH
##  FMSYref        0.93        0.91        0.73
##      AvC        0.64        0.61        0.47
##   Iratio        0.75        0.74        0.53
##  Islope1        0.72        0.69        0.50
##     MyMP        0.81        0.78        0.58

Robustness tests visualisation

robust_long <- robust_table %>%
  select(MP, P_green_Base, P_green_LowM, P_green_LowH) %>%
  pivot_longer(-MP, names_to = "Scenario", values_to = "P_green") %>%
  mutate(Scenario = gsub("P_green_", "", Scenario),
         Scenario = factor(Scenario, levels = c("Base", "LowM", "LowH")))

ggplot(robust_long, aes(MP, P_green, fill = Scenario)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c("Base" = "#1C7293", "LowM" = "#E07A5F",
                                "LowH" = "#F2CC8F"),
                    labels = c("Base case", "Lower M (−50%)",
                               "Lower steepness")) +
  labs(title = "Robustness: P(SB > SBMSY) Across Scenarios",
       x = "Management Procedure", y = "Probability") +
  theme_minimal(base_size = 14) +
  ylim(0, 1) +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))

Question: Which MP is most robust (i.e., more consistent performance across “what-if” scenarios)?


Exercises (EXTRA)

Exercise A: Modify MyMP

Go back to the MyMP code and change the adjustment factors. Try different combinations and re-run the MSE:

  • Symmetric: change 0.80 to 0.90 (equal 10% max up and down)
  • More precautionary: change to 1.05 up, 0.70 down
  • Longer window: change the 3-year window to 5-years

How do these changes affect the trade-off between yield and stock status?

Exercise B: Add a Model-Based MP

So far all our real MPs are empirical. Try adding a model-based MP that runs an internal stock assessment:

MPs_new <- c("FMSYref", "AvC", "Iratio", "Islope1", "MyMP", "DDSS_MSY")
MSE_new <- Project(Hist, MPs = MPs_new)
Tplot(MSE_new)

How does the model-based MP compare to the empirical ones? Is doing something more complex (i.e., model based) always better?

Exercise C: Severe Depletion

What happens if the stock is much more depleted than we assumed?

OM_depleted <- OM
OM_depleted@D <- c(0.05, 0.15)
Hist_depleted <- Simulate(OM_depleted)
MSE_depleted <- Project(Hist_depleted, MPs = MPs)
Tplot(MSE_depleted)

What MP performs better under a more pessimistic starting place? Does MyMP’s, that is more precautionary and reduces more TAC when needed, helps more when the stock is severely depleted?

Exercise D: Write a Management Brief

You are advising a regional fisheries body. Based on your MSE results, write a 1-paragraph recommendation addressing:

  1. Which MP do you recommend and why?
  2. What are the trade-offs of your choice?
  3. How confident are you (based on robustness results)?

There is no single correct answer. The choice depends on which objective the manager prioritizes. Your job as a scientist is to clearly explain the trade-off space


cat("Congratulations on completing your first MSE!\n")
## Congratulations on completing your first MSE!