FA-65611 / Ecological population dynamics / Open access
Baranov catch and age-structured survival: plus group · case 01
Old fish disappear from the stock after one year in the oldest class.
ROOT CAUSE
The plus group is dropped instead of accumulating survivors.
VERIFIED REPAIR
Restore the plus group rule: `surv[:-2] + [surv[-2] + surv[-1]]`.
Unsuccessful approach: Appending the oldest survivors as a new class grows the vector each year.
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 * math.exp(-z_a) for n, z_a in zip(numbers, z)]
nxt = [float(recruits)] + 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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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, 148.976], 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, 303.265], 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, 327.492], 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, 74.082, 59.265], 62.204] | [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204] | Failed |
| regression: no mortality at all | [[0.0, 0.0], [50.0, 50.0], 0.0] | [[0.0, 0.0], [50.0, 90.0], 0.0] | Failed |
| regression: heavy fishing | [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 23.457], 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, 211.406, 144.237], 158.31] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / d585e3621cecf985845d9a27a2c6e14459b4f296d1d1182da12028d3bb14aee2
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(-z_a) for n, z_a in zip(numbers, z)]
nxt = [float(recruits)] + surv[:-1] + [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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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, 148.976, 74.488], 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, 303.265, 99.317], 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, 327.492, 163.746], 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, 74.082, 59.265, 44.449], 62.204] | [[25.918, 20.735, 15.551], [100.0, 74.082, 103.715], 62.204] | Failed |
| regression: no mortality at all | [[0.0, 0.0], [50.0, 50.0, 40.0], 0.0] | [[0.0, 0.0], [50.0, 90.0], 0.0] | Failed |
| regression: heavy fishing | [[140.702, 171.239, 126.692, 63.346, 31.673], [350.0, 188.947, 85.951, 46.914, 23.457, 11.729], 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, 211.406, 144.237, 57.695], 158.31] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / ed1ef6d73599a115cb1424eb71e394ef390dac4fba0fd5ddc2906f54fcb0f189
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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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]),
('regression: 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 |
| regression: 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 / 98662200ee66c1961d8e8622b259a8afde60b5b19ee8f243d1a769d7cef1c796
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.448998+00:00.
Case digest / b00255d1feca0a9eacc43abb8e4cd23ddfa9377a7d8daaf0fbef5f04af8450f4