Spaces:
Sleeping
Sleeping
File size: 2,996 Bytes
ba8004e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | """
PID controller baseline for blood glucose management.
A simple Proportional-Integral-Derivative controller targeting 120 mg/dL
(centre of the safe 70–180 range). This mirrors the control strategy used
by commercial insulin pumps and serves as a benchmark that RL agents
should eventually outperform.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from models import GlucoAction
class PIDController:
"""
PID controller for insulin dosing.
Adjusts basal insulin rate proportionally to the error between current
glucose and target, with integral and derivative terms for steady-state
accuracy and overshoot damping. Adds a simple correction bolus when
glucose is very high.
Args:
target_glucose: Desired glucose level in mg/dL (default 120).
kp: Proportional gain.
ki: Integral gain.
kd: Derivative gain.
"""
def __init__(
self,
target_glucose: float = 120.0,
kp: float = 0.02,
ki: float = 0.0005,
kd: float = 0.01,
):
self.target = target_glucose
self.kp = kp
self.ki = ki
self.kd = kd
self.integral = 0.0
self.prev_error = 0.0
def reset(self) -> None:
"""Reset the integral and derivative state for a new episode."""
self.integral = 0.0
self.prev_error = 0.0
def act(self, glucose: float) -> GlucoAction:
"""
Compute insulin action given current glucose reading.
Args:
glucose: Current blood glucose in mg/dL.
Returns:
GlucoAction with basal_rate and bolus_dose.
"""
error = glucose - self.target
# Integral with anti-windup clamping
self.integral += error
self.integral = max(-500.0, min(500.0, self.integral))
# Reset integral when glucose crosses below target to prevent
# accumulated error from driving dangerous over-delivery
if glucose < self.target:
self.integral = min(self.integral, 0.0)
derivative = error - self.prev_error
self.prev_error = error
# PID-adjusted basal rate (baseline ~1.0 U/hr)
basal_adjustment = (
self.kp * error + self.ki * self.integral + self.kd * derivative
)
basal_rate = max(0.0, min(5.0, 1.0 + basal_adjustment))
# Simple correction bolus when glucose is very high
if glucose > 200.0:
bolus_dose = max(0.0, (glucose - 200.0) / 50.0)
else:
bolus_dose = 0.0
# Safety: if glucose is dropping low, cut insulin aggressively
if glucose < 90.0:
basal_rate = max(0.0, basal_rate * 0.3)
bolus_dose = 0.0
if glucose < 70.0:
basal_rate = 0.0
bolus_dose = 0.0
return GlucoAction(
basal_rate=round(basal_rate, 4),
bolus_dose=round(min(bolus_dose, 20.0), 4),
)
|