FAILURE MAP
← Case archive

FA-65826 / Ecological population dynamics / Open access

Count-based population viability (diffusion approximation): distance to threshold · case 01

Risk is computed from the first census rather than the current population.

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

ROOT CAUSE

The starting abundance is the first count.

VERIFIED REPAIR

Restore the distance to threshold rule: `d = math.log(counts[-1] / threshold)`.

Unsuccessful approach: Using the peak count overstates the distance to the threshold.

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 + 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[0] / 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
  ('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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: 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.19763][-0.062113, 0.004743, 0.688257]Failed
regression: fluctuating stable[0.009758, 0.046826, 0.203751][0.009758, 0.046826, 0.191687]Failed
control: growing[0.157691, 0.003032, 0.0][0.157691, 0.003032, 0.0]Passed
control: already below threshold[-0.660878, 0.130471, 1.0][-0.660878, 0.130471, 1.0]Passed
control: 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.834919][-0.102165, 1.188964, 0.882919]Failed

SHA-256 / 418fcf748e3e75d094eb4861c456e39f04b4f108ebcca1387470acc15c3c547e

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 = [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(max(counts) / 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
  ('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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: 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.19763][-0.062113, 0.004743, 0.688257]Failed
regression: fluctuating stable[0.009758, 0.046826, 0.161525][0.009758, 0.046826, 0.191687]Failed
control: growing[0.157691, 0.003032, 0.0][0.157691, 0.003032, 0.0]Passed
control: already below threshold[-0.660878, 0.130471, 1.0][-0.660878, 0.130471, 1.0]Passed
control: 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.795455][-0.102165, 1.188964, 0.882919]Failed

SHA-256 / 627c0d761025acd2d7a6d6246a0c91d07dc493373fef2d3b0671528b806637dd

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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: constant decline', ([80, 40, 20], 5, 5), [-0.693147, 0.0, 1.0]),
  ('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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: already below threshold', ([30, 20, 8], 10, 10), [-0.660878, 0.130471, 1.0]),
  ('control: 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]),
  ('control: growing', ([30, 36, 40, 50, 55, 66], 10, 30), [0.157691, 0.003032, 0.0]),
  ('control: 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
control: growing[0.157691, 0.003032, 0.0][0.157691, 0.003032, 0.0]Passed
control: already below threshold[-0.660878, 0.130471, 1.0][-0.660878, 0.130471, 1.0]Passed
control: 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 / e1d333b24f53737090baae6f35c2f9920ffc82b1734110f282ea5a14456371b0

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.646034+00:00.

Case digest / 6ab03821cec0d38366250db3c6a67e3c7a8cf7d87da138444903583e1792be58