FA-65606 / Ecological population dynamics / Open access
Baranov catch and age-structured survival: catch fraction · case 01
Catch ignores fish that die naturally before they can be caught.
ROOT CAUSE
Catch is computed as if fishing were the only hazard.
VERIFIED REPAIR
Restore the catch fraction rule: `f / z_a * n * (1 - math.exp(-z_a))`.
Unsuccessful approach: Using the surviving fraction instead of the dead fraction inverts the equation.
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 = [n * (1 - math.exp(-f)) 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 | [[95.163, 155.509, 118.041, 59.02], [900.0, 740.818, 363.918, 223.463], 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, 402.582], 156.571] | [[78.694, 57.533], [400.0, 402.582], 136.227] | Failed |
| control: 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 | [[157.388, 189.636, 139.761, 69.881, 34.94], [350.0, 188.947, 85.951, 46.914, 35.186], 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, 211.406, 201.932], 169.769] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / 447eb2bf60d55b465e36a4ff71bf5c7d098fcd6dbf3dda86f0aca854ded5ba8a
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 * 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 | [[246.939, 218.351, 106.411, 53.206], [900.0, 740.818, 363.918, 223.463], 624.907] | [[86.394, 141.649, 107.875, 53.937], [900.0, 740.818, 363.918, 223.463], 389.855] | Failed |
| regression: two ages | [[121.306, 56.753], [400.0, 402.582], 178.059] | [[78.694, 57.533], [400.0, 402.582], 136.227] | Failed |
| control: 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 | [[74.082, 59.265, 44.449], [100.0, 74.082, 103.715], 177.796] | [[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 | [[125.964, 68.761, 38.825, 19.413, 9.706], [350.0, 188.947, 85.951, 46.914, 35.186], 262.67] | [[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 | [[120.804, 104.9, 41.96], [0.0, 211.406, 201.932], 267.664] | [[50.625, 76.918, 30.767], [0.0, 211.406, 201.932], 158.31] | Failed |
SHA-256 / 3d07dce41092880da7a1bbfa8c388e6f1a033966e20f872e74d515da83d80ff9
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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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]),
('control: 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 |
| control: 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 / 94b335b4af31fd4253723941643c2fe2104927e4b46cad9f521a976f452de98c
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.448517+00:00.
Case digest / e3339ff19d42e186cb0c848a01add33e3a453af08ca61bb393a93ecd011b6b28