FAILURE MAP
← Case archive

FA-64961 / Epidemic compartment models / Open access

SIRS waning-immunity simulation: waning destination · case 01

Population shrinks steadily as immunity wanes.

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

ROOT CAUSE

Waned individuals leave R but are never returned to S.

VERIFIED REPAIR

Restore the waning destination rule: `s += wane - inf`.

Unsuccessful approach: Scaling the waning inflow by S/pop still loses part of the waning flow.

Case contract

Daily forward-Euler SIRS; waning flow is R/immunity_days back to S (immunity_days 0 means permanent immunity); cumulative incidence sums every S->I flow including reinfections; return [S, I, R, cumulative incidence] rounded to 3.

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(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s -= inf
        i += inf - rec
        r += rec - wane
        cumulative += inf
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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: seasonal coronavirus[190.011, 0.436, 160.305, 804.989][543.377, 58.225, 398.398, 1339.789]Failed
control: permanent immunity sentinel[48.675, 7.802, 443.523, 446.325][48.675, 7.802, 443.523, 446.325]Passed
regression: short immunity endemic[82.245, 0.0, 0.17, 709.755][333.272, 133.331, 333.396, 3385.33]Failed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[50.0, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Failed
regression: long immunity single wave[8.652, 0.045, 1536.0, 1981.348][376.102, 3.345, 1620.554, 2084.424]Failed
regression: one day immunity[111.765, 15.863, 5.335, 185.235][188.175, 86.35, 25.475, 420.744]Failed

SHA-256 / dd333b110978f6328ea9aeec84b23afedbe198c1c7d754024f39997c66d0eaf2

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s += wane * s / pop - inf
        i += inf - rec
        r += rec - wane
        cumulative += inf
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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: seasonal coronavirus[311.309, 2.166, 199.791, 929.698][543.377, 58.225, 398.398, 1339.789]Failed
control: permanent immunity sentinel[48.675, 7.802, 443.523, 446.325][48.675, 7.802, 443.523, 446.325]Passed
regression: short immunity endemic[163.727, 0.002, 0.303, 844.559][333.272, 133.331, 333.396, 3385.33]Failed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[80.406, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Failed
regression: long immunity single wave[10.792, 0.047, 1539.902, 1986.297][376.102, 3.345, 1620.554, 2084.424]Failed
regression: one day immunity[143.903, 53.642, 16.965, 345.859][188.175, 86.35, 25.475, 420.744]Failed

SHA-256 / feb4c8d4d0e571b4544a6848345e53da041271e04eed630d36807ecd7becc6c5

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s += wane - inf
        i += inf - rec
        r += rec - wane
        cumulative += inf
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('control: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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: seasonal coronavirus[543.377, 58.225, 398.398, 1339.789][543.377, 58.225, 398.398, 1339.789]Passed
control: permanent immunity sentinel[48.675, 7.802, 443.523, 446.325][48.675, 7.802, 443.523, 446.325]Passed
regression: short immunity endemic[333.272, 133.331, 333.396, 3385.33][333.272, 133.331, 333.396, 3385.33]Passed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[98.35, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Passed
regression: long immunity single wave[376.102, 3.345, 1620.554, 2084.424][376.102, 3.345, 1620.554, 2084.424]Passed
regression: one day immunity[188.175, 86.35, 25.475, 420.744][188.175, 86.35, 25.475, 420.744]Passed

SHA-256 / df8c15c73a7837b2c59733bfecc9f738043adaa0df71d35c354f83b563e05452

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

Case digest / 77ddd64e4ed17de8039a5aa006bda216a17e2d873ef427dc3be3aff16fb250ae