FAILURE MAP
← Case archive

FA-65616 / Ecological population dynamics / Open access

Baranov catch and age-structured survival: survival · case 01

High mortality produces negative numbers at age.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Survival uses the linear 1-Z approximation.

VERIFIED REPAIR

Restore the survival rule: `surv = [n * math.exp(-z_a)`.

Unsuccessful approach: Survival ignores fishing mortality.

Case contract

Z_a = F_a + M; catch_a = F_a/Z_a*N_a*(1-exp(-Z_a)) (0 when Z=0); survivors N_a*exp(-Z_a) age one year; the last class is a plus group; next year starts with recruits at age 0; return [catch rounded 3, next vector rounded 3, total catch rounded 3]; at least two age classes.

Why this case matters

Population projections set harvest quotas, conservation status and pest-control timing; a wrong update order, boundary or rate conversion silently changes management advice.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(numbers, f_by_age, m, recruits):
    z = [f + m for f in f_by_age]
    catch = [f / z_a * n * (1 - math.exp(-z_a)) if z_a > 0 else 0.0 for f, z_a, n in zip(f_by_age, z, numbers)]
    surv = [n * (1 - z_a) for n, z_a in zip(numbers, z)]
    nxt = [float(recruits)] + surv[:-2] + [surv[-2] + surv[-1]]
    return [[round(c, 3) for c in catch], [round(x, 3) for x in nxt], round(sum(catch), 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: haddock four ages[[86.394, 141.649, 107.875, 53.937], [900.0, 700.0, 300.0, 135.0], 389.855][[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]Failed
regression: two ages[[78.694, 57.533], [400.0, 310.0], 136.227][[78.694, 57.533], [400.0, 402.582], 136.227]Failed
regression: unfished[[0.0, 0.0, 0.0], [700.0, 640.0, 480.0], 0.0][[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]Failed
regression: no natural mortality[[25.918, 20.735, 15.551], [100.0, 70.0, 98.0], 62.204][[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]Failed
control: no mortality at all[[0.0, 0.0], [50.0, 90.0], 0.0][[0.0, 0.0], [50.0, 90.0], 0.0]Passed
regression: heavy fishing[[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 100.0, -75.0, -90.0, -67.5], 533.652][[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]Failed
regression: recruitment failure[[50.625, 76.918, 30.767], [0.0, 195.0, 157.5], 158.31][[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31]Failed

SHA-256 / 1f7ac9b56bf3bea4eba790d826038d982b4f484a5a1efc86b0d560df4035d066

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(numbers, f_by_age, m, recruits):
    z = [f + m for f in f_by_age]
    catch = [f / z_a * n * (1 - math.exp(-z_a)) if z_a > 0 else 0.0 for f, z_a, n in zip(f_by_age, z, numbers)]
    surv = [n * math.exp(-m) for n, z_a in zip(numbers, z)]
    nxt = [float(recruits)] + surv[:-2] + [surv[-2] + surv[-1]]
    return [[round(c, 3) for c in catch], [round(x, 3) for x in nxt], round(sum(catch), 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: haddock four ages[[86.394, 141.649, 107.875, 53.937], [900.0, 818.731, 491.238, 368.429], 389.855][[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]Failed
regression: two ages[[78.694, 57.533], [400.0, 518.573], 136.227][[78.694, 57.533], [400.0, 402.582], 136.227]Failed
regression: unfished[[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0][[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]Passed
regression: no natural mortality[[25.918, 20.735, 15.551], [100.0, 100.0, 140.0], 62.204][[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]Failed
control: no mortality at all[[0.0, 0.0], [50.0, 90.0], 0.0][[0.0, 0.0], [50.0, 90.0], 0.0]Passed
regression: heavy fishing[[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 311.52, 233.64, 155.76, 116.82], 533.652][[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]Failed
regression: recruitment failure[[50.625, 76.918, 30.767], [0.0, 258.212, 301.248], 158.31][[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31]Failed

SHA-256 / bca1500108db71d7a3aaf040bc9bcb5bfb713aed4a7d4fb13e9e582315b1a4d5

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(numbers, f_by_age, m, recruits):
    z = [f + m for f in f_by_age]
    catch = [f / z_a * n * (1 - math.exp(-z_a)) if z_a > 0 else 0.0 for f, z_a, n in zip(f_by_age, z, numbers)]
    surv = [n * math.exp(-z_a) for n, z_a in zip(numbers, z)]
    nxt = [float(recruits)] + surv[:-2] + [surv[-2] + surv[-1]]
    return [[round(c, 3) for c in catch], [round(x, 3) for x in nxt], round(sum(catch), 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])],
 [('regression: haddock four ages',
   ([1000, 600, 300, 150], [0.1, 0.3, 0.5, 0.5], 0.2, 900),
   [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]),
  ('regression: two ages', ([500, 200], [0.2, 0.4], 0.3, 400), [[78.694, 57.533], [400.0, 402.582], 136.227]),
  ('regression: unfished',
   ([800, 400, 200], [0.0, 0.0, 0.0], 0.2, 700),
   [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]),
  ('regression: no natural mortality',
   ([100, 80, 60], [0.3, 0.3, 0.3], 0.0, 100),
   [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]),
  ('control: no mortality at all', ([50, 40], [0.0, 0.0], 0.0, 50), [[0.0, 0.0], [50.0, 90.0], 0.0]),
  ('regression: heavy fishing',
   ([400, 300, 200, 100, 50], [0.5, 1.0, 1.2, 1.2, 1.2], 0.25, 350),
   [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]),
  ('regression: recruitment failure',
   ([300, 250, 100], [0.2, 0.4, 0.4], 0.15, 0),
   [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: haddock four ages[[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855][[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855]Passed
regression: two ages[[78.694, 57.533], [400.0, 402.582], 136.227][[78.694, 57.533], [400.0, 402.582], 136.227]Passed
regression: unfished[[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0][[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0]Passed
regression: no natural mortality[[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204][[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204]Passed
control: no mortality at all[[0.0, 0.0], [50.0, 90.0], 0.0][[0.0, 0.0], [50.0, 90.0], 0.0]Passed
regression: heavy fishing[[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652][[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 35.186], 533.652]Passed
regression: recruitment failure[[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31][[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31]Passed

SHA-256 / 727ed7da95d3fe138770c84d2d78caeb3bb03b14eb1eccaaec3ab2c636fe4c01

Verification & scope

Deterministic bounded teaching model with a stipulated contract; not a validated scientific or public-health modelling library. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:47:35.706668+00:00.

Case digest / 50e53154b65fc453bf1cdcaa38deed825e29cd4eeef0e22351d858a2c31781cc