FAILURE MAP
← Case archive

FA-65816 / Ecological population dynamics / Open access

Count-based population viability (diffusion approximation): sample variance · case 01

Environmental variance is underestimated, understating extinction risk.

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

ROOT CAUSE

The population variance divisor q is used instead of q-1.

VERIFIED REPAIR

Restore the sample variance rule: `var = sum((x - mu) ** 2 for x in logs) / (q - 1)`.

Unsuccessful approach: Dividing by q+1 biases the variance further downward.

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
    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]),
  ('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]),
  ('regression: 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]),
  ('regression: 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])],
 [('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: 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]),
  ('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.003952, 0.653307][-0.062113, 0.004743, 0.688257]Failed
regression: fluctuating stable[0.009758, 0.037461, 0.141144][0.009758, 0.046826, 0.191687]Failed
regression: growing[0.157691, 0.002425, 0.0][0.157691, 0.003032, 0.0]Failed
regression: already below threshold[-0.660878, 0.065236, 1.0][-0.660878, 0.130471, 1.0]Failed
control: constant decline[-0.693147, 0.0, 1.0][-0.693147, 0.0, 1.0]Passed
control: too shortNoneNonePassed
regression: high variance[-0.102165, 0.951171, 0.876849][-0.102165, 1.188964, 0.882919]Failed

SHA-256 / 04d456d9596dae3aa0e7fbf2cb0db60ef99d8550bb5e988d3f58e7d52e8bc6c9

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(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]),
  ('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]),
  ('regression: 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]),
  ('regression: 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])],
 [('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: 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]),
  ('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.003388, 0.664893][-0.062113, 0.004743, 0.688257]Failed
regression: fluctuating stable[0.009758, 0.031217, 0.10478][0.009758, 0.046826, 0.191687]Failed
regression: growing[0.157691, 0.002021, 0.0][0.157691, 0.003032, 0.0]Failed
regression: already below threshold[-0.660878, 0.04349, 1.0][-0.660878, 0.130471, 1.0]Failed
control: constant decline[-0.693147, 0.0, 1.0][-0.693147, 0.0, 1.0]Passed
control: too shortNoneNonePassed
regression: high variance[-0.102165, 0.792643, 0.872269][-0.102165, 1.188964, 0.882919]Failed

SHA-256 / 46ce6bdab59c7bf30ea775b74e9dfa53f220bd5c2a8683a24f6f560783899dc6

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]),
  ('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]),
  ('regression: 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]),
  ('regression: 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])],
 [('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: 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]),
  ('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
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
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 / 6441c4866d9b6c03e3d87eaf7240db1ef75582cc308ea2af6a2bd82f722fd329

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

Case digest / b15e5d94e6e8ab4122c4e0ed4bf4917aa4dd97768980433cf1cc32f343440ac0