sim-tools 0.4.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sim-tools
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Simulation Tools for Education and Practice
5
5
  Project-URL: Homepage, https://github.com/TomMonks/sim-tools
6
6
  Project-URL: Bug Tracker, https://github.com/TomMonks/sim-tools/issues
@@ -20,35 +20,56 @@ Requires-Dist: scikit-learn>=1.0.0
20
20
  Requires-Dist: scipy>=1.4.1
21
21
  Description-Content-Type: text/markdown
22
22
 
23
- # sim-tools: tools to support the simulation process in python.
23
+ # `sim-tools`: tools to support the Discrete-Event Simulation process in python.
24
24
 
25
25
  [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TomMonks/sim-tools/HEAD)
26
26
  [![DOI](https://zenodo.org/badge/225608065.svg)](https://zenodo.org/badge/latestdoi/225608065)
27
27
  [![PyPI version fury.io](https://badge.fury.io/py/sim-tools.svg)](https://pypi.python.org/pypi/sim-tools/)
28
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/sim-tools/badges/version.svg)](https://anaconda.org/conda-forge/sim-tools)
29
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/sim-tools/badges/platforms.svg)](https://anaconda.org/conda-forge/sim-tools)
28
30
  [![Read the Docs](https://readthedocs.org/projects/pip/badge/?version=latest)](https://tommonks.github.io/sim-tools)
29
31
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
30
32
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/release/python-360+/)
31
33
  [![License: MIT](https://img.shields.io/badge/ORCID-0000--0003--2631--4481-brightgreen)](https://orcid.org/0000-0003-2631-4481)
32
34
 
33
- sim-tools is being developed to support simulation education and applied simulation research. It is MIT licensed and freely available to practitioners, students and researchers via PyPi. There is a longer term plan to make sim-tools available via conda-forge.
35
+
36
+ `sim-tools` is being developed to support Discrete-Event Simulation (DES) education and applied simulation research. It is MIT licensed and freely available to practitioners, students and researchers via [PyPi](https://pypi.org/project/sim-tools/) and [conda-forge](https://anaconda.org/conda-forge/sim-tools)
34
37
 
35
38
  # Vision for sim-tools
36
39
 
37
- 1. Deliver high quality reliable code for simulation education and practice with full documentation.
40
+ 1. Deliver high quality reliable code for DES education and practice with full documentation.
38
41
  2. Provide a simple to use pythonic interface.
39
- 3. To improve the quality of simulation education and encourage the use of best practice.
42
+ 3. To improve the quality of DES education using FOSS tools and encourage the use of best practice.
40
43
 
41
44
  # Features:
42
45
 
43
- 1. Implementation of classic optimisation via Simulation procedures such as KN, KN++, OBCA and OBCA-m
46
+ 1. Implementation of classic Optimisation via Simulation procedures such as KN, KN++, OBCA and OBCA-m
44
47
  2. Distributions module that includes classes that encapsulate a random number stream, seed, and distribution parameters.
45
- 3. Implementation of Thinning to sample from Non-stationary poisson processes in a discrete-event simulation
48
+ 3. Implementation of Thinning to sample from Non-stationary poisson processes in a DES.
49
+
50
+ ## Installation
51
+
52
+ ### Pip and PyPi
53
+
54
+ ```bash
55
+ pip install sim-tools
56
+ ```
57
+
58
+ ### Conda-forge
59
+
60
+ ```bash
61
+ conda install -c conda-forge sim-tools
62
+ ```
63
+
64
+ ### Binder
65
+
66
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TomMonks/sim-tools/HEAD)
67
+
46
68
 
47
- ## Three simple ways to explore sim-tools
69
+ ## Learn how to use `sim-tools`
48
70
 
49
- 1. `pip install sim-tools`
50
- 2. Click on the launch-binder at the top of this readme. This will open example Jupyter notebooks in the cloud via Binder.
51
- 3. Oneline documentation: https://tommonks.github.io/sim-tools
71
+ * Online documentation: https://tommonks.github.io/sim-tools
72
+ * Introduction to DES in python: https://health-data-science-or.github.io/simpy-streamlit-tutorial/
52
73
 
53
74
  ## Citation
54
75
 
@@ -1,32 +1,53 @@
1
- # sim-tools: tools to support the simulation process in python.
1
+ # `sim-tools`: tools to support the Discrete-Event Simulation process in python.
2
2
 
3
3
  [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TomMonks/sim-tools/HEAD)
4
4
  [![DOI](https://zenodo.org/badge/225608065.svg)](https://zenodo.org/badge/latestdoi/225608065)
5
5
  [![PyPI version fury.io](https://badge.fury.io/py/sim-tools.svg)](https://pypi.python.org/pypi/sim-tools/)
6
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/sim-tools/badges/version.svg)](https://anaconda.org/conda-forge/sim-tools)
7
+ [![Anaconda-Server Badge](https://anaconda.org/conda-forge/sim-tools/badges/platforms.svg)](https://anaconda.org/conda-forge/sim-tools)
6
8
  [![Read the Docs](https://readthedocs.org/projects/pip/badge/?version=latest)](https://tommonks.github.io/sim-tools)
7
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
10
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/release/python-360+/)
9
11
  [![License: MIT](https://img.shields.io/badge/ORCID-0000--0003--2631--4481-brightgreen)](https://orcid.org/0000-0003-2631-4481)
10
12
 
11
- sim-tools is being developed to support simulation education and applied simulation research. It is MIT licensed and freely available to practitioners, students and researchers via PyPi. There is a longer term plan to make sim-tools available via conda-forge.
13
+
14
+ `sim-tools` is being developed to support Discrete-Event Simulation (DES) education and applied simulation research. It is MIT licensed and freely available to practitioners, students and researchers via [PyPi](https://pypi.org/project/sim-tools/) and [conda-forge](https://anaconda.org/conda-forge/sim-tools)
12
15
 
13
16
  # Vision for sim-tools
14
17
 
15
- 1. Deliver high quality reliable code for simulation education and practice with full documentation.
18
+ 1. Deliver high quality reliable code for DES education and practice with full documentation.
16
19
  2. Provide a simple to use pythonic interface.
17
- 3. To improve the quality of simulation education and encourage the use of best practice.
20
+ 3. To improve the quality of DES education using FOSS tools and encourage the use of best practice.
18
21
 
19
22
  # Features:
20
23
 
21
- 1. Implementation of classic optimisation via Simulation procedures such as KN, KN++, OBCA and OBCA-m
24
+ 1. Implementation of classic Optimisation via Simulation procedures such as KN, KN++, OBCA and OBCA-m
22
25
  2. Distributions module that includes classes that encapsulate a random number stream, seed, and distribution parameters.
23
- 3. Implementation of Thinning to sample from Non-stationary poisson processes in a discrete-event simulation
26
+ 3. Implementation of Thinning to sample from Non-stationary poisson processes in a DES.
27
+
28
+ ## Installation
29
+
30
+ ### Pip and PyPi
31
+
32
+ ```bash
33
+ pip install sim-tools
34
+ ```
35
+
36
+ ### Conda-forge
37
+
38
+ ```bash
39
+ conda install -c conda-forge sim-tools
40
+ ```
41
+
42
+ ### Binder
43
+
44
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/TomMonks/sim-tools/HEAD)
45
+
24
46
 
25
- ## Three simple ways to explore sim-tools
47
+ ## Learn how to use `sim-tools`
26
48
 
27
- 1. `pip install sim-tools`
28
- 2. Click on the launch-binder at the top of this readme. This will open example Jupyter notebooks in the cloud via Binder.
29
- 3. Oneline documentation: https://tommonks.github.io/sim-tools
49
+ * Online documentation: https://tommonks.github.io/sim-tools
50
+ * Introduction to DES in python: https://health-data-science-or.github.io/simpy-streamlit-tutorial/
30
51
 
31
52
  ## Citation
32
53
 
@@ -0,0 +1,4 @@
1
+ __version__ = '0.6.0'
2
+ __author__ = 'Thomas Monks'
3
+
4
+ from . import datasets, distributions, time_dependent, ovs
@@ -169,70 +169,66 @@ class Lognormal(Distribution):
169
169
 
170
170
 
171
171
  class Normal(Distribution):
172
- """
172
+ '''
173
173
  Convenience class for the normal distribution.
174
174
  packages up distribution parameters, seed and random generator.
175
175
 
176
- Option to prevent negative samples by resampling
177
-
178
- """
179
-
176
+ Use the minimum parameter to truncate the distribution
177
+ '''
180
178
  def __init__(
181
179
  self,
182
180
  mean: float,
183
181
  sigma: float,
184
- allow_neg: Optional[bool] = True,
182
+ minimum: Optional[float] = None,
185
183
  random_seed: Optional[int] = None,
186
184
  ):
187
- """
185
+ '''
188
186
  Constructor
189
-
187
+
190
188
  Params:
191
189
  ------
192
190
  mean: float
193
191
  The mean of the normal distribution
194
-
192
+
195
193
  sigma: float
196
194
  The stdev of the normal distribution
197
195
 
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
+
202
200
  random_seed: int, optional (default=None)
203
201
  A random seed to reproduce samples. If set to none then a unique
204
202
  sample is created.
205
- """
206
- super().__init__(random_seed)
203
+ '''
204
+ self.rng = np.random.default_rng(seed=random_seed)
207
205
  self.mean = mean
208
206
  self.sigma = sigma
209
- self.allow_neg = allow_neg
210
-
207
+ self.minimum = minimum
208
+
211
209
  def sample(self, size: Optional[int] = None) -> float | np.ndarray:
212
- """
210
+ '''
213
211
  Generate a sample from the normal distribution
214
-
212
+
215
213
  Params:
216
214
  -------
217
215
  size: int, optional (default=None)
218
216
  the number of samples to return. If size=None then a single
219
217
  sample is returned.
220
- """
221
- # initial sample
218
+ '''
222
219
  samples = self.rng.normal(self.mean, self.sigma, size=size)
223
220
 
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
226
229
  return samples
227
230
 
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]
234
231
 
235
- return samples
236
232
 
237
233
 
238
234
  class Uniform(Distribution):
@@ -0,0 +1,266 @@
1
+ """
2
+ Classes and functions to support time dependent samplingm in DES models.
3
+ """
4
+
5
+ import itertools
6
+ import numpy as np
7
+ import pandas as pd
8
+ import matplotlib.pyplot as plt
9
+
10
+ from typing import Optional, Tuple
11
+
12
+
13
+ class NSPPThinning:
14
+ """
15
+ Non Stationary Poisson Process via Thinning.
16
+
17
+ Thinning is an acceptance-rejection approach to sampling
18
+ inter-arrival times (IAT) from a time dependent distribution
19
+ where each time period follows its own exponential distribution.
20
+
21
+ There are two random variables employed in sampling: an exponential
22
+ distribution (used to sample IAT) and a uniform distibution (used
23
+ to accept/reject samples).
24
+
25
+ All IATs are sampled from an Exponential distribution with the highest
26
+ arrival rate (most frequent). These arrivals are then rejected (thinned)
27
+ proportional to the ratio of the current arrival rate to the maximum
28
+ arrival rate. The algorithm executes until a sample is accepted. The IAT
29
+ returned is the sum of all the IATs that were sampled.
30
+
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ data,
36
+ random_seed1: Optional[int] = None,
37
+ random_seed2: Optional[int] = None,
38
+ ):
39
+ """
40
+ Non Stationary Poisson Process via Thinning.
41
+
42
+ Time dependency is handled for a single table
43
+ consisting of equally spaced intervals.
44
+
45
+ Params:
46
+ ------
47
+ data: pandas.DataFrame
48
+ list of time points during a period for transition between rates
49
+ and list arrival rates in that period. Labels should be "t"
50
+ and "arrival_rate" respectively.
51
+
52
+ random_seed1: int, optional (default=None)
53
+ Random seed for exponential distribution
54
+
55
+ random_seed2: int
56
+ Random seed for the uniform distribution used
57
+ for acceptance/rejection sampling.
58
+ """
59
+ self.data = data
60
+ self.arr_rng = np.random.default_rng(random_seed1)
61
+ self.thinning_rng = np.random.default_rng(random_seed2)
62
+ self.lambda_max = data["arrival_rate"].max()
63
+ self.min_iat = data["mean_iat"].min()
64
+ # assumes all other intervals are equal in length.
65
+ self.interval = int(data.iloc[1]["t"] - data.iloc[0]["t"])
66
+ self.rejects_last_sample = None
67
+
68
+ def sample(self, simulation_time: float) -> float:
69
+ """
70
+ Run a single iteration of acceptance-rejection
71
+ thinning alg to sample the next inter-arrival time
72
+
73
+ Params:
74
+ ------
75
+ simulation_time: float
76
+ The current simulation time. This is used to look up
77
+ the arrival rate for the time period.
78
+
79
+ Returns:
80
+ -------
81
+ float
82
+ The inter-arrival time
83
+ """
84
+ for _ in itertools.count():
85
+ # this gives us the index of dataframe to use
86
+ t = int(simulation_time // self.interval) % len(self.data)
87
+ lambda_t = self.data["arrival_rate"].iloc[t]
88
+
89
+ # set to a large number so that at least 1 sample taken!
90
+ u = np.inf
91
+
92
+ # included for audit and tracking purposes.
93
+ self.rejects_last_sample = 0.0
94
+
95
+ interarrival_time = 0.0
96
+
97
+ # reject samples if u >= lambda_t / lambda_max
98
+ while u >= (lambda_t / self.lambda_max):
99
+ self.rejects_last_sample += 1
100
+ interarrival_time += self.arr_rng.exponential(self.min_iat)
101
+ u = self.thinning_rng.uniform(0.0, 1.0)
102
+
103
+ return interarrival_time
104
+
105
+
106
+ def nspp_simulation(
107
+ arrival_profile: pd.DataFrame,
108
+ run_length: Optional[float] = None,
109
+ n_reps: Optional[int] = 1000,
110
+ ) -> pd.DataFrame:
111
+ """
112
+ Generate a pandas dataframe that contains multiple replications of
113
+ a non-stationary poisson process for the set arrival profile.
114
+
115
+ This uses the sim-tools NSPPThinning class.
116
+
117
+ Useful for validating the the NSPP has been set up correctly and is producing the
118
+ desired profile for the simulation model.
119
+
120
+ On each replication the function counts the number of arrivals during the intervals
121
+ from the arrival profile. Returns a data frame with reps (rows) and interval arrivals
122
+ (columns)
123
+
124
+ Parameters:
125
+ -----------
126
+ arrival_profile: pandas.DataFrame
127
+ The arrival profile is a pandas data frame containing 't', 'arrival_rate' and
128
+ 'mean_iat' columns.
129
+
130
+ run_length: float, optional (default=None)
131
+ How long should the simulation be run. If none then uses the last value in 't'
132
+ + the interval (assumes equal width intervals)
133
+
134
+ n_reps: int, optional (default=1000)
135
+ The number of replications to run.
136
+
137
+ Returns:
138
+ --------
139
+ pd.DataFrame.
140
+
141
+
142
+ """
143
+ # replication results
144
+ replication_results = []
145
+
146
+ # multiple replications
147
+ for rep in range(n_reps):
148
+
149
+ # method for producing n non-overlapping streams
150
+ seed_sequence = np.random.SeedSequence(rep)
151
+
152
+ # Generate n high quality child seeds
153
+ seeds = seed_sequence.spawn(2)
154
+
155
+ # create nspp
156
+ nspp_rng = NSPPThinning(arrival_profile, seeds[0], seeds[1])
157
+
158
+ # if no run length has been set....
159
+ if run_length is None:
160
+ run_length = (
161
+ arrival_profile["t"].iloc[len(arrival_profile) - 1] + nspp_rng.interval
162
+ )
163
+
164
+ # list - each item is an interval in the arrival profile
165
+ interval_samples = [0] * arrival_profile.shape[0]
166
+ simulation_time = 0.0
167
+ while simulation_time < run_length:
168
+ iat = nspp_rng.sample(simulation_time)
169
+ simulation_time += iat
170
+
171
+
172
+ if simulation_time < run_length:
173
+ # data collection: add one to count for hour of the day
174
+ # note list NSPPThinning this assume equal intervals
175
+ interval_of_day = (
176
+ int(simulation_time // nspp_rng.interval) % len(arrival_profile)
177
+ )
178
+ interval_samples[interval_of_day] += 1
179
+
180
+ replication_results.append(interval_samples)
181
+
182
+ # produce summary chart of arrivals per interval
183
+ # format in a dataframe
184
+ df_replications = pd.DataFrame(replication_results)
185
+ df_replications.index = np.arange(1, len(df_replications) + 1)
186
+ df_replications.index.name = "rep"
187
+
188
+ return df_replications
189
+
190
+
191
+ def nspp_plot(
192
+ arrival_profile: pd.DataFrame,
193
+ run_length: Optional[float] = None,
194
+ n_reps: Optional[int] = 1000,
195
+ ) -> Tuple[plt.Figure, plt.Axes]:
196
+ """Generate a matplotlib chart to visualise a non-stationary poisson process
197
+ for the set arrival profile.
198
+
199
+ This uses the sim-tools NSPPThinning class.
200
+
201
+ Useful for validating the the NSPP has been set up correctly and is producing the
202
+ desired profile for the simulation model.
203
+
204
+ Parameters:
205
+ ----------
206
+ arrival_profile: pandas.DataFrame
207
+ The arrival profile is a pandas data frame containing 't', 'arrival_rate' and
208
+ 'mean_iat' columns.
209
+
210
+ run_length: float, optional (default=None)
211
+ How long should the simulation be run. If none then uses the last value in 't'
212
+ + the interval (assumes equal width intervals)
213
+
214
+ n_reps: int, optional (default=1000)
215
+ The number of replications to run.
216
+ """
217
+
218
+ # verification of arrival_profile
219
+
220
+ # is it a dataframe
221
+ if not isinstance(arrival_profile, pd.DataFrame):
222
+ raise ValueError(
223
+ f"arrival_profile expected pd.DataFrame " f"got {type(arrival_profile)}"
224
+ )
225
+
226
+ # all columns are present
227
+ required_columns = ["t", "arrival_rate", "mean_iat"]
228
+ for col in required_columns:
229
+ if col not in arrival_profile.columns:
230
+ raise ValueError(
231
+ f"arrival_profile must contain "
232
+ f"the following columns: {required_columns}. "
233
+ )
234
+
235
+ # generate the sample data
236
+ df_interval_results = nspp_simulation(arrival_profile, run_length, n_reps)
237
+
238
+ interval_means = df_interval_results.mean(axis=0)
239
+ interval_sd = df_interval_results.std(axis=0)
240
+
241
+ upper = interval_means + interval_sd
242
+ lower = interval_means - interval_sd
243
+ lower[lower < 0] = 0
244
+
245
+ # visualise
246
+ fig = plt.figure(figsize=(12, 3))
247
+ ax = fig.add_subplot()
248
+
249
+ # chart x ticks
250
+ x_values = np.arange(0, arrival_profile.shape[0])
251
+
252
+ # plot in this case returns a 2D line plot object
253
+ _ = ax.plot(arrival_profile["t"], interval_means, label="Mean")
254
+ _ = ax.fill_between(arrival_profile["t"], lower, upper, alpha=0.2, label="+-1SD")
255
+
256
+ # chart appearance
257
+ _ = ax.legend(loc="best", ncol=3)
258
+ _ = ax.set_ylim(
259
+ 0,
260
+ )
261
+ _ = ax.set_xlim(0, arrival_profile.shape[0] - 1)
262
+ _ = ax.set_ylabel("arrivals")
263
+ _ = ax.set_xlabel("interval (from profile)")
264
+ _ = plt.xticks(arrival_profile["t"])
265
+
266
+ return fig, ax
@@ -0,0 +1,182 @@
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
+ from typing import Optional
9
+
10
+ DEFAULT_DEBUG = False
11
+
12
+ CONFIG_ERROR = ("Your trace has not been initialised. "
13
+ "Call super__init__(debug=True) in class initialiser"
14
+ "or omit debug for default of no trace.")
15
+
16
+
17
+ ## single rich console - module level.
18
+ _console = Console()
19
+
20
+ class Traceable(ABC):
21
+ '''Provides basic trace functionality for a process to subclass.
22
+
23
+ Abstract base class Traceable
24
+
25
+ Subclasses must call
26
+
27
+ super().__init__(debug=True) in their __init__() method to
28
+ initialise trace.
29
+
30
+ Subclasses inherit the following methods:
31
+
32
+ trace() - use this function print out a traceable event
33
+
34
+ _trace_config(): use this function to return a dict containing
35
+ the trace configuration for the class. Subclasses should
36
+ override it to implement custom formatting.
37
+
38
+ Notes:
39
+ -----
40
+ This class provides the same functionality as the function `trace()`
41
+ in an object orientated framework. It in theory means cleaner code
42
+ as the call to trace requires less parameters. However, it must
43
+ be setup correctly.
44
+ '''
45
+ def __init__(self, debug: Optional[bool] = DEFAULT_DEBUG):
46
+ """Initialise Traceable
47
+
48
+ Parameters:
49
+ ----------
50
+ debug: bool, Optional (default=False)
51
+ show trace(True). do not show trace (False)
52
+ """
53
+ self.debug = debug
54
+ self._config = Traceable._default_config()
55
+
56
+ @classmethod
57
+ def _default_config(cls) -> dict:
58
+ """Returns a default trace configuration"""
59
+ config = {
60
+ "class":None,
61
+ "class_colour":"bold blue",
62
+ "time_colour":'bold blue',
63
+ "time_dp":2,
64
+ "message_colour":'black',
65
+ "tracked":None
66
+ }
67
+ return config
68
+
69
+ def _trace_config(self) -> dict:
70
+ """Overload to return a custom trace configuration"""
71
+ return Traceable._default_config()
72
+
73
+ def trace(self, time: float, msg: Optional[str] = None, process_id: Optional[str] = None):
74
+ '''Display a formatted trace of a simulated event.
75
+
76
+ Implemented with the rich library Console() object.
77
+
78
+ Parameters:
79
+ ----------
80
+ time: float
81
+ The simulation time
82
+
83
+ msg: str, Optional (default=None)
84
+ Event message to display to user
85
+
86
+ process_id: str, Optional (default=None)
87
+ Display an unique identifer for the trace message
88
+
89
+ '''
90
+
91
+ # did not initialise trace
92
+ if not hasattr(self, '_config'):
93
+ raise AttributeError(CONFIG_ERROR)
94
+
95
+ # if in debug mode
96
+ if self.debug:
97
+
98
+ # check for override to default configs
99
+ process_config = self._trace_config()
100
+ self._config.update(process_config)
101
+
102
+ # conditional logic to limit tracking to specific processes/entities
103
+ if self._config['tracked'] is None or process_id in self._config['tracked']:
104
+
105
+ # display and format time stamp
106
+ out = f"[{self._config['time_colour']}][{time:.{self._config['time_dp']}f}]:[/{self._config['time_colour']}]"
107
+
108
+ # if provided display and format a process ID
109
+ if self._config['class'] is not None and process_id is not None:
110
+ out += f"[{self._config['class_colour']}]<{self._config['class']} {process_id}>: [/{self._config['class_colour']}]"
111
+
112
+ # format traced event message
113
+ out += f"[{self._config['message_colour']}]{msg}[/{self._config['message_colour']}]"
114
+
115
+ # print to rich console
116
+ _console.print(out)
117
+
118
+
119
+ def trace(time: float, debug: Optional[bool] = DEFAULT_DEBUG, msg: Optional[str] = None,
120
+ identifier: Optional[str] = None, config: Optional[dict] = None):
121
+ """Display a formatted trace of a simulated event.
122
+
123
+ Implemented with the rich library Console() object.
124
+
125
+ Parameters:
126
+ ----------
127
+ time: float
128
+ The simulation time
129
+
130
+ debug: bool, Optional (default=False)
131
+ show trace(True). do not show trace (False)
132
+
133
+ msg: str, Optional (default=None)
134
+ Event message to display to user
135
+
136
+ identifier: str, Optional (default=None)
137
+ Display an unique identifier for the trace message
138
+
139
+ config: dict, Optional (default=None)
140
+ If None then default colouring is applied to a message
141
+ Options (with corresponding defaults) include:
142
+
143
+ "name":None,
144
+ "name_colour":"bold blue",
145
+ "time_colour":'bold blue',
146
+ "time_dp":2,
147
+ "message_colour":'black',
148
+ "tracked":None
149
+
150
+ Use tracked to only show trace for specific process IDs.
151
+ For example if a process are labelled as integers from
152
+ 1 to n and we wished to track processes 5, 6 and 25. Then we would set
153
+ tracked = [1, 6, 25]
154
+
155
+ """
156
+
157
+ # get default and then update with user settings.
158
+ _config = Traceable._default_config()
159
+ if config is None:
160
+ _config['class'] = "event"
161
+ else:
162
+ # update with user settings.
163
+ _config.update(config)
164
+
165
+ # if in debug mode
166
+ if debug:
167
+
168
+ # conditional logic to limit tracking to specific processes/entities
169
+ if _config['tracked'] is None or identifier in _config['tracked']:
170
+
171
+ # display and format time stamp
172
+ out = f"[{_config['time_colour']}][{time:.{_config['time_dp']}f}]:[/{_config['time_colour']}]"
173
+
174
+ # if provided display and format a process ID
175
+ if _config['class'] is not None and identifier is not None:
176
+ out += f"[{_config['class_colour']}]<{_config['class']} {identifier}>: [/{_config['class_colour']}]"
177
+
178
+ # format traced event message
179
+ out += f"[{_config['message_colour']}]{msg}[/{_config['message_colour']}]"
180
+
181
+ # print to rich console
182
+ _console.print(out)
@@ -1,4 +0,0 @@
1
- __version__ = '0.4.0'
2
- __author__ = 'Thomas Monks'
3
-
4
- from . import datasets, distributions, time_dependent, ovs
@@ -1,100 +0,0 @@
1
- """
2
- Classes and functions to support time dependent samplingm in DES models.
3
- """
4
-
5
- import itertools
6
- import numpy as np
7
-
8
- from typing import Optional
9
-
10
-
11
- class NSPPThinning:
12
- """
13
- Non Stationary Poisson Process via Thinning.
14
-
15
- Thinning is an acceptance-rejection approach to sampling
16
- inter-arrival times (IAT) from a time dependent distribution
17
- where each time period follows its own exponential distribution.
18
-
19
- There are two random variables employed in sampling: an exponential
20
- distribution (used to sample IAT) and a uniform distibution (used
21
- to accept/reject samples).
22
-
23
- All IATs are sampled from an Exponential distribution with the highest
24
- arrival rate (most frequent). These arrivals are then rejected (thinned)
25
- proportional to the ratio of the current arrival rate to the maximum
26
- arrival rate. The algorithm executes until a sample is accepted. The IAT
27
- returned is the sum of all the IATs that were sampled.
28
-
29
- """
30
-
31
- def __init__(
32
- self,
33
- data,
34
- random_seed1: Optional[int] = None,
35
- random_seed2: Optional[int] = None,
36
- ):
37
- """
38
- Non Stationary Poisson Process via Thinning.
39
-
40
- Time dependency is handled for a single table
41
- consisting of equally spaced intervals.
42
-
43
- Params:
44
- ------
45
- data: pandas.DataFrame
46
- list of time points during a period for transition between rates
47
- and list arrival rates in that period. Labels should be "t"
48
- and "arrival_rate" respectively.
49
-
50
- random_seed1: int, optional (default=None)
51
- Random seed for exponential distribution
52
-
53
- random_seed2: int
54
- Random seed for the uniform distribution used
55
- for acceptance/rejection sampling.
56
- """
57
- self.data = data
58
- self.arr_rng = np.random.default_rng(random_seed1)
59
- self.thinning_rng = np.random.default_rng(random_seed2)
60
- self.lambda_max = data["arrival_rate"].max()
61
- # assumes all other intervals are equal in length.
62
- self.interval = int(data.iloc[1]["t"] - data.iloc[0]["t"])
63
- self.rejects_last_sample = None
64
-
65
- def sample(self, simulation_time: float) -> float:
66
- """
67
- Run a single iteration of acceptance-rejection
68
- thinning alg to sample the next inter-arrival time
69
-
70
- Params:
71
- ------
72
- simulation_time: float
73
- The current simulation time. This is used to look up
74
- the arrival rate for the time period.
75
-
76
- Returns:
77
- -------
78
- float
79
- The inter-arrival time
80
- """
81
- for _ in itertools.count():
82
- # this gives us the index of dataframe to use
83
- t = int(simulation_time // self.interval) % len(self.data)
84
- lambda_t = self.data["arrival_rate"].iloc[t]
85
-
86
- # set to a large number so that at least 1 sample taken!
87
- u = np.Inf
88
-
89
- # included for audit and tracking purposes.
90
- self.rejects_last_sample = 0.0
91
-
92
- interarrival_time = 0.0
93
-
94
- # reject samples if u >= lambda_t / lambda_max
95
- while u >= (lambda_t / self.lambda_max):
96
- self.rejects_last_sample += 1
97
- interarrival_time += self.arr_rng.exponential(1 / self.lambda_max)
98
- u = self.thinning_rng.uniform(0.0, 1.0)
99
-
100
- return interarrival_time
File without changes
File without changes
File without changes