salabim 24.0.13__tar.gz → 24.0.14.post4__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {salabim-24.0.13 → salabim-24.0.14.post4}/PKG-INFO +2 -2
- {salabim-24.0.13 → salabim-24.0.14.post4}/README.md +1 -1
- {salabim-24.0.13 → salabim-24.0.14.post4}/pyproject.toml +1 -1
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/salabim.py +404 -35
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/PKG-INFO +2 -2
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_distributions.py +43 -2
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/DejaVuSansMono.ttf +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/LICENSE.txt +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/__init__.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/calibri.ttf +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/mplus-1m-regular.ttf +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/SOURCES.txt +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/dependency_links.txt +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/top_level.txt +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/setup.cfg +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test salabim.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_cap_now.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_componentgenerator.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_datetime.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_misc.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_monitor.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_process.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_queue.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_state.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_store.py +0 -0
- {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_timeunit.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: salabim
|
3
|
-
Version: 24.0.
|
3
|
+
Version: 24.0.14.post4
|
4
4
|
Summary: salabim - discrete event simulation in Python
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
6
6
|
Project-URL: Homepage, https://salabim.org
|
@@ -46,7 +46,7 @@ In contrast to some other Python DES packages, salabim does not require the use
|
|
46
46
|
### Features and documentation
|
47
47
|
|
48
48
|
- Cross-platform support: salabim runs on Windows, macOS, Linux, iOS/iPadOS (Pythonista), and can even be used with "Python In Excel".
|
49
|
-
- Comprehensive documentation: Visit [www.salabim.org/manual](www.salabim.org/manual) for detailed documentation.
|
49
|
+
- Comprehensive documentation: Visit [www.salabim.org/manual](https://www.salabim.org/manual) for detailed documentation.
|
50
50
|
|
51
51
|
### Resources
|
52
52
|
|
@@ -33,7 +33,7 @@ In contrast to some other Python DES packages, salabim does not require the use
|
|
33
33
|
### Features and documentation
|
34
34
|
|
35
35
|
- Cross-platform support: salabim runs on Windows, macOS, Linux, iOS/iPadOS (Pythonista), and can even be used with "Python In Excel".
|
36
|
-
- Comprehensive documentation: Visit [www.salabim.org/manual](www.salabim.org/manual) for detailed documentation.
|
36
|
+
- Comprehensive documentation: Visit [www.salabim.org/manual](https://www.salabim.org/manual) for detailed documentation.
|
37
37
|
|
38
38
|
### Resources
|
39
39
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
8
8
|
{name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com"}
|
9
9
|
]
|
10
10
|
description = "salabim - discrete event simulation in Python"
|
11
|
-
version = "24.0.
|
11
|
+
version = "24.0.14-4"
|
12
12
|
readme = "README.md"
|
13
13
|
requires-python = ">=3.7"
|
14
14
|
dependencies = [
|
@@ -1,13 +1,13 @@
|
|
1
|
-
# _ _ _ ____ _ _ ___ _
|
2
|
-
# ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | || | / _ \ / ||
|
3
|
-
# / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) || || |_ | | | | | |
|
4
|
-
# \__ \| (_| || || (_| || |_) || || | | | | | / __/ |__ _| _ | |_| | _ | |
|
5
|
-
# |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____| |_| (_) \___/ (_)|_
|
1
|
+
# _ _ _ ____ _ _ ___ _ _ _
|
2
|
+
# ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | || | / _ \ / || || |
|
3
|
+
# / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) || || |_ | | | | | || || |_
|
4
|
+
# \__ \| (_| || || (_| || |_) || || | | | | | / __/ |__ _| _ | |_| | _ | ||__ _|
|
5
|
+
# |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____| |_| (_) \___/ (_)|_| |_|
|
6
6
|
# discrete event simulation
|
7
7
|
#
|
8
8
|
# see www.salabim.org for more information, the documentation and license information
|
9
9
|
|
10
|
-
__version__ = "24.0.
|
10
|
+
__version__ = "24.0.14"
|
11
11
|
|
12
12
|
import heapq
|
13
13
|
import random
|
@@ -9055,7 +9055,6 @@ by adding:
|
|
9055
9055
|
else:
|
9056
9056
|
return
|
9057
9057
|
|
9058
|
-
|
9059
9058
|
def _trywait(self):
|
9060
9059
|
if self.status.value == interrupted:
|
9061
9060
|
return False
|
@@ -10207,7 +10206,7 @@ class Event(Component):
|
|
10207
10206
|
"""
|
10208
10207
|
return self._action_taken
|
10209
10208
|
|
10210
|
-
|
10209
|
+
|
10211
10210
|
class Environment:
|
10212
10211
|
"""
|
10213
10212
|
environment object
|
@@ -11349,13 +11348,13 @@ class Environment:
|
|
11349
11348
|
|
11350
11349
|
if width is not None:
|
11351
11350
|
if self._width != width:
|
11352
|
-
self._width = width
|
11351
|
+
self._width = int(width)
|
11353
11352
|
frame_changed = True
|
11354
11353
|
width_changed = True
|
11355
11354
|
|
11356
11355
|
if height is not None:
|
11357
11356
|
if self._height != height:
|
11358
|
-
self._height = height
|
11357
|
+
self._height = int(height)
|
11359
11358
|
frame_changed = True
|
11360
11359
|
height_changed = True
|
11361
11360
|
|
@@ -11804,7 +11803,7 @@ class Environment:
|
|
11804
11803
|
for ao in an_objects:
|
11805
11804
|
ao.make_pil_image(self.t())
|
11806
11805
|
if ao._image_visible and (include_topleft or not ao.getattr("in_topleft", False)):
|
11807
|
-
image.paste(ao._image, (int(ao._image_x), int(self._height - ao._image_y - ao._image.size[1])), ao._image)
|
11806
|
+
image.paste(ao._image, (int(ao._image_x), int(self._height - ao._image_y - ao._image.size[1])), ao._image.convert("RGBA"))
|
11808
11807
|
|
11809
11808
|
return image.convert(mode)
|
11810
11809
|
|
@@ -12779,6 +12778,31 @@ class Environment:
|
|
12779
12778
|
self.animation_parameters(synced=value, animate=None)
|
12780
12779
|
return self._synced
|
12781
12780
|
|
12781
|
+
def minimized(self, value: bool=None)-> bool:
|
12782
|
+
"""
|
12783
|
+
minimized
|
12784
|
+
|
12785
|
+
Parameters
|
12786
|
+
----------
|
12787
|
+
value : bool
|
12788
|
+
if True, minimize the curent animation window
|
12789
|
+
|
12790
|
+
if False, (re)show the current animation window
|
12791
|
+
|
12792
|
+
if None (default): no action
|
12793
|
+
|
12794
|
+
Returns
|
12795
|
+
-------
|
12796
|
+
current state of the animation window : bool
|
12797
|
+
True if current animation windows is minimized, False otherwise
|
12798
|
+
"""
|
12799
|
+
if value is not None:
|
12800
|
+
if value:
|
12801
|
+
self.root.withdraw()
|
12802
|
+
else:
|
12803
|
+
self.root.deiconify()
|
12804
|
+
return not bool(self.root.winfo_viewable())
|
12805
|
+
|
12782
12806
|
def speed(self, value: float = None) -> float:
|
12783
12807
|
"""
|
12784
12808
|
speed
|
@@ -13201,7 +13225,7 @@ class Environment:
|
|
13201
13225
|
if co is None:
|
13202
13226
|
if len(g.canvas_objects) >= self._maximum_number_of_bitmaps:
|
13203
13227
|
if overflow_image is None:
|
13204
|
-
overflow_image = Image.new("RGBA", (self._width, self._height), (0, 0, 0, 0))
|
13228
|
+
overflow_image = Image.new("RGBA", (int(self._width), int(self._height)), (0, 0, 0, 0))
|
13205
13229
|
overflow_image.paste(ao._image, (int(ao._image_x), int(self._height - ao._image_y - ao._image.size[1])), ao._image)
|
13206
13230
|
ao.canvas_object = None
|
13207
13231
|
else:
|
@@ -15426,10 +15450,20 @@ class Animate2dBase(DynamicClass):
|
|
15426
15450
|
spec = self.image(t)
|
15427
15451
|
image_container = ImageContainer(spec)
|
15428
15452
|
width = self.width(t)
|
15453
|
+
height = self.height(t)
|
15454
|
+
|
15429
15455
|
if width is None:
|
15430
|
-
|
15456
|
+
if height is None:
|
15457
|
+
width = image_container.images[0].size[0]
|
15458
|
+
height = image_container.images[0].size[1]
|
15459
|
+
else:
|
15460
|
+
width = height * image_container.images[0].size[0] / image_container.images[0].size[1]
|
15461
|
+
else:
|
15462
|
+
if height is None:
|
15463
|
+
height = width * image_container.images[0].size[1] / image_container.images[0].size[0]
|
15464
|
+
else:
|
15465
|
+
...
|
15431
15466
|
|
15432
|
-
height = width * image_container.images[0].size[1] / image_container.images[0].size[0]
|
15433
15467
|
if not self.screen_coordinates:
|
15434
15468
|
width *= self.env._scale
|
15435
15469
|
height *= self.env._scale
|
@@ -15448,7 +15482,6 @@ class Animate2dBase(DynamicClass):
|
|
15448
15482
|
offsety = offsety * self.env._scale
|
15449
15483
|
|
15450
15484
|
alpha = int(self.alpha(t))
|
15451
|
-
|
15452
15485
|
image, id = image_container.get_image(
|
15453
15486
|
(t - self.animation_start(t)) * self.animation_speed(t),
|
15454
15487
|
repeat=self.animation_repeat(t),
|
@@ -15456,6 +15489,7 @@ class Animate2dBase(DynamicClass):
|
|
15456
15489
|
t_from=self.animation_from(t),
|
15457
15490
|
t_to=self.animation_to(t),
|
15458
15491
|
)
|
15492
|
+
|
15459
15493
|
self._image_ident = (spec, id, width, height, angle, alpha, flip_horizontal, flip_vertical)
|
15460
15494
|
|
15461
15495
|
if self._image_ident != self._image_ident_prev:
|
@@ -15736,6 +15770,9 @@ class AnimateClassic(Animate2dBase):
|
|
15736
15770
|
def width(self, t):
|
15737
15771
|
return self.master.width(t)
|
15738
15772
|
|
15773
|
+
def height(self, t):
|
15774
|
+
return self.master.height(t)
|
15775
|
+
|
15739
15776
|
def anchor(self, t):
|
15740
15777
|
return self.master.anchor(t)
|
15741
15778
|
|
@@ -15963,6 +16000,11 @@ class Animate:
|
|
15963
16000
|
|
15964
16001
|
if omitted or None, no scaling
|
15965
16002
|
|
16003
|
+
height0 : float
|
16004
|
+
width of the image to be displayed at time t0
|
16005
|
+
|
16006
|
+
if omitted or None, no scaling
|
16007
|
+
|
15966
16008
|
t1 : float
|
15967
16009
|
time of end of the animation (default inf)
|
15968
16010
|
|
@@ -16031,6 +16073,9 @@ class Animate:
|
|
16031
16073
|
width1 : float
|
16032
16074
|
width of the image to be displayed at time t1 (default: width0)
|
16033
16075
|
|
16076
|
+
height1 : float
|
16077
|
+
width of the image to be displayed at time t1 (default: height0)
|
16078
|
+
|
16034
16079
|
over3d : bool
|
16035
16080
|
if True, this object will be rendered to the OpenGL window
|
16036
16081
|
|
@@ -16099,6 +16144,7 @@ class Animate:
|
|
16099
16144
|
font -
|
16100
16145
|
fontsize0,fontsize1 -
|
16101
16146
|
width0,width1 -
|
16147
|
+
height0,height1 -
|
16102
16148
|
====================== ========= ========= ========= ========= ========= =========
|
16103
16149
|
"""
|
16104
16150
|
|
@@ -16134,6 +16180,7 @@ class Animate:
|
|
16134
16180
|
alpha0: float = 255,
|
16135
16181
|
fontsize0: float = 20,
|
16136
16182
|
width0: float = None,
|
16183
|
+
height0: float = None,
|
16137
16184
|
t1: float = None,
|
16138
16185
|
x1: float = None,
|
16139
16186
|
y1: float = None,
|
@@ -16152,6 +16199,7 @@ class Animate:
|
|
16152
16199
|
alpha1: float = None,
|
16153
16200
|
fontsize1: float = None,
|
16154
16201
|
width1: float = None,
|
16202
|
+
height1: float = None,
|
16155
16203
|
xy_anchor: str = "",
|
16156
16204
|
over3d: bool = None,
|
16157
16205
|
flip_horizontal: bool = False,
|
@@ -16198,10 +16246,12 @@ class Animate:
|
|
16198
16246
|
self.text0 = text
|
16199
16247
|
|
16200
16248
|
if image is None:
|
16201
|
-
self.width0 = 0 # just to be able to
|
16249
|
+
self.width0 = 0 # just to be able to interpolat
|
16250
|
+
self.height0 = 0
|
16202
16251
|
else:
|
16203
16252
|
self.image0 = image
|
16204
16253
|
self.width0 = width0 # None means original size
|
16254
|
+
self.height0 = height0
|
16205
16255
|
|
16206
16256
|
self.as_points0 = as_points
|
16207
16257
|
self.font0 = font
|
@@ -16263,7 +16313,7 @@ class Animate:
|
|
16263
16313
|
self.alpha1 = self.alpha0 if alpha1 is None else alpha1
|
16264
16314
|
self.fontsize1 = self.fontsize0 if fontsize1 is None else fontsize1
|
16265
16315
|
self.width1 = self.width0 if width1 is None else width1
|
16266
|
-
|
16316
|
+
self.height1 = self.height0 if height1 is None else height1
|
16267
16317
|
self.t1 = inf if t1 is None else t1
|
16268
16318
|
if self.env._animate_debug:
|
16269
16319
|
self.caller = self.env._frame_to_lineno(_get_caller_frame(), add_filename=True)
|
@@ -16322,6 +16372,7 @@ class Animate:
|
|
16322
16372
|
alpha0=None,
|
16323
16373
|
fontsize0=None,
|
16324
16374
|
width0=None,
|
16375
|
+
height0=None,
|
16325
16376
|
xy_anchor1=None,
|
16326
16377
|
as_points=None,
|
16327
16378
|
t1=None,
|
@@ -16342,6 +16393,7 @@ class Animate:
|
|
16342
16393
|
alpha1=None,
|
16343
16394
|
fontsize1=None,
|
16344
16395
|
width1=None,
|
16396
|
+
height1=None,
|
16345
16397
|
flip_horizontal=None,
|
16346
16398
|
flip_vertical=None,
|
16347
16399
|
animation_start=None,
|
@@ -16494,6 +16546,11 @@ class Animate:
|
|
16494
16546
|
|
16495
16547
|
if None, the original width of the image will be used
|
16496
16548
|
|
16549
|
+
height0 : float
|
16550
|
+
height of the image to be displayed at time t0 (default see below)
|
16551
|
+
|
16552
|
+
if None, the original height of the image will be used
|
16553
|
+
|
16497
16554
|
t1 : float
|
16498
16555
|
time of end of the animation (default: inf)
|
16499
16556
|
|
@@ -16560,6 +16617,8 @@ class Animate:
|
|
16560
16617
|
width1 : float
|
16561
16618
|
width of the image to be displayed at time t1 (default: width0)
|
16562
16619
|
|
16620
|
+
height1 : float
|
16621
|
+
height of the image to be displayed at time t1 (default: height0)
|
16563
16622
|
|
16564
16623
|
Note
|
16565
16624
|
----
|
@@ -16595,6 +16654,8 @@ class Animate:
|
|
16595
16654
|
self.max_lines0 = max_lines
|
16596
16655
|
|
16597
16656
|
self.width0 = self.width() if width0 is None else width0
|
16657
|
+
self.height0 = self.height() if height0 is None else height0
|
16658
|
+
|
16598
16659
|
if image is not None:
|
16599
16660
|
self.image0 = image
|
16600
16661
|
|
@@ -16641,6 +16702,7 @@ class Animate:
|
|
16641
16702
|
self.alpha1 = self.alpha0 if alpha1 is None else alpha1
|
16642
16703
|
self.fontsize1 = self.fontsize0 if fontsize1 is None else fontsize1
|
16643
16704
|
self.width1 = self.width0 if width1 is None else width1
|
16705
|
+
self.height1 = self.height0 if height1 is None else height1
|
16644
16706
|
self.xy_anchor1 = self.xy_anchor0 if xy_anchor1 is None else xy_anchor1
|
16645
16707
|
|
16646
16708
|
self.t1 = inf if t1 is None else t1
|
@@ -16938,7 +17000,7 @@ class Animate:
|
|
16938
17000
|
|
16939
17001
|
def width(self, t=None):
|
16940
17002
|
"""
|
16941
|
-
width
|
17003
|
+
width of an animated image object. May be overridden.
|
16942
17004
|
|
16943
17005
|
Parameters
|
16944
17006
|
----------
|
@@ -16963,6 +17025,33 @@ class Animate:
|
|
16963
17025
|
|
16964
17026
|
return interpolate((self.env._now if t is None else t), self.t0, self.t1, width0, width1)
|
16965
17027
|
|
17028
|
+
def height(self, t=None):
|
17029
|
+
"""
|
17030
|
+
height of an animated image object. May be overridden.
|
17031
|
+
|
17032
|
+
Parameters
|
17033
|
+
----------
|
17034
|
+
t : float
|
17035
|
+
current time
|
17036
|
+
|
17037
|
+
Returns
|
17038
|
+
-------
|
17039
|
+
height : float
|
17040
|
+
default behaviour: linear interpolation between self.height0 and self.height1
|
17041
|
+
|
17042
|
+
if None, the original height of the image will be used
|
17043
|
+
"""
|
17044
|
+
height0 = self.height0
|
17045
|
+
height1 = self.height1
|
17046
|
+
if height0 is None and height1 is None:
|
17047
|
+
return None
|
17048
|
+
if height0 is None:
|
17049
|
+
height0 = ImageContainer(self.image0).images[0].size[0]
|
17050
|
+
if height1 is None:
|
17051
|
+
height1 = ImageContainer(self.image1).images[0].size[0]
|
17052
|
+
|
17053
|
+
return interpolate((self.env._now if t is None else t), self.t0, self.t1, height0, height1)
|
17054
|
+
|
16966
17055
|
def fontsize(self, t=None):
|
16967
17056
|
"""
|
16968
17057
|
fontsize of an animate object. May be overridden.
|
@@ -19656,6 +19745,8 @@ class AnimateImage(Animate2dBase):
|
|
19656
19745
|
width : float
|
19657
19746
|
width of the image (default: None = no scaling)
|
19658
19747
|
|
19748
|
+
heighth : float
|
19749
|
+
height of the image (default: None = no scaling)
|
19659
19750
|
|
19660
19751
|
text : str, tuple or list
|
19661
19752
|
the text to be displayed
|
@@ -19791,6 +19882,7 @@ class AnimateImage(Animate2dBase):
|
|
19791
19882
|
x: Union[float, Callable] = None,
|
19792
19883
|
y: Union[float, Callable] = None,
|
19793
19884
|
width: Union[float, Callable] = None,
|
19885
|
+
height: Union[float, Callable] = None,
|
19794
19886
|
text: Union[str, Callable] = None,
|
19795
19887
|
fontsize: Union[float, Callable] = None,
|
19796
19888
|
textcolor: Union[ColorType, Callable] = None,
|
@@ -19833,6 +19925,7 @@ class AnimateImage(Animate2dBase):
|
|
19833
19925
|
x=0,
|
19834
19926
|
y=0,
|
19835
19927
|
width=None,
|
19928
|
+
height=None,
|
19836
19929
|
text="",
|
19837
19930
|
fontsize=15,
|
19838
19931
|
textcolor="bg",
|
@@ -19915,10 +20008,10 @@ class ComponentGenerator(Component):
|
|
19915
20008
|
|
19916
20009
|
Parameters
|
19917
20010
|
----------
|
19918
|
-
component_class : callable, usually a subclass of Component or Pdf or Cdf distribution
|
20011
|
+
component_class : callable, usually a subclass of Component or Pdf/Pmf or Cdf distribution
|
19919
20012
|
the type of components to be generated
|
19920
20013
|
|
19921
|
-
in case of a distribution, the Pdf or Cdf should return a callable
|
20014
|
+
in case of a distribution, the Pdf/Pmf or Cdf should return a callable
|
19922
20015
|
|
19923
20016
|
generator_name : str
|
19924
20017
|
name of the component generator.
|
@@ -20478,7 +20571,7 @@ class _Distribution:
|
|
20478
20571
|
If, after number_of_tries retries, the sampled value is still not within the given bounds,
|
20479
20572
|
fail_value will be returned
|
20480
20573
|
|
20481
|
-
Samples that cannot be converted (only possible with
|
20574
|
+
Samples that cannot be converted (only possible with /Pmf and CumPdf/CumPmf) to float
|
20482
20575
|
are assumed to be within the bounds.
|
20483
20576
|
"""
|
20484
20577
|
return Bounded(self, lowerbound, upperbound, fail_value, number_of_retries, include_lowerbound, include_upperbound).sample()
|
@@ -20718,7 +20811,7 @@ class Bounded(_Distribution):
|
|
20718
20811
|
If, after number_of_tries retries, the sampled value is still not within the given bounds,
|
20719
20812
|
fail_value will be returned
|
20720
20813
|
|
20721
|
-
Samples that cannot be converted to float (only possible with Pdf and CumPdf)
|
20814
|
+
Samples that cannot be converted to float (only possible with Pdf/Pmf and CumPdf)
|
20722
20815
|
are assumed to be within the bounds.
|
20723
20816
|
"""
|
20724
20817
|
|
@@ -22214,6 +22307,9 @@ class Pdf(_Distribution):
|
|
22214
22307
|
|
22215
22308
|
If it is a salabim distribution, not the distribution,
|
22216
22309
|
but a sample will be returned when calling sample.
|
22310
|
+
|
22311
|
+
|
22312
|
+
This method is also available under the name Pmf
|
22217
22313
|
"""
|
22218
22314
|
|
22219
22315
|
def __init__(self, spec: Union[Iterable, Dict], probabilities=None, time_unit: str = None, randomstream: Any = None, env: "Environment" = None):
|
@@ -22358,9 +22454,151 @@ class Pdf(_Distribution):
|
|
22358
22454
|
return self._mean
|
22359
22455
|
|
22360
22456
|
|
22457
|
+
class Pmf(Pdf):
|
22458
|
+
"""
|
22459
|
+
Probability mass function
|
22460
|
+
|
22461
|
+
Parameters
|
22462
|
+
----------
|
22463
|
+
spec : list, tuple or dict
|
22464
|
+
either
|
22465
|
+
|
22466
|
+
- if no probabilities specified:
|
22467
|
+
|
22468
|
+
list/tuple with x-values and corresponding probability
|
22469
|
+
dict where the keys are re x-values and the values are probabilities
|
22470
|
+
(x0, p0, x1, p1, ...xn,pn)
|
22471
|
+
|
22472
|
+
- if probabilities is specified:
|
22473
|
+
|
22474
|
+
list with x-values
|
22475
|
+
|
22476
|
+
probabilities : iterable or float
|
22477
|
+
if omitted, spec contains the probabilities
|
22478
|
+
|
22479
|
+
the iterable (p0, p1, ...pn) contains the probabilities of the corresponding
|
22480
|
+
x-values from spec.
|
22481
|
+
|
22482
|
+
alternatively, if a float is given (e.g. 1), all x-values
|
22483
|
+
have equal probability. The value is not important.
|
22484
|
+
|
22485
|
+
time_unit : str
|
22486
|
+
specifies the time unit
|
22487
|
+
|
22488
|
+
must be one of "years", "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds"
|
22489
|
+
|
22490
|
+
default : no conversion
|
22491
|
+
|
22492
|
+
|
22493
|
+
randomstream : randomstream
|
22494
|
+
if omitted, random will be used
|
22495
|
+
|
22496
|
+
if used as random.Random(12299)
|
22497
|
+
it assigns a new stream with the specified seed
|
22498
|
+
|
22499
|
+
env : Environment
|
22500
|
+
environment where the distribution is defined
|
22501
|
+
|
22502
|
+
if omitted, default_env will be used
|
22503
|
+
|
22504
|
+
Note
|
22505
|
+
----
|
22506
|
+
p0+p1=...+pn>0
|
22507
|
+
|
22508
|
+
all densities are auto scaled according to the sum of p0 to pn,
|
22509
|
+
so no need to have p0 to pn add up to 1 or 100.
|
22510
|
+
|
22511
|
+
The x-values can be any type.
|
22512
|
+
|
22513
|
+
If it is a salabim distribution, not the distribution,
|
22514
|
+
but a sample will be returned when calling sample.
|
22515
|
+
|
22516
|
+
This method is also available under the name Pdf
|
22517
|
+
|
22518
|
+
"""
|
22519
|
+
|
22520
|
+
def __repr__(self):
|
22521
|
+
return "Pmf"
|
22522
|
+
|
22523
|
+
def print_info(self, as_str: bool = False, file: TextIO = None) -> str:
|
22524
|
+
"""
|
22525
|
+
prints information about the distribution
|
22526
|
+
|
22527
|
+
Parameters
|
22528
|
+
----------
|
22529
|
+
as_str: bool
|
22530
|
+
if False (default), print the info
|
22531
|
+
if True, return a string containing the info
|
22532
|
+
|
22533
|
+
file: file
|
22534
|
+
if None(default), all output is directed to stdout
|
22535
|
+
|
22536
|
+
otherwise, the output is directed to the file
|
22537
|
+
|
22538
|
+
Returns
|
22539
|
+
-------
|
22540
|
+
info (if as_str is True) : str
|
22541
|
+
"""
|
22542
|
+
result = []
|
22543
|
+
result.append("Pmf distribution " + hex(id(self)))
|
22544
|
+
result.append(" randomstream=" + hex(id(self.randomstream)))
|
22545
|
+
return return_or_print(result, as_str, file)
|
22546
|
+
|
22547
|
+
def sample(self, n: int = None) -> Any:
|
22548
|
+
"""
|
22549
|
+
Parameters
|
22550
|
+
----------
|
22551
|
+
n : number of samples : int
|
22552
|
+
if not specified, specifies just return one sample, as usual
|
22553
|
+
|
22554
|
+
if specified, return a list of n sampled values from the distribution without replacement.
|
22555
|
+
This requires that all probabilities are equal.
|
22556
|
+
|
22557
|
+
If n > number of values in the Pmf distribution, n is assumed to be the number of values
|
22558
|
+
in the distribution.
|
22559
|
+
|
22560
|
+
If a sampled value is a distribution, a sample from that distribution will be returned.
|
22561
|
+
|
22562
|
+
Returns
|
22563
|
+
-------
|
22564
|
+
Sample of the distribution : any (usually float) or list
|
22565
|
+
In case n is specified, returns a list of n values
|
22566
|
+
|
22567
|
+
"""
|
22568
|
+
if self.supports_n:
|
22569
|
+
if n is None:
|
22570
|
+
return self.randomstream.sample(self._x, 1)[0]
|
22571
|
+
else:
|
22572
|
+
if n < 0:
|
22573
|
+
raise ValueError("n < 0")
|
22574
|
+
n = min(n, len(self._x))
|
22575
|
+
xs = self.randomstream.sample(self._x, n)
|
22576
|
+
return [x.sample() if isinstance(x, _Distribution) else x for x in xs]
|
22577
|
+
else:
|
22578
|
+
if n is None:
|
22579
|
+
r = self.randomstream.random()
|
22580
|
+
for cum, x in zip([0] + self._cum, [0] + self._x):
|
22581
|
+
if r <= cum:
|
22582
|
+
if isinstance(x, _Distribution):
|
22583
|
+
return x.sample()
|
22584
|
+
return x
|
22585
|
+
else:
|
22586
|
+
raise ValueError("not all probabilities are the same")
|
22587
|
+
|
22588
|
+
def mean(self) -> float:
|
22589
|
+
"""
|
22590
|
+
Returns
|
22591
|
+
-------
|
22592
|
+
mean of the distribution : float
|
22593
|
+
if the mean can't be calculated (if not all x-values are scalars or distributions),
|
22594
|
+
nan will be returned.
|
22595
|
+
"""
|
22596
|
+
return self._mean
|
22597
|
+
|
22598
|
+
|
22361
22599
|
class CumPdf(_Distribution):
|
22362
22600
|
"""
|
22363
|
-
Cumulative Probability
|
22601
|
+
Cumulative Probability mass function
|
22364
22602
|
|
22365
22603
|
Parameters
|
22366
22604
|
----------
|
@@ -22413,6 +22651,8 @@ class CumPdf(_Distribution):
|
|
22413
22651
|
|
22414
22652
|
If it is a salabim distribution, not the distribution,
|
22415
22653
|
but a sample will be returned when calling sample.
|
22654
|
+
|
22655
|
+
This method is also available under the name CumPmf
|
22416
22656
|
"""
|
22417
22657
|
|
22418
22658
|
def __init__(
|
@@ -22528,6 +22768,116 @@ class CumPdf(_Distribution):
|
|
22528
22768
|
return self._mean
|
22529
22769
|
|
22530
22770
|
|
22771
|
+
class CumPmf(CumPdf):
|
22772
|
+
"""
|
22773
|
+
Cumulative Probability mass function
|
22774
|
+
|
22775
|
+
Parameters
|
22776
|
+
----------
|
22777
|
+
spec : list or tuple
|
22778
|
+
either
|
22779
|
+
|
22780
|
+
- if no cumprobabilities specified:
|
22781
|
+
|
22782
|
+
list with x-values and corresponding cumulative probability
|
22783
|
+
(x0, p0, x1, p1, ...xn,pn)
|
22784
|
+
|
22785
|
+
- if cumprobabilities is specified:
|
22786
|
+
|
22787
|
+
list with x-values
|
22788
|
+
|
22789
|
+
cumprobabilities : list, tuple or float
|
22790
|
+
if omitted, spec contains the probabilities
|
22791
|
+
|
22792
|
+
the list (p0, p1, ...pn) contains the cumulative probabilities of the corresponding
|
22793
|
+
x-values from spec.
|
22794
|
+
|
22795
|
+
|
22796
|
+
time_unit : str
|
22797
|
+
specifies the time unit
|
22798
|
+
|
22799
|
+
must be one of "years", "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds"
|
22800
|
+
|
22801
|
+
default : no conversion
|
22802
|
+
|
22803
|
+
|
22804
|
+
randomstream : randomstream
|
22805
|
+
if omitted, random will be used
|
22806
|
+
|
22807
|
+
if used as random.Random(12299)
|
22808
|
+
it assigns a new stream with the specified seed
|
22809
|
+
|
22810
|
+
env : Environment
|
22811
|
+
environment where the distribution is defined
|
22812
|
+
|
22813
|
+
if omitted, default_env will be used
|
22814
|
+
|
22815
|
+
Note
|
22816
|
+
----
|
22817
|
+
p0<=p1<=..pn>0
|
22818
|
+
|
22819
|
+
all densities are auto scaled according to pn,
|
22820
|
+
so no need to have pn be 1 or 100.
|
22821
|
+
|
22822
|
+
The x-values can be any type.
|
22823
|
+
|
22824
|
+
If it is a salabim distribution, not the distribution,
|
22825
|
+
but a sample will be returned when calling sample.
|
22826
|
+
|
22827
|
+
This method is also available under the name CumPdf
|
22828
|
+
"""
|
22829
|
+
|
22830
|
+
def __repr__(self):
|
22831
|
+
return "CumPmf"
|
22832
|
+
|
22833
|
+
def print_info(self, as_str: bool = False, file: TextIO = None) -> str:
|
22834
|
+
"""
|
22835
|
+
prints information about the distribution
|
22836
|
+
|
22837
|
+
Parameters
|
22838
|
+
----------
|
22839
|
+
as_str: bool
|
22840
|
+
if False (default), print the info
|
22841
|
+
if True, return a string containing the info
|
22842
|
+
|
22843
|
+
file: file
|
22844
|
+
if None(default), all output is directed to stdout
|
22845
|
+
|
22846
|
+
otherwise, the output is directed to the file
|
22847
|
+
|
22848
|
+
Returns
|
22849
|
+
-------
|
22850
|
+
info (if as_str is True) : str
|
22851
|
+
"""
|
22852
|
+
result = []
|
22853
|
+
result.append("CumPmf distribution " + hex(id(self)))
|
22854
|
+
result.append(" randomstream=" + hex(id(self.randomstream)))
|
22855
|
+
return return_or_print(result, as_str, file)
|
22856
|
+
|
22857
|
+
def sample(self) -> Any:
|
22858
|
+
"""
|
22859
|
+
Returns
|
22860
|
+
-------
|
22861
|
+
Sample of the distribution : any (usually float)
|
22862
|
+
"""
|
22863
|
+
r = self.randomstream.random()
|
22864
|
+
for cum, x in zip([0] + self._cum, [0] + self._x):
|
22865
|
+
if r <= cum:
|
22866
|
+
if isinstance(x, _Distribution):
|
22867
|
+
return x.sample()
|
22868
|
+
return x
|
22869
|
+
|
22870
|
+
def mean(self) -> float:
|
22871
|
+
"""
|
22872
|
+
Returns
|
22873
|
+
-------
|
22874
|
+
mean of the distribution : float
|
22875
|
+
if the mean can't be calculated (if not all x-values are scalars or distributions),
|
22876
|
+
nan will be returned.
|
22877
|
+
"""
|
22878
|
+
return self._mean
|
22879
|
+
|
22880
|
+
|
22531
22881
|
class External(_Distribution):
|
22532
22882
|
"""
|
22533
22883
|
External distribution function
|
@@ -24127,7 +24477,7 @@ class _APNG:
|
|
24127
24477
|
|
24128
24478
|
def to_bytes(self):
|
24129
24479
|
CHUNK_BEFORE_IDAT = {"cHRM", "gAMA", "iCCP", "sBIT", "sRGB", "bKGD", "hIST", "tRNS", "pHYs", "sPLT", "tIME", "PLTE"}
|
24130
|
-
PNG_SIGN = b"\x89\x50\
|
24480
|
+
PNG_SIGN = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
|
24131
24481
|
out = [PNG_SIGN]
|
24132
24482
|
other_chunks = []
|
24133
24483
|
seq = 0
|
@@ -25060,6 +25410,11 @@ class Animate3dObj(Animate3dBase):
|
|
25060
25410
|
):
|
25061
25411
|
super().__init__(visible=visible, arg=arg, layer=layer, parent=parent, env=env, **kwargs)
|
25062
25412
|
|
25413
|
+
global pywavefront
|
25414
|
+
global visualization
|
25415
|
+
global pyglet
|
25416
|
+
|
25417
|
+
|
25063
25418
|
self.x = x
|
25064
25419
|
self.y = y
|
25065
25420
|
self.z = z
|
@@ -25081,18 +25436,26 @@ class Animate3dObj(Animate3dBase):
|
|
25081
25436
|
self.y_offset = 0
|
25082
25437
|
self.z_offset = 0
|
25083
25438
|
|
25084
|
-
|
25085
|
-
|
25086
|
-
|
25087
|
-
|
25088
|
-
|
25089
|
-
|
25090
|
-
|
25091
|
-
|
25439
|
+
try:
|
25440
|
+
import pywavefront
|
25441
|
+
except ImportError:
|
25442
|
+
pywavefront=None
|
25443
|
+
|
25444
|
+
try:
|
25445
|
+
import pyglet # this is a requirement for visualization!
|
25446
|
+
except ImportError:
|
25447
|
+
pyglet=None
|
25448
|
+
|
25449
|
+
from pywavefront import visualization
|
25092
25450
|
|
25093
25451
|
def draw(self, t):
|
25452
|
+
global pywavefront
|
25453
|
+
global visualization
|
25454
|
+
global pyglet
|
25094
25455
|
if pywavefront is None:
|
25095
25456
|
raise ImportError("Animate3dObj requires pywavefront. Not found")
|
25457
|
+
if pyglet is None:
|
25458
|
+
raise ImportError("Animate3dObj requires pyglet. Not found")
|
25096
25459
|
|
25097
25460
|
obj_filename = Path(self.filename(t))
|
25098
25461
|
if not obj_filename.suffix:
|
@@ -26601,8 +26964,8 @@ def getfont(fontname, fontsize):
|
|
26601
26964
|
return getfont.lookup[(fontname, fontsize)]
|
26602
26965
|
else:
|
26603
26966
|
getfont.lookup = {}
|
26604
|
-
if fontname=="":
|
26605
|
-
a=1
|
26967
|
+
if fontname == "":
|
26968
|
+
a = 1
|
26606
26969
|
if isinstance(fontname, str):
|
26607
26970
|
fontlist1 = [fontname]
|
26608
26971
|
else:
|
@@ -26837,17 +27200,23 @@ def can_animate3d(try_only: bool = True) -> bool:
|
|
26837
27200
|
import OpenGL.GL as gl
|
26838
27201
|
import OpenGL.GLU as glu
|
26839
27202
|
import OpenGL.GLUT as glut
|
27203
|
+
import OpenGL
|
26840
27204
|
except ImportError:
|
26841
27205
|
if try_only:
|
26842
27206
|
return False
|
26843
27207
|
else:
|
26844
27208
|
raise ImportError("OpenGL is required for animation3d. Install with pip install PyOpenGL or see salabim manual")
|
27209
|
+
try:
|
27210
|
+
glut.glutInit()
|
27211
|
+
except OpenGL.error.NullFunctionError:
|
27212
|
+
raise ImportError("Installed OpenGL does not support glut. Try 'pip install OpenGL-glut' or see the salabim documentation")
|
27213
|
+
|
26845
27214
|
return True
|
26846
27215
|
else:
|
26847
27216
|
if try_only:
|
26848
27217
|
return False
|
26849
27218
|
else:
|
26850
|
-
raise ImportError("cannot
|
27219
|
+
raise ImportError("cannot animate, let alone animate3d")
|
26851
27220
|
|
26852
27221
|
|
26853
27222
|
def can_video(try_only: bool = True) -> bool:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: salabim
|
3
|
-
Version: 24.0.
|
3
|
+
Version: 24.0.14.post4
|
4
4
|
Summary: salabim - discrete event simulation in Python
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
6
6
|
Project-URL: Homepage, https://salabim.org
|
@@ -46,7 +46,7 @@ In contrast to some other Python DES packages, salabim does not require the use
|
|
46
46
|
### Features and documentation
|
47
47
|
|
48
48
|
- Cross-platform support: salabim runs on Windows, macOS, Linux, iOS/iPadOS (Pythonista), and can even be used with "Python In Excel".
|
49
|
-
- Comprehensive documentation: Visit [www.salabim.org/manual](www.salabim.org/manual) for detailed documentation.
|
49
|
+
- Comprehensive documentation: Visit [www.salabim.org/manual](https://www.salabim.org/manual) for detailed documentation.
|
50
50
|
|
51
51
|
### Resources
|
52
52
|
|
@@ -1,7 +1,16 @@
|
|
1
|
-
import salabim as sim
|
2
1
|
import pytest
|
3
2
|
import collections
|
3
|
+
from pathlib import Path
|
4
|
+
import os
|
5
|
+
import sys
|
4
6
|
|
7
|
+
if __name__ == "__main__":
|
8
|
+
file_folder = Path(__file__).parent
|
9
|
+
top_folder = (file_folder / "..").resolve()
|
10
|
+
sys.path.insert(0, str(top_folder))
|
11
|
+
os.chdir(file_folder)
|
12
|
+
|
13
|
+
import salabim as sim
|
5
14
|
|
6
15
|
def collect(dis, n=10000):
|
7
16
|
m = sim.Monitor()
|
@@ -85,11 +94,13 @@ def test_int_uniform():
|
|
85
94
|
assert set(m.x()) == {1, 2, 3, 4, 5, 6}
|
86
95
|
count = collections.Counter(m.x())
|
87
96
|
for v, n in count.items():
|
88
|
-
assert n == pytest.approx(10000 / 6, rel=
|
97
|
+
assert n == pytest.approx(10000 / 6, rel=10)
|
89
98
|
|
90
99
|
|
91
100
|
def test_pdf():
|
92
101
|
env = sim.Environment()
|
102
|
+
d=sim.Pdf((1,10,2,70,3,20))
|
103
|
+
assert str(d)=="Pdf"
|
93
104
|
m = collect(sim.Pdf((1,10,2,70,3,20)))
|
94
105
|
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
95
106
|
m = collect(sim.Pdf((1,2,3),(10,70,20)))
|
@@ -100,6 +111,36 @@ def test_pdf():
|
|
100
111
|
m = collect(sim.Pdf(d))
|
101
112
|
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
102
113
|
|
114
|
+
|
115
|
+
def test_pmf():
|
116
|
+
env = sim.Environment()
|
117
|
+
d=sim.Pmf((1,10,2,70,3,20))
|
118
|
+
assert str(d)=="Pmf"
|
119
|
+
m = collect(sim.Pmf((1,10,2,70,3,20)))
|
120
|
+
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
121
|
+
m = collect(sim.Pmf((1,2,3),(10,70,20)))
|
122
|
+
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
123
|
+
d = {1:10, 2:70, 3:20}
|
124
|
+
m = collect(sim.Pmf(d.keys(),d.values()))
|
125
|
+
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
126
|
+
m = collect(sim.Pmf(d))
|
127
|
+
assert m.mean() == pytest.approx(2.1, rel=1e-2)
|
128
|
+
|
129
|
+
def test_cumpdf():
|
130
|
+
env = sim.Environment()
|
131
|
+
d=sim.CumPdf((1, 10, 2, 80, 3,100))
|
132
|
+
assert str(d)=="CumPdf"
|
133
|
+
m = collect(d)
|
134
|
+
assert m.mean() == pytest.approx(2, rel=1e-1)
|
135
|
+
|
136
|
+
def test_cumpmf():
|
137
|
+
env = sim.Environment()
|
138
|
+
d=sim.CumPmf((1, 10, 2, 80, 3,100))
|
139
|
+
assert str(d)=="CumPmf"
|
140
|
+
m = collect(d)
|
141
|
+
assert m.mean() == pytest.approx(2, rel=1e-1)
|
142
|
+
|
143
|
+
|
103
144
|
def test_scipy_distribution():
|
104
145
|
try:
|
105
146
|
import scipy.stats as st
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|