sim-tools 0.7.0__tar.gz → 0.8.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.4
2
2
  Name: sim-tools
3
- Version: 0.7.0
3
+ Version: 0.8.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
@@ -93,7 +93,7 @@ If you use sim0tools for research, a practical report, education or any reason p
93
93
 
94
94
  # Online Tutorials
95
95
 
96
- * Optimisation Via Simulation [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/TomMonks/sim-tools/blob/master/examples/sw21_tutorial.ipynb)
96
+ * Optimisation Via Simulation [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/TomMonks/sim-tools/blob/master/docs/02_ovs/03_sw21_tutorial.ipynb)
97
97
 
98
98
 
99
99
  ## Contributing to sim-tools
@@ -107,3 +107,23 @@ Development environment:
107
107
  * `conda activate sim_tools`
108
108
 
109
109
  **All contributions are welcome!**
110
+
111
+ ## Tips
112
+
113
+ Once in the `sim_tools` environment, you can run tests using the following command:
114
+
115
+ ```
116
+ pytest
117
+ ```
118
+
119
+ To view the documentation, navigate to the top level directory of the code repository in your terminal and issue the following command to build the Jupyter Book:
120
+
121
+ ```
122
+ jb build docs/
123
+ ```
124
+
125
+ To lint the repository, run:
126
+
127
+ ```
128
+ bash lint.sh
129
+ ```
@@ -70,7 +70,7 @@ If you use sim0tools for research, a practical report, education or any reason p
70
70
 
71
71
  # Online Tutorials
72
72
 
73
- * Optimisation Via Simulation [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/TomMonks/sim-tools/blob/master/examples/sw21_tutorial.ipynb)
73
+ * Optimisation Via Simulation [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/TomMonks/sim-tools/blob/master/docs/02_ovs/03_sw21_tutorial.ipynb)
74
74
 
75
75
 
76
76
  ## Contributing to sim-tools
@@ -84,3 +84,23 @@ Development environment:
84
84
  * `conda activate sim_tools`
85
85
 
86
86
  **All contributions are welcome!**
87
+
88
+ ## Tips
89
+
90
+ Once in the `sim_tools` environment, you can run tests using the following command:
91
+
92
+ ```
93
+ pytest
94
+ ```
95
+
96
+ To view the documentation, navigate to the top level directory of the code repository in your terminal and issue the following command to build the Jupyter Book:
97
+
98
+ ```
99
+ jb build docs/
100
+ ```
101
+
102
+ To lint the repository, run:
103
+
104
+ ```
105
+ bash lint.sh
106
+ ```
@@ -1,4 +1,5 @@
1
- __version__ = '0.7.0'
1
+ """sim-tools"""
2
+ __version__ = '0.8.0'
2
3
  __author__ = 'Thomas Monks'
3
4
 
4
- from . import datasets, distributions, time_dependent, ovs, output_analysis
5
+ from . import datasets, distributions, time_dependent, ovs, output_analysis
@@ -1,15 +1,19 @@
1
1
  """
2
2
  Datasets module
3
3
 
4
- Contains functions for loading example data
5
- to demonstrate sim-tools functionality.
4
+ Contains functions for loading example data to demonstrate sim-tools
5
+ functionality.
6
6
  """
7
- import pandas as pd
7
+
8
8
  from pathlib import Path
9
9
 
10
+ import pandas as pd
11
+
12
+
10
13
  FILE_NAME_NSPP_1 = "nspp_example1.csv"
11
14
  PATH_NSPP_1 = Path(__file__).parent.joinpath("data", FILE_NAME_NSPP_1)
12
15
 
16
+
13
17
  def load_banks_et_al_nspp() -> pd.DataFrame:
14
18
  '''
15
19
  Load example Non-stationary poisson process
@@ -25,4 +29,4 @@ def load_banks_et_al_nspp() -> pd.DataFrame:
25
29
  '''
26
30
  arrivals = pd.read_csv(PATH_NSPP_1)
27
31
  arrivals['arrival_rate'] = 1 / arrivals['mean_iat']
28
- return arrivals
32
+ return arrivals
@@ -1,23 +1,23 @@
1
+ # pylint: disable=too-many-lines
1
2
  """
2
- Convenient encapsulation of distributions
3
- and sampling from distributions not directly
4
- available in scipy or numpy.
3
+ Convenient encapsulation of distributions and sampling from distributions not
4
+ directly available in scipy or numpy.
5
5
 
6
6
  Useful for simulation.
7
7
 
8
8
  Each distribution has its own random number stream
9
9
  that can be set by a seed.
10
-
11
10
  """
