FA-65521 / Ecological population dynamics / Open access
Two-sample mark-recapture abundance: zero-recapture Petersen · case 01
A survey with no recaptures reports a finite abundance.
ROOT CAUSE
R=0 is replaced by 1 instead of reporting the estimate as undefined.
VERIFIED REPAIR
Restore the zero-recapture Petersen rule: `return [None, None]`.
Unsuccessful approach: Reporting zero abundance contradicts the animals already caught.
Case contract
Recaptures must be in [0, min(marked, caught)] else None; 'lp' = M*C/R ([None, None] when R=0, no variance); Chapman = (M+1)(C+1)/(R+1)-1 with variance (M+1)(C+1)(M-R)(C-R)/((R+1)^2 (R+2)); return [estimate rounded 2, variance rounded 2 or None].
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(marked, caught, recaptured, method):
if recaptured < 0 or recaptured > min(marked, caught):
return None
if method == 'lp':
if recaptured == 0:
recaptured = 1
return [round(marked * caught / recaptured, 2), None]
est = (marked + 1) * (caught + 1) / (recaptured + 1) - 1
var = (marked + 1) * (caught + 1) * (marked - recaptured) * (caught - recaptured) / ((recaptured + 1) ** 2 * (recaptured + 2))
return [round(est, 2), round(var, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None)],
[('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84])]]
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 |
|---|---|---|---|
| control: trout chapman | [580.95, 10077.1] | [580.95, 10077.1] | Passed |
| control: trout petersen | [600.0, None] | [600.0, None] | Passed |
| control: zero recaptures chapman | [2090.0, 2091000.0] | [2090.0, 2091000.0] | Passed |
| regression: zero recaptures petersen | [2000.0, None] | [None, None] | Failed |
| control: all recaptured | [30.0, 0.0] | [30.0, 0.0] | Passed |
| control: recaptures exceed catch | None | None | Passed |
| control: recaptures exceed marked | None | None | Passed |
SHA-256 / 8c7c9c0a3ce07914e141110c4f5de1a5a311eaa79938ed5fb84788c00a75adf8
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(marked, caught, recaptured, method):
if recaptured < 0 or recaptured > min(marked, caught):
return None
if method == 'lp':
if recaptured == 0:
return [0.0, None]
return [round(marked * caught / recaptured, 2), None]
est = (marked + 1) * (caught + 1) / (recaptured + 1) - 1
var = (marked + 1) * (caught + 1) * (marked - recaptured) * (caught - recaptured) / ((recaptured + 1) ** 2 * (recaptured + 2))
return [round(est, 2), round(var, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None)],
[('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84])]]
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 |
|---|---|---|---|
| control: trout chapman | [580.95, 10077.1] | [580.95, 10077.1] | Passed |
| control: trout petersen | [600.0, None] | [600.0, None] | Passed |
| control: zero recaptures chapman | [2090.0, 2091000.0] | [2090.0, 2091000.0] | Passed |
| regression: zero recaptures petersen | [0.0, None] | [None, None] | Failed |
| control: all recaptured | [30.0, 0.0] | [30.0, 0.0] | Passed |
| control: recaptures exceed catch | None | None | Passed |
| control: recaptures exceed marked | None | None | Passed |
SHA-256 / 1ac06a2a89a6f270db8f3bf91506bde0bb0475cfabde1b9453bd44300714744f
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(marked, caught, recaptured, method):
if recaptured < 0 or recaptured > min(marked, caught):
return None
if method == 'lp':
if recaptured == 0:
return [None, None]
return [round(marked * caught / recaptured, 2), None]
est = (marked + 1) * (caught + 1) / (recaptured + 1) - 1
var = (marked + 1) * (caught + 1) * (marked - recaptured) * (caught - recaptured) / ((recaptured + 1) ** 2 * (recaptured + 2))
return [round(est, 2), round(var, 2)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None)],
[('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84]),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: trout chapman', (120, 100, 20, 'chapman'), [580.95, 10077.1]),
('control: trout petersen', (120, 100, 20, 'lp'), [600.0, None]),
('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: negative recapture', (20, 20, -1, 'lp'), None)],
[('control: zero recaptures chapman', (50, 40, 0, 'chapman'), [2090.0, 2091000.0]),
('regression: zero recaptures petersen', (50, 40, 0, 'lp'), [None, None]),
('control: all recaptured', (30, 30, 30, 'chapman'), [30.0, 0.0]),
('control: recaptures exceed catch', (80, 10, 12, 'chapman'), None),
('control: recaptures exceed marked', (10, 80, 12, 'lp'), None),
('control: small sample', (15, 12, 3, 'chapman'), [51.0, 280.8]),
('control: butterflies', (200, 150, 45, 'chapman'), [658.8, 4966.84])]]
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 |
|---|---|---|---|
| control: trout chapman | [580.95, 10077.1] | [580.95, 10077.1] | Passed |
| control: trout petersen | [600.0, None] | [600.0, None] | Passed |
| control: zero recaptures chapman | [2090.0, 2091000.0] | [2090.0, 2091000.0] | Passed |
| regression: zero recaptures petersen | [None, None] | [None, None] | Passed |
| control: all recaptured | [30.0, 0.0] | [30.0, 0.0] | Passed |
| control: recaptures exceed catch | None | None | Passed |
| control: recaptures exceed marked | None | None | Passed |
SHA-256 / a29674c3b8f281c1558755fbc77fa62f8ead5268b30145004fbd6e3eb228608b
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:34.697312+00:00.
Case digest / ba62b3046a932f281534dcc26508bc7b2ff17fd6e876dec65a75f8607f0c2414