FA-65616 / Ecological population dynamics / Open access
Baranov catch and age-structured survival: survival · case 01
High mortality produces negative numbers at age.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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