salabim 24.0.12__py3-none-any.whl → 24.0.14.post3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
salabim/salabim.py CHANGED
@@ -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.12"
10
+ __version__ = "24.0.14"
11
11
 
12
12
  import heapq
13
13
  import random
@@ -60,7 +60,7 @@ PyDroid = sys.platform == "linux" and any("pydroid" in v for v in os.environ.val
60
60
  PyPy = platform.python_implementation() == "PyPy"
61
61
  Chromebook = "penguin" in platform.uname()
62
62
  PythonInExcel = not ("__file__" in globals())
63
- AnacondaCode = sys.platform=="emscripten"
63
+ AnacondaCode = sys.platform == "emscripten"
64
64
 
65
65
 
66
66
  def a_log(*args):
@@ -154,8 +154,9 @@ nan = float("nan")
154
154
  if Pythonista or AnacondaCode or PythonInExcel:
155
155
  _yieldless = False
156
156
  else:
157
- _yieldless=True
158
-
157
+ _yieldless = True
158
+
159
+
159
160
  class QueueFullError(Exception):
160
161
  pass
161
162
 
@@ -4144,7 +4145,7 @@ if Pythonista:
4144
4145
  try:
4145
4146
  ims = scene.load_pil_image(capture_image)
4146
4147
  except SystemError:
4147
- im_file = "temp.png" # hack for Pythonista 3.4 ***
4148
+ im_file = "temp.png" # hack for Pythonista 3.4
4148
4149
  capture_image.save(im_file, "PNG")
4149
4150
  ims = scene.load_image_file(im_file)
4150
4151
  scene.image(ims, 0, 0, *capture_image.size)
@@ -5717,7 +5718,7 @@ class Queue:
5717
5718
 
5718
5719
  class Store(Queue):
5719
5720
  def __init__(self, name: str = None, monitor: Any = True, fill: Iterable = None, capacity: float = inf, env: "Environment" = None, *args, **kwargs) -> None:
5720
- super().__init__(name=name, monitor=monitor,fill=None,capacity=capacity, env=env, *args, **kwargs)
5721
+ super().__init__(name=name, monitor=monitor, fill=None, capacity=capacity, env=env, *args, **kwargs)
5721
5722
 
5722
5723
  with self.env.suppress_trace():
5723
5724
  self._to_store_requesters = Queue(f"{name}.to_store_requesters", env=env)
@@ -7981,6 +7982,10 @@ by adding:
7981
7982
  ----
7982
7983
  Only if yieldless is False: if to be used for the current component, use ``yield self.cancel()``.
7983
7984
  """
7985
+ if self.status.value == data:
7986
+ if self.env._trace:
7987
+ self.env.print_trace("", "", "cancel (on data component) " + self.name() + " " + self._modetxt())
7988
+ return
7984
7989
  if self.status.value != current:
7985
7990
  self._checkisnotdata()
7986
7991
  self._remove()
@@ -9048,10 +9053,7 @@ by adding:
9048
9053
  self.status._value = waiting
9049
9054
  self._reschedule(scheduled_time, schedule_priority, urgent, "wait", cap_now)
9050
9055
  else:
9051
- return # ***
9052
- if self.env._yieldless:
9053
- if self is self.env._current_component:
9054
- self.env._glet.switch()
9056
+ return
9055
9057
 
9056
9058
  def _trywait(self):
9057
9059
  if self.status.value == interrupted:
@@ -10000,6 +10002,211 @@ by adding:
10000
10002
  return None
10001
10003
 
10002
10004
 
10005
+ class Event(Component):
10006
+ """
10007
+ Event object
10008
+
10009
+ An event object is a specialized Component that is usually not subclassed.
10010
+
10011
+ Apart from the usual Component parameters it has an action parameter, to specifies what should
10012
+ happen after becoming active. This action is usually a lambda function.
10013
+
10014
+ Parameters
10015
+ ----------
10016
+ action : callable
10017
+ function called when the component becomes current.
10018
+
10019
+ action_string : str
10020
+ string to be printed in trace when action gets executed (default: "action")
10021
+
10022
+ name : str
10023
+ name of the component.
10024
+
10025
+ if the name ends with a period (.),
10026
+ auto serializing will be applied
10027
+
10028
+ if the name end with a comma,
10029
+ auto serializing starting at 1 will be applied
10030
+
10031
+ if omitted, the name will be derived from the class
10032
+ it is defined in (lowercased)
10033
+
10034
+ at : float or distribution
10035
+ schedule time
10036
+
10037
+ if omitted, now is used
10038
+
10039
+ if distribution, the distribution is sampled
10040
+
10041
+ delay : float or distributiom
10042
+ schedule with a delay
10043
+
10044
+ if omitted, no delay
10045
+
10046
+ if distribution, the distribution is sampled
10047
+
10048
+ priority : float
10049
+ priority
10050
+
10051
+ default: 0
10052
+
10053
+ if a component has the same time on the event list, this component is sorted accoring to
10054
+ the priority.
10055
+
10056
+ urgent : bool
10057
+ urgency indicator
10058
+
10059
+ if False (default), the component will be scheduled
10060
+ behind all other components scheduled
10061
+ for the same time and priority
10062
+
10063
+ if True, the component will be scheduled
10064
+ in front of all components scheduled
10065
+ for the same time and priority
10066
+
10067
+ suppress_trace : bool
10068
+ suppress_trace indicator
10069
+
10070
+ if True, this component will be excluded from the trace
10071
+
10072
+ If False (default), the component will be traced
10073
+
10074
+ Can be queried or set later with the suppress_trace method.
10075
+
10076
+ suppress_pause_at_step : bool
10077
+ suppress_pause_at_step indicator
10078
+
10079
+ if True, if this component becomes current, do not pause when stepping
10080
+
10081
+ If False (default), the component will be paused when stepping
10082
+
10083
+ Can be queried or set later with the suppress_pause_at_step method.
10084
+
10085
+ skip_standby : bool
10086
+ skip_standby indicator
10087
+
10088
+ if True, after this component became current, do not activate standby components
10089
+
10090
+ If False (default), after the component became current activate standby components
10091
+
10092
+ Can be queried or set later with the skip_standby method.
10093
+
10094
+ mode : str preferred
10095
+ mode
10096
+
10097
+ will be used in trace and can be used in animations
10098
+
10099
+ if omitted, the mode will be "".
10100
+
10101
+ also mode_time will be set to now.
10102
+
10103
+ cap_now : bool
10104
+ indicator whether times (at, delay) in the past are allowed. If, so now() will be used.
10105
+ default: sys.default_cap_now(), usualy False
10106
+
10107
+ env : Environment
10108
+ environment where the component is defined
10109
+
10110
+ if omitted, default_env will be used
10111
+ """
10112
+
10113
+ def __init__(
10114
+ self,
10115
+ action: Callable,
10116
+ action_string="action",
10117
+ name: str = None,
10118
+ at: Union[float, Callable] = None,
10119
+ delay: Union[float, Callable] = None,
10120
+ priority: float = None,
10121
+ urgent: bool = None,
10122
+ suppress_trace: bool = False,
10123
+ suppress_pause_at_step: bool = False,
10124
+ skip_standby: bool = False,
10125
+ mode: str = "",
10126
+ cap_now: bool = None,
10127
+ env: "Environment" = None,
10128
+ **kwargs,
10129
+ ):
10130
+ self._action = action
10131
+ self._action_string = action_string
10132
+ self._action_taken = False
10133
+ if env is None:
10134
+ env = g.default_env
10135
+ super().__init__(
10136
+ name=name,
10137
+ at=at,
10138
+ delay=delay,
10139
+ priority=priority,
10140
+ urgent=urgent,
10141
+ suppress_trace=suppress_trace,
10142
+ suppress_pause_at_step=suppress_pause_at_step,
10143
+ skip_standby=skip_standby,
10144
+ mode=mode,
10145
+ cap_now=cap_now,
10146
+ env=env,
10147
+ process="process" if env._yieldless else "process_yield",
10148
+ **kwargs,
10149
+ )
10150
+
10151
+ def process_yield(self):
10152
+ self.env.print_trace("", "", self._action_string, "")
10153
+ self._action()
10154
+ self._action_taken = True
10155
+ return
10156
+ yield 1 # just to make it a generator
10157
+
10158
+ def process(self):
10159
+ self.env.print_trace("", "", self._action_string, "")
10160
+ self._action()
10161
+ self._action_taken = True
10162
+
10163
+ def action(self, value=None):
10164
+ """
10165
+ action
10166
+
10167
+ Parameters
10168
+ ----------
10169
+ value : callable
10170
+ new action callable
10171
+
10172
+ Returns
10173
+ -------
10174
+ current action : callable
10175
+ """
10176
+ if value is not None:
10177
+ self._action = value
10178
+ return self._action
10179
+
10180
+ def action_string(self, value=None):
10181
+ """
10182
+ action_string
10183
+
10184
+ Parameters
10185
+ ----------
10186
+ value : string
10187
+ new action_string
10188
+
10189
+ Returns
10190
+ -------
10191
+ current action_string : string
10192
+ """
10193
+
10194
+ if value is not None:
10195
+ self._action_string = value
10196
+ return self._action_string
10197
+
10198
+ def action_taken(self):
10199
+ """
10200
+ action_taken
10201
+
10202
+ Returns
10203
+ -------
10204
+ action taken: bool
10205
+ True if action has been taken, False if not
10206
+ """
10207
+ return self._action_taken
10208
+
10209
+
10003
10210
  class Environment:
10004
10211
  """
10005
10212
  environment object
@@ -10791,7 +10998,7 @@ class Environment:
10791
10998
  if c.overridden_lineno:
10792
10999
  self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.overridden_lineno))
