Skip to content

Commit

Permalink
Merge pull request #25 from TomMonks/dev
Browse files Browse the repository at this point in the history
v0.5.0
  • Loading branch information
TomMonks authored Jun 21, 2024
2 parents 3e71b8f + 5a8c188 commit 3e3de81
Show file tree
Hide file tree
Showing 12 changed files with 3,141 additions and 33 deletions.
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Change log

## v0.5.0

### Added

* EXPERIMENTAL: added `trace` module with `Traceable` class for colour coding output from different processes and tracking individual patients.

### Fixed

* DIST: fix to `NSPPThinning` sampling to pre-calcualte mean IAT to ensure that correct exponential mean is used.
* DIST: normal distribution allows minimum value and truncates automaticalled instead of resampling.

## v0.4.0

* BUILD: Dropped legacy `setuptools` and migrated package build to `hatch`
Expand Down
7 changes: 7 additions & 0 deletions docs/03_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change log

## v0.4.0

### Changes

* BUILD: Dropped legacy `setuptools` and migrated package build to `hatch`
* BUILD: Removed `setup.py`, `requirements.txt` and `MANIFEST` in favour of `pyproject.toml`

## v0.3.0

* Distributions classes now have python type hints.
Expand Down
2,945 changes: 2,945 additions & 0 deletions docs/03_trace/01_model.ipynb

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions docs/03_trace/data/ed_arrivals.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
period,arrival_rate
6AM-7AM,2.36666666666667
7AM-8AM,2.8
8AM-9AM,8.83333333333333
9AM-10AM,10.4333333333333
10AM-11AM,14.8
11AM-12PM,26.2666666666667
12PM-1PM,31.4
1PM-2PM,18.0666666666667
2PM-3PM,16.4666666666667
3PM-4PM,12.0333333333333
4PM-5PM,11.6
5PM-6PM,28.8666666666667
6PM-7PM,18.0333333333333
7PM-8PM,11.5
8PM-9PM,5.3
9PM-10PM,4.06666666666667
10PM-11PM,2.2
11PM-12AM,2.1
7 changes: 7 additions & 0 deletions docs/03_trace/data/tbl_row_headers.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Mean waiting time (mins)
Triage
Registation
Examination
Non-trauma treatment
Trauma stabilisation
Trauma treatment
16 changes: 16 additions & 0 deletions docs/03_trace/output/table_3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
\begin{table}
\tbl{Simulation results that can be verified by our example reproducible pipeline.}
\label{tab:table3}
\begin{tabular}{lrrrrr}
\toprule
Mean waiting time (mins) & base & triage+1 & exam+1 & treat+1 & triage+exam \\
\midrule
Triage & 32.560000 & 1.260000 & 32.560000 & 32.560000 & 1.260000 \\
Registation & 104.690000 & 131.820000 & 104.690000 & 104.690000 & 131.820000 \\
Examination & 23.360000 & 24.440000 & 0.140000 & 23.360000 & 0.140000 \\
Non-trauma treatment & 130.730000 & 133.090000 & 144.500000 & 2.150000 & 147.810000 \\
Trauma stabilisation & 166.980000 & 189.670000 & 166.980000 & 166.980000 & 189.670000 \\
Trauma treatment & 14.390000 & 14.770000 & 14.390000 & 14.390000 & 14.770000 \\
\bottomrule
\end{tabular}
\end{table}
3 changes: 3 additions & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ parts:
chapters:
- file: 01_sampling/01_distributions_examples
- file: 01_sampling/02_time_dependent_examples
- caption: Debugging
chapters:
- file: 03_trace/01_model
- caption: Optimisation
chapters:
- file: 02_ovs/03_sw21_tutorial
Expand Down
10 changes: 9 additions & 1 deletion feature_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ module: warm-up
* MSER- 5


module: trace
* enhanced trace functions and classes.


module: results visualisation
** Standard ways to compare scenarios?

Expand All @@ -51,4 +55,8 @@ module: distributions
module: distributions

* Empirical
* NSPP via thinning
* NSPP via thinning

## v0.5.0

Enhanced trace functionality
2 changes: 1 addition & 1 deletion sim_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.4.0'
__version__ = '0.5.0'
__author__ = 'Thomas Monks'

from . import datasets, distributions, time_dependent, ovs
56 changes: 26 additions & 30 deletions sim_tools/distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,70 +169,66 @@ def sample(self, size: Optional[int] = None) -> float | np.ndarray:


class Normal(Distribution):
"""
'''
Convenience class for the normal distribution.
packages up distribution parameters, seed and random generator.
Option to prevent negative samples by resampling
"""

