Skip to content

Commit 3e3de81

Browse files
authored
Merge pull request #25 from TomMonks/dev
v0.5.0
2 parents 3e71b8f + 5a8c188 commit 3e3de81

File tree

12 files changed

+3141
-33
lines changed

12 files changed

+3141
-33
lines changed

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Change log
22

3+
## v0.5.0
4+
5+
### Added
6+
7+
* EXPERIMENTAL: added `trace` module with `Traceable` class for colour coding output from different processes and tracking individual patients.
8+
9+
### Fixed
10+
11+
* DIST: fix to `NSPPThinning` sampling to pre-calcualte mean IAT to ensure that correct exponential mean is used.
12+
* DIST: normal distribution allows minimum value and truncates automaticalled instead of resampling.
13+
314
## v0.4.0
415

516
* BUILD: Dropped legacy `setuptools` and migrated package build to `hatch`

docs/03_CHANGES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Change log
22

3+
## v0.4.0
4+
5+
### Changes
6+
7+
* BUILD: Dropped legacy `setuptools` and migrated package build to `hatch`
8+
* BUILD: Removed `setup.py`, `requirements.txt` and `MANIFEST` in favour of `pyproject.toml`
9+
310
## v0.3.0
411

512
* Distributions classes now have python type hints.

docs/03_trace/01_model.ipynb

Lines changed: 2945 additions & 0 deletions
Large diffs are not rendered by default.

docs/03_trace/data/ed_arrivals.csv

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
period,arrival_rate
2+
6AM-7AM,2.36666666666667
3+
7AM-8AM,2.8
4+
8AM-9AM,8.83333333333333
5+
9AM-10AM,10.4333333333333
6+
10AM-11AM,14.8
7+
11AM-12PM,26.2666666666667
8+
12PM-1PM,31.4
9+
1PM-2PM,18.0666666666667
10+
2PM-3PM,16.4666666666667
11+
3PM-4PM,12.0333333333333
12+
4PM-5PM,11.6
13+
5PM-6PM,28.8666666666667
14+
6PM-7PM,18.0333333333333
15+
7PM-8PM,11.5
16+
8PM-9PM,5.3
17+
9PM-10PM,4.06666666666667
18+
10PM-11PM,2.2
19+
11PM-12AM,2.1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Mean waiting time (mins)
2+
Triage
3+
Registation
4+
Examination
5+
Non-trauma treatment
6+
Trauma stabilisation
7+
Trauma treatment

docs/03_trace/output/table_3.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
\begin{table}
2+
\tbl{Simulation results that can be verified by our example reproducible pipeline.}
3+
\label{tab:table3}
4+
\begin{tabular}{lrrrrr}
5+
\toprule
6+
Mean waiting time (mins) & base & triage+1 & exam+1 & treat+1 & triage+exam \\
7+
\midrule
8+
Triage & 32.560000 & 1.260000 & 32.560000 & 32.560000 & 1.260000 \\
9+
Registation & 104.690000 & 131.820000 & 104.690000 & 104.690000 & 131.820000 \\
10+
Examination & 23.360000 & 24.440000 & 0.140000 & 23.360000 & 0.140000 \\
11+
Non-trauma treatment & 130.730000 & 133.090000 & 144.500000 & 2.150000 & 147.810000 \\
12+
Trauma stabilisation & 166.980000 & 189.670000 & 166.980000 & 166.980000 & 189.670000 \\
13+
Trauma treatment & 14.390000 & 14.770000 & 14.390000 & 14.390000 & 14.770000 \\
14+
\bottomrule
15+
\end{tabular}
16+
\end{table}

docs/_toc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ parts:
55
chapters:
66
- file: 01_sampling/01_distributions_examples
77
- file: 01_sampling/02_time_dependent_examples
8+
- caption: Debugging
9+
chapters:
10+
- file: 03_trace/01_model
811
- caption: Optimisation
912
chapters:
1013
- file: 02_ovs/03_sw21_tutorial

feature_list.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ module: warm-up
2525
* MSER- 5
2626

2727

28+
module: trace
29+
* enhanced trace functions and classes.
30+
31+
2832
module: results visualisation
2933
** Standard ways to compare scenarios?
3034

@@ -51,4 +55,8 @@ module: distributions
5155
module: distributions
5256

5357
* Empirical
54-
* NSPP via thinning
58+
* NSPP via thinning
59+
60+
## v0.5.0
61+
62+
Enhanced trace functionality

sim_tools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '0.4.0'
1+
__version__ = '0.5.0'
22
__author__ = 'Thomas Monks'
33

44
from . import datasets, distributions, time_dependent, ovs

sim_tools/distributions.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -169,70 +169,66 @@ def sample(self, size: Optional[int] = None) -> float | np.ndarray:
169169

170170

171171
class Normal(Distribution):
172-
"""
172+
'''
173173
Convenience class for the normal distribution.
174174
packages up distribution parameters, seed and random generator.
175175
176-
Option to prevent negative samples by resampling
177-
178-
"""
179-
176+
Use the minimum parameter to truncate the distribution
177+
'''
180178
def __init__(
181179
self,
182180
mean: float,
183181
sigma: float,
184-
allow_neg: Optional[bool] = True,
182+
minimum: Optional[float] = None,
185183
random_seed: Optional[int] = None,
186184
):
187-
"""
185+
'''
188186
Constructor
189-
187+
190188
Params:
191189
------
192190
mean: float
193191
The mean of the normal distribution
194-
192+
195193
sigma: float
196194
The stdev of the normal distribution
197195
198-
allow_neg: bool, optional (default=True)
199-
False = resample on negative values
200-
True = negative samples allowed.
201-
196+
minimum: float
197+
Truncate the normal distribution to a minimum
198+
value.
199+
202200
random_seed: int, optional (default=None)
203201
A random seed to reproduce samples. If set to none then a unique
204202
sample is created.
205-
"""
206-
super().__init__(random_seed)
203+
'''
204+
self.rng = np.random.default_rng(seed=random_seed)
207205
self.mean = mean
208206
self.sigma = sigma
209-
self.allow_neg = allow_neg
210-
207+
self.minimum = minimum
208+
211209
def sample(self, size: Optional[int] = None) -> float | np.ndarray:
212-
"""
210+
'''
213211
Generate a sample from the normal distribution
214-
212+
215213
Params:
216214
-------
217215
size: int, optional (default=None)
218216
the number of samples to return. If size=None then a single
219217
sample is returned.
220-
"""
221-
# initial sample
218+
'''
222219
samples = self.rng.normal(self.mean, self.sigma, size=size)
223220

224-
# no need to check if neg allowed.
225-
if self.allow_neg:
221+
if self.minimum is None:
222+
return samples
223+
elif size is None:
224+
return max(self.minimum, samples)
225+
else:
226+
# index of samples with negative value
227+
neg_idx = np.where(samples < 0)[0]
228+
samples[neg_idx] = self.minimum
226229
return samples
227230

228-
# repeatedly resample negative values
229-
negs = np.where(samples < 0)[0]
230-
while len(negs) > 0:
231-
resample = self.rng.normal(self.mean, self.sigma, size=len(negs))
232-
samples[negs] = resample
233-
negs = np.where(samples < 0)[0]
234231

235-
return samples
236232

237233

238234
class Uniform(Distribution):

sim_tools/time_dependent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(
5858
self.arr_rng = np.random.default_rng(random_seed1)
5959
self.thinning_rng = np.random.default_rng(random_seed2)
6060
self.lambda_max = data["arrival_rate"].max()
61+
self.min_iat = data["mean_iat"].min()
6162
# assumes all other intervals are equal in length.
6263
self.interval = int(data.iloc[1]["t"] - data.iloc[0]["t"])
6364
self.rejects_last_sample = None
@@ -94,7 +95,7 @@ def sample(self, simulation_time: float) -> float:
9495
# reject samples if u >= lambda_t / lambda_max
9596
while u >= (lambda_t / self.lambda_max):
9697
self.rejects_last_sample += 1
97-
interarrival_time += self.arr_rng.exponential(1 / self.lambda_max)
98+
interarrival_time += self.arr_rng.exponential(self.min_iat)
9899
u = self.thinning_rng.uniform(0.0, 1.0)
99100

100101
return interarrival_time

sim_tools/trace.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Simple functionality aiming to enhanced a users a
3+
ability to trace and debug simulation models.
4+
"""
5+
6+
from abc import ABC
7+
from rich.console import Console
8+
9+
DEFAULT_DEBUG = False
10+
11+
CONFIG_ERROR = ("Your trace has not been initialised. "
12+
"Call super__init__(debug=True) in class initialiser"
13+
"or omit debug for default of no trace.")
14+
15+
16+
## single rich console - module level.
17+
_console = Console()
18+
19+
class Traceable(ABC):
20+
'''Provides basic trace functionality for a process to subclass
21+
22+
Abstract base class Traceable
23+
24+
Subclasses must call
25+
26+
super().__init__(debug=True) in their __init__() method to
27+
initialise trace.
28+
29+
Subclasses inherit the following methods:
30+
31+
trace() - use this function print out a traceable event
32+
33+
_trace_config(): use this function to return a dict containing
34+
the trace configuration for the class.
35+
'''
36+
def __init__(self, debug=DEFAULT_DEBUG):
37+
self.debug = debug
38+
self._config = self._default_config()
39+
40+
def _default_config(self):
41+
"""Returns a default trace configuration"""
42+
config = {
43+
"name":None,
44+
"name_colour":"bold blue",
45+
"time_colour":'bold blue',
46+
"time_dp":2,
47+
"message_colour":'black',
48+
"tracked":None
49+
}
50+
return config
51+
52+
53+
def _trace_config(self):
54+
config = {
55+
"name":None,
56+
"name_colour":"bold blue",
57+
"time_colour":'bold blue',
58+
"time_dp":2,
59+
"message_colour":'black',
60+
"tracked":None
61+
}
62+
return config
63+
64+
65+
def trace(self, time, msg=None, process_id=None):
66+
'''
67+
Display a trace of an event
68+
'''
69+
70+
if not hasattr(self, '_config'):
71+
raise AttributeError(CONFIG_ERROR)
72+
73+
# if in debug mode
74+
if self.debug:
75+
76+
# check for override to default configs
77+
process_config = self._trace_config()
78+
self._config.update(process_config)
79+
80+
# conditional logic to limit tracking to specific processes/entities
81+
if self._config['tracked'] is None or process_id in self._config['tracked']:
82+
83+
# display and format time stamp
84+
out = f"[{self._config['time_colour']}][{time:.{self._config['time_dp']}f}]:[/{self._config['time_colour']}]"
85+
86+
# if provided display and format a process ID
87+
if self._config['name'] is not None and process_id is not None:
88+
out += f"[{self._config['name_colour']}]<{self._config['name']} {process_id}>: [/{self._config['name_colour']}]"
89+
90+
# format traced event message
91+
out += f"[{self._config['message_colour']}]{msg}[/{self._config['message_colour']}]"
92+
93+
# print to rich console
94+
_console.print(out)
95+

0 commit comments

Comments
 (0)