Simulation with simGlucose - rland93/uciAPC GitHub Wiki
SimGlucose is a set of software patients, a simulator environment, and a controller API.
Patients
Software Patients - these are like real patients, only in software. simGlucose uses a multi-compartment model to estimate blood glucose response to meals:
We are not concerned with the internals of the model; instead, we treat the simGlucose implementation as a "black box," measuring only the outputs from the in silico sensor and inputs of the in silico insulin pump. We trust that the model is accurate enough.
Let's create a software patient:
from simglucose.patient.t1dpatient import T1DPatient
patientID = 12
patient = T1DPatient.withID(12)
We chose patient 12 -- a.k.a adult#001
.
Sensors
The simulator also models the noise that would be induced from a sensor. While we can evaluate the performance of our controller based on the the "true" blood glucose value known to the simulator, we cannot use the real value as an input to the controller, because that information is hidden behind the noise and unreliability of a sensor. We can choose from CGM sensors that were commercially available when the 2008 version of the simulator was available:
Dexcom
- 3 min sample timeGuardianRT
- 5 min sample timeNavigator
- 1 min sample time
While better sensors are available now, worse sensors will be fine -- the extra noise or lower sampling rate from a low-tech sensor adds to the challenge of writing a controller. Let's choose Dexcom
:
from simglucose.sensor.cgm import CGMSensor, CGMNoise
sim_sensor = CGMSensor.withName('Dexcom')
Pumps
simGlucose also models insulin pumps. Some pumps can fine-tune bolus amounts to a greater degree than others, and have different maximum and minimum doses. simGlucose models two (with very minor differences between them):
Cozmo
- Deltec Cozmo, discontinued in 2009.Insulet
- Omnipod, manufactured by Insulet.
Let's specify a pump:
from simglucose.actuator.pump import InsulinPump
sim_pump = InsulinPump.withName('Insulet')
We chose Insulet as a reward for their not going out of business, but it doesn't matter.
Simulator Environment
Simulator Environment is the external forces that our patient will experience. We need to model how the patient's blood sugar responds to meals and doses administered by the controller. The simulator environment needs to know which patients are in it, which sensor the patients are using, which pump they are using, and what the scenario
is.
Scenario
A scenario
is a pre-defined set of events against which we test our controller. We want our scenario to run for a while, so that we find how our controller is doing, and we want it to include 1 or more meals, so we can evaluate how the controller responds to meals.
For now, we are implementing a time-invariant controller. That means that the response of the controller does not depend at all on the time of day. Therefore, the timing of meals will have no effect on controller performance -- to our controller, there is no difference between breakfast and lunch. Future versions of the controller may need to take the time of day into account, since meals can have a slightly different effect depending on what time they have been eaten.
For now, our environment can be random. We specify a seed so that we can recreate this simulator. Let's create a scenario:
from datetime import timedelta, datetime
from simglucose.simulation.scenario_gen import RandomScenario
RANDOM_SEED = 25
sim_start_time = datetime.now()
sim_run_time = timedelta(hours=24)
sim_scenario = RandomScenario(start_time = sim_start_time, seed = RANDOM_SEED)
We'll run this scenario for 24 hours, starting at the current time. Now, we plug that into our environment:
from simglucose.simulation.env import T1DSimEnv
environment = T1DSimEnv(patient, sim_sensor, sim_pump, sim_scenario)
Controller
Now, we're only missing one thing, a controller
. See the Controller page for documentation. For now, let's get a controller that does nothing, just to see if our implementation works:
from controller import blankController
controller = blankController(0)
The controller object will read CGM values from the simulator environment and respond according to its logic.
Running the simulator and results
Once we've created a controller, we will want to simulate the response. We create a simulator and run it:
from simglucose.simulation.sim_engine import SimObj, sim
# script saves csv(s) into this path
results_path = './results/'
simulator = SimObj(
environment,
controller,
sim_run_time,
animate=False,
path = results_path
sim(simulator)
This will save a CSV file to the /results/
folder that will contain all the data from the simulation:
BG
- the true blood glucoseCGM
- the CGM blood glucoseCHO
- grams of carbohydrate (of a meal)insulin
- insulin delivered during that intervalLBGI
- Low blood glucose indicatorHBGI
- High blood glucose indicatorRisk
- Risk indicator
We can evaluate our controller based on the parameters LGBI
, HGBI
, and Risk
, or by analyzing the values of BG
directly. LGBI
goes up when the blood glucose drops below a hypoglycemic level; HGBI
goes up when the blood glucose rises above a hyperglycemic level; and Risk
goes up when either LGBI
or HGBI
are above 0. An ideal controller would minimize all three values, even after a meal -- not too high, not too low.
We can specify animate=True
when we instantiate our simObj
. This will plot the data in real time, as the patient is being simulated. It is significantly slower to animate the simulation, so setting this flag is only useful when quickly prototyping.
The code used here can be found in example1.py.