12
11
 
13
12
  from abc import ABC, abstractmethod
14
13
  import math
15
- import numpy as np
16
-
17
14
  from typing import Optional, Tuple
15
+
16
+ import numpy as np
18
17
  import numpy.typing as npt
19
18
 
20
19
 
20
+ # pylint: disable=too-few-public-methods
21
21
  class Distribution(ABC):
22
22
  """
23
23
  Distribution abstract class
@@ -42,9 +42,9 @@ class Distribution(ABC):
42
42
  -------
43
43
  np.ndarray or scalar
44
44
  """
45
- pass
46
45
 
47
46
 
47
+ # pylint: disable=too-few-public-methods
48
48
  class Exponential(Distribution):
49
49
  """
50
50
  Convenience class for the exponential distribution.
@@ -80,6 +80,7 @@ class Exponential(Distribution):
80
80
  return self.rng.exponential(self.mean, size=size)
81
81
 
82
82
 
83
+ # pylint: disable=too-few-public-methods
83
84
  class Bernoulli(Distribution):
84
85
  """
85
86
  Convenience class for the Bernoulli distribution.
@@ -120,7 +121,12 @@ class Lognormal(Distribution):
120
121
  Encapsulates a lognormal distirbution
121
122
  """
122
123
 
123
- def __init__(self, mean: float, stdev: float, random_seed: Optional[int] = None):
124
+ def __init__(
125
+ self,
126
+ mean: float,
127
+ stdev: float,
128
+ random_seed: Optional[int] = None
129
+ ):
124
130
  """
125
131
  Params:
126
132
  -------
@@ -184,19 +190,19 @@ class Normal(Distribution):
184
190
  ):
185
191
  '''
186
192
  Constructor
187
-
193
+
188
194
  Params:
189
195
  ------
190
196
  mean: float
191
197
  The mean of the normal distribution
192
-
198
+
193
199
  sigma: float
194
200
  The stdev of the normal distribution
195
201
 
196
202
  minimum: float
197
203
  Truncate the normal distribution to a minimum
198
204
  value.
199
-
205
+
200
206
  random_seed: int, optional (default=None)
201
207
  A random seed to reproduce samples. If set to none then a unique
202
208
  sample is created.
@@ -205,11 +211,11 @@ class Normal(Distribution):
205
211
  self.mean = mean
206
212
  self.sigma = sigma
207
213
  self.minimum = minimum
208
-
214
+
209
215
  def sample(self, size: Optional[int] = None) -> float | np.ndarray:
210
216
  '''
211
217
  Generate a sample from the normal distribution
212
-
218
+
213
219
  Params:
214
220
  -------
215
221
  size: int, optional (default=None)
@@ -220,15 +226,14 @@ class Normal(Distribution):
220
226
 
221
227
  if self.minimum is None:
222
228
  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
229
- return samples
230
229
 
230
+ if size is None:
231
+ return max(self.minimum, samples)
231
232
 
233
+ # index of samples with negative value
234
+ neg_idx = np.where(samples < 0)[0]
235
+ samples[neg_idx] = self.minimum
236
+ return samples
232
237
 
233
238
 
234
239
  class Uniform(Distribution):
@@ -279,7 +284,11 @@ class Triangular(Distribution):
279
284
  """
280
285
 
281
286
  def __init__(
282
- self, low: float, mode: float, high: float, random_seed: Optional[int] = None
287
+ self,
288
+ low: float,
289
+ mode: float,
290
+ high: float,
291
+ random_seed: Optional[int] = None
283
292
  ) -> float | np.ndarray:
284
293
  super().__init__(random_seed)
285
294
  self.low = low
@@ -311,8 +320,7 @@ class FixedDistribution(Distribution):
311
320
  """
312
321
  if size is not None:
313
322
  return np.full(size, self.value)
314
- else:
315
- return self.value
323
+ return self.value
316
324
 
317
325
 
318
326
  class CombinationDistribution(Distribution):
@@ -383,7 +391,10 @@ class ContinuousEmpirical(Distribution):
383
391
  self.upper_bounds = np.asarray(upper_bounds)
384
392
  self.cumulative_probs = self.create_cumulative_probs(freq)
385
393
 
