salabim 24.0.11.post1__py3-none-any.whl → 24.0.13__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.11"
10
+ __version__ = "24.0.13"
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)
@@ -5716,15 +5717,19 @@ class Queue:
5716
5717
 
5717
5718
 
5718
5719
  class Store(Queue):
5719
- def __init__(self, name: str = None, capacity: int = inf, env: "Environment" = None, *args, **kwargs):
5720
- super().__init__(name=name, capacity=capacity, env=env, *args, **kwargs)
5720
+ def __init__(self, name: str = None, monitor: Any = True, fill: Iterable = None, capacity: float = inf, env: "Environment" = None, *args, **kwargs) -> None:
5721
+ super().__init__(name=name, monitor=monitor, fill=None, capacity=capacity, env=env, *args, **kwargs)
5722
+
5721
5723
  with self.env.suppress_trace():
5722
5724
  self._to_store_requesters = Queue(f"{name}.to_store_requesters", env=env)
5723
5725
  self._to_store_requesters._isinternal = True
5724
5726
  self._from_store_requesters = Queue(f"{name}.from_store_requesters", env=env)
5725
5727
  self._from_store_requesters._isinternal = True
5726
5728
 
5727
- self.setup(*args, **kwargs)
5729
+ if fill is not None: # this cannot be done by Queue.__init__ as the requesters are not defined at that time
5730
+ with self.env.suppress_trace():
5731
+ for c in fill:
5732
+ c.enter(self)
5728
5733
 
5729
5734
  def set_capacity(self, cap: float) -> None:
5730
5735
  """
@@ -7977,6 +7982,10 @@ by adding:
7977
7982
  ----
7978
7983
  Only if yieldless is False: if to be used for the current component, use ``yield self.cancel()``.
7979
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
7980
7989
  if self.status.value != current:
7981
7990
  self._checkisnotdata()
7982
7991
  self._remove()
@@ -9044,10 +9053,8 @@ by adding:
9044
9053
  self.status._value = waiting
9045
9054
  self._reschedule(scheduled_time, schedule_priority, urgent, "wait", cap_now)
9046
9055
  else:
9047
- return # ***
9048
- if self.env._yieldless:
9049
- if self is self.env._current_component:
9050
- self.env._glet.switch()
9056
+ return
9057
+
9051
9058
 
9052
9059
  def _trywait(self):
9053
9060
  if self.status.value == interrupted:
@@ -9996,6 +10003,211 @@ by adding:
9996
10003
  return None
9997
10004
 
9998
10005
 
10006
+ class Event(Component):
10007
+ """
10008
+ Event object
10009
+
10010
+ An event object is a specialized Component that is usually not subclassed.
10011
+
10012
+ Apart from the usual Component parameters it has an action parameter, to specifies what should
10013
+ happen after becoming active. This action is usually a lambda function.
10014
+
10015
+ Parameters
10016
+ ----------
10017
+ action : callable
10018
+ function called when the component becomes current.
10019
+
10020
+ action_string : str
10021
+ string to be printed in trace when action gets executed (default: "action")
10022
+
10023
+ name : str
10024
+ name of the component.
10025
+
10026
+ if the name ends with a period (.),
10027
+ auto serializing will be applied
10028
+
10029
+ if the name end with a comma,
10030
+ auto serializing starting at 1 will be applied
10031
+
10032
+ if omitted, the name will be derived from the class
10033
+ it is defined in (lowercased)
10034
+
10035
+ at : float or distribution
10036
+ schedule time
10037
+
10038
+ if omitted, now is used
10039
+
10040
+ if distribution, the distribution is sampled
10041
+
10042
+ delay : float or distributiom
10043
+ schedule with a delay
10044
+
10045
+ if omitted, no delay
10046
+
10047
+ if distribution, the distribution is sampled
10048
+
10049
+ priority : float
10050
+ priority
10051
+
10052
+ default: 0
10053
+
10054
+ if a component has the same time on the event list, this component is sorted accoring to
10055
+ the priority.
10056
+
10057
+ urgent : bool
10058
+ urgency indicator
10059
+
10060
+ if False (default), the component will be scheduled
10061
+ behind all other components scheduled
10062
+ for the same time and priority
10063
+
10064
+ if True, the component will be scheduled
10065
+ in front of all components scheduled
10066
+ for the same time and priority
10067
+
10068
+ suppress_trace : bool
10069
+ suppress_trace indicator
10070
+
10071
+ if True, this component will be excluded from the trace
10072
+
10073
+ If False (default), the component will be traced
10074
+
10075
+ Can be queried or set later with the suppress_trace method.
10076
+
10077
+ suppress_pause_at_step : bool
10078
+ suppress_pause_at_step indicator
10079
+
10080
+ if True, if this component becomes current, do not pause when stepping
10081
+
10082
+ If False (default), the component will be paused when stepping
10083
+
10084
+ Can be queried or set later with the suppress_pause_at_step method.
10085
+
10086
+ skip_standby : bool
10087
+ skip_standby indicator
10088
+
10089
+ if True, after this component became current, do not activate standby components
10090
+
10091
+ If False (default), after the component became current activate standby components
10092
+
10093
+ Can be queried or set later with the skip_standby method.
10094
+
10095
+ mode : str preferred
10096
+ mode
10097
+
10098
+ will be used in trace and can be used in animations
10099
+
10100
+ if omitted, the mode will be "".
10101
+
10102
+ also mode_time will be set to now.
10103
+
10104
+ cap_now : bool
10105
+ indicator whether times (at, delay) in the past are allowed. If, so now() will be used.
10106
+ default: sys.default_cap_now(), usualy False
10107
+
10108
+ env : Environment
10109
+ environment where the component is defined
10110
+
10111
+ if omitted, default_env will be used
10112
+ """
10113
+
10114
+ def __init__(
10115
+ self,
10116
+ action: Callable,
10117
+ action_string="action",
10118
+ name: str = None,
10119
+ at: Union[float, Callable] = None,
10120
+ delay: Union[float, Callable] = None,
10121
+ priority: float = None,
10122
+ urgent: bool = None,
10123
+ suppress_trace: bool = False,
10124
+ suppress_pause_at_step: bool = False,
10125
+ skip_standby: bool = False,
10126
+ mode: str = "",
10127
+ cap_now: bool = None,
10128
+ env: "Environment" = None,
10129
+ **kwargs,
10130
+ ):
10131
+ self._action = action
10132
+ self._action_string = action_string
10133
+ self._action_taken = False
10134
+ if env is None:
10135
+ env = g.default_env
10136
+ super().__init__(
10137
+ name=name,
10138
+ at=at,
10139
+ delay=delay,
10140
+ priority=priority,
10141
+ urgent=urgent,
10142
+ suppress_trace=suppress_trace,
10143
+ suppress_pause_at_step=suppress_pause_at_step,
10144
+ skip_standby=skip_standby,
10145
+ mode=mode,
10146
+ cap_now=cap_now,
10147
+ env=env,
10148
+ process="process" if env._yieldless else "process_yield",
10149
+ **kwargs,
10150
+ )
10151
+
10152
+ def process_yield(self):
10153
+ self.env.print_trace("", "", self._action_string, "")
10154
+ self._action()
10155
+ self._action_taken = True
10156
+ return
10157
+ yield 1 # just to make it a generator
10158
+
10159
+ def process(self):
10160
+ self.env.print_trace("", "", self._action_string, "")
10161
+ self._action()
10162
+ self._action_taken = True
10163
+
10164
+ def action(self, value=None):
10165
+ """
10166
+ action
10167
+
10168
+ Parameters
10169
+ ----------
10170
+ value : callable
10171
+ new action callable
10172
+
10173
+ Returns
10174
+ -------
10175
+ current action : callable
10176
+ """
10177
+ if value is not None:
10178
+ self._action = value
10179
+ return self._action
10180
+
10181
+ def action_string(self, value=None):
10182
+ """
10183
+ action_string
10184
+
10185
+ Parameters
10186
+ ----------
10187
+ value : string
10188
+ new action_string
10189
+
10190
+ Returns
10191
+ -------
10192
+ current action_string : string
10193
+ """
10194
+
10195
+ if value is not None:
10196
+ self._action_string = value
10197
+ return self._action_string
10198
+
10199
+ def action_taken(self):
10200
+ """
10201
+ action_taken
10202
+
10203
+ Returns
10204
+ -------
10205
+ action taken: bool
10206
+ True if action has been taken, False if not
10207
+ """
10208
+ return self._action_taken
10209
+
10210
+
9999
10211
  class Environment:
10000
10212
  """
10001
10213
  environment object
@@ -10787,7 +10999,7 @@ class Environment:
10787
10999
  if c.overridden_lineno:
10788
11000
  self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.overridden_lineno))
10789
11001
  else:
10790
- self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt())) # ***
11002
+ self.print_trace(self.time_to_str(self._now - self._offset), c.name(), "current", s0=un_na(c.lineno_txt()))
10791
11003
  if c == self._main:
10792
11004
  self.running = False
10793
11005
  return
@@ -11502,7 +11714,7 @@ class Environment:
11502
11714
  if self._video_pingpong:
11503
11715
  self._images.extend(self._images[::-1])
11504
11716
  if self._video_repeat == 1: # in case of repeat == 1, loop should not be specified (otherwise, it might show twice)
