FA-64941 / Epidemic compartment models / Open access
Reed-Frost expected chain-binomial generations: extinction stop · case 01
Chains are truncated while fractional expected cases would still infect others.
ROOT CAUSE
Expected (fractional) case counts below one are treated as extinction.
VERIFIED REPAIR
Restore the extinction stop rule: `if c < 1e-9:`.
Unsuccessful approach: A 0.5 threshold is still an arbitrary integer-case heuristic, not the 1e-9 contract.
Case contract
Expected Reed-Frost: C[t+1] = S[t]*(1-(1-p)**C[t]), S[t+1]=S[t]-C[t+1]; cases are infectious for one generation only; stop early once expected cases fall below 1e-9; return [cases per generation including the index generation rounded to 4, final size including index cases rounded to 4]; None for invalid p or negative counts.
Why this case matters
Compartmental epidemic calculations drive outbreak forecasts, vaccine targets and hospital planning; a single wrong flow, rate conversion or boundary silently changes every downstream number.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(p, s0, i0, generations):
if not 0 <= p <= 1 or s0 < 0 or i0 < 0:
return None
q = 1 - p
s = float(s0)
c = float(i0)
cases = [round(c, 4)]
for _ in range(generations):
nxt = s * (1 - q ** c)
s -= nxt
c = nxt
cases.append(round(c, 4))
if c < 1:
break
return [cases, round(s0 + i0 - s, 4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None)],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0])]]
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: household of 10 | [[1.0, 0.9], 1.9] | [[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798] | Failed |
| regression: school class | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844], 9.5931] | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969] | Failed |
| regression: high contact probability | [[2.0, 15.0, 4.9998, 0.0001], 22.0] | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | Failed |
| control: p zero no spread | [[1.0, 0.0], 1.0] | [[1.0, 0.0], 1.0] | Passed |
| control: p one immediate saturation | [[1.0, 10.0, 0.0], 11.0] | [[1.0, 10.0, 0.0], 11.0] | Passed |
| control: no index cases | [[0.0, 0.0], 0.0] | [[0.0, 0.0], 0.0] | Passed |
| control: invalid p | None | None | Passed |
SHA-256 / a0e53d074a1749abed5ac5193072cf354ed721d3b36d0e5bfbc15a3b5ce56808
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(p, s0, i0, generations):
if not 0 <= p <= 1 or s0 < 0 or i0 < 0:
return None
q = 1 - p
s = float(s0)
c = float(i0)
cases = [round(c, 4)]
for _ in range(generations):
nxt = s * (1 - q ** c)
s -= nxt
c = nxt
cases.append(round(c, 4))
if c < 0.5:
break
return [cases, round(s0 + i0 - s, 4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None)],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0])]]
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: household of 10 | [[1.0, 0.9, 0.7328, 0.5474, 0.3822], 3.5624] | [[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798] | Failed |
| regression: school class | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969] | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969] | Passed |
| regression: high contact probability | [[2.0, 15.0, 4.9998, 0.0001], 22.0] | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | Failed |
| control: p zero no spread | [[1.0, 0.0], 1.0] | [[1.0, 0.0], 1.0] | Passed |
| control: p one immediate saturation | [[1.0, 10.0, 0.0], 11.0] | [[1.0, 10.0, 0.0], 11.0] | Passed |
| control: no index cases | [[0.0, 0.0], 0.0] | [[0.0, 0.0], 0.0] | Passed |
| control: invalid p | None | None | Passed |
SHA-256 / 98002716afe081a840f2baf54fa74ee35573ea47631a07e667fd4b476bf48039
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(p, s0, i0, generations):
if not 0 <= p <= 1 or s0 < 0 or i0 < 0:
return None
q = 1 - p
s = float(s0)
c = float(i0)
cases = [round(c, 4)]
for _ in range(generations):
nxt = s * (1 - q ** c)
s -= nxt
c = nxt
cases.append(round(c, 4))
if c < 1e-9:
break
return [cases, round(s0 + i0 - s, 4)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None)],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0]),
('regression: fading chain',
(0.02, 30, 1, 10),
[[1.0, 0.6, 0.3542, 0.2071, 0.1204, 0.0698, 0.0404, 0.0233, 0.0135, 0.0078, 0.0045], 2.4409]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: household of 10',
(0.1, 9, 1, 6),
[[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]),
('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: large ward',
(0.03, 60, 3, 8),
[[3.0, 5.2396, 8.0777, 10.182, 9.733, 6.8673, 3.7561, 1.7453, 0.7455], 49.3465]),
('control: two generations only', (0.15, 40, 4, 2), [[4.0, 19.1198, 19.9465], 43.0662])],
[('regression: school class',
(0.04, 29, 1, 8),
[[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969]),
('regression: high contact probability', (0.5, 20, 2, 5), [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]),
('control: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('control: p one immediate saturation', (1.0, 10, 1, 4), [[1.0, 10.0, 0.0], 11.0]),
('control: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('control: zero generations', (0.3, 10, 2, 0), [[2.0], 2.0])]]
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: household of 10 | [[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798] | [[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798] | Passed |
| regression: school class | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969] | [[1.0, 1.16, 1.2876, 1.3596, 1.3602, 1.2872, 1.1541, 0.9844, 0.8038], 10.3969] | Passed |
| regression: high contact probability | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | Passed |
| control: p zero no spread | [[1.0, 0.0], 1.0] | [[1.0, 0.0], 1.0] | Passed |
| control: p one immediate saturation | [[1.0, 10.0, 0.0], 11.0] | [[1.0, 10.0, 0.0], 11.0] | Passed |
| control: no index cases | [[0.0, 0.0], 0.0] | [[0.0, 0.0], 0.0] | Passed |
| control: invalid p | None | None | Passed |
SHA-256 / 6462cec6885ce4702b7c860cbd6f29894afc43445e4d8d318efa152d0a6b83cd
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:29.452206+00:00.
Case digest / 20e85b8290de1cad895e1c8dd6f24dd13dc44f47860ceeb79c2e43f2635adb8d