386
- def create_cumulative_probs(self, freq: npt.ArrayLike) -> npt.NDArray[float]:
394
+ def create_cumulative_probs(
395
+ self,
396
+ freq: npt.ArrayLike
397
+ ) -> npt.NDArray[float]:
387
398
  """
388
399
  Calculate cumulative relative frequency from
389
400
  frequency
@@ -416,29 +427,30 @@ class ContinuousEmpirical(Distribution):
416
427
  size = 1
417
428
 
418
429
  samples = []
419
- for i in range(size):
420
- # Sample a value U from the uniform(0, 1) distribution
421
- U = self.rng.random()
430
+ for _ in range(size):
431
+ # Sample a value u from the uniform(0, 1) distribution
432
+ u = self.rng.random()
422
433
 
423
434
  # Obtain lower and upper bounds of a sample from the
424
435
  # discrete empirical distribution
425
- idx = np.searchsorted(self.cumulative_probs, U)
436
+ idx = np.searchsorted(self.cumulative_probs, u)
426
437
  lb, ub = self.lower_bounds[idx], self.upper_bounds[idx]
427
438
 
428
- # Use linear interpolation of U between
439
+ # Use linear interpolation of u between
429
440
  # the lower and upper bound to obtain a continuous value
430
- continuous_value = lb + (ub - lb) * (U - self.cumulative_probs[idx - 1]) / (
431
- self.cumulative_probs[idx] - self.cumulative_probs[idx - 1]
441
+ continuous_value = (
442
+ lb + (ub - lb) * (u - self.cumulative_probs[idx - 1]) / (
443
+ self.cumulative_probs[idx] - self.cumulative_probs[idx - 1]
444
+ )
432
445
  )
433
446
 
434
447
  samples.append(continuous_value)
435
448
 
436
449
  if size == 1:
437
- # .item() ensure returned as python 'float'
450
+ # .item() ensure returned as python 'float'
438
451
  # as opposed to np.float64
439
452
  return samples[0].item()
440
- else:
441
- return np.asarray(samples)
453
+ return np.asarray(samples)
442
454
 
443
455
 
444
456
  class Erlang(Distribution):
@@ -519,8 +531,9 @@ class Weibull(Distribution):
519
531
  """
520
532
  Weibull distribution
521
533
 
522
- The Weibull takes shape (alpha) and scale (beta) parameters. Both shape and scale
523
- should be > 0. The higher the scale parameters the more variance in the samples.
534
+ The Weibull takes shape (alpha) and scale (beta) parameters. Both shape
535
+ and scale should be > 0. The higher the scale parameters the more variance
536
+ in the samples.
524
537
 
525
538
  This implementation also includes a third parameter "location"
526
539
  (default = 0) to shift the distribution if a lower bound is needed.
@@ -653,7 +666,10 @@ class Gamma(Distribution):
653
666
  return self.alpha * (self.beta**2)
654
667
 
655
668
  @staticmethod
656
- def params_from_mean_and_var(mean: float, var: float) -> Tuple[float, float]:
669
+ def params_from_mean_and_var(
670
+ mean: float,
671
+ var: float
672
+ ) -> Tuple[float, float]:
657
673
  """
658
674
  Helper static method to get alpha and beta parameters
659
675
  from a mean and variance.
@@ -706,7 +722,7 @@ class Beta(Distribution):
706
722
  2. Distribution of a random proportion
707
723
  3. Time to complete a task.
708
724
  """
