Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Random init bg does not work issue #82

Merged
merged 6 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="simglucose",
version="0.2.9",
version="0.2.10",
description="A Type-1 Diabetes Simulator as a Reinforcement Learning Environment in OpenAI gym or rllab (python implementation of UVa/Padova Simulator)",
url="https://github.com/jxx123/simglucose",
author="Jinyu Xie",
Expand Down
99 changes: 52 additions & 47 deletions simglucose/patient/t1dpatient.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,28 @@

logger = logging.getLogger(__name__)

Action = namedtuple("patient_action", ['CHO', 'insulin'])
Observation = namedtuple("observation", ['Gsub'])
Action = namedtuple("patient_action", ["CHO", "insulin"])
Observation = namedtuple("observation", ["Gsub"])

PATIENT_PARA_FILE = pkg_resources.resource_filename(
'simglucose', 'params/vpatient_params.csv')
"simglucose", "params/vpatient_params.csv"
)


class T1DPatient(Patient):
SAMPLE_TIME = 1 # min
EAT_RATE = 5 # g/min CHO

def __init__(self,
params,
init_state=None,
random_init_bg=False,
seed=None,
t0=0):
'''
def __init__(self, params, init_state=None, random_init_bg=False, seed=None, t0=0):
"""
T1DPatient constructor.
Inputs:
- params: a pandas sequence
- init_state: customized initial state.
If not specified, load the default initial state in
params.iloc[2:15]
- t0: simulation start time, it is 0 by default
'''
"""
self._params = params
self._init_state = init_state
self.random_init_bg = random_init_bg
Expand All @@ -43,26 +39,26 @@ def __init__(self,

@classmethod
def withID(cls, patient_id, **kwargs):
'''
"""
Construct patient by patient_id
id are integers from 1 to 30.
1 - 10: adolescent#001 - adolescent#010
11 - 20: adult#001 - adult#001
21 - 30: child#001 - child#010
'''
"""
patient_params = pd.read_csv(PATIENT_PARA_FILE)
params = patient_params.iloc[patient_id - 1, :]
return cls(params, **kwargs)

@classmethod
def withName(cls, name, **kwargs):
'''
"""
Construct patient by name.
Names can be
adolescent#001 - adolescent#010
adult#001 - adult#001
child#001 - child#010
'''
"""
patient_params = pd.read_csv(PATIENT_PARA_FILE)
params = patient_params.loc[patient_params.Name == name].squeeze()
return cls(params, **kwargs)
Expand All @@ -86,33 +82,33 @@ def step(self, action):

# Detect eating or not and update last digestion amount
if action.CHO > 0 and self._last_action.CHO <= 0:
logger.info('t = {}, patient starts eating ...'.format(self.t))
logger.info("t = {}, patient starts eating ...".format(self.t))
self._last_Qsto = self.state[0] + self.state[1] # unit: mg
self._last_foodtaken = 0 # unit: g
self.is_eating = True

if to_eat > 0:
logger.debug('t = {}, patient eats {} g'.format(
self.t, action.CHO))
logger.debug("t = {}, patient eats {} g".format(self.t, action.CHO))

if self.is_eating:
self._last_foodtaken += action.CHO # g

# Detect eating ended
if action.CHO <= 0 and self._last_action.CHO > 0:
logger.info('t = {}, Patient finishes eating!'.format(self.t))
logger.info("t = {}, Patient finishes eating!".format(self.t))
self.is_eating = False

# Update last input
self._last_action = action

# ODE solver
self._odesolver.set_f_params(action, self._params, self._last_Qsto,
self._last_foodtaken)
self._odesolver.set_f_params(
action, self._params, self._last_Qsto, self._last_foodtaken
)
if self._odesolver.successful():
self._odesolver.integrate(self._odesolver.t + self.sample_time)
else:
logger.error('ODE solver failed!!')
logger.error("ODE solver failed!!")
raise

@staticmethod
Expand All @@ -136,8 +132,10 @@ def model(t, x, action, params, last_Qsto, last_foodtaken):
aa = 5 / (2 * Dbar * (1 - params.b))
cc = 5 / (2 * Dbar * params.d)
kgut = params.kmin + (params.kmax - params.kmin) / 2 * (
np.tanh(aa * (qsto - params.b * Dbar)) -
np.tanh(cc * (qsto - params.d * Dbar)) + 2)
np.tanh(aa * (qsto - params.b * Dbar))
- np.tanh(cc * (qsto - params.d * Dbar))
+ 2
)
else:
kgut = params.kmax

Expand All @@ -162,8 +160,7 @@ def model(t, x, action, params, last_Qsto, last_foodtaken):

# glucose kinetics
# plus dextrose IV injection input u[2] if needed
dxdt[3] = max(EGPt, 0) + Rat - Uiit - Et - \
params.k1 * x[3] + params.k2 * x[4]
dxdt[3] = max(EGPt, 0) + Rat - Uiit - Et - params.k1 * x[3] + params.k2 * x[4]
dxdt[3] = (x[3] >= 0) * dxdt[3]

Vmt = params.Vm0 + params.Vmx * x[6]
Expand All @@ -173,8 +170,12 @@ def model(t, x, action, params, last_Qsto, last_foodtaken):
dxdt[4] = (x[4] >= 0) * dxdt[4]

# insulin kinetics
dxdt[5] = -(params.m2 + params.m4) * x[5] + params.m1 * x[9] + params.ka1 * \
x[10] + params.ka2 * x[11] # plus insulin IV injection u[3] if needed
dxdt[5] = (
-(params.m2 + params.m4) * x[5]
+ params.m1 * x[9]
+ params.ka1 * x[10]
+ params.ka2 * x[11]
) # plus insulin IV injection u[3] if needed
It = x[5] / params.Vi
dxdt[5] = (x[5] >= 0) * dxdt[5]

Expand All @@ -198,34 +199,33 @@ def model(t, x, action, params, last_Qsto, last_foodtaken):
dxdt[11] = (x[11] >= 0) * dxdt[11]

# subcutaneous glucose
dxdt[12] = (-params.ksc * x[12] + params.ksc * x[3])
dxdt[12] = -params.ksc * x[12] + params.ksc * x[3]
dxdt[12] = (x[12] >= 0) * dxdt[12]

if action.insulin > basal:
logger.debug('t = {}, injecting insulin: {}'.format(
t, action.insulin))
logger.debug("t = {}, injecting insulin: {}".format(t, action.insulin))

return dxdt

@property
def observation(self):
'''
"""
return the observation from patient
for now, only the subcutaneous glucose level is returned
TODO: add heart rate as an observation
'''
"""
GM = self.state[12] # subcutaneous glucose (mg/kg)
Gsub = GM / self._params.Vg
observation = Observation(Gsub=Gsub)
return observation

def _announce_meal(self, meal):
'''
"""
patient announces meal.
The announced meal will be added to self.planned_meal
The meal is consumed in self.EAT_RATE
The function will return the amount to eat at current time
'''
"""
self.planned_meal += meal
if self.planned_meal > 0:
to_eat = min(self.EAT_RATE, self.planned_meal)
Expand All @@ -245,9 +245,9 @@ def seed(self, seed):
self.reset()

def reset(self):
'''
"""
Reset the patient state to default intial state
'''
"""
if self._init_state is None:
self.init_state = self._params.iloc[2:15]
else:
Expand All @@ -257,13 +257,17 @@ def reset(self):
if self.random_init_bg:
# Only randomize glucose related states, x4, x5, and x13
mean = [
1.0 * self.init_state[3], 1.0 * self.init_state[4],
1.0 * self.init_state[12]
1.0 * self.init_state[3],
1.0 * self.init_state[4],
1.0 * self.init_state[12],
]
cov = np.diag([
0.1 * self.init_state[3], 0.1 * self.init_state[4],
0.1 * self.init_state[12]
])
cov = np.diag(
[
0.1 * self.init_state[3],
0.1 * self.init_state[4],
0.1 * self.init_state[12],
]
)
bg_init = self.random_state.multivariate_normal(mean, cov)
self.init_state[3] = 1.0 * bg_init[0]
self.init_state[4] = 1.0 * bg_init[1]
Expand All @@ -273,28 +277,28 @@ def reset(self):
self._last_foodtaken = 0
self.name = self._params.Name

self._odesolver = ode(self.model).set_integrator('dopri5')
self._odesolver = ode(self.model).set_integrator("dopri5")
self._odesolver.set_initial_value(self.init_state, self.t0)

self._last_action = Action(CHO=0, insulin=0)
self.is_eating = False
self.planned_meal = 0


if __name__ == '__main__':
if __name__ == "__main__":
logger.setLevel(logging.INFO)
# create console handler and set level to debug
ch = logging.StreamHandler()
# ch.setLevel(logging.DEBUG)
ch.setLevel(logging.INFO)
# create formatter
formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s')
formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)

p = T1DPatient.withName('adolescent#001')
p = T1DPatient.withName("adolescent#001")
basal = p._params.u2ss * p._params.BW / 6000 # U/min
t = []
CHO = []
Expand All @@ -316,6 +320,7 @@ def reset(self):
p.step(act)

import matplotlib.pyplot as plt

fig, ax = plt.subplots(3, sharex=True)
ax[0].plot(t, BG)
ax[1].plot(t, CHO)
Expand Down
Loading
Loading