FAILURE MAP
← Case archive

FA-64976 / Epidemic compartment models / Open access

SIS endemic equilibrium convergence: equilibrium formula · case 01

The endemic prevalence target is the susceptible equilibrium, so convergence is judged against the wrong level.

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

ROOT CAUSE

pop/R0 (equilibrium S) is used as the endemic prevalence.

VERIFIED REPAIR

Restore the equilibrium formula rule: `target = pop * (1 - 1 / r0) if r0 > 1 else 0.0`.

Unsuccessful approach: pop*(R0-1) is not bounded by the population.

Case contract

SIS with susceptibles pop-I; endemic target pop*(1-1/R0) if R0=beta/gamma>1 else 0; iterate daily Euler I += beta*I*(pop-I)/pop - gamma*I until |I-target| <= tol*pop or max_days steps; return [target rounded 4, days taken, I rounded 4] or None for non-positive beta, gamma or pop.

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, pop, i0, tol, max_days):
    if beta <= 0 or gamma <= 0 or pop <= 0:
        return None
    r0 = beta / gamma
    target = pop / r0 if r0 > 1 else 0.0
    i = float(i0)
    day = 0
    while abs(i - target) > tol * pop and day < max_days:
        i += beta * i * (pop - i) / pop - gamma * i
        day += 1
    return [round(target, 4), day, round(i, 4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])]]
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: gonorrhoea-like endemic[333.3333, 200, 666.6667][666.6667, 42, 657.0888]Failed
control: subcritical dies out[0.0, 21, 4.9755][0.0, 21, 4.9755]Passed
regression: start above equilibrium[500.0, 17, 504.3274][500.0, 17, 504.3274]Passed
regression: already at equilibrium[500.0, 0, 500.0][500.0, 0, 500.0]Passed
regression: tight tolerance capped[1600.0, 30, 4.288][400.0, 30, 4.288]Failed
regression: zero day cap[33.3333, 0, 1.0][66.6667, 0, 1.0]Failed
control: invalid gammaNoneNonePassed

SHA-256 / cb9484374ef521880f1276d9db96e45bf7836476ed48867fbe58877e466a304b

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, pop, i0, tol, max_days):
    if beta <= 0 or gamma <= 0 or pop <= 0:
        return None
    r0 = beta / gamma
    target = pop * (r0 - 1) if r0 > 1 else 0.0
    i = float(i0)
    day = 0
    while abs(i - target) > tol * pop and day < max_days:
        i += beta * i * (pop - i) / pop - gamma * i
        day += 1
    return [round(target, 4), day, round(i, 4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])]]
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: gonorrhoea-like endemic[2000.0, 200, 666.6667][666.6667, 42, 657.0888]Failed
control: subcritical dies out[0.0, 21, 4.9755][0.0, 21, 4.9755]Passed
regression: start above equilibrium[1000.0, 100, 500.0][500.0, 17, 504.3274]Failed
regression: already at equilibrium[1000.0, 50, 500.0][500.0, 0, 500.0]Failed
regression: tight tolerance capped[500.0, 30, 4.288][400.0, 30, 4.288]Failed
regression: zero day cap[200.0, 0, 1.0][66.6667, 0, 1.0]Failed
control: invalid gammaNoneNonePassed

SHA-256 / 6df96a94eb300918357cbf376e4cc5cd62e465255e3062d9f598adb71c1b4241

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, pop, i0, tol, max_days):
    if beta <= 0 or gamma <= 0 or pop <= 0:
        return None
    r0 = beta / gamma
    target = pop * (1 - 1 / r0) if r0 > 1 else 0.0
    i = float(i0)
    day = 0
    while abs(i - target) > tol * pop and day < max_days:
        i += beta * i * (pop - i) / pop - gamma * i
        day += 1
    return [round(target, 4), day, round(i, 4)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])],
 [('regression: gonorrhoea-like endemic', (0.3, 0.1, 1000, 10, 0.01, 200), [666.6667, 42, 657.0888]),
  ('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: start above equilibrium', (0.4, 0.2, 1000, 900, 0.005, 100), [500.0, 17, 504.3274]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None)],
 [('control: subcritical dies out', (0.1, 0.2, 500, 50, 0.01, 200), [0.0, 21, 4.9755]),
  ('regression: already at equilibrium', (0.4, 0.2, 1000, 500, 0.01, 50), [500.0, 0, 500.0]),
  ('regression: tight tolerance capped', (0.25, 0.2, 2000, 1, 0.0001, 30), [400.0, 30, 4.288]),
  ('regression: zero day cap', (0.3, 0.1, 100, 1, 0.01, 0), [66.6667, 0, 1.0]),
  ('control: invalid gamma', (0.3, 0.0, 100, 1, 0.01, 10), None),
  ('regression: fast dynamics', (0.9, 0.3, 300, 3, 0.02, 100), [200.0, 14, 197.4596]),
  ('regression: near threshold', (0.21, 0.2, 10000, 100, 0.001, 60), [476.1905, 60, 155.2383])]]
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: gonorrhoea-like endemic[666.6667, 42, 657.0888][666.6667, 42, 657.0888]Passed
control: subcritical dies out[0.0, 21, 4.9755][0.0, 21, 4.9755]Passed
regression: start above equilibrium[500.0, 17, 504.3274][500.0, 17, 504.3274]Passed
regression: already at equilibrium[500.0, 0, 500.0][500.0, 0, 500.0]Passed
regression: tight tolerance capped[400.0, 30, 4.288][400.0, 30, 4.288]Passed
regression: zero day cap[66.6667, 0, 1.0][66.6667, 0, 1.0]Passed
control: invalid gammaNoneNonePassed

SHA-256 / c63a6a56305effe6399407f1738165daff5ae49d8c323b41866cf0cf3da62581

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

Case digest / 407ff203dde3deba752f8c655b269901544f62da08f2b2feba3d9bd67ef3d7f1