11505
- if PythonInExcel or AnacondaCode: # ***
11717
+ if PythonInExcel or AnacondaCode:
11506
11718
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11507
11719
  self._images[0].save(
11508
11720
  f,
@@ -11523,7 +11735,7 @@ class Environment:
11523
11735
  optimize=False,
11524
11736
  )
11525
11737
  else:
11526
- if PythonInExcel or AnacondaCode: # ***
11738
+ if PythonInExcel or AnacondaCode:
11527
11739
  with b64_file_handler(self._video_name, mode="b", result=_pie_result) as f:
11528
11740
  self._images[0].save(
11529
11741
  f,
@@ -15308,8 +15520,6 @@ class Animate2dBase(DynamicClass):
15308
15520
 
15309
15521
  if not self.screen_coordinates:
15310
15522
  fontsize = fontsize * self.env._scale
15311
- # offsetx = offsetx * self.env._scale # ***
15312
- # offsety = offsety * self.env._scale # ***
15313
15523
  text_anchor = self.text_anchor(t)
15314
15524
 
15315
15525
  if self.attached_to:
@@ -15343,6 +15553,7 @@ class Animate2dBase(DynamicClass):
15343
15553
  qx = (x - self.env._x0) * self.env._scale
15344
15554
  qy = (y - self.env._y0) * self.env._scale
15345
15555
  max_lines = self.max_lines(t)
15556
+
15346
15557
  self._image_ident = (text, fontname, fontsize, angle, textcolor, max_lines)
15347
15558
  if self._image_ident != self._image_ident_prev:
15348
15559
  font, heightA = getfont(fontname, fontsize)
@@ -19818,6 +20029,13 @@ class ComponentGenerator(Component):
19818
20029
 
19819
20030
  e.g. env.main().activate()
19820
20031
 
20032
+ moments : iterable
20033
+ specifies the moments when the components have to be generated. It is not required that these are sorted.
20034
+
20035
+ note that the moments are specified in the current time unit
20036
+
20037
+ cannot be used together with at, delay, till, duration, number, iat,force_at, force_till, disturbance or equidistant
20038
+
19821
20039
  env : Environment
19822
20040
  environment where the component is defined
19823
20041
 
@@ -19846,6 +20064,7 @@ class ComponentGenerator(Component):
19846
20064
  disturbance: Callable = None,
19847
20065
  equidistant: bool = False,
19848
20066
  at_end: Callable = None,
20067
+ moments: Iterable = None,
19849
20068
  env: "Environment" = None,
19850
20069
  **kwargs,
19851
20070
  ):
@@ -19863,6 +20082,15 @@ class ComponentGenerator(Component):
19863
20082
 
19864
20083
  if not callable(component_class):
19865
20084
  raise ValueError("component_class must be a callable")
20085
+ if moments is not None:
20086
+ if any(prop for prop in (at, delay, till, duration, number, iat, force_at, force_till, disturbance, equidistant)):
20087
+ raise ValueError(
20088
+ "specifying at, delay, till,duration, number, iat,force_at, force_till, disturbance or equidistant is not allowed, if moments is specified"
20089
+ )
20090
+ if callable(moments):
20091
+ moments = moments()
20092
+ moments = sorted([env.spec_to_time(moment) for moment in moments])
20093
+
19866
20094
  self.component_class = component_class
19867
20095
  self.iat = iat
19868
20096
  self.disturbance = disturbance
@@ -19905,25 +20133,26 @@ class ComponentGenerator(Component):
19905
20133
  at = None
19906
20134
  process = ""
19907
20135
  else:
19908
- if self.iat is None and not equidistant:
19909
- if till == inf or self.number == inf:
19910
- raise ValueError("iat not specified --> till and number need to be specified")
19911
- if disturbance is not None:
19912
- raise ValueError("iat not specified --> disturbance not allowed")
19913
-
19914
- samples = sorted([Uniform(at, till)() for _ in range(self.number)])
19915
- if force_at or force_till:
19916
- if number == 1:
19917
- if force_at and force_till:
19918
- raise ValueError("force_at and force_till does not allow number=1")
19919
- samples = [at] if force_at else [till]
19920
- else:
19921
- v_at = at if force_at else samples[0]
19922
- v_till = till if force_till else samples[-1]
19923
- min_sample = samples[0]
19924
- max_sample = samples[-1]
19925
- samples = [interpolate(sample, min_sample, max_sample, v_at, v_till) for sample in samples]
19926
- self.intervals = [t1 - t0 for t0, t1 in zip([0] + samples, samples)]
20136
+ if (self.iat is None and not equidistant) or moments:
20137
+ if not moments:
20138
+ if till == inf or self.number == inf:
20139
+ raise ValueError("iat not specified --> till and number need to be specified")
20140
+ if disturbance is not None:
20141
+ raise ValueError("iat not specified --> disturbance not allowed")
20142
+
20143
+ moments = sorted([Uniform(at, till)() for _ in range(self.number)])
20144
+ if force_at or force_till:
20145
+ if number == 1:
20146
+ if force_at and force_till:
20147
+ raise ValueError("force_at and force_till does not allow number=1")
20148
+ moments = [at] if force_at else [till]
20149
+ else:
20150
+ v_at = at if force_at else moments[0]
20151
+ v_till = till if force_till else moments[-1]
20152
+ min_moment = moments[0]
20153
+ max_moment = moments[-1]
20154
+ moments = [interpolate(moment, min_moment, max_moment, v_at, v_till) for moment in moments]
20155
+ self.intervals = [t1 - t0 for t0, t1 in zip([0] + moments, moments)]
19927
20156
  at = self.intervals[0]
