Skip to content

Counter-reg differencing over-subtracts, crashing high-running counterfactuals (fixed in 876a08a) #2

Description

@loopkitdev

Summary

The counterfactual counter-regulation term in ClosedLoopSimulator.swift was difference-based:
crSub = (counterreg(counter_BG) − counterreg(real_BG))·dt.

When a counterfactual ran high above a real low — precisely the less-aggressive / lows-reducing arm (e.g. low insulin-needs, or any hypo-prevention candidate) — it shed the full real-low defense rateA, driving the arm implausibly below the real substrate.

Why it matters (methodological)

Systematic downward bias against lows-reducing candidates: the sim manufactured lows for any arm running high over a real low. This inflated the low-insulin-needs reference baseline and ate candidate lift — undermining exactly the frontier comparisons we care about (lows-prevention). Compounded by compression lows: a fake real low → fake rateA → spurious crash.

Evidence

Per-step decomposition of a needs-0.6 arm (aliased dataset user2, 2026-05-24 03:53):

  • patient ICE (NIE) flat +5 mg/dL/5min
  • insulin term (m·candPhys) flat ~−8
  • counter-reg-diff term: 0 → −8 → −16 → −24 → −28 — matches the total ΔBG exactly.

The arm crashed 117 → 30 mg/dL (below the real substrate at 39), with IOB negative — neither insulin nor physiology.

Root cause

realBGdelta only carries a real-low defense when the real trace is actually low; subtracting the full modeled counter-reg rate from a high-running arm is wrong. The counter-reg magnitude (up to 8 mg/dL·min⁻¹) differenced at full strength is also very strong.

History

Counter-reg was originally additive (1cba461: a positive defense when the sim goes low). Commit 4826d3c changed it to differencing to preserve identity / avoid double-counting (real counter-reg is already in the ICE/realBGdelta). That change introduced the shedding artifact.

Fix (876a08a)

Gate the whole term on belowC > 0 — apply only when the counterfactual itself is below onset. Preserves identity (0 when counter==real) and the extra-defense-when-counter-lower case; removes the high-side crash. Cost: a high counter keeps a small inherited real-low lift (minor).

Impact: needs-0.60 t<54 0.569 → 0.051; needs-1.0 also shifts (t<54 1.18→1.09).

Follow-ups

  • Regenerate prior validations (rloop, orefuser2) and all lift numbers — this physics change shifts every counterfactual result.
  • Related open item: carrying real ICE into a far-diverged counterfactual is unreliable in general (not just counter-reg). Consider a divergence guardrail.
  • m (fidelity sensitivity) inference has a 30-min trailing-window lag — stays elevated ~30 min after a steep drop even as ICE turns positive. Not a bug per se, but worth documenting.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions