pyworkforce 0.5.0.dev0__tar.gz → 0.5.2__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.
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/PKG-INFO +75 -41
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/README.md +71 -53
- pyworkforce-0.5.2/pyworkforce/_version.py +1 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/erlang.py +81 -77
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/test_erlang.py +35 -5
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/test_multi_erlang.py +18 -9
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/binary_programming.py +26 -25
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/base.py +15 -13
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/shifts_selection.py +30 -28
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/grid.py +1 -1
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/tests/test_parameter_grid.py +13 -6
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/PKG-INFO +75 -41
- pyworkforce-0.5.2/pyworkforce.egg-info/requires.txt +4 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/setup.py +11 -11
- pyworkforce-0.5.0.dev0/pyworkforce/_version.py +0 -1
- pyworkforce-0.5.0.dev0/pyworkforce.egg-info/requires.txt +0 -4
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/LICENSE +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/tests/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/tests/test_rostering.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/test_shifts.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/test_utils.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/utils.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/tests/__init__.py +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/SOURCES.txt +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/dependency_links.txt +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/top_level.txt +0 -0
- {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyworkforce
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Common tools for workforce management, schedule and optimization problems
|
|
5
5
|
Home-page: https://github.com/rodrigo-arenas/pyworkforce
|
|
6
6
|
Author: Rodrigo Arenas
|
|
@@ -11,69 +11,100 @@ Project-URL: Source Code, https://github.com/rodrigo-arenas/pyworkforce
|
|
|
11
11
|
Project-URL: Bug Tracker, https://github.com/rodrigo-arenas/pyworkforce/issues
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.
|
|
17
|
-
Requires-Python: >=3.
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.12,<3.15
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=1.26.0
|
|
21
|
+
Requires-Dist: ortools>=9.12.4544
|
|
22
|
+
Requires-Dist: pandas>=2.2.0
|
|
23
|
+
Requires-Dist: joblib>=1.4.0
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: license
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
20
36
|
|
|
21
37
|
|
|
22
38
|
[](https://www.travis-ci.com/rodrigo-arenas/pyworkforce)
|
|
23
39
|
[](https://codecov.io/github/rodrigo-arenas/pyworkforce?branch=main)
|
|
24
40
|
[](https://badge.fury.io/py/pyworkforce)
|
|
25
|
-
[](https://www.python.org/downloads/)
|
|
26
42
|
|
|
27
43
|
|
|
28
44
|
# pyworkforce
|
|
29
|
-
|
|
45
|
+
Tools for workforce management problems such as queue staffing, shift scheduling,
|
|
46
|
+
rostering, and operations research optimization.
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
The full documentation is available at
|
|
49
|
+
[pyworkforce.readthedocs.io](https://pyworkforce.readthedocs.io/en/stable/).
|
|
32
50
|
|
|
33
|
-
##
|
|
34
|
-
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
We recommend installing pyworkforce in a virtual environment:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install pyworkforce
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
pyworkforce supports Python 3.12, 3.13, and 3.14.
|
|
60
|
+
|
|
61
|
+
If you are using Anaconda and run into installation issues, update the environment first:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
conda update --all
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If the issue is related to OR-Tools, check the
|
|
68
|
+
[OR-Tools installation guide](https://github.com/google/or-tools#installation).
|
|
69
|
+
|
|
70
|
+
For runnable examples, see the
|
|
71
|
+
[examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples).
|
|
72
|
+
|
|
73
|
+
## What pyworkforce Does
|
|
74
|
+
|
|
75
|
+
pyworkforce is organized around three planning steps:
|
|
35
76
|
|
|
36
77
|
### Queuing
|
|
37
|
-
|
|
78
|
+
|
|
79
|
+
Use `pyworkforce.queuing` when you need to estimate how many resources are required
|
|
80
|
+
to handle incoming work, for example calls arriving at a call center. The current
|
|
81
|
+
implementation uses Erlang C assumptions: constant arrival rate, infinite queue,
|
|
82
|
+
and no customer dropout.
|
|
38
83
|
|
|
39
84
|

|
|
40
85
|
|
|
41
|
-
- **queuing.ErlangC:**
|
|
42
|
-
|
|
86
|
+
- **queuing.ErlangC:** Calculate staffing requirements and performance metrics for one queue scenario.
|
|
87
|
+
- **queuing.MultiErlangC:** Run multiple Erlang C scenarios from a parameter grid.
|
|
43
88
|
|
|
44
89
|
### Scheduling
|
|
45
90
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- **scheduling.MinAbsDifference:** This module finds the "optimal" assignation by minimizing the total absolute
|
|
49
|
-
differences between required resources per interval against the scheduled resources found by the solver.
|
|
50
|
-
- **scheduling.MinRequiredResources**: This module finds the "optimal" assignation by minimizing the total
|
|
51
|
-
weighted amount of scheduled resources (optionally weighted by shift cost), it ensures that in all intervals, there are
|
|
52
|
-
never fewer resources shifted than the ones required per period.
|
|
53
|
-
|
|
54
|
-
### Rostering
|
|
55
|
-
|
|
56
|
-
It assigns a list of resources to a list of required positions per day and shifts; it takes into account
|
|
57
|
-
different restrictions as shift bans, consecutive shifts, resting days, and others.
|
|
58
|
-
It also introduces soft restrictions like shift preferences.
|
|
59
|
-
|
|
60
|
-
# Usage:
|
|
61
|
-
Install pyworkforce
|
|
91
|
+
Use `pyworkforce.scheduling` when you already know the required resources by time
|
|
92
|
+
interval and need to choose how many people to place on each predefined shift.
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
- **scheduling.MinAbsDifference:** Minimizes the total absolute difference between required and scheduled resources.
|
|
95
|
+
- **scheduling.MinRequiredResources:** Minimizes the total weighted number of scheduled resources while ensuring every interval is covered.
|
|
64
96
|
|
|
65
|
-
|
|
66
|
-
pip install pyworkforce
|
|
67
|
-
```
|
|
97
|
+
### Rostering
|
|
68
98
|
|
|
69
|
-
|
|
99
|
+
Use `pyworkforce.rostering` when you have named resources and need to assign them
|
|
100
|
+
to days and shifts while respecting rules such as banned shifts, rest days,
|
|
101
|
+
minimum working hours, and preferences.
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
[examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples)
|
|
103
|
+
- **rostering.MinHoursRoster:** Builds a resource-level roster that covers shift requirements with the minimum scheduled hours.
|
|
73
104
|
|
|
74
105
|
### Queue systems:
|
|
75
106
|
|
|
76
|
-
A brief introduction can be found in this [medium post](https://
|
|
107
|
+
A brief introduction can be found in this [medium post](https://towardsdatascience.com/workforce-planning-optimization-using-python-69af0ef9011a)
|
|
77
108
|
|
|
78
109
|
#### Example:
|
|
79
110
|
|
|
@@ -94,7 +125,8 @@ Output:
|
|
|
94
125
|
'waiting_probability': 0.1741319335950498}
|
|
95
126
|
```
|
|
96
127
|
|
|
97
|
-
If you want to run
|
|
128
|
+
If you want to run several scenarios at the same time, use `MultiErlangC`.
|
|
129
|
+
For example, this tries different service-level targets:
|
|
98
130
|
|
|
99
131
|
```python
|
|
100
132
|
from pyworkforce.queuing import MultiErlangC
|
|
@@ -135,18 +167,20 @@ Output:
|
|
|
135
167
|
```
|
|
136
168
|
### Scheduling
|
|
137
169
|
|
|
170
|
+
A brief introduction can be found in this [medium post](https://towardsdatascience.com/how-to-solve-scheduling-problems-in-python-36a9af8de451)
|
|
171
|
+
|
|
138
172
|
#### Example:
|
|
139
173
|
|
|
140
174
|
```python
|
|
141
175
|
from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources
|
|
142
176
|
|
|
143
|
-
# Rows are
|
|
177
|
+
# Rows are days. Each value is the number of required positions for one hour of the day.
|
|
144
178
|
required_resources = [
|
|
145
179
|
[9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7],
|
|
146
180
|
[13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8]
|
|
147
181
|
]
|
|
148
182
|
|
|
149
|
-
# Each
|
|
183
|
+
# Each shift has 24 entries, one per hour. Use 1 if the shift covers that hour, otherwise 0.
|
|
150
184
|
shifts_coverage = {"Morning": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
151
185
|
"Afternoon": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
152
186
|
"Night": [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
|
@@ -2,58 +2,73 @@
|
|
|
2
2
|
[](https://www.travis-ci.com/rodrigo-arenas/pyworkforce)
|
|
3
3
|
[](https://codecov.io/github/rodrigo-arenas/pyworkforce?branch=main)
|
|
4
4
|
[](https://badge.fury.io/py/pyworkforce)
|
|
5
|
-
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# pyworkforce
|
|
9
|
+
Tools for workforce management problems such as queue staffing, shift scheduling,
|
|
10
|
+
rostering, and operations research optimization.
|
|
11
|
+
|
|
12
|
+
The full documentation is available at
|
|
13
|
+
[pyworkforce.readthedocs.io](https://pyworkforce.readthedocs.io/en/stable/).
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
We recommend installing pyworkforce in a virtual environment:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install pyworkforce
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
pyworkforce supports Python 3.12, 3.13, and 3.14.
|
|
24
|
+
|
|
25
|
+
If you are using Anaconda and run into installation issues, update the environment first:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
conda update --all
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If the issue is related to OR-Tools, check the
|
|
32
|
+
[OR-Tools installation guide](https://github.com/google/or-tools#installation).
|
|
33
|
+
|
|
34
|
+
For runnable examples, see the
|
|
35
|
+
[examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples).
|
|
36
|
+
|
|
37
|
+
## What pyworkforce Does
|
|
38
|
+
|
|
39
|
+
pyworkforce is organized around three planning steps:
|
|
40
|
+
|
|
41
|
+
### Queuing
|
|
42
|
+
|
|
43
|
+
Use `pyworkforce.queuing` when you need to estimate how many resources are required
|
|
44
|
+
to handle incoming work, for example calls arriving at a call center. The current
|
|
45
|
+
implementation uses Erlang C assumptions: constant arrival rate, infinite queue,
|
|
46
|
+
and no customer dropout.
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
- **queuing.ErlangC:** Calculate staffing requirements and performance metrics for one queue scenario.
|
|
51
|
+
- **queuing.MultiErlangC:** Run multiple Erlang C scenarios from a parameter grid.
|
|
52
|
+
|
|
53
|
+
### Scheduling
|
|
54
|
+
|
|
55
|
+
Use `pyworkforce.scheduling` when you already know the required resources by time
|
|
56
|
+
interval and need to choose how many people to place on each predefined shift.
|
|
57
|
+
|
|
58
|
+
- **scheduling.MinAbsDifference:** Minimizes the total absolute difference between required and scheduled resources.
|
|
59
|
+
- **scheduling.MinRequiredResources:** Minimizes the total weighted number of scheduled resources while ensuring every interval is covered.
|
|
60
|
+
|
|
61
|
+
### Rostering
|
|
62
|
+
|
|
63
|
+
Use `pyworkforce.rostering` when you have named resources and need to assign them
|
|
64
|
+
to days and shifts while respecting rules such as banned shifts, rest days,
|
|
65
|
+
minimum working hours, and preferences.
|
|
66
|
+
|
|
67
|
+
- **rostering.MinHoursRoster:** Builds a resource-level roster that covers shift requirements with the minimum scheduled hours.
|
|
53
68
|
|
|
54
69
|
### Queue systems:
|
|
55
70
|
|
|
56
|
-
A brief introduction can be found in this [medium post](https://
|
|
71
|
+
A brief introduction can be found in this [medium post](https://towardsdatascience.com/workforce-planning-optimization-using-python-69af0ef9011a)
|
|
57
72
|
|
|
58
73
|
#### Example:
|
|
59
74
|
|
|
@@ -74,7 +89,8 @@ Output:
|
|
|
74
89
|
'waiting_probability': 0.1741319335950498}
|
|
75
90
|
```
|
|
76
91
|
|
|
77
|
-
If you want to run
|
|
92
|
+
If you want to run several scenarios at the same time, use `MultiErlangC`.
|
|
93
|
+
For example, this tries different service-level targets:
|
|
78
94
|
|
|
79
95
|
```python
|
|
80
96
|
from pyworkforce.queuing import MultiErlangC
|
|
@@ -115,18 +131,20 @@ Output:
|
|
|
115
131
|
```
|
|
116
132
|
### Scheduling
|
|
117
133
|
|
|
134
|
+
A brief introduction can be found in this [medium post](https://towardsdatascience.com/how-to-solve-scheduling-problems-in-python-36a9af8de451)
|
|
135
|
+
|
|
118
136
|
#### Example:
|
|
119
137
|
|
|
120
138
|
```python
|
|
121
139
|
from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources
|
|
122
140
|
|
|
123
|
-
# Rows are
|
|
141
|
+
# Rows are days. Each value is the number of required positions for one hour of the day.
|
|
124
142
|
required_resources = [
|
|
125
143
|
[9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7],
|
|
126
144
|
[13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8]
|
|
127
145
|
]
|
|
128
146
|
|
|
129
|
-
# Each
|
|
147
|
+
# Each shift has 24 entries, one per hour. Use 1 if the shift covers that hour, otherwise 0.
|
|
130
148
|
shifts_coverage = {"Morning": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
131
149
|
"Afternoon": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
|
132
150
|
"Night": [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
|
@@ -181,4 +199,4 @@ Output:
|
|
|
181
199
|
{'day': 1, 'shift': 'Afternoon', 'resources': 20},
|
|
182
200
|
{'day': 1, 'shift': 'Night', 'resources': 23},
|
|
183
201
|
{'day': 1, 'shift': 'Mixed', 'resources': 0}]}
|
|
184
|
-
```
|
|
202
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.2"
|
|
@@ -5,22 +5,22 @@ from joblib import Parallel, delayed
|
|
|
5
5
|
|
|
6
6
|
class ErlangC:
|
|
7
7
|
"""
|
|
8
|
-
Computes the number of positions required to
|
|
9
|
-
queue system
|
|
8
|
+
Computes the number of positions required to handle transactions in an
|
|
9
|
+
Erlang C queue system. Implementation inspired by:
|
|
10
10
|
https://lucidmanager.org/data-science/call-centre-workforce-planning-erlang-c-in-r/
|
|
11
11
|
|
|
12
12
|
Parameters
|
|
13
13
|
----------
|
|
14
14
|
transactions: float,
|
|
15
|
-
|
|
15
|
+
Total number of transactions arriving in the interval.
|
|
16
16
|
aht: float,
|
|
17
17
|
Average handling time of a transaction (minutes).
|
|
18
18
|
asa: float,
|
|
19
19
|
The required average speed of answer (minutes).
|
|
20
20
|
interval: int,
|
|
21
|
-
Interval length
|
|
21
|
+
Interval length, in minutes.
|
|
22
22
|
shrinkage: float,
|
|
23
|
-
|
|
23
|
+
Fraction of time that an operator unit is not available.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
def __init__(self, transactions: float, aht: float, asa: float,
|
|
@@ -46,51 +46,61 @@ class ErlangC:
|
|
|
46
46
|
self.aht = aht
|
|
47
47
|
self.interval = interval
|
|
48
48
|
self.asa = asa
|
|
49
|
-
self.intensity = (self.n_transactions / self.interval) * self.aht
|
|
50
|
-
self.shrinkage = shrinkage
|
|
51
|
-
|
|
52
|
-
def
|
|
49
|
+
self.intensity = (self.n_transactions / self.interval) * self.aht
|
|
50
|
+
self.shrinkage = shrinkage
|
|
51
|
+
|
|
52
|
+
def _productive_positions(self, positions: int, scale_positions: bool = False):
|
|
53
|
+
if scale_positions:
|
|
54
|
+
productive_positions = floor((1 - self.shrinkage) * positions)
|
|
55
|
+
else:
|
|
56
|
+
productive_positions = positions
|
|
57
|
+
|
|
58
|
+
if productive_positions <= 0:
|
|
59
|
+
raise ValueError("productive positions must be greater than 0")
|
|
60
|
+
|
|
61
|
+
if productive_positions <= self.intensity:
|
|
62
|
+
raise ValueError("positions must be greater than traffic intensity")
|
|
63
|
+
|
|
64
|
+
return productive_positions
|
|
65
|
+
|
|
66
|
+
def waiting_probability(self, positions: int, scale_positions: bool = False):
|
|
53
67
|
"""
|
|
54
|
-
Returns the probability
|
|
68
|
+
Returns the probability that a transaction waits in queue.
|
|
55
69
|
|
|
56
70
|
Parameters
|
|
57
71
|
----------
|
|
58
|
-
positions: int,
|
|
59
|
-
The number of positions to
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
positions: int,
|
|
73
|
+
The number of positions available to handle transactions.
|
|
74
|
+
Productive positions must be greater than traffic intensity.
|
|
75
|
+
scale_positions: bool, default=False
|
|
76
|
+
Set to True when ``positions`` includes shrinkage.
|
|
62
77
|
|
|
63
78
|
"""
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
erlang_b_inverse = 1
|
|
71
|
-
for position in range(1, productive_positions + 1):
|
|
72
|
-
erlang_b_inverse = 1 + (erlang_b_inverse * position / self.intensity)
|
|
80
|
+
productive_positions = self._productive_positions(positions, scale_positions)
|
|
81
|
+
|
|
82
|
+
erlang_b_inverse = 1
|
|
83
|
+
for position in range(1, productive_positions + 1):
|
|
84
|
+
erlang_b_inverse = 1 + (erlang_b_inverse * position / self.intensity)
|
|
73
85
|
|
|
74
86
|
erlang_b = 1 / erlang_b_inverse
|
|
75
87
|
return productive_positions * erlang_b / (productive_positions - self.intensity * (1 - erlang_b))
|
|
76
88
|
|
|
77
89
|
def service_level(self, positions: int, scale_positions: bool = False):
|
|
78
90
|
"""
|
|
79
|
-
Returns the expected service level
|
|
91
|
+
Returns the expected service level for a number of positions.
|
|
80
92
|
|
|
81
93
|
Parameters
|
|
82
94
|
----------
|
|
83
95
|
|
|
84
|
-
positions: int,
|
|
85
|
-
The number of positions
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
positions: int,
|
|
97
|
+
The number of positions available to handle transactions.
|
|
98
|
+
Productive positions must be greater than traffic intensity.
|
|
99
|
+
scale_positions: bool, default = False
|
|
100
|
+
Set to True when ``positions`` includes shrinkage.
|
|
88
101
|
|
|
89
102
|
"""
|
|
90
|
-
|
|
91
|
-
productive_positions = floor((1 - self.shrinkage) * positions)
|
|
92
|
-
else:
|
|
93
|
-
productive_positions = positions
|
|
103
|
+
productive_positions = self._productive_positions(positions, scale_positions)
|
|
94
104
|
|
|
95
105
|
probability_wait = self.waiting_probability(productive_positions, scale_positions=False)
|
|
96
106
|
exponential = exp(-(productive_positions - self.intensity) * (self.asa / self.aht))
|
|
@@ -98,57 +108,58 @@ class ErlangC:
|
|
|
98
108
|
|
|
99
109
|
def achieved_occupancy(self, positions: int, scale_positions: bool = False):
|
|
100
110
|
"""
|
|
101
|
-
Returns the expected occupancy of positions
|
|
111
|
+
Returns the expected occupancy of positions.
|
|
102
112
|
|
|
103
113
|
Parameters
|
|
104
114
|
----------
|
|
105
115
|
|
|
106
|
-
positions: int,
|
|
107
|
-
The number of raw positions
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
positions: int,
|
|
117
|
+
The number of raw positions.
|
|
118
|
+
Productive positions must be greater than traffic intensity.
|
|
119
|
+
scale_positions: bool, default=False
|
|
120
|
+
Set to True when ``positions`` includes shrinkage.
|
|
110
121
|
|
|
111
122
|
"""
|
|
112
|
-
|
|
113
|
-
productive_positions = floor((1 - self.shrinkage) * positions)
|
|
114
|
-
else:
|
|
115
|
-
productive_positions = positions
|
|
123
|
+
productive_positions = self._productive_positions(positions, scale_positions)
|
|
116
124
|
|
|
117
125
|
return self.intensity / productive_positions
|
|
118
126
|
|
|
119
127
|
def required_positions(self, service_level: float, max_occupancy: float = 1.0):
|
|
120
128
|
"""
|
|
121
|
-
Computes the
|
|
129
|
+
Computes the required positions for a target service level.
|
|
122
130
|
|
|
123
131
|
Parameters
|
|
124
132
|
----------
|
|
125
133
|
|
|
126
134
|
service_level: float,
|
|
127
|
-
Target service level
|
|
128
|
-
max_occupancy: float,
|
|
129
|
-
The maximum fraction of time that a transaction can occupy a position
|
|
135
|
+
Target service level.
|
|
136
|
+
max_occupancy: float,
|
|
137
|
+
The maximum fraction of time that a transaction can occupy a position.
|
|
138
|
+
Must be greater than 0 and less than or equal to 1.
|
|
130
139
|
|
|
131
140
|
Returns
|
|
132
141
|
-------
|
|
133
142
|
|
|
134
143
|
raw_positions: int,
|
|
135
|
-
|
|
144
|
+
Required positions before applying shrinkage.
|
|
136
145
|
positions: int,
|
|
137
|
-
|
|
146
|
+
Positions needed after applying shrinkage.
|
|
138
147
|
service_level: float,
|
|
139
|
-
|
|
140
|
-
before the asa time
|
|
148
|
+
Fraction of transactions expected to reach a position before the target ASA.
|
|
141
149
|
occupancy: float,
|
|
142
|
-
|
|
150
|
+
Expected occupancy of positions.
|
|
143
151
|
waiting_probability: float,
|
|
144
|
-
|
|
152
|
+
Probability that a transaction waits in queue.
|
|
145
153
|
"""
|
|
146
154
|
|
|
147
155
|
if service_level < 0 or service_level > 1:
|
|
148
156
|
raise ValueError("service_level must be between 0 and 1")
|
|
149
157
|
|
|
150
|
-
if max_occupancy < 0 or max_occupancy > 1:
|
|
151
|
-
raise ValueError("max_occupancy must be between 0 and 1")
|
|
158
|
+
if max_occupancy < 0 or max_occupancy > 1:
|
|
159
|
+
raise ValueError("max_occupancy must be between 0 and 1")
|
|
160
|
+
|
|
161
|
+
if max_occupancy == 0:
|
|
162
|
+
raise ValueError("max_occupancy must be greater than 0")
|
|
152
163
|
|
|
153
164
|
positions = round(self.intensity + 1)
|
|
154
165
|
achieved_service_level = self.service_level(positions, scale_positions=False)
|
|
@@ -177,47 +188,40 @@ class ErlangC:
|
|
|
177
188
|
|
|
178
189
|
class MultiErlangC:
|
|
179
190
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
Runs Erlang C calculations over multiple parameter combinations.
|
|
192
|
+
|
|
193
|
+
This class uses joblib's ``Parallel`` to evaluate every combination from
|
|
194
|
+
``param_grid`` and the method-specific argument grid. Its interface is
|
|
195
|
+
inspired by scikit-learn's grid search utilities.
|
|
184
196
|
|
|
185
197
|
Parameters
|
|
186
198
|
----------
|
|
187
199
|
|
|
188
200
|
param_grid: dict,
|
|
189
|
-
Dictionary with
|
|
190
|
-
expected parameter and
|
|
201
|
+
Dictionary with :class:`ErlangC` initialization parameters. Each key must be an
|
|
202
|
+
expected parameter, and each value must be a list of options to iterate over.
|
|
191
203
|
example: {"transactions": [100, 200], "aht": [3], "interval": [30], "asa": [20 / 60], "shrinkage": [0.3]}
|
|
192
204
|
n_jobs: int, default=2
|
|
193
|
-
|
|
205
|
+
Maximum number of concurrently running jobs.
|
|
194
206
|
If -1 all CPUs are used. If 1 is given, no parallel computing code is used at all, which is useful for debugging.
|
|
195
207
|
For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used.
|
|
196
208
|
None is a marker for ‘unset’ that will be interpreted as n_jobs=1 (sequential execution)
|
|
197
209
|
unless the call is performed under a parallel_backend() context manager that sets another value for n_jobs.
|
|
198
210
|
pre_dispatch: {"all", int, or expression}, default='2 * n_jobs'
|
|
199
|
-
|
|
211
|
+
Number of task batches to pre-dispatch. Default is ``2*n_jobs``.
|
|
200
212
|
See joblib's documentation for more details: https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html
|
|
201
213
|
|
|
202
214
|
Attributes
|
|
203
215
|
----------
|
|
204
216
|
|
|
205
217
|
waiting_probability_params: list[tuple],
|
|
206
|
-
|
|
207
|
-
arguments_grid for waiting_probability method,corresponding to the same order returned
|
|
208
|
-
by the MultiErlangC.waiting_probability method.
|
|
218
|
+
Parameters used for each ``waiting_probability`` result, in result order.
|
|
209
219
|
service_level_params: list[tuple],
|
|
210
|
-
|
|
211
|
-
arguments_grid for service_level method,corresponding to the same order returned
|
|
212
|
-
by the MultiErlangC.service_level method.
|
|
220
|
+
Parameters used for each ``service_level`` result, in result order.
|
|
213
221
|
achieved_occupancy_params: list[tuple],
|
|
214
|
-
|
|
215
|
-
arguments_grid for achieved_occupancy method,corresponding to the same order returned
|
|
216
|
-
by the MultiErlangC.achieved_occupancy method.
|
|
222
|
+
Parameters used for each ``achieved_occupancy`` result, in result order.
|
|
217
223
|
required_positions_params: list[tuple],
|
|
218
|
-
|
|
219
|
-
arguments_grid for required_positions method,corresponding to the same order returned
|
|
220
|
-
by the MultiErlangC.required_positions method.
|
|
224
|
+
Parameters used for each ``required_positions`` result, in result order.
|
|
221
225
|
"""
|
|
222
226
|
|
|
223
227
|
def __init__(self, param_grid: dict, n_jobs: int = 2, pre_dispatch: str = '2 * n_jobs'):
|
|
@@ -347,8 +351,8 @@ class MultiErlangC:
|
|
|
347
351
|
if len(solutions) < 1: # noqa
|
|
348
352
|
raise ValueError("Could not find any solution, make sure the param_grid is defined correctly")
|
|
349
353
|
|
|
350
|
-
if len(solutions) != combinations:
|
|
351
|
-
raise ValueError('Inconsistent results. Expected {} '
|
|
352
|
-
'solutions, got {}'
|
|
353
|
-
.format(
|
|
354
|
-
len(solutions))) # noqa
|
|
354
|
+
if len(solutions) != combinations:
|
|
355
|
+
raise ValueError('Inconsistent results. Expected {} '
|
|
356
|
+
'solutions, got {}'
|
|
357
|
+
.format(combinations,
|
|
358
|
+
len(solutions))) # noqa
|