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.
Files changed (26) hide show
  1. {salabim-24.0.13 → salabim-24.0.14.post4}/PKG-INFO +2 -2
  2. {salabim-24.0.13 → salabim-24.0.14.post4}/README.md +1 -1
  3. {salabim-24.0.13 → salabim-24.0.14.post4}/pyproject.toml +1 -1
  4. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/salabim.py +404 -35
  5. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/PKG-INFO +2 -2
  6. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_distributions.py +43 -2
  7. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/DejaVuSansMono.ttf +0 -0
  8. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/LICENSE.txt +0 -0
  9. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/__init__.py +0 -0
  10. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/calibri.ttf +0 -0
  11. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim/mplus-1m-regular.ttf +0 -0
  12. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/SOURCES.txt +0 -0
  13. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/dependency_links.txt +0 -0
  14. {salabim-24.0.13 → salabim-24.0.14.post4}/salabim.egg-info/top_level.txt +0 -0
  15. {salabim-24.0.13 → salabim-24.0.14.post4}/setup.cfg +0 -0
  16. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test salabim.py +0 -0
  17. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_cap_now.py +0 -0
  18. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_componentgenerator.py +0 -0
  19. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_datetime.py +0 -0
  20. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_misc.py +0 -0
  21. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_monitor.py +0 -0
  22. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_process.py +0 -0
  23. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_queue.py +0 -0
  24. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_state.py +0 -0
  25. {salabim-24.0.13 → salabim-24.0.14.post4}/tests/test_store.py +0 -0
  26. {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.13
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.13"
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.13"
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
- width = image_container.images[0].size[0]
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 interpolate
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 position of an animated image object. May be overridden.
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 Pdf and CumPdf) to float
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 distribution function
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\x4E\x47\x0D\x0A\x1A\x0A"
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
- if "pywavefront" not in sys.modules:
25085
- global pywavefront
25086
- global visualization
25087
- try:
25088
- import pywavefront
25089
- from pywavefront import visualization
25090
- except ImportError:
25091
- pywavefront = None
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 even animate, let alone animate3d")
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.13
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=1e-1)
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