19928
20157
  self.intervals[0] = 0
19929
20158
  process = "do_spread_yieldless" if env._yieldless else "do_spread"
@@ -26312,7 +26541,7 @@ def fonts():
26312
26541
 
26313
26542
  for dir, recursive in dir_recursives:
26314
26543
  for file_path in dir.glob("**/*.*" if recursive else "*.*"):
26315
- if file_path.suffix.lower() == ".ttf": # ***
26544
+ if file_path.suffix.lower() == ".ttf":
26316
26545
  file = str(file_path)
26317
26546
  fn = os.path.basename(file).split(".")[0]
26318
26547
  if "_std_fonts" in globals() and fn in _std_fonts(): # test for availabiitly, because of minimized version
@@ -26372,7 +26601,8 @@ def getfont(fontname, fontsize):
26372
26601
  return getfont.lookup[(fontname, fontsize)]
26373
26602
  else:
26374
26603
  getfont.lookup = {}
26375
-
26604
+ if fontname=="":
26605
+ a=1
26376
26606
  if isinstance(fontname, str):
26377
26607
  fontlist1 = [fontname]
26378
26608
  else:
@@ -26405,7 +26635,8 @@ def getfont(fontname, fontsize):
26405
26635
  result = ImageFont.truetype(filename, size=int(fontsize))
26406
26636
  else:
26407
26637
  # refer to https://github.com/python-pillow/Pillow/issues/3730 for explanation (in order to load >= 500 fonts)
26408
- result = ImageFont.truetype(font=io.BytesIO(open(filename, "rb").read()), size=int(fontsize))
26638
+ with open(filename, "rb") as f:
26639
+ result = ImageFont.truetype(font=io.BytesIO(f.read()), size=int(fontsize))
26409
26640
  break
26410
26641
  except Exception:
26411
26642
  raise
@@ -26798,7 +27029,7 @@ class ImageContainer:
26798
27029
  if not (0 <= t_from < t_to):
26799
27030
  raise ValueError(f"animation_from={t_from} not with 0 and animation_to={t_to}")
26800
27031
  if t_to > self._duration:
26801
- raise ValueError(f"animation_to={t_to} > duration={duration}")
27032
+ raise ValueError(f"animation_to={t_to} > duration={self._duration}")
26802
27033
  if pingpong:
26803
27034
  interval = 2 * (t_to - t_from)
26804
27035
  else:
@@ -26944,7 +27175,7 @@ def reset() -> None:
26944
27175
  g.tkinter_loaded = "?"
26945
27176
  g.image_container_cache = {}
26946
27177
  g._default_cap_now = False
26947
- g._captured_stdout=[]
27178
+ g._captured_stdout = []
26948
27179
 
26949
27180
  random_seed() # always start with seed 1234567
26950
27181
 
@@ -27177,7 +27408,6 @@ reset()
27177
27408
  set_environment_aliases()
27178
27409
 
27179
27410
  if __name__ == "__main__":
27180
-
27181
27411
  sys.path.insert(0, str(Path(__file__).parent / ".." / "misc"))
27182
27412
  try:
27183
27413
  import salabim_exp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: salabim
3
- Version: 24.0.11.post1
3
+ Version: 24.0.13
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=NzhLkqRi8niqF9eQQPZRxJueQBROP0084q6qHrY6Qzc,1103953
7
+ salabim-24.0.13.dist-info/METADATA,sha256=-xWemNdEOZ-EbstXw49IqGKhr97mg85QxyuDLa2LtGA,3450
8
+ salabim-24.0.13.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
9
+ salabim-24.0.13.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
+ salabim-24.0.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (75.1.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=8dANioKbD1covX06fZohLeNttQVbalrN6q8eODdZYTU,1097286
7
- salabim-24.0.11.post1.dist-info/METADATA,sha256=g39m6E1GfEa8lmL3Ji7vWbz2MpGvYClI1oedRWT2zQc,3456
8
- salabim-24.0.11.post1.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
9
- salabim-24.0.11.post1.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
- salabim-24.0.11.post1.dist-info/RECORD,,