709
-
725
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
710
726
  def __init__(
711
727
  self,
712
728
  alpha1: float,
@@ -753,7 +769,8 @@ class Beta(Distribution):
753
769
  numpy array returned.
754
770
  """
755
771
  return self.min + (
756
- (self.max - self.min) * self.rng.beta(self.alpha1, self.alpha2, size)
772
+ (self.max - self.min) *
773
+ self.rng.beta(self.alpha1, self.alpha2, size)
757
774
  )
758
775
 
759
776
 
@@ -792,7 +809,8 @@ class Discrete(Distribution):
792
809
  sample is created.
793
810
  """
794
811
  if len(values) != len(freq):
795
- raise ValueError("values and freq arguments must be of equal length")
812
+ raise ValueError(
813
+ "values and freq arguments must be of equal length")
796
814
 
797
815
  super().__init__(random_seed)
798
816
  self.values = np.asarray(values)
@@ -809,13 +827,11 @@ class Discrete(Distribution):
809
827
  Number of samples to return. If integer then
810
828
  numpy array returned.
811
829
  """
812
- sample = self.rng.choice(self.values, p=self.probabilities, size=size)
830
+ sample = self.rng.choice(self.values, p=self.probabilities, size=size)
813
831
 
814
832
  if size is None:
815
833
  return sample.item()
816
- else:
817
- return sample
818
-
834
+ return sample
819
835
 
820
836
 
821
837
  class TruncatedDistribution(Distribution):
@@ -860,9 +876,8 @@ class TruncatedDistribution(Distribution):
860
876
  samples[samples < self.lower_bound] = self.lower_bound
861
877
  return samples
862
878
 
863
- else:
864
- sample = self.dist.sample()
865
- return max(self.lower_bound, sample)
879
+ sample = self.dist.sample()
880
+ return max(self.lower_bound, sample)
866
881
 
867
882
 
868
883
  class RawEmpirical(Distribution):
@@ -877,7 +892,11 @@ class RawEmpirical(Distribution):
877
892
  are representative of the real world system.
878
893
  """
879
894
 
880
- def __init__(self, values: npt.ArrayLike, random_seed: Optional[int] = None):
895
+ def __init__(
896
+ self,
897
+ values: npt.ArrayLike,
898
+ random_seed: Optional[int] = None
899
+ ):
881
900
  """
882
901
  RawEmpirical
883
902
 
@@ -937,7 +956,12 @@ class PearsonV(Distribution):
937
956
 
938
957
  """
939
958
 
940
- def __init__(self, alpha: float, beta: float, random_seed: Optional[int] = None):
959
+ def __init__(
960
+ self,
961
+ alpha: float,
962
+ beta: float,
963
+ random_seed: Optional[int] = None
964
+ ):
941
965
  """
942
966
  PearsonV
943
967
 
@@ -968,9 +992,8 @@ class PearsonV(Distribution):
968
992
  """
969
993
  if self.alpha > 1.0:
970
994
  return self.beta / (self.alpha - 1)
971
- else:
972
- msg = "Cannot directly compute mean when alpha <= 1.0"
973
- raise ValueError(msg)
995
+ msg = "Cannot directly compute mean when alpha <= 1.0"
996
+ raise ValueError(msg)
974
997
 
975
998
  def var(self) -> float:
976
999
  """
@@ -979,10 +1002,10 @@ class PearsonV(Distribution):
979
1002
  If alpha <= 2.0 raises a ValueError
980
1003
  """
981
1004
  if self.alpha > 2.0:
982
- return (self.beta**2) / (((self.alpha - 1) ** 2) * (self.alpha - 2))
983
- else:
984
- msg = "Cannot directly compute var when alpha <= 2.0"
985
- raise ValueError(msg)
1005
+ return (
1006
+ self.beta**2) / (((self.alpha - 1) ** 2) * (self.alpha - 2))
1007
+ msg = "Cannot directly compute var when alpha <= 2.0"
1008
+ raise ValueError(msg)
986
1009
 
987
1010
  def sample(self, size: Optional[int] = None) -> float | np.ndarray:
988
1011
  """
@@ -1061,10 +1084,12 @@ class PearsonVI(Distribution):
1061
1084
  self.beta = beta
1062
1085
 
1063
1086
  def mean(self) -> float:
1087
+ """
1088
+ Compute the mean.
1089
+ """
1064
1090
  if self.alpha2 > 1.0:
1065
1091
  return (self.beta * self.alpha1) / (self.alpha2 - 1)
1066
- else:
1067
- raise ValueError("Cannot compute mean when alpha2 <= 1.0")
1092
+ raise ValueError("Cannot compute mean when alpha2 <= 1.0")
1068
1093
 
1069
1094
  def var(self) -> float:
1070
1095
  """
@@ -1076,9 +1101,8 @@ class PearsonVI(Distribution):
1076
1101
  return (
1077
1102
  (self.beta**2) * self.alpha1 * (self.alpha1 + self.alpha2 - 1)
1078
1103
  ) / (((self.alpha2 - 1) ** 2) * (self.alpha2 - 2))
1079
- else:
1080
- msg = "Cannot directly compute var when alpha2 <= 2.0"
1081
- raise ValueError(msg)
1104
+ msg = "Cannot directly compute var when alpha2 <= 2.0"
1105
+ raise ValueError(msg)
1082
1106
 
1083
1107
  def sample(self, size: Optional[int] = None) -> float | np.ndarray:
1084
1108
  """
@@ -1091,8 +1115,8 @@ class PearsonVI(Distribution):
1091
1115
  numpy array returned.
1092
1116
  """
1093
1117
  # Pearson6(a1,a2,b)=b∗X/(1−X), where X=Beta(a1,a2,1)
1094
- X = self.rng.beta(self.alpha1, self.alpha2, size)
1095
- return self.beta * X / (1 - X)
1118
+ x = self.rng.beta(self.alpha1, self.alpha2, size)
1119
+ return self.beta * x / (1 - x)
1096
1120
 
1097
1121
 
1098
1122
  class ErlangK(Distribution):