Skip to content

Commit c3a784e

Browse files
committed
Added more probability distributions
1 parent 94da9c5 commit c3a784e

File tree

6 files changed

+112
-18
lines changed

6 files changed

+112
-18
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ The zip package also includes some example files (as plain Python files and as J
1515

1616
## Requirements
1717

18-
* **Python 3.7 or higher** is needed to execute QueueSim.
18+
* **Python 3.9 or higher** is needed to execute QueueSim.
19+
* **`scipy`** is used to generate pseudo-random numbers of some probability distributions
1920
* **`numpy`** is used by `queuesim.statistics` and in several example files.
2021
* **`pandas`** and **`scipy`** are used in `queuesim.analytic`.
2122
* The visualizations in the example Jupyter notebooks use **`matplotlib`** and **`seaborn`**.

README_distributions.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ To make thinks as easy as possible there are some lambda factory methods in `que
77
* Exponential distribution: `exp(mean)`
88
* Log-normal distribution: `log_normal(mean, sd)`
99
* Gamma distribution: `gamma(mean, sd)`
10-
* Continuous Uniform distribution: `uniform(low, high)`
10+
* Erlang distribution: `erlang(mean, sd)`
11+
* Half-normal distribution: `half_normal(low, mean)`
12+
* Continuous uniform distribution: `uniform(low, high)`
1113
* Triangular distribution: `uniform(low, most_likely, high)`
14+
* Trapezoid distribution: `trapezoid(a, b, c, d)`
15+
* Beta distribution: `beta(alpha, beta, low, high)`
1216
* Deterministic: `deterministic(fixed_value)`
1317
* Empirical: `empirical(options)` where `options` is a `dict` of values to rates
1418

1519
Since the gamma distribution is a generalization of the Erlang distribution, the quite common Erlang distribution is also covered.
1620

17-
Note that the parameters for `log_normal` and `gamma` are mean (`mean`) and standard deviation (`sd`). So no manual converting from mean and standard deviation to $\mu$, $\sigma$, ... is needed.
21+
Note that the parameters for `log_normal`, `gamma` and `erlang`are mean (`mean`) and standard deviation (`sd`). So **no** manual converting from mean and standard deviation to $\mu$, $\sigma$, ... is needed.
1822

1923
The generator functions will return strings containing lambda expressions. The strings are evaluated inside the stations on first usage. This is needed for serialization for multi-process simulation. If you do not want to use multi-process parallelization, you can also add the parameter `as_lambda=True` to get lambda expressions directly. The stations will understand both: strings and lambdas.

queuesim/analytic/tools.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
__license__ = "Apache 2.0"
2424

2525

26-
# "prod" is introduced in Python 3.8.
27-
# We are 3.7 compatible.
28-
29-
# from math import prod
26+
from math import prod
3027

3128

3229
def power_factorial(x: float, n: int) -> float:
@@ -42,7 +39,4 @@ def power_factorial(x: float, n: int) -> float:
4239
if n == 0:
4340
return 1
4441

45-
# return prod([x / i for i in range(1, n + 1)])
46-
prod: float = 1
47-
for i in range(1, n + 1): prod *= x / i
48-
return prod
42+
return prod([x / i for i in range(1, n + 1)])

queuesim/random_dist.py

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Callable, Union
44
import math
55
import random
6+
import scipy.stats as stats
67

78

89
__title__ = "queuesim"
@@ -75,8 +76,9 @@ def gamma(mean: float, std: float, as_lambda: bool = False) -> Union[str, Callab
7576
Returns:
7677
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
7778
"""
78-
assert std >= 0
79-
beta: float = mean / (std * std)
79+
assert mean > 0
80+
assert std > 0
81+
beta: float = mean / (std ** 2)
8082
alpha: float = mean * beta
8183
beta: float = 1 / beta
8284
if as_lambda:
@@ -85,6 +87,28 @@ def gamma(mean: float, std: float, as_lambda: bool = False) -> Union[str, Callab
8587
return "lambda: random.gammavariate(" + str(alpha) + ", " + str(beta) + ")"
8688

8789

90+
def erlang(mean: float, std: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
91+
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, with a pseudorandom number generator for the Erlang distribution
92+
93+
Args:
94+
mean (float): Mean
95+
std (float): Standard deviation
96+
as_lambda (bool, optional): Should a lambda expression (True) or a string (False) be returned? Defaults to False.
97+
98+
Returns:
99+
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
100+
"""
101+
assert mean > 0
102+
assert std > 0
103+
104+
scale: float = std**2 / mean
105+
a: int = max(1, round(mean / scale))
106+
if as_lambda:
107+
return lambda: stats.erlang.rvs(a, scale=scale)
108+
else:
109+
return "lambda: stats.erlang.rvs(" + str(a) + ", scale = " + str(scale) + ")"
110+
111+
88112
def uniform(low: float, high: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
89113
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, with a pseudorandom number generator for the uniform distribution
90114
@@ -120,6 +144,70 @@ def triangular(low: float, most_likely: float, high: float, as_lambda: bool = Fa
120144
return "lambda: random.triangular(" + str(low) + ", " + str(high) + ", " + str(most_likely) + ")"
121145

122146

147+
def trapezoid(a: float, b: float, c: float, d: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
148+
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, with a pseudorandom number generator for the trapezoid distribution
149+
150+
Args:
151+
a (float): Minimum value of the support
152+
b (float): x value of the left side of the highest density
153+
c (float): x value of the right side of the highest density
154+
d (float): Maximum value of the support
155+
as_lambda (bool, optional): Should a lambda expression (True) or a string (False) be returned? Defaults to False.
156+
157+
Returns:
158+
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
159+
"""
160+
c_shape = (b - a) / (d - a)
161+
d_shape = (c - a) / (d - a)
162+
loc = a
163+
scale = d - a
164+
if as_lambda:
165+
return lambda: stats.trapezoid.rvs(c_shape, d_shape, loc=loc, scale=scale)
166+
else:
167+
return "lambda: stats.trapezoid.rvs(" + str(c_shape) + ", " + str(d_shape) + ", loc=" + str(loc) + ", scale=" + str(scale) + ")"
168+
169+
170+
def beta(alpha: float, beta: float, low: float, high: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
171+
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, with a pseudorandom number generator for the beta distribution
172+
173+
Args:
174+
alpha (float): Alpha parameter of the beta distribution
175+
beta (float): Beta parameter of the beta distribution
176+
low (float): Minimum value of the support
177+
high (float): Maximum value of the support
178+
as_lambda (bool, optional): Should a lambda expression (True) or a string (False) be returned? Defaults to False.
179+
180+
Returns:
181+
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
182+
"""
183+
loc = low
184+
scale = high - loc
185+
if as_lambda:
186+
return lambda: stats.beta.rvs(alpha, beta, loc=loc, scale=scale)
187+
else:
188+
return "lambda: stats.beta.rvs(" + str(alpha) + ", " + str(beta) + ", loc=" + str(loc) + ", scale=" + str(scale) + ")"
189+
190+
191+
def half_normal(low: float, mean: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
192+
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, with a pseudorandom number generator for the half-normal distribution
193+
194+
Args:
195+
low (float): Minimum value of the support
196+
mean (float): Mean of the half-normal distribution
197+
as_lambda (bool, optional): Should a lambda expression (True) or a string (False) be returned? Defaults to False.
198+
199+
Returns:
200+
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
201+
"""
202+
assert mean > low
203+
loc = low
204+
scale = (mean - low) * math.sqrt(math.pi / 2)
205+
if as_lambda:
206+
return lambda: stats.halfnorm.rvs(loc=loc, scale=scale)
207+
else:
208+
return "lambda: stats.halfnorm.rvs(loc=" + str(loc) + ", scale=" + str(scale) + ")"
209+
210+
123211
def deterministic(mean: float, as_lambda: bool = False) -> Union[str, Callable[[], float]]:
124212
"""Generates a lambda expression or a string that can be evaluated to a lambda expression, which returns a fixed number (which is a special case of a pseudorandom number generator)
125213
@@ -136,13 +224,13 @@ def deterministic(mean: float, as_lambda: bool = False) -> Union[str, Callable[[
136224
return "lambda: " + str(mean)
137225

138226

139-
def empirical_helper(values: dict) -> float:
140-
rate_sum: float = sum(values.values())
227+
def empirical_helper(rate_sum, values: dict) -> float:
141228
rnd: float = random.random() * rate_sum
142229
s: float = 0
143230
for key in values:
144231
s += values[key]
145-
if s >= rnd: return key
232+
if s >= rnd:
233+
return float(key)
146234
return 0
147235

148236

@@ -156,7 +244,8 @@ def empirical(values: dict, as_lambda: bool = False) -> Union[str, Callable[[],
156244
Returns:
157245
Union[str, Callable[[], float]]: Lambda expression or string with lambda expression for random number generator
158246
"""
247+
rate_sum: float = sum(values.values())
159248
if as_lambda:
160-
return lambda: empirical_helper(values)
249+
return lambda: empirical_helper(rate_sum, values)
161250
else:
162-
return "lambda: empirical_helper(" + str(values) + ")"
251+
return "lambda: empirical_helper(" + str(rate_sum) + ", " + str(values) + ")"

queuesim/stations.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Classes for modelling the stations of a queueing model"""
22

33
from typing import Any, Optional
4+
import math # math, random, and scipy.stats are used when evaluating pseudo-random number generators from strings
45
import random
6+
import scipy.stats as stats
7+
from .random_dist import empirical_helper
58
from .descore import Event, Simulator
69
from .statistics import RecordDiscrete, RecordContinuous, RecordOptions
710

queuesim/stations.pyx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Classes for modelling the stations of a queueing model"""
22

33
from typing import Any, Optional
4+
import math # math, random, and scipy.stats are used when evaluating pseudo-random number generators from strings
45
import random
6+
import scipy.stats as stats
7+
from .random_dist import empirical_helper
58
from .descore import Event, Simulator
69
from .statistics import RecordDiscrete, RecordContinuous, RecordOptions
710
import cython

0 commit comments

Comments
 (0)