10793
11000
  else:
10794
- self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt())) # ***
11001
+ self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt()))
10795
11002
  if c == self._main:
10796
11003
  self.running = False
10797
11004
  return
@@ -11141,13 +11348,13 @@ class Environment:
11141
11348
 
11142
11349
  if width is not None:
11143
11350
  if self._width != width:
11144
- self._width = width
11351
+ self._width = int(width)
11145
11352
  frame_changed = True
11146
11353
  width_changed = True
11147
11354
 
11148
11355
  if height is not None:
11149
11356
  if self._height != height:
11150
- self._height = height
11357
+ self._height = int(height)
11151
11358
  frame_changed = True
11152
11359
  height_changed = True
11153
11360
 
@@ -11506,7 +11713,7 @@ class Environment:
11506
11713
  if self._video_pingpong:
11507
11714
  self._images.extend(self._images[::-1])
11508
11715
  if self._video_repeat == 1: # in case of repeat == 1, loop should not be specified (otherwise, it might show twice)
11509
- if PythonInExcel or AnacondaCode: # ***
11716
+ if PythonInExcel or AnacondaCode:
11510
11717
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11511
11718
  self._images[0].save(
11512
11719
  f,
@@ -11527,7 +11734,7 @@ class Environment:
11527
11734
  optimize=False,
11528
11735
  )
11529
11736
  else:
11530
- if PythonInExcel or AnacondaCode: # ***
11737
+ if PythonInExcel or AnacondaCode:
11531
11738
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11532
11739
  self._images[0].save(
11533
11740
  f,
@@ -11596,7 +11803,8 @@ class Environment:
11596
11803
  for ao in an_objects:
11597
11804
  ao.make_pil_image(self.t())
11598
11805
  if ao._image_visible and (include_topleft or not ao.getattr("in_topleft", False)):
11599
- 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"),)
11807
+
11600
11808
 
11601
11809
  return image.convert(mode)
11602
11810
 
@@ -12993,7 +13201,7 @@ class Environment:
12993
13201
  if co is None:
12994
13202
  if len(g.canvas_objects) >= self._maximum_number_of_bitmaps:
12995
13203
  if overflow_image is None:
12996
- overflow_image = Image.new("RGBA", (self._width, self._height), (0, 0, 0, 0))
13204
+ overflow_image = Image.new("RGBA", (int(self._width), int(self._height)), (0, 0, 0, 0))
12997
13205
  overflow_image.paste(ao._image, (int(ao._image_x), int(self._height - ao._image_y - ao._image.size[1])), ao._image)
12998
13206
  ao.canvas_object = None
12999
13207
  else:
@@ -15218,10 +15426,20 @@ class Animate2dBase(DynamicClass):
15218
15426
  spec = self.image(t)
15219
15427
  image_container = ImageContainer(spec)
15220
15428
  width = self.width(t)
15429
+ height = self.height(t)
15430
+
15221
15431
  if width is None:
15222
- width = image_container.images[0].size[0]
15432
+ if height is None:
15433
+ width = image_container.images[0].size[0]
15434
+ height = image_container.images[0].size[1]
15435
+ else:
15436
+ width = height * image_container.images[0].size[0] / image_container.images[0].size[1]
15437
+ else:
15438
+ if height is None:
15439
+ height = width * image_container.images[0].size[1] / image_container.images[0].size[0]
15440
+ else:
15441
+ ...
15223
15442
 
15224
- height = width * image_container.images[0].size[1] / image_container.images[0].size[0]
15225
15443
  if not self.screen_coordinates:
15226
15444
  width *= self.env._scale
15227
15445
  height *= self.env._scale
@@ -15240,7 +15458,6 @@ class Animate2dBase(DynamicClass):
15240
15458
  offsety = offsety * self.env._scale
15241
15459
 
15242
15460
  alpha = int(self.alpha(t))
15243
-
15244
15461
  image, id = image_container.get_image(
15245
15462
  (t - self.animation_start(t)) * self.animation_speed(t),
15246
15463
  repeat=self.animation_repeat(t),
@@ -15248,6 +15465,7 @@ class Animate2dBase(DynamicClass):
15248
15465
  t_from=self.animation_from(t),
15249
15466
  t_to=self.animation_to(t),
15250
15467
  )
15468
+
15251
15469
  self._image_ident = (spec, id, width, height, angle, alpha, flip_horizontal, flip_vertical)
15252
15470
 
15253
15471
  if self._image_ident != self._image_ident_prev:
@@ -15312,8 +15530,6 @@ class Animate2dBase(DynamicClass):
15312
15530
 
15313
15531
  if not self.screen_coordinates:
15314
15532
  fontsize = fontsize * self.env._scale
15315
- # offsetx = offsetx * self.env._scale # ***
15316
- # offsety = offsety * self.env._scale # ***
15317
15533
  text_anchor = self.text_anchor(t)
15318
15534
 
15319
15535
  if self.attached_to:
@@ -15347,6 +15563,7 @@ class Animate2dBase(DynamicClass):
15347
15563
  qx = (x - self.env._x0) * self.env._scale
15348
15564
  qy = (y - self.env._y0) * self.env._scale
15349
15565
  max_lines = self.max_lines(t)
15566
+
15350
15567
  self._image_ident = (text, fontname, fontsize, angle, textcolor, max_lines)
15351
15568
  if self._image_ident != self._image_ident_prev:
15352
15569
  font, heightA = getfont(fontname, fontsize)
@@ -15529,6 +15746,9 @@ class AnimateClassic(Animate2dBase):
15529
15746
  def width(self, t):
15530
15747
  return self.master.width(t)
15531
15748
 
15749
+ def height(self, t):
15750
+ return self.master.height(t)
15751
+
15532
15752
  def anchor(self, t):
15533
15753
  return self.master.anchor(t)
15534
15754
 
@@ -15756,6 +15976,11 @@ class Animate:
15756
15976
 
15757
15977
  if omitted or None, no scaling
15758
15978
 
15979
+ height0 : float
15980
+ width of the image to be displayed at time t0
15981
+
15982
+ if omitted or None, no scaling
15983
+
15759
15984
  t1 : float
15760
15985
  time of end of the animation (default inf)
15761
15986
 
@@ -15824,6 +16049,9 @@ class Animate:
15824
16049
  width1 : float
15825
16050
  width of the image to be displayed at time t1 (default: width0)
15826
16051
 
16052
+ height1 : float
16053
+ width of the image to be displayed at time t1 (default: height0)
16054
+
15827
16055
  over3d : bool
15828
16056
  if True, this object will be rendered to the OpenGL window
15829
16057
 
@@ -15892,6 +16120,7 @@ class Animate:
15892
16120
  font -
15893
16121
  fontsize0,fontsize1 -
15894
16122
  width0,width1 -
16123
+ height0,height1 -
15895
16124
  ====================== ========= ========= ========= ========= ========= =========
15896
16125
  """
15897
16126
 
@@ -15927,6 +16156,7 @@ class Animate:
15927
16156
  alpha0: float = 255,
15928
16157
  fontsize0: float = 20,
15929
16158
  width0: float = None,
16159
+ height0: float = None,
15930
16160
  t1: float = None,
15931
16161
  x1: float = None,
15932
16162
  y1: float = None,
@@ -15945,6 +16175,7 @@ class Animate:
15945
16175
  alpha1: float = None,
15946
16176
  fontsize1: float = None,
15947
16177
  width1: float = None,
16178
+ height1: float = None,
15948
16179
  xy_anchor: str = "",
15949
16180
  over3d: bool = None,
15950
16181
  flip_horizontal: bool = False,
@@ -15991,10 +16222,12 @@ class Animate:
15991
16222
  self.text0 = text
15992
16223
 
15993
16224
  if image is None:
15994
- self.width0 = 0 # just to be able to interpolate
16225
+ self.width0 = 0 # just to be able to interpolat
16226
+ self.height0 = 0
15995
16227
  else:
15996
16228
  self.image0 = image
15997
16229
  self.width0 = width0 # None means original size
16230
+ self.height0 = height0
15998
16231
 
15999
16232
  self.as_points0 = as_points
16000
16233
  self.font0 = font
@@ -16056,7 +16289,7 @@ class Animate:
16056
16289
  self.alpha1 = self.alpha0 if alpha1 is None else alpha1
16057
16290
  self.fontsize1 = self.fontsize0 if fontsize1 is None else fontsize1
16058
16291
  self.width1 = self.width0 if width1 is None else width1
16059
-
16292
+ self.height1 = self.height0 if height1 is None else height1
16060
16293
  self.t1 = inf if t1 is None else t1
16061
16294
  if self.env._animate_debug:
16062
16295
  self.caller = self.env._frame_to_lineno(_get_caller_frame(), add_filename=True)
@@ -16115,6 +16348,7 @@ class Animate:
16115
16348
  alpha0=None,
16116
16349
  fontsize0=None,
16117
16350
  width0=None,
16351
+ height0=None,
16118
16352
  xy_anchor1=None,
16119
16353
  as_points=None,
16120
16354
  t1=None,
@@ -16135,6 +16369,7 @@ class Animate:
16135
16369
  alpha1=None,
16136
16370
  fontsize1=None,
16137
16371
  width1=None,
16372
+ height1=None,
16138
16373
  flip_horizontal=None,
16139
16374
  flip_vertical=None,
16140
16375
  animation_start=None,
@@ -16287,6 +16522,11 @@ class Animate:
16287
16522
 
16288
16523
  if None, the original width of the image will be used
16289
16524
 
16525
+ height0 : float
16526
+ height of the image to be displayed at time t0 (default see below)
16527
+
16528
+ if None, the original height of the image will be used
16529
+
16290
16530
  t1 : float
16291
16531
  time of end of the animation (default: inf)
16292
16532
 
@@ -16353,6 +16593,8 @@ class Animate:
16353
16593
  width1 : float
16354
16594
  width of the image to be displayed at time t1 (default: width0)
16355
16595
 
16596
+ height1 : float
16597
+ height of the image to be displayed at time t1 (default: height0)
16356
16598
 
16357
16599
  Note
16358
16600
  ----
@@ -16388,6 +16630,8 @@ class Animate:
16388
16630
  self.max_lines0 = max_lines
16389
16631
 
16390
16632
  self.width0 = self.width() if width0 is None else width0
16633
+ self.height0 = self.height() if height0 is None else height0
16634
+
16391
16635
  if image is not None:
16392
16636
  self.image0 = image
16393
16637
 
@@ -16434,6 +16678,7 @@ class Animate:
16434
16678
  self.alpha1 = self.alpha0 if alpha1 is None else alpha1
16435
16679
  self.fontsize1 = self.fontsize0 if fontsize1 is None else fontsize1
16436
16680
  self.width1 = self.width0 if width1 is None else width1
16681
+ self.height1 = self.height0 if height1 is None else height1
16437
16682
  self.xy_anchor1 = self.xy_anchor0 if xy_anchor1 is None else xy_anchor1
16438
16683
 
16439
16684
  self.t1 = inf if t1 is None else t1
@@ -16731,7 +16976,7 @@ class Animate:
16731
16976
 
16732
16977
  def width(self, t=None):
16733
16978
  """
16734
- width position of an animated image object. May be overridden.
16979
+ width of an animated image object. May be overridden.
16735
16980
 
16736
16981
  Parameters
16737
16982
  ----------
@@ -16756,6 +17001,33 @@ class Animate:
16756
17001
 
16757
17002
  return interpolate((self.env._now if t is None else t), self.t0, self.t1, width0, width1)
16758
17003
 
17004
+ def height(self, t=None):
17005
+ """
17006
+ height of an animated image object. May be overridden.
17007
+
17008
+ Parameters
17009
+ ----------
17010
+ t : float
17011
+ current time
17012
+
17013
+ Returns
17014
+ -------
17015
+ height : float
17016
+ default behaviour: linear interpolation between self.height0 and self.height1
17017
+
17018
+ if None, the original height of the image will be used
17019
+ """
17020
+ height0 = self.height0
17021
+ height1 = self.height1
17022
+ if height0 is None and height1 is None:
17023
+ return None
17024
+ if height0 is None:
17025
+ height0 = ImageContainer(self.image0).images[0].size[0]
17026
+ if height1 is None:
17027
+ height1 = ImageContainer(self.image1).images[0].size[0]
17028
+
17029
+ return interpolate((self.env._now if t is None else t), self.t0, self.t1, height0, height1)
17030
+
16759
17031
  def fontsize(self, t=None):
16760
17032
  """
16761
17033
  fontsize of an animate object. May be overridden.
@@ -19449,6 +19721,8 @@ class AnimateImage(Animate2dBase):
19449
19721
  width : float
19450
19722
  width of the image (default: None = no scaling)
19451
19723
 
19724
+ heighth : float
19725
+ height of the image (default: None = no scaling)
19452
19726
 
19453
19727
  text : str, tuple or list
19454
19728
  the text to be displayed
@@ -19584,6 +19858,7 @@ class AnimateImage(Animate2dBase):
19584
19858
  x: Union[float, Callable] = None,
19585
19859
  y: Union[float, Callable] = None,
19586
19860
  width: Union[float, Callable] = None,
19861
+ height: Union[float, Callable] = None,
19587
19862
  text: Union[str, Callable] = None,
19588
19863
  fontsize: Union[float, Callable] = None,
19589
19864
  textcolor: Union[ColorType, Callable] = None,
@@ -19626,6 +19901,7 @@ class AnimateImage(Animate2dBase):
19626
19901
  x=0,
19627
19902
  y=0,
19628
19903
  width=None,
19904
+ height=None,
19629
19905
  text="",
19630
19906
  fontsize=15,
19631
19907
  textcolor="bg",
@@ -19708,10 +19984,10 @@ class ComponentGenerator(Component):
19708
19984
 
19709
19985
  Parameters
19710
19986
  ----------
19711
- component_class : callable, usually a subclass of Component or Pdf or Cdf distribution
19987
+ component_class : callable, usually a subclass of Component or Pdf/Pmf or Cdf distribution
19712
19988
  the type of components to be generated
19713
19989
 
19714
- in case of a distribution, the Pdf or Cdf should return a callable
19990
+ in case of a distribution, the Pdf/Pmf or Cdf should return a callable
19715
19991
 
19716
19992
  generator_name : str
19717
19993
  name of the component generator.
@@ -19822,6 +20098,13 @@ class ComponentGenerator(Component):
19822
20098
 
19823
20099
  e.g. env.main().activate()
19824
20100
 
20101
+ moments : iterable
20102
+ specifies the moments when the components have to be generated. It is not required that these are sorted.
20103
+
20104
+ note that the moments are specified in the current time unit
20105
+
20106
+ cannot be used together with at, delay, till, duration, number, iat,force_at, force_till, disturbance or equidistant
20107
+
19825
20108
  env : Environment
19826
20109
  environment where the component is defined
19827
20110
 
@@ -19850,6 +20133,7 @@ class ComponentGenerator(Component):
19850
20133
  disturbance: Callable = None,
19851
20134
  equidistant: bool = False,
19852
20135
  at_end: Callable = None,
20136
+ moments: Iterable = None,
19853
20137
  env: "Environment" = None,
19854
20138
  **kwargs,
19855
20139
  ):
@@ -19867,6 +20151,15 @@ class ComponentGenerator(Component):
19867
20151
 
19868
20152
  if not callable(component_class):
19869
20153
  raise ValueError("component_class must be a callable")
20154
+ if moments is not None:
20155
+ if any(prop for prop in (at, delay, till, duration, number, iat, force_at, force_till, disturbance, equidistant)):
20156
+ raise ValueError(
20157
+ "specifying at, delay, till,duration, number, iat,force_at, force_till, disturbance or equidistant is not allowed, if moments is specified"
20158
+ )
20159
+ if callable(moments):
20160
+ moments = moments()
20161
+ moments = sorted([env.spec_to_time(moment) for moment in moments])
20162
+
19870
20163
  self.component_class = component_class
19871
20164
  self.iat = iat
19872
20165
  self.disturbance = disturbance
@@ -19909,25 +20202,26 @@ class ComponentGenerator(Component):
19909
20202
  at = None
19910
20203
  process = ""
19911
20204
  else:
19912
- if self.iat is None and not equidistant:
19913
- if till == inf or self.number == inf:
19914
- raise ValueError("iat not specified --> till and number need to be specified")
19915
- if disturbance is not None:
19916
- raise ValueError("iat not specified --> disturbance not allowed")
19917
-
19918
- samples = sorted([Uniform(at, till)() for _ in range(self.number)])
19919
- if force_at or force_till:
19920
- if number == 1:
19921
- if force_at and force_till:
19922
- raise ValueError("force_at and force_till does not allow number=1")
19923
- samples = [at] if force_at else [till]
19924
- else:
19925
- v_at = at if force_at else samples[0]
19926
- v_till = till if force_till else samples[-1]
19927
- min_sample = samples[0]
19928
- max_sample = samples[-1]
19929
- samples = [interpolate(sample, min_sample, max_sample, v_at, v_till) for sample in samples]
19930
- self.intervals = [t1 - t0 for t0, t1 in zip([0] + samples, samples)]
20205
+ if (self.iat is None and not equidistant) or moments:
20206
+ if not moments:
20207
+ if till == inf or self.number == inf:
20208
+ raise ValueError("iat not specified --> till and number need to be specified")
20209
+ if disturbance is not None:
20210
+ raise ValueError("iat not specified --> disturbance not allowed")
20211
+
20212
+ moments = sorted([Uniform(at, till)() for _ in range(self.number)])
20213
+ if force_at or force_till:
20214
+ if number == 1:
20215
+ if force_at and force_till:
20216
+ raise ValueError("force_at and force_till does not allow number=1")
20217
+ moments = [at] if force_at else [till]
20218
+ else:
20219
+ v_at = at if force_at else moments[0]
20220
+ v_till = till if force_till else moments[-1]
20221
+ min_moment = moments[0]
20222
+ max_moment = moments[-1]
20223
+ moments = [interpolate(moment, min_moment, max_moment, v_at, v_till) for moment in moments]
20224
+ self.intervals = [t1 - t0 for t0, t1 in zip([0] + moments, moments)]
19931
20225
  at = self.intervals[0]
19932
20226
  self.intervals[0] = 0
19933
20227
  process = "do_spread_yieldless" if env._yieldless else "do_spread"
@@ -20253,7 +20547,7 @@ class _Distribution:
20253
20547
  If, after number_of_tries retries, the sampled value is still not within the given bounds,
20254
20548
  fail_value will be returned
20255
20549
 
20256
- Samples that cannot be converted (only possible with Pdf and CumPdf) to float
20550
+ Samples that cannot be converted (only possible with /Pmf and CumPdf/CumPmf) to float
20257
20551
  are assumed to be within the bounds.
20258
20552
  """
20259
20553
  return Bounded(self, lowerbound, upperbound, fail_value, number_of_retries, include_lowerbound, include_upperbound).sample()
@@ -20493,7 +20787,7 @@ class Bounded(_Distribution):
20493
20787
  If, after number_of_tries retries, the sampled value is still not within the given bounds,
20494
20788
  fail_value will be returned
20495
20789
 
20496
- Samples that cannot be converted to float (only possible with Pdf and CumPdf)
20790
+ Samples that cannot be converted to float (only possible with Pdf/Pmf and CumPdf)
20497
20791
  are assumed to be within the bounds.
20498
20792
  """
20499
20793
 
@@ -21989,6 +22283,9 @@ class Pdf(_Distribution):
21989
22283
 
21990
22284
  If it is a salabim distribution, not the distribution,
21991
22285
  but a sample will be returned when calling sample.
22286
+
22287
+
22288
+ This method is also available under the name Pmf
21992
22289
  """
21993
22290
 
21994
22291
  def __init__(self, spec: Union[Iterable, Dict], probabilities=None, time_unit: str = None, randomstream: Any = None, env: "Environment" = None):
@@ -22133,9 +22430,151 @@ class Pdf(_Distribution):
22133
22430
  return self._mean
22134
22431
 
22135
22432
 
22433
+ class Pmf(Pdf):
22434
+ """
22435
+ Probability mass function
22436
+
22437
+ Parameters
22438
+ ----------
22439
+ spec : list, tuple or dict
22440
+ either
22441
+
22442
+ - if no probabilities specified:
22443
+
22444
+ list/tuple with x-values and corresponding probability
22445
+ dict where the keys are re x-values and the values are probabilities
22446
+ (x0, p0, x1, p1, ...xn,pn)
22447
+
22448
+ - if probabilities is specified:
22449
+
22450
+ list with x-values
22451
+
22452
+ probabilities : iterable or float
22453
+ if omitted, spec contains the probabilities
22454
+
22455
+ the iterable (p0, p1, ...pn) contains the probabilities of the corresponding
22456
+ x-values from spec.
22457
+
22458
+ alternatively, if a float is given (e.g. 1), all x-values
22459
+ have equal probability. The value is not important.
22460
+
22461
+ time_unit : str
22462
+ specifies the time unit
22463
+
22464
+ must be one of "years", "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds"
22465
+
22466
+ default : no conversion
22467
+
22468
+
22469
+ randomstream : randomstream
22470
+ if omitted, random will be used
22471
+
22472
+ if used as random.Random(12299)
22473
+ it assigns a new stream with the specified seed
22474
+
22475
+ env : Environment
22476
+ environment where the distribution is defined
22477
+
22478
+ if omitted, default_env will be used
22479
+
22480
+ Note
22481
+ ----
22482
+ p0+p1=...+pn>0
22483
+
22484
+ all densities are auto scaled according to the sum of p0 to pn,
22485
+ so no need to have p0 to pn add up to 1 or 100.
22486
+
22487
+ The x-values can be any type.
22488
+
22489
+ If it is a salabim distribution, not the distribution,
22490
+ but a sample will be returned when calling sample.
22491
+
22492
+ This method is also available under the name Pdf
22493
+
22494
+ """
22495
+
22496
+ def __repr__(self):
22497
+ return "Pmf"
22498
+
22499
+ def print_info(self, as_str: bool = False, file: TextIO = None) -> str:
22500
+ """
22501
+ prints information about the distribution
22502
+
22503
+ Parameters
22504
+ ----------
22505
+ as_str: bool
22506
+ if False (default), print the info
22507
+ if True, return a string containing the info
22508
+
22509
+ file: file
22510
+ if None(default), all output is directed to stdout
22511
+
22512
+ otherwise, the output is directed to the file
22513
+
22514
+ Returns
22515
+ -------
22516
+ info (if as_str is True) : str
22517
+ """
22518
+ result = []
22519
+ result.append("Pmf distribution " + hex(id(self)))
22520
+ result.append(" randomstream=" + hex(id(self.randomstream)))
22521
+ return return_or_print(result, as_str, file)
22522
+
22523
+ def sample(self, n: int = None) -> Any:
22524
+ """
22525
+ Parameters
22526
+ ----------
22527
+ n : number of samples : int
22528
+ if not specified, specifies just return one sample, as usual
22529
+
22530
+ if specified, return a list of n sampled values from the distribution without replacement.
22531
+ This requires that all probabilities are equal.
22532
+
22533
+ If n > number of values in the Pmf distribution, n is assumed to be the number of values
22534
+ in the distribution.
22535
+
22536
+ If a sampled value is a distribution, a sample from that distribution will be returned.
22537
+
22538
+ Returns
22539
+ -------
22540
+ Sample of the distribution : any (usually float) or list
22541
+ In case n is specified, returns a list of n values
22542
+
22543
+ """
22544
+ if self.supports_n:
22545
+ if n is None:
22546
+ return self.randomstream.sample(self._x, 1)[0]
22547
+ else:
22548
+ if n < 0:
22549
+ raise ValueError("n < 0")
22550
+ n = min(n, len(self._x))
22551
+ xs = self.randomstream.sample(self._x, n)
22552
+ return [x.sample() if isinstance(x, _Distribution) else x for x in xs]
22553
+ else:
22554
+ if n is None:
22555
+ r = self.randomstream.random()
22556
+ for cum, x in zip([0] + self._cum, [0] + self._x):
22557
+ if r <= cum:
22558
+ if isinstance(x, _Distribution):
22559
+ return x.sample()
22560
+ return x
22561
+ else:
22562
+ raise ValueError("not all probabilities are the same")
22563
+
22564
+ def mean(self) -> float:
22565
+ """
22566
+ Returns
22567
+ -------
22568
+ mean of the distribution : float
22569
+ if the mean can't be calculated (if not all x-values are scalars or distributions),
22570
+ nan will be returned.
22571
+ """
22572
+ return self._mean
22573
+
22574
+
22136
22575
  class CumPdf(_Distribution):
22137
22576
  """
22138
- Cumulative Probability distribution function
22577
+ Cumulative Probability mass function
22139
22578
 
22140
22579
  Parameters
22141
22580
  ----------
@@ -22188,6 +22627,8 @@ class CumPdf(_Distribution):
22188
22627
 
22189
22628
  If it is a salabim distribution, not the distribution,
22190
22629
  but a sample will be returned when calling sample.
22630
+
22631
+ This method is also available under the name CumPmf
22191
22632
  """
22192
22633
 
22193
22634
  def __init__(
@@ -22303,6 +22744,116 @@ class CumPdf(_Distribution):
22303
22744
  return self._mean
22304
22745
 
22305
22746
 
22747
+ class CumPmf(CumPdf):
22748
+ """
22749
+ Cumulative Probability mass function
22750
+
22751
+ Parameters
22752
+ ----------
22753
+ spec : list or tuple
22754
+ either
22755
+
22756
+ - if no cumprobabilities specified:
22757
+
22758
+ list with x-values and corresponding cumulative probability
22759
+ (x0, p0, x1, p1, ...xn,pn)
22760
+
22761
+ - if cumprobabilities is specified:
22762
+
22763
+ list with x-values
22764
+
22765
+ cumprobabilities : list, tuple or float
22766
+ if omitted, spec contains the probabilities
22767
+
22768
+ the list (p0, p1, ...pn) contains the cumulative probabilities of the corresponding
22769
+ x-values from spec.
22770
+
22771
+
22772
+ time_unit : str
22773
+ specifies the time unit
22774
+
22775
+ must be one of "years", "weeks", "days", "hours", "minutes", "seconds", "milliseconds", "microseconds"
22776
+
22777
+ default : no conversion
22778
+
22779
+
22780
+ randomstream : randomstream
22781
+ if omitted, random will be used
22782
+
22783
+ if used as random.Random(12299)
22784
+ it assigns a new stream with the specified seed
22785
+
22786
+ env : Environment
22787
+ environment where the distribution is defined
22788
+
22789
+ if omitted, default_env will be used
22790
+
22791
+ Note
22792
+ ----
22793
+ p0<=p1<=..pn>0
22794
+
22795
+ all densities are auto scaled according to pn,
22796
+ so no need to have pn be 1 or 100.
22797
+
22798
+ The x-values can be any type.
22799
+
22800
+ If it is a salabim distribution, not the distribution,
22801
+ but a sample will be returned when calling sample.
22802
+
22803
+ This method is also available under the name CumPdf
22804
+ """
22805
+
22806
+ def __repr__(self):
22807
+ return "CumPmf"
22808
+
22809
+ def print_info(self, as_str: bool = False, file: TextIO = None) -> str:
22810
+ """
22811
+ prints information about the distribution
22812
+
22813
+ Parameters
22814
+ ----------
22815
+ as_str: bool
22816
+ if False (default), print the info
22817
+ if True, return a string containing the info
22818
+
22819
+ file: file
22820
+ if None(default), all output is directed to stdout
22821
+
22822
+ otherwise, the output is directed to the file
22823
+
22824
+ Returns
22825
+ -------
22826
+ info (if as_str is True) : str
22827
+ """
22828
+ result = []
22829
+ result.append("CumPmf distribution " + hex(id(self)))
22830
+ result.append(" randomstream=" + hex(id(self.randomstream)))
22831
+ return return_or_print(result, as_str, file)
22832
+
22833
+ def sample(self) -> Any:
22834
+ """
22835
+ Returns
22836
+ -------
22837
+ Sample of the distribution : any (usually float)
22838
+ """
22839
+ r = self.randomstream.random()
22840
+ for cum, x in zip([0] + self._cum, [0] + self._x):
22841
+ if r <= cum:
22842
+ if isinstance(x, _Distribution):
22843
+ return x.sample()
22844
+ return x
22845
+
22846
+ def mean(self) -> float:
22847
+ """
22848
+ Returns
22849
+ -------
22850
+ mean of the distribution : float
22851
+ if the mean can't be calculated (if not all x-values are scalars or distributions),
22852
+ nan will be returned.
22853
+ """
22854
+ return self._mean
22855
+
22856
+
22306
22857
  class External(_Distribution):
22307
22858
  """
22308
22859
  External distribution function
@@ -23902,7 +24453,7 @@ class _APNG:
23902
24453
 
23903
24454
  def to_bytes(self):
23904
24455
  CHUNK_BEFORE_IDAT = {"cHRM", "gAMA", "iCCP", "sBIT", "sRGB", "bKGD", "hIST", "tRNS", "pHYs", "sPLT", "tIME", "PLTE"}
23905
- PNG_SIGN = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"
24456
+ PNG_SIGN = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
23906
24457
  out = [PNG_SIGN]
23907
24458
  other_chunks = []
23908
24459
  seq = 0
@@ -26316,7 +26867,7 @@ def fonts():
26316
26867
 
26317
26868
  for dir, recursive in dir_recursives:
26318
26869
  for file_path in dir.glob("**/*.*" if recursive else "*.*"):
26319
- if file_path.suffix.lower() == ".ttf": # ***
26870
+ if file_path.suffix.lower() == ".ttf":
26320
26871
  file = str(file_path)
26321
26872
  fn = os.path.basename(file).split(".")[0]
26322
26873
  if "_std_fonts" in globals() and fn in _std_fonts(): # test for availabiitly, because of minimized version
@@ -26376,7 +26927,8 @@ def getfont(fontname, fontsize):
26376
26927
  return getfont.lookup[(fontname, fontsize)]
26377
26928
  else:
26378
26929
  getfont.lookup = {}
26379
-
26930
+ if fontname == "":
26931
+ a = 1
26380
26932
  if isinstance(fontname, str):
26381
26933
  fontlist1 = [fontname]
26382
26934
  else:
@@ -26409,7 +26961,8 @@ def getfont(fontname, fontsize):
26409
26961
  result = ImageFont.truetype(filename, size=int(fontsize))
26410
26962
  else:
26411
26963
  # refer to https://github.com/python-pillow/Pillow/issues/3730 for explanation (in order to load >= 500 fonts)
26412
- result = ImageFont.truetype(font=io.BytesIO(open(filename, "rb").read()), size=int(fontsize))
26964
+ with open(filename, "rb") as f:
26965
+ result = ImageFont.truetype(font=io.BytesIO(f.read()), size=int(fontsize))
26413
26966
  break
26414
26967
  except Exception:
26415
26968
  raise
@@ -26948,7 +27501,7 @@ def reset() -> None:
26948
27501
  g.tkinter_loaded = "?"
26949
27502
  g.image_container_cache = {}
26950
27503
  g._default_cap_now = False
26951
- g._captured_stdout=[]
27504
+ g._captured_stdout = []
26952
27505
 
26953
27506
  random_seed() # always start with seed 1234567
26954
27507
 
@@ -27181,7 +27734,6 @@ reset()
27181
27734
  set_environment_aliases()
27182
27735
 
27183
27736
  if __name__ == "__main__":
27184
-
27185
27737
  sys.path.insert(0, str(Path(__file__).parent / ".." / "misc"))
27186
27738
  try:
27187
27739
  import salabim_exp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: salabim
3
- Version: 24.0.12
3
+ Version: 24.0.14.post3
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
@@ -0,0 +1,10 @@
1
+ salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
+ salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
3
+ salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
+ salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
+ salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
+ salabim/salabim.py,sha256=LkXx4KGzg9FaYfe4NUBb2Zm2Iy4v1B2kyHks7cwsWoI,1113664
7
+ salabim-24.0.14.post3.dist-info/METADATA,sha256=4yHEwkcBMUAj2CtFsi3y4s7zkJTzSvLkkm0h3g6vgiI,3456
8
+ salabim-24.0.14.post3.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
9
+ salabim-24.0.14.post3.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
+ salabim-24.0.14.post3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
- salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
3
- salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
- salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
- salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
- salabim/salabim.py,sha256=i5fPqlJSk3PRWFFZ7Z6RVsIWRI0EwQIeaN99fu5mCV0,1097596
7
- salabim-24.0.12.dist-info/METADATA,sha256=PlkVJ2Qk6CFySH31iSJDmGZQ2Yrzolam1TIIuwqQN3A,3450
8
- salabim-24.0.12.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
9
- salabim-24.0.12.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
- salabim-24.0.12.dist-info/RECORD,,