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:

https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2681269/figure/fig1/

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 time
  • GuardianRT - 5 min sample time
  • Navigator - 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):

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 glucose
  • CGM - the CGM blood glucose
  • CHO - grams of carbohydrate (of a meal)
  • insulin - insulin delivered during that interval
  • LBGI - Low blood glucose indicator
  • HBGI - High blood glucose indicator
  • Risk - 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.