FA-65601 / Ecological population dynamics / Open access
Baranov catch and age-structured survival: total mortality · case 01
Survivors ignore natural mortality and catches are overstated.
ROOT CAUSE
Total mortality omits M.
VERIFIED REPAIR
Restore the total mortality rule: `z = [f + m for f in f_by_age]`.
Unsuccessful approach: Taking the larger of F and M is not the sum of competing hazards.
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 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 | [[95.163, 155.509, 118.041, 59.02], [900.0, 904.837, 444.491, 272.939], 427.733] | [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855] | Failed |
| regression: two ages | [[90.635, 65.936], [400.0, 543.429], 156.571] | [[78.694, 57.533], [400.0, 402.582], 136.227] | Failed |
| regression: unfished | [[0.0, 0.0, 0.0], [700.0, 800.0, 600.0], 0.0] | [[0.0, 0.0, 0.0], [700.0, 654.985, 491.238], 0.0] | Failed |
| control: 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 | [[157.388, 189.636, 139.761, 69.881, 34.94], [350.0, 242.612, 110.364, 60.239, 45.179], 591.606] | [[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 | [[54.381, 82.42, 32.968], [0.0, 245.619, 234.612], 169.769] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / dca6f28842d3fa0f24105c9b08fc57aa788285308711b9b18287878b002ee38e
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 = [max(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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 | [[90.635, 155.509, 118.041, 59.02], [900.0, 818.731, 444.491, 272.939], 423.205] | [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855] | Failed |
| regression: two ages | [[86.394, 65.936], [400.0, 504.473], 152.33] | [[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 |
| control: 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 | [[157.388, 189.636, 139.761, 69.881, 34.94], [350.0, 242.612, 110.364, 60.239, 45.179], 591.606] | [[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 | [[54.381, 82.42, 32.968], [0.0, 245.619, 234.612], 169.769] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / 3ac40e65a39d169b59863040d9bdba241d38de5725fb246cbf0a330f6997fb6e
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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 |
| control: 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 / 022f7aea6359b22138fc50e1920e2e4b7ea45d76c6177c50a6fecd1e32bced2b
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.370349+00:00.
Case digest / 3aa6d0319ccf840a3dab1a06369a32c4f24b0b7c04a8f82cd882e7a2ecbeacb0