FA-65821 / Ecological population dynamics / Open access
Count-based population viability (diffusion approximation): log ratio direction · case 01
Declining populations appear to grow.
ROOT CAUSE
Growth increments are computed backward in time.
VERIFIED REPAIR
Restore the log ratio direction rule: `math.log(counts[t + 1] / counts[t])`.
Unsuccessful approach: Arithmetic per-capita changes are not log increments.
Case contract
Log growth increments ln(N[t+1]/N[t]); mu = mean, sigma^2 = sample variance (q-1); d = ln(N_last/threshold); d<=0 gives probability 1.0; zero variance gives 1.0 if mu*T <= -d else 0.0; otherwise P = Phi((-d-muT)/(s sqrtT)) + exp(-2 mu d/s^2) Phi((-d+muT)/(s sqrtT)) capped at 1; return [mu, var, P] rounded 6; None with fewer than three counts, non-positive counts or threshold.
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(counts, threshold, horizon):
if len(counts) < 3 or min(counts) <= 0 or threshold <= 0:
return None
logs = [math.log(counts[t] / counts[t + 1]) for t in range(len(counts) - 1)]
q = len(logs)
mu = sum(logs) / q
var = sum((x - mu) ** 2 for x in logs) / (q - 1)
d = math.log(counts[-1] / threshold)
if d <= 0:
return [round(mu, 6), round(var, 6), 1.0]
if var == 0:
return [round(mu, 6), 0.0, 1.0 if mu * horizon <= -d else 0.0]
sd = math.sqrt(var * horizon)
phi = lambda z: 0.5 * (1 + math.erf(z / math.sqrt(2)))
prob = phi((-d - mu * horizon) / sd) + math.exp(-2 * mu * d / var) * phi((-d + mu * horizon) / sd)
return [round(mu, 6), round(var, 6), round(min(prob, 1.0), 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])]]
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: grizzly census decline | [0.062113, 0.004743, 0.0] | [-0.062113, 0.004743, 0.688257] | Failed |
| regression: fluctuating stable | [-0.009758, 0.046826, 0.382595] | [0.009758, 0.046826, 0.191687] | Failed |
| regression: growing | [-0.157691, 0.003032, 1.0] | [0.157691, 0.003032, 0.0] | Failed |
| regression: already below threshold | [0.660878, 0.130471, 1.0] | [-0.660878, 0.130471, 1.0] | Failed |
| regression: constant decline | [0.693147, 0.0, 0.0] | [-0.693147, 0.0, 1.0] | Failed |
| control: too short | None | None | Passed |
| regression: high variance | [0.102165, 1.188964, 0.69575] | [-0.102165, 1.188964, 0.882919] | Failed |
SHA-256 / 675a0c6969f65ff955f8e8a93c70fe178e1eff35ea32b17767d807c49199ecab
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(counts, threshold, horizon):
if len(counts) < 3 or min(counts) <= 0 or threshold <= 0:
return None
logs = [(counts[t + 1] - counts[t]) / counts[t] for t in range(len(counts) - 1)]
q = len(logs)
mu = sum(logs) / q
var = sum((x - mu) ** 2 for x in logs) / (q - 1)
d = math.log(counts[-1] / threshold)
if d <= 0:
return [round(mu, 6), round(var, 6), 1.0]
if var == 0:
return [round(mu, 6), 0.0, 1.0 if mu * horizon <= -d else 0.0]
sd = math.sqrt(var * horizon)
phi = lambda z: 0.5 * (1 + math.erf(z / math.sqrt(2)))
prob = phi((-d - mu * horizon) / sd) + math.exp(-2 * mu * d / var) * phi((-d + mu * horizon) / sd)
return [round(mu, 6), round(var, 6), round(min(prob, 1.0), 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])]]
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: grizzly census decline | [-0.058351, 0.004296, 0.597774] | [-0.062113, 0.004743, 0.688257] | Failed |
| regression: fluctuating stable | [0.028224, 0.044394, 0.072404] | [0.009758, 0.046826, 0.191687] | Failed |
| regression: growing | [0.172222, 0.004136, 0.0] | [0.157691, 0.003032, 0.0] | Failed |
| regression: already below threshold | [-0.466667, 0.035556, 1.0] | [-0.660878, 0.130471, 1.0] | Failed |
| regression: constant decline | [-0.5, 0.0, 1.0] | [-0.693147, 0.0, 1.0] | Failed |
| control: too short | None | None | Passed |
| regression: high variance | [0.435897, 2.022982, 0.542469] | [-0.102165, 1.188964, 0.882919] | Failed |
SHA-256 / 3cdfdbeda7a5ecfc5df1881f373f2dd2ded5cdd84e5473758b7e84c30fdf0dcf
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(counts, threshold, horizon):
if len(counts) < 3 or min(counts) <= 0 or threshold <= 0:
return None
logs = [math.log(counts[t + 1] / counts[t]) for t in range(len(counts) - 1)]
q = len(logs)
mu = sum(logs) / q
var = sum((x - mu) ** 2 for x in logs) / (q - 1)
d = math.log(counts[-1] / threshold)
if d <= 0:
return [round(mu, 6), round(var, 6), 1.0]
if var == 0:
return [round(mu, 6), 0.0, 1.0 if mu * horizon <= -d else 0.0]
sd = math.sqrt(var * horizon)
phi = lambda z: 0.5 * (1 + math.erf(z / math.sqrt(2)))
prob = phi((-d - mu * horizon) / sd) + math.exp(-2 * mu * d / var) * phi((-d + mu * horizon) / sd)
return [round(mu, 6), round(var, 6), round(min(prob, 1.0), 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])],
[('regression: grizzly census decline',
([45, 42, 44, 38, 36, 35, 31], 10, 20),
[-0.062113, 0.004743, 0.688257]),
('regression: fluctuating stable', ([100, 120, 90, 110, 95, 105], 20, 50), [0.009758, 0.046826, 0.191687]),
('regression: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
('regression: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
('control: too short', ([10, 9], 5, 5), None),
('regression: high variance', ([200, 100, 260, 90, 300, 120], 30, 25), [-0.102165, 1.188964, 0.882919]),
('regression: peak then decline', ([60, 120, 80, 70, 65], 15, 40), [0.020011, 0.222192, 0.539603])]]
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: grizzly census decline | [-0.062113, 0.004743, 0.688257] | [-0.062113, 0.004743, 0.688257] | Passed |
| regression: fluctuating stable | [0.009758, 0.046826, 0.191687] | [0.009758, 0.046826, 0.191687] | Passed |
| regression: growing | [0.157691, 0.003032, 0.0] | [0.157691, 0.003032, 0.0] | Passed |
| regression: already below threshold | [-0.660878, 0.130471, 1.0] | [-0.660878, 0.130471, 1.0] | Passed |
| regression: constant decline | [-0.693147, 0.0, 1.0] | [-0.693147, 0.0, 1.0] | Passed |
| control: too short | None | None | Passed |
| regression: high variance | [-0.102165, 1.188964, 0.882919] | [-0.102165, 1.188964, 0.882919] | Passed |
SHA-256 / 224fd87bd87b66522bf4033a8ea99bb4cae55b1befe873ddfb9d850c796214fc
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:37.388171+00:00.
Case digest / c0ed9088fd211f48485baba5c297bba49a24eebc099dbe32f54a9c5cacceb736