Skip to content

Commit 5a49671

Browse files
authored
Merge pull request #24 from pythonhealthdatascience/dev
Dev
2 parents 29fa1c0 + 1da57f0 commit 5a49671

24 files changed

+2971
-2088
lines changed

.pylintrc

Lines changed: 0 additions & 4 deletions
This file was deleted.

docs/hsma_changes.md

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class Patient:
7373
self.time_with_nurse = np.nan
7474
```
7575

76+
This enables metrics like `count_unseen` and `mean_q_time_unseen`, which can show backlog in the system at the end of the simulation.
77+
7678
## Seeds
7779

7880
The HSMA models use a multiplied version of the `run_number` as a seed when sampling:
@@ -111,24 +113,31 @@ class g:
111113
g.patient_inter = 10
112114
```
113115

114-
To resolve this, the template creates an instance of the parameter class, and never uses or alters it directly. For clarity, the class is called `Defaults()` and instances are typicallyed called `param`.
116+
To resolve this, the template creates an instance of the parameter class, and never uses or alters it directly. For clarity, the class is called `Param()` and instances are typically called `param`.
115117

116118
```
117-
class Defaults():
118-
def __init__(self):
119-
self.patient_inter = 5
120-
self.mean_n_consult_time = 35
121-
self.number_of_nurses = 9
122-
self.warm_up_period = 0
123-
self.data_collection_period = 600
124-
self.number_of_runs = 5
125-
self.audit_interval = 5
126-
self.scenario_name = 0
127-
128-
...
129-
130-
param = Defaults()
131-
param.patient_inter = 10
119+
class Param:
120+
"""
121+
Default parameters for simulation.
122+
123+
Attributes are described in initialisation docstring.
124+
"""
125+
def __init__(
126+
self,
127+
patient_inter=4,
128+
mean_n_consult_time=10,
129+
number_of_nurses=5,
130+
warm_up_period=1440*13,
131+
data_collection_period=1440*30,
132+
number_of_runs=31,
133+
audit_interval=120, # every 2 hours
134+
scenario_name=0,
135+
cores=-1,
136+
logger=SimLogger(log_to_console=False, log_to_file=False)
137+
):
138+
...
139+
140+
param = Defaults(patient_inter = 10)
132141
model = Model(param)
133142
```
134143

@@ -179,6 +188,51 @@ run_results = {
179188
self.run_results_df = pd.DataFrame(run_results_list)
180189
```
181190

191+
### First arrival at time 0
192+
193+
In the HSMA model, there will always be an arrival at time 0, since that is the first action in `generator_patient_arrivals()`:
194+
195+
```
196+
def generator_patient_arrivals(self):
197+
while True:
198+
self.patient_counter += 1
199+
p = Patient(self.patient_counter)
200+
sampled_inter = random.expovariate(1.0 / g.patient_inter)
201+
yield self.env.timeout(sampled_inter)
202+
```
203+
204+
This has been altered, so the first patient arrives after a sampled inter-arrival time, by moving the sampling to the start of the method.
205+
206+
```
207+
def generate_patient_arrivals(self):
208+
while True:
209+
sampled_inter = self.patient_inter_arrival_dist.sample()
210+
yield self.env.timeout(sampled_inter)
211+
p = Patient(len(self.patients) + 1)
212+
p.arrival_time = self.env.now
213+
...
214+
```
215+
216+
### MonitoredResource and warm-up
217+
218+
The same metrics can be calculated in several possible ways. In this model, time-weighted averages have been included using the `MonitoredResource`. This requires methods `init_results_variables()` and `warm_up_complete()`, which have meant that implementation of warm-up can be done using `warm_up_complete()` rather than by checking whether warm up is complete every time a metric is saved.
219+
220+
```
221+
def warm_up_complete(self):
222+
if self.param.warm_up_period > 0:
223+
yield self.env.timeout(self.param.warm_up_period)
224+
self.init_results_variables()
225+
```
226+
227+
As opposed to, for example:
228+
229+
```
230+
if self.env.now > g.warm_up_period:
231+
self.results_df.at[patient.id, "Q Time Nurse"] = (
232+
patient.q_time_nurse
233+
)
234+
```
235+
182236
## Extra features
183237

184238
### Prevent addition of new attributes to the parameter class
@@ -191,15 +245,8 @@ The parameter class includes a function that:
191245
This is to avoid an error where it looks like a parameter has been changed, but actually the wrong attribute name was used - for example, setting `param.nurses = 3` and thinking this has reduced the number of nurse resources, but actually this is based on `param.number_of_nurses` which remains set to 9.
192246

193247
```
194-
class Defaults():
195-
def __init__(self):
196-
object.__setattr__(self, '_initialising', True)
197-
198-
self.patient_inter = 5
199-
self.mean_n_consult_time = 35
200-
...
201-
202-
object.__setattr__(self, '_initialising', False)
248+
class Param():
249+
...
203250
204251
def __setattr__(self, name, value):
205252
if hasattr(self, '_initialising') and self._initialising:
@@ -242,7 +289,7 @@ for rule, param_names in validation_rules.items():
242289

243290
### Tests
244291

245-
The files `test_unittest.py` and `test_backtest.py` include various tests which check the model is functioning as expected. For example, checking that:
292+
The files `test_unittest_model.py`, `test_unittest_logger.py` and `test_backtest.py` include various tests which check the model is functioning as expected. For example, checking that:
246293

247294
* It is not possible to add new attributes to the parameter class.
248295
* Invalid inputs to `Model()` return an `ValueError`.
@@ -256,11 +303,15 @@ The `summary_stats()` function is used to find the overall results from across t
256303

257304
It can also be applied to other results dataframes if desired.
258305

306+
### Logger
307+
308+
The `SimLogger` class will generate logs which can be saved to a file or printed to a console. This includes information on when patients arrive and are seen. This can be helpful for understanding the simulation or when debugging.
309+
259310
### Other minor changes
260311

261312
There are a few smaller changes to the model with minimal impact on function. These include:
262313

263-
* **Names** - for example, `Patient.id` was changed to `Patient.patient_id`, as the patient objects are used to create the patient-level results dataframe, and want "patient_id" as the column name.
314+
* **Names** - for example, `Patient.id` was changed to `Patient.patient_id`, as the patient objects are used to create the patient-level results dataframe, and want "patient_id" as the column name. Also, `Trial` was changed to `Runner` with methods changed to e.g. `run_reps()`.
264315
* **Comments and docstrings**
265316
* **Removed `patient_counter`** - as can just use the length of the `patients` list now.
266317
* **Interval audit** - records cumulative mean wait time, as well as utilisation.

0 commit comments

Comments
 (0)