FAILURE MAP
← Case archive

FA-64931 / Epidemic compartment models / Open access

Reed-Frost expected chain-binomial generations: one-generation infectivity · case 01

Epidemics saturate too quickly because every past case keeps transmitting.

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

ROOT CAUSE

Cases accumulate across generations instead of being infectious for one generation.

VERIFIED REPAIR

Restore the one-generation infectivity rule: `c = nxt`.

Unsuccessful approach: Carrying half of the previous generation forward is still a multi-generation infectious period.

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 + 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]),
  ('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: 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),
  ('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: 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),
  ('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: 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]),
  ('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: 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),
  ('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 fixtureActualExpectedOutcome
regression: household of 10[[1.0, 1.9, 3.3695, 5.3509, 7.3544, 8.781, 9.5167], 9.5167][[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]Failed
regression: school class[[1.0, 2.16, 4.5097, 8.7957, 15.1923, 22.0357, 26.7605, 28.9135, 29.6662], 29.6662][[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, 17.0, 22.0, 22.0, 22.0, 22.0], 22.0][[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]Failed
regression: p zero no spread[[1.0, 1.0, 1.0, 1.0, 1.0], 1.0][[1.0, 0.0], 1.0]Failed
control: no index cases[[0.0, 0.0], 0.0][[0.0, 0.0], 0.0]Passed
control: invalid pNoneNonePassed
control: zero generations[[2.0], 2.0][[2.0], 2.0]Passed

SHA-256 / f515906cdc1d4aa0547114898692f1f1ac00f7811ad58d910e158db48b5846ea

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 + c / 2
        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]),
  ('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: 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),
  ('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: 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),
  ('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: 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]),
  ('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: 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),
  ('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 fixtureActualExpectedOutcome
regression: household of 10[[1.0, 1.4, 1.8108, 2.1194, 2.2155, 2.0694, 1.7513], 7.0588][[1.0, 0.9, 0.7328, 0.5474, 0.3822, 0.2541, 0.1633], 3.9798]Failed
regression: school class[[1.0, 1.66, 2.6541, 3.9984, 5.5148, 6.7546, 7.1926, 6.6537, 5.4578], 23.1719][[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, 16.0, 12.9999, 6.5, 3.25, 1.625], 22.0][[2.0, 15.0, 4.9998, 0.0001, 0.0], 22.0]Failed
regression: p zero no spread[[1.0, 0.5, 0.25, 0.125, 0.0625], 1.0][[1.0, 0.0], 1.0]Failed
control: no index cases[[0.0, 0.0], 0.0][[0.0, 0.0], 0.0]Passed
control: invalid pNoneNonePassed
control: zero generations[[2.0], 2.0][[2.0], 2.0]Passed

SHA-256 / f0626a5d90ec0bb379503a37867ca43296aad38eb152cad2166e1c029a3bf5dd

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]),
  ('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: 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),
  ('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: 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),
  ('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: 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]),
  ('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: 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),
  ('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 fixtureActualExpectedOutcome
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
control: no index cases[[0.0, 0.0], 0.0][[0.0, 0.0], 0.0]Passed
control: invalid pNoneNonePassed
control: zero generations[[2.0], 2.0][[2.0], 2.0]Passed

SHA-256 / 6d3b01b8ed8589d5b348cac8214cf97adb3646407601007e59a092e79ce81b41

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

Case digest / 857d1b7316e0c2ef912b5ba92ae205f94ffc7e01ccb0989d677b9eb41c34f79d