FAILURE MAP
← Case archive

FA-65821 / Ecological population dynamics / Open access

Count-based population viability (diffusion approximation): log ratio direction · case 01

Declining populations appear to grow.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

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 fixtureActualExpectedOutcome
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 shortNoneNonePassed
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 fixtureActualExpectedOutcome
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 shortNoneNonePassed
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 fixtureActualExpectedOutcome
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 shortNoneNonePassed
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