FA-64946 / Epidemic compartment models / Open access
Reed-Frost expected chain-binomial generations: final size accounting · case 01
The final size misses the index cases.
ROOT CAUSE
The attack total counts only susceptibles depleted, excluding the index generation.
VERIFIED REPAIR
Restore the final size accounting rule: `round(s0 + i0 - s, 4)`.
Unsuccessful approach: Subtracting the last generation drops cases that were already infected.
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 < 1e-9:
break
return [cases, round(s0 - 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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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: 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])],
[('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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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: 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]),
('regression: 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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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]),
('regression: 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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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], 2.9798] | [[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], 9.3969] | [[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, 0.0], 20.0] | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | Failed |
| regression: p zero no spread | [[1.0, 0.0], 0.0] | [[1.0, 0.0], 1.0] | Failed |
| regression: p one immediate saturation | [[1.0, 10.0, 0.0], 10.0] | [[1.0, 10.0, 0.0], 11.0] | Failed |
| 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 / 6dbeeba15a6e6c90bce1a96f73a9495a82938ede76dca152a3db29359f01b966
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 < 1e-9:
break
return [cases, round(s0 + i0 - s - cases[-1], 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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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: 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])],
[('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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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: 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]),
('regression: 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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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]),
('regression: 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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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.8165] | [[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], 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, 0.0], 22.0] | [[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0] | Passed |
| regression: p zero no spread | [[1.0, 0.0], 1.0] | [[1.0, 0.0], 1.0] | Passed |
| regression: 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 / 8ff34e0977ad525d1004c10ce66afa85864f6c5d4dbb216b16d5b21849095342
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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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: 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])],
[('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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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: 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]),
('regression: 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: no index cases', (0.2, 10, 0, 4), [[0.0, 0.0], 0.0]),
('control: invalid p', (1.5, 10, 1, 4), None),
('regression: 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]),
('regression: 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]),
('regression: p zero no spread', (0.0, 10, 1, 4), [[1.0, 0.0], 1.0]),
('regression: 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: 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 |
| regression: p zero no spread | [[1.0, 0.0], 1.0] | [[1.0, 0.0], 1.0] | Passed |
| regression: 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 / b795cbbd2761f816db10733352a12383410380796711167175ca5b957b539a25
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.459236+00:00.
Case digest / 01c9ec150441684e23822d037bfae697846699e45493a101b4b5cd8a2fb0cf3a