Use the minimum parameter to truncate the distribution
'''
def __init__(
self,
mean: float,
sigma: float,
allow_neg: Optional[bool] = True,
minimum: Optional[float] = None,
random_seed: Optional[int] = None,
):
"""
'''
Constructor
Params:
------
mean: float
The mean of the normal distribution
sigma: float
The stdev of the normal distribution
allow_neg: bool, optional (default=True)
False = resample on negative values
True = negative samples allowed.
minimum: float
Truncate the normal distribution to a minimum
value.
random_seed: int, optional (default=None)
A random seed to reproduce samples. If set to none then a unique
sample is created.
"""
super().__init__(random_seed)
'''
self.rng = np.random.default_rng(seed=random_seed)
self.mean = mean
self.sigma = sigma
self.allow_neg = allow_neg

self.minimum = minimum
def sample(self, size: Optional[int] = None) -> float | np.ndarray:
"""
'''
Generate a sample from the normal distribution
Params:
-------
size: int, optional (default=None)
the number of samples to return. If size=None then a single
sample is returned.
"""
# initial sample
'''
samples = self.rng.normal(self.mean, self.sigma, size=size)

# no need to check if neg allowed.
if self.allow_neg:
if self.minimum is None:
return samples
elif size is None:
return max(self.minimum, samples)
else:
# index of samples with negative value
neg_idx = np.where(samples < 0)[0]
samples[neg_idx] = self.minimum
return samples

# repeatedly resample negative values
negs = np.where(samples < 0)[0]
while len(negs) > 0:
resample = self.rng.normal(self.mean, self.sigma, size=len(negs))
samples[negs] = resample
negs = np.where(samples < 0)[0]

return samples


class Uniform(Distribution):
Expand Down
3 changes: 2 additions & 1 deletion sim_tools/time_dependent.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(
self.arr_rng = np.random.default_rng(random_seed1)
self.thinning_rng = np.random.default_rng(random_seed2)
self.lambda_max = data["arrival_rate"].max()
self.min_iat = data["mean_iat"].min()
# assumes all other intervals are equal in length.
self.interval = int(data.iloc[1]["t"] - data.iloc[0]["t"])
self.rejects_last_sample = None
Expand Down Expand Up @@ -94,7 +95,7 @@ def sample(self, simulation_time: float) -> float:
# reject samples if u >= lambda_t / lambda_max
while u >= (lambda_t / self.lambda_max):
self.rejects_last_sample += 1
interarrival_time += self.arr_rng.exponential(1 / self.lambda_max)
interarrival_time += self.arr_rng.exponential(self.min_iat)
u = self.thinning_rng.uniform(0.0, 1.0)

return interarrival_time
95 changes: 95 additions & 0 deletions sim_tools/trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Simple functionality aiming to enhanced a users a
ability to trace and debug simulation models.
"""

from abc import ABC
from rich.console import Console

DEFAULT_DEBUG = False

CONFIG_ERROR = ("Your trace has not been initialised. "
"Call super__init__(debug=True) in class initialiser"
"or omit debug for default of no trace.")


## single rich console - module level.
_console = Console()

class Traceable(ABC):
'''Provides basic trace functionality for a process to subclass
Abstract base class Traceable
Subclasses must call
super().__init__(debug=True) in their __init__() method to
initialise trace.
Subclasses inherit the following methods:
trace() - use this function print out a traceable event
_trace_config(): use this function to return a dict containing
the trace configuration for the class.
'''
def __init__(self, debug=DEFAULT_DEBUG):
self.debug = debug
self._config = self._default_config()

def _default_config(self):
"""Returns a default trace configuration"""
config = {
"name":None,
"name_colour":"bold blue",
"time_colour":'bold blue',
"time_dp":2,
"message_colour":'black',
"tracked":None
}
return config


def _trace_config(self):
config = {
"name":None,
"name_colour":"bold blue",
"time_colour":'bold blue',
"time_dp":2,
"message_colour":'black',
"tracked":None
}
return config


def trace(self, time, msg=None, process_id=None):
'''
Display a trace of an event
'''

if not hasattr(self, '_config'):
raise AttributeError(CONFIG_ERROR)

# if in debug mode
if self.debug:

# check for override to default configs
process_config = self._trace_config()
self._config.update(process_config)

# conditional logic to limit tracking to specific processes/entities
if self._config['tracked'] is None or process_id in self._config['tracked']:

# display and format time stamp
out = f"[{self._config['time_colour']}][{time:.{self._config['time_dp']}f}]:[/{self._config['time_colour']}]"

# if provided display and format a process ID
if self._config['name'] is not None and process_id is not None:
out += f"[{self._config['name_colour']}]<{self._config['name']} {process_id}>: [/{self._config['name_colour']}]"

# format traced event message
out += f"[{self._config['message_colour']}]{msg}[/{self._config['message_colour']}]"

# print to rich console
_console.print(out)

0 comments on commit 3e3de81

Please sign in to comment.