changelog | salabim | discrete event simulation

version 25.0.3 2025-01-24

version 25.0.2 2025-01-18

version 25.0.1 2025-01-17

version 25.0.0 2025-01-15

version 24.0.18 2024-12-22

version 24.0.17 2024-11-19

version 24.0.16 2024-11-14

version 24.0.15 2024-10-30

version 24.0.14 2024-10-22

version 24.0.13 2024-09-23

This will generate Ships at t=10, t=20 and t=50. The times should be specified in the current time unit. The moments do not have to be sorted.

Note that moment cannot be used together with at, delay, till,duration, number, iat,force_at, force_till, disturbance or equidistant

version 24.0.12 2024-08-27

version 24.0.11 2024-08-18

version 24.0.10 2024-08-06

version 24.0.9 2024-06-30

version 24.0.8 2024-06-26

version 24.0.7 2024-06-25

24.0.6 2024-06-20

From this version on, the monitor associated with a state (State.value) has a value property. In previous version only _value was supported.

So now, we can say

as an alternative to

version 24.0.5 2024-05-16

Note that add_now is True by default, whereas in salabim <= 24.0.4 now was not added. (Inspired by a comment by Yin Chi Chang)

Older versions

ple:

Enjoy this new feature!

Changed functionality (0)

The behaviour of setting/getting attributes in AnimateCombined is changed.

If you query an attribute of a combined animation object, it will now return the value of the attribute, only if all combining animation objects (that have this attribute) are the equal. If they are not equal or no attribute is found at all, a ValueError will be raised.

If you set an attribute, it will set that attribute only for combining animation objects that have indeed that attribute present. It is not an error if no attribute is found at all.

Examples: an0 = sim.AnimateRectangle(spec=(-10,-10,10,10)) an1 = sim.AnimateCircle(radius=5, fillcolor="white") an2 = sim.AnimateCircle(radius=10, fillcolor="red")

Changed functionality (1)

sim.Resource now has an initial_claimed_quantity parameter, that can be used only for anonymous resources. If not zero, the resource starts with a quantity in it.

Example: r = sim.Resource("r", capacity=100, initial_claimed_quantity=50, anonymous=True)

Changed functionality (2)

The over3d parameter of sim.AnimateText, sim.AnimateLine, sim.AnimatePoints, sim.AnimatePolygon, sim.AnimateImage, sim.AnimateRectangle is now dynamic.

Bugfix (0)

There was a bug in AnimateLine, AnimateRectangle and AnimatePolygon that prevented from using as_points properly. Fixed.

version 22.0.2 2022-04-30

Added functionality (0)

All methods that set a scheduled time now have a parameter cap_now:

Normally, a scheduled time in the past raises a exception. If cap_now is True, a scheduled time in the past will change that time into now. E.g. yield self.hold(sim.Normal(2,1)) would normally raise an exception after a couple of samples. But yield self.hold(sim.Normal(2,1), cap_now=True) would cap the negative samples to 0, thus avoiding an exception. Be careful using this feature as you might miss a serious problem.

It is even possible to set cap_now globaly with sim.default_cap_now(True).

And there's a context manager sim.cap_now to temporarily override the global default_cap_now: with sim.cap_now(): yield self.hold(-1) This will hold for a duration of 0!

The documentation has a section on this functionality, now.

Added functionality (1)

The ComponentGenerator() class now suppports a disturbance for iat type generators. This can be very useful to define a recurring event (e.g. a regular arrival of a bus), which deviates from the 'timetable'. Like sim.ComponentGenerator(Bus, iat=60, disturbance=sim.Uniform(-1, 5))

Added functionality (2)

ComponentGenerator now supports any callable as the component_class. If disturbance is specified (see above), component_class has to be a subclass of ComponentClass, however.

Added functionality (3)

The function interp offers the same basic functionality as numpy.interp. In fact it uses numpy if available. This can be very useful for animations where the location changes at certain point in time, like AnimationRectangle((-10,-10,10,10), x=100, y=lambda t: sim.interp(t, (0,10,20,30),(100,200,500,1000)))

Added functionality (4)

Salabim can now optionally show times and durations as dates/timedeltas. Therefore, sim.Environment() has a new parameter datetime0. If datetime0 is set to True, the base date will be 1 January 1970. Alternatively, a datetime.datatime date can be given. Note that if no time unit is specified, and datetime0 is given, the time unit will be set to seconds automatically. If datetime0 is set, the methods Environment.time_to_str and Environment.duration_to_str will return a nicely formatted date/time or duration, like Fri 2022-04-29 20:55:06 for a date/time or 00:04:45 for a duration. Note that these are used in the trace and in animations (to show the time). Of course, it is still possible to override these two methods.

Furthermore, the following methods are added to salabim

Particulary datetime_to_t can be very useful to read information from an external source (file, API, ...) and translated into simulation time. In order to output these times, just use t_to_datetime with an appropriate format.

Also, the method Environment.reset_now is now also datetime0 aware.

The documentation has a section on this functionality, now.

Added functionality (5)

Under Pythonista, salabim can now also run correctly in retina mode (native resolution) on iPhones and other screens with a scale factor that is not 2. Note that the buttons are still not shown.

Bug fix (0)

When stopping an animation from the menu (with the Stop button), the SimulationStopped exception was incorrectly not raised in case synced is False. Fixed.

version 22.0.1 2022-02-12

Added functionality (0)

Monitors now have an x_map method, which makes it possible to create a new monitor:

Also new for this version is t_multiply for level monitors. With this method, all t values will be multiplied with a given, positive factor. Example: m = sim.Monitor('m', level=True) m.tally(1) env.run(1) m.tally(2) env.run(1) m.tally(3) env.run(8) print(m.bin_duration(2, 3)) m10 = m.t_multiply(10) print(m10.bin_duration(2, 3)) will print 8.0 80.0

Added functionality (1)

The method Component.line_number() will return the line_number in the process where the component will become current next. This can be extremely useful when debugging a complex model by observing the animation. For data components, the string "N/A" will be returned.

Added functionality (2)

The method Environment.camera_auto_print() can be used to enable/disable printing of the changed camera parameters on a given key. If this parameter is True, the method camera_print will be called upon each and every camera control key, like Up, z, Ctrl-Down, etc. This is useful to make the specification for Environment.camera_move() method (see below).

The output of camera_print, now also includes a timestamp (as a comment), like view(x_eye=-102.5950, y_eye=-97.3592, z_eye=100, x_center=50, y_center=50, z_center=0, field_of_view_y=50) # t=0 view(x_eye=-200,y_eye=-200) # t=2 view(x_eye=-100,y_eye=-100) # t=5

Added functionality (3)

The new method Environment.camera_move() can be used to automatically follow a number of recorded camera positions. These are usualy recorded with Environment.camera_camera_auto_print(True). The captured output can be used as a specification to this method. The movement will be lagged by the (simulation) time specified with lag, so a nice smooth movement is the result, which is particularly useful for presentation quality videos.

Normally, the spec parameter will be a triple quoted string, like: env.camera_move("""\ view(x_eye=-102.5950, y_eye=-97.3592, z_eye=100, x_center=50, y_center=50, z_center=0, field_of_view_y=50) # t=0 view(x_eye=-200,y_eye=-200) # t=2 view(x_eye=-100,y_eye=-100) # t=5 """, lag=1.5)

Added functionality (4)

It is now possible to make the tkinter animation window invisible with the visible parameter of Environment.animation(): env.animation_parameters(visible=False) The same can be realized with Environment.visible: env.visible(False) With the latter method, you can also query the current status.

This functionality can be useful when doing realtime simulations with a custom GUI or similar.

Added functionality (5)

It is now possible to exclude certain animation objects from a video by setting visible to "not in video". Those animation objects are still shown in the normal animation window. E.g. sim.AnimateText(text="this will be visible in the animation window, but not in the video", x=10, y=10, visible="not in video")

Similarly, it also possible to exclude certain animation objects from the animation window, but leave it in the video. E.g. sim.AnimateText(text="this will be visible in the video, but not in the animation window", x=10, y=10, visible="only in video")

Note that this functionality does not apply to 3D animation objects (including over3d animation objects).

Added functionality (6)

The method Environment.is_videoing() can be used to check whether a video is being recorded. This can be useful when running dependent on whether a video is recorded. E.g. filename = ... # if filename == "", run infinitely, otherwise, run for 5 time units. with video(filename): if env.is_videoing(): env.run(5) else: env.run(sim.inf)

Improved functionality (0)

The trace facility now also shows the (next) line number for passivate and standby calls. The same holds for components that are being interrupted or resumed.

Improved functionality (1)

The value of a state in the trace is now printed with repr() instead of str(), so it's easier to see the value (particularly for the null string of strings with only blanks), e.g. 32 s2 create value = '' instead of 32 s2 create value =

Bugfix (0)

A bug in Monitor.sysweight() made that Monitor.x() didn't work properly. Fixed.

Bugfix (1)

The line number of an activated process that was not a generator object (i.e. without a yield) was misaligned. Fixed.

Documentation fix (0)

In several Animation classes the text_offsetx and text_offsety parameters were incorrectly documented as textoffsetx and textoffsety. Fixed.

version 22.0.0 2022-01-09

Added functionality (0)

It is now possible to add a 2D overlay to a 3d animation. This overlay can be extremely useful for showing status information, like the time. And logos and ... Particularly videos can be much more professional and instructive.

All normal 2d animation objects are available for overlaying: Animate, AnimateCircle, AnimateImage, AnimateLine, AnimatePolygon, AnimatePoints, AnimateRectangle and AnimateText Also, the AnimateMonitor and AnimateQueue support overlaying. All these classes have an optional parameter over3d.

Note that the optional parameter defaults to False, but can be overridden with the sim.default_override() function. On top of that, it is possible to use the sim.over3d() context manager to temporarily override the default like, with sim.over3d(): AnimateCircle(radius=50, x=100, y=100) AnimateRectangle((-20, -20, 20, 20), x=100, y=100) These two animation objects will now be shown as an overlay on the 3d animation.

For AnimateQueue, the Component.animation_objects_over3d method should specify the objects , just like Component.animation_objects for ordinary 2d animations. By default, this is a defined as a square (same as in ordinary 2d).

Changed functionality (0)

From now on, there are several ways that resource requests are honoured (specifified in Resource parameters): honor_only_first: if True, only the first of the requesters queue will be honoured honor_only_highest_priority: if True, only components with the priority of the first in requesters will be honoured As both parameters are False by default, existing models will still work as before.

Changed functionality (1)

The way AnimateImage and Animate(image=...) handles the given image spec has been changed, and is much more robust now. That means that an image will not be read util really required. So, if animation is not activated, the image file will not be opened (and could be even absent). Also, read images from a file will be cached automatically, so it is very easy to cycle through several images without a performance problems. It is now possible to say an = sim.AnimateImage(spec="a.png") ... an.spec = "b.png" , so no more need to use sim.spec_to_image(), although that function still exists.

Related to this is that in case no width is given, the width of the image will be assessed as late as possible. The method .width() of an image animation object will return None if no width is specified. If it is required to get the actual (physical) width of a spec, there's now the function sim.spec_to_image_width()

Changed fuctionality (2)

To avoid confusion, it is not allowed anymore to change fps video_width video_height video_mode video_repeat video_pingpong when a video is being recorded.

Changed functionality (3)

When a simulation raised a SimulationStopped exception, a video being recorded will be closed automatically.

Added functionality (0)

An animation can now be stopped with Ctrl-C.

Bugfix (0)

Under Pythonista, changing fonts didn't work properly in version 21.1.7. Fixed.

Bugfix (1)

If Map was used in a specification of a Distribution, an attribute error was raised. Fixed.

Internal change (0)

Salabim now uses f-strings extensively (mainly automatically converted by flynt). This makes reading and maintaining the source easier (and arguably faster).

version 21.1.7 2021-12-13

Added functionality (1)

During an animation, it is now possible to:

Added functionality (0)

Queues can now have limited capacity, by setting the capacity parameter of Queue(), e.g. q = sim.Queue('q', capacity=5) If a queue exceeds that capacity, a QueueFullError exception will be raised. So, it is possible to do try: c.enter(q) except sim.QueueFullError: experienced_full += 1
Queue.capacity is a level monitor that can be changed like q.capacity.value = 10 , so it is also possible to make a normal unrestricted queue into a queue with a limited capacity. q = sim.Queue('q') ... q.capacity.value = 5 Important: if the queue contains more components than the new capacity, all components will stay in the queue.

The methods union, intersection, difference, symmetric_diffence, and the operator +, -, ^, |, & will not use the capacity in any way.

The methods copy and move do not copy the capacity by default, but may do so if copy_capacity=True is given with the call.

Changed functionality (0)

When an animation is stopped, either by the user or an error, now a SimulationStopped exception is raised. This makes that a simulation will not continue in that case, as it did before.

If you just want to be able to stop a run from the animation menu in a clean way, use something like try: env.run() queue.print_histograms() except sim.SimulationStopped: pass

Changed functionality (1)

The s1 parameter in print_trace was always padded to a length of 10.

From now on this length is dynamically defined, although it is highly recommended that the method always returns the same length string.

In relation to this, the Environment.time_to_str_format() method has been phased out.

The same functionality as env.time_to_str_format("{:10.4f}") can be obtained with: sim.Enviroment.time_to_str = lambda self, t: f"{t:10.4f}" or class MyEnvironment(sim.Component): def time_to_str(self, t): return f"{t:10.4f}" ... env = MyEnvironment()

With this change in functionality, it is possible to show the time even as a proper date/time, like sim.Enviroment.time_to_str = lambda self, t: datetime.datetime.utcfromtimestamp(t).strftime("%Y-%m-%d %H:%M:%S")

Note that the Environment.time_to_str() method is also used to show the current time in animations.

Related to this change is a new method Environment.duration_to_str, that is used whenever a duration has to be printed in a trace. This method defaults to a 0.3f formatted string, but may be overridden.

Removed functionality (2)

The height, linecolor and linewidth parameters of AnimateButton were completely ignored (apart from in the Pythonista implementation). Therefore, these have been removed completely. The documentation/docstring will be updated accordingly.

Pythonista specific improvements / bugfixes (0)

Using one of the standard fonts now do not have to call fonts(), thus avoiding a significant delay.

AnimateSlider() now calls the action function (if any) at start up, as with the tkinter version.

The action function in AnimateSlider gets a string as parameter now instead of a float, as with the tkinter version.

Bugfix (0)

A serious bug prevented animations to run properly under Pythonista. Fixed.

Bugfix (1)

Bug in the interpretation of multiple states in Component.wait() fixed.

Bugfix (2)

When producing an animated gif or png video, pingpong was always applied, regardless of the pingpong parameter. Fixed.

Bug fix (3)

When video_repeat was 1 (for animated GIF videos), the video was played twice on certain platforms. Fixed.

Bug fix (4)

The maximum number of fonts that could be use in one animation was approximately 500. From this version on, the number of fonts that can be handled is only limited by the available memory.

Speed improvement(0)

If a font can't be located directly, the salabim and current directory are not searched recursively anymore.

Documentation update (0)

The docstring for Environment.video() now makes clear that the video_width video_height video_mode video_repeat video_pingpong parameters at the time Environment.video is called are used. Any change after will be ignored and only applied to another Environment.video call.

Documentation update (0)

The docstring for Component.request() and Component.wait() now makes clear that the priority parameter refers to the fail event.

version 21.1.6 2021-11-07

Changed functionality (0)

Labels in AnimateMonitor() / Monintor.animate() are now suppressed if the corresponding y-value is outside the 'y range'.

Changed functionality (1)

The default for priority in Environment.run() is now inf (was 0). This makes that, by default, all components scheduled for the end moment will become active before the run ends

Changed functionality (2)

When an_menu() was issued from a program, the simulation wasn't paused until the next tkinter tick. From this version, the simulation will pause exactly at the current time (just prior to the next event). This functionality is also available under Pythonista.

Added checks (0)

A call to Component.wait is now more strictly checked for errors.

Bugfix (0)

In version 21.1.4 a bug was introduced that made it impossible to add audio to a video. Fixed.

Bugfix (1)

Although State.animate was phased out already in version 2.3.0, the State.init method still contained an animation_objects parameter, that is not used anymore. So, both the docstring, documentation and the parameter heading have been updated accordingly.

Bugfix (2)

Bug in Monitor.median caused an error when interpolation was not specified. Fixed.

Bugfix (3)

AnimateSlide did not observe the width and height parameters at all (apart from under Pythonista). Fixed.

Documentation and docstring enhancement (0)

All animation objects now have the remove() method documented.

Documentation bugfix (0)

The default value label_anchor parameter of AnimateMonitor / Monitor.animate was documented to be 'sw', where it should have been 'e'. Fixed.

Internal change (0)

Check for correct interpolation in Monitor.percentile() / Monitor.median() now done at start of the method, thus being more robust.

version 21.1.5 2021-09-01

Changed functionality / bug fix (0)

The Monitor.percentile method has been completely rewritten, to fix bugs and introduce the interpolation parameter.

For non weighted monitors, the method is now exactly equivalent to the numpy percentile function. So it is now possible to use the interpolation parameters as follows: This optional parameter specifies the interpolation method to use when the desired percentile lies between two data points i < j: ‘linear’: i + (j - i) * fraction, where fraction is the fractional part of the index surrounded by i and j ‘lower’: i. ‘higher’: j. ‘nearest’: i or j, whichever is nearest. ‘midpoint’: (i + j) / 2. For weighted and level monitors the behaviour is quite different (there's no equivalent in numpy), as only tallied values will be returned, apart from when the value is undetermined, i.e. on change of value. In that case, the interpolation parameter will be applied as follows: ‘linear’, 'midpoint': mean of the two tallied values ‘lower’: lower tallied value ‘higher’: highed tallie value

In all cases, interpolation defaults to 'linear'.

The test_monitor.py file has been updated to test the new functionality.

Added functionality (0)

The Monitor.median method now also supports the interpolation parameter (see above description of percentile)

Added functionality (1)

The classes AnimateCircle, AnimateImage, AnimateLine, AnimatePolygon, AnimateRectangle, AnimateText, Animate3dBar, Animate3dBox, Animate3dGrid, Animate3dLine, Animate3dObj, Animate3dRectangle, Animate3dSphere and AnimateQueue now have additional method: add_attr .

This is useful to add attribute(s) to an animation object, without having to assign the object a name and assign the attribute(s) in seperate line(s). So, instead of for xx in range(10): an = sim.AnimateRectangle(spec=lambda arg,t: (arg.xx,0,arg.xx+10,arg.xx+t*10)) an.xx = xx we can now say for xx in range(10): sim.AnimateRectangle(spec=lambda arg,t: (arg.xx, 0, arg.xx + 10, arg.xx + t * 10)).add_attr(xx=xx * 20)

It is possible to assign several attributes at once, like: sim.AnimateRectangle(spec=lambda arg,t: (arg.xx, arg.yy,arg.xx + 10,arg.xx + t * 10)).add_attr(xx=xx * 20, yy=xx * 5) , but also like sim.AnimateRectangle(spec=lambda arg,t: (arg.xx, arg.yy,arg.xx + 10, arg.xx + t * 10)).add_attr(xx=xx * 20).add_attr(yy=xx * 5)

The method add_attr just returns the "calling" object, thus allowing "daisy chanining".

Note that it is not allowed to add any attributes that are already defined. So sim.AnimateRectangle(spec=(0, 0, 10, 10).add_attr(x=5) is not allowed as x is already defined by salabim.

Added functionality (2)

The method env.animate() now can use '?', to enable animation, if possible. If not possible, animation is ignored.

The method env.animate3d() now can use '?', to enable 3D-animation, if possible. If not possible, 3D-animation is ignored.

Both functionalities are also available via env.animation_parameters().

Bug fix (0)

The offsetx and offsety parameters of all animation objects were always in screen_coordinates, even if screen_coordinates == False. Fixed.

Documentation bug fix (0)

Docstring of AnimateMonitor and Monitor.animate() updated.

Bug fix (1)

Under Pythonista, 3D animation was just ignored, instead of issueing an error message (as Pythonista does not support OpenGL). Fixed.

version 21.1.4 2021-07-18

New functionality (0)

Introduced Environment.insert_frame() that can insert a PIL image or file (specified as str or pathlib.Path) into the video stream. This is particularly useful to add an introduction and explanatory frames. Note that each frame is 1/30 seconds and you can specify the number of frames. The image is automatically scaled to fit the current video resolution.

New functionality (1)

The method Environment.snapshot() now has an extra parameter 'video_mode' that can be "2d" (the default), "3d" or "screen". Note that "3d" is only available if animate3d is True. The resulting image is never scaled.

Changed functionality (0)

The method Environment.Animate3dObj now postpones the actual loading of the file till the moment it is required. That makes it possible to call Animate3dObj even if no 3d animation is activated and even possible. Also, the filename and show_warnings may now be changed dynamically.

Internal changes (0)

Changed the internals of _save_frame (now via insert_frame) and _capture_image.

Internal changes (1)

If ImageGrab is not available on a platform, video_mode 'screen' is not supported for video and snapshot and properly detected.

version 21.1.3 2021-07-07

New functionality (0)

From this version on, it is possible to select the "video_mode" of a video. You can choose from

New functionality (1)

It is now possible to specify the resolution of the to be recorded video. This can be done with Environment.video_width() and Environment.video_height(). Alternatively, the video_width and video_height parameters of Environment.animation_parameters may be used as well. If the video_width/video_height is "auto" (the default), the resolution is taken from the video_mode at the time video recording was started. The captured images are always scaled to the given resolution, which might lead to horizontal or vertical black bars to pad the image.

Added functionality (0)

The camera control keys in 3d animation now work also when the OpenGL window has the focus.

version 21.1.2 2021-07-03

Bugfix (0)

Version 21.1.1 tried erroneously to import ycecream. This dependency has been removed.

BTW, my ycecream package is a very useful tool for debugging and benchmarking any Python program, including salabim models. See www.github.com/salabim/ycecream , if you are interested.

version 21.1.1 2021-07-02

New functionality (0)

Introduced Animate3dSphere to animate a sphere.

Enhanced functionality (0)

Blind animation now also supports 3D animations. Note that OpenGL needs to be installed, though.

Functional change (0)

The way the mode monitor was implemented made that the garbage collector could never remove a component, thus causing a memory leak. In order to change the behaviour, it won't be possible to set the mode of a component with Component.mode.value = ... anymore (as was mentioned in the 20.0.4 release notes). You have to set the mode via one of the process control functions or with set_mode().

Bugfix (0)

A bug in version 21.1.0 required to have OpenGL installed, even when no 3D animation was used. Fixed. This bugfix also makes startup of non 3D animation models faster.

Bugfix (1)

Under certain conditions, the file/linenumber in the trace was not shown correctly in the trace. Fixed.

Sample models update (0)

Updated several models in the sample models folder.

Documentation update (0)

A section on how to install OpenGL under Windows has been added to the documentation. Here's the same text (slightly differently formatted): If, after
pip install PyOpenGL you get a runtime error that glutInit is not defined, try to install from the Unofficial Windows Binaries for Python Extension Packages site at https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl Find the right 3.1.5 or later version for you (e.g. for Python 3.7, 64 bits you use PyOpenGL-3.1.5-cp37-cp37m-win_amd64.whl) and download that. Then issue pip install wheelfile ,like (for the above package) pip install PyOpenGL-3.1.5-cp37-cp37m-win_amd64.whl and it should work. It is reecommended to install pyopengl_accelerate in the same way, like pip install PyOpenGL_accelerate-3.1.5-cp37-cp37m-win_amd64.whl

version 21.1.0 2021-06-17

Added functionality (0)

This version introduces 3D animation. That's a very powerful feature that requires users at least to know how to do 2D animation.

Note that the API is still work in progress and might change.

Note that the documetation is not properly updated. Only the reference section contains is now up-to-date. I intend to update the documentation soon. Volunteers to help with that are welcome!

The sample 3d models folder on GitHub contains a couple of demo models, that could be used as a start.

Changed functionality (0)

When Stop is pressed from the menu, the program now correctly exits, instead of returning to main.

Changed functionality (1)

Color name 'lightgrey' added, as this is present in the X11 color name standard, but was absent in salabim. Color name 'sandybrown' updated, to be in line with the X11 color name standard

Bugfix (0)

If video specified an extension with a coded, like .mp4+h264, the output file extension incorrectly contained the codec information. From this version on, the codec information is skipped in the resulting filename.

version 21.0.4 2021-05-01

Bugfix (0)

When a non-level monitor was sliced, the stop value was included in the slice, so causing an overlap with a slice that started at that value. Fixed.

Bugfix (1)

When run on a Chromebook (at least on a Lenovo Duet), the menu buttons were far too big. Fixed.

Bugfix (2)

On Linux platforms fonts not having the .ttf extension (particularly .TTF) were not found. Fixed.

Bugfix (3)

PeriodMonitors now get the stats_only flag from the parent monitor. PeriodMonitors now correctly tally the weight.

version 21.0.3 2021-03-20

Added functionality (0)

Environment.snapshot() now also supports the .ico image file format. This may be useful when (mis)using salabim as an icon generator.

Bug fix (0)

Bug in initializing system monitors at init of a component (not setting env to self.env). Fixed.

Bug fix (1)

When producing videos with blind_animation=True (to allow running without tkinter installed), the Environment.animation_pre_tick_sys() method was not called prior to saving a frame, causing problems with animating queues. Fixed.

version 21.0.2 2021-01-25

Improved functionality (0)

The probabilities parameter of sim.Pdf can now be any iterable, so not just list or tuple. This can be useful when using the keys or values of a dict as probabilities.

Improved functionality (1)

The spec parameter of sim.Pdf can now be a dict, where the keys are the x-values and the values are the probabilities. In that case, the probabilities parameter can't be used. E.g. d = sim.Pdf({1:10, 2:70, 3:20}) d = sim.Pdf(dict(red=45, yellow=10, green=45))

Change in distribution (0)

The test scripts are now also available on GitHub (in the /test folder).

Bug fix (0)

Slicing of a merged level monitor didn't work properly, because the attribute start of the merged monitor was not set correctly. Fixed.

 

version 21.0.1 2021-01-14

Bug fix (0)

Bug in Component._remove() fixed.

version 21.0.0 2021-01-14

New functionality (0)

Added Environment.title() With this, the title of the canvas window can be set. The title is set to "", the title will be completely suppressed.

Added Environment.show_menu_buttons() With this, the menu buttons can be hidden

Added parameter title and show_menu_buttons to Environment.animation_parameters

The title of the canvas window now defaults to salabim, instead of tk. This can be overruled with Environment.title()

version 20.0.6 2020-12-18

New functionality (0)

Monitors can now optionally collect statistics only. This minimizes memory usage as individual tallies will not be stored in that case. But beware that some important functionality is not available in stats_only monitors (see below).

You can define the stats_only status at creation time of the monitor, like: m = sim.Monitor('m', stats_only=True)

But, it is also possible to reset a monitor with a different stats_only value. This can be useful if you want a system monitor, like Component.mode or Component.status to not keep individual tallied values: class Car(sim.Component): def setup(self): self.mode.reset(stats_only=True) When stats_only is active, values are always forced to numeric, with a fallback value of 0.

Monitor with stats_only=True support call/get (without arguments), base_name, deregister, duration, duration_zero, maximum, mean, minimum, monitor, name, number_of_entries, number_of_entries_zero, print_histogram, print_histograms, print_statistics, register, rename, reset, reset_monitors, sequence_number, setup, stats_only, std, t, tally, weight, weight_zero

Monitors with stats_only=True do NOT support (these will raise a NotImplementedError): call/get (with an argument), arithmeric operations (+, *, /), animate, bin_duration, bin_number_of_entries, bin_weight, freeze, histogram_autoscale, median, merge, multiply, percentile, slice, slicing, to_days, to_hours, to_microseconds, to_milliseconds, to_minutes, to_seconds, to_time_unit, to_weeks, to_years, tx, value_duration, value_number_of_entries, value_weight, values, x, xduration, xt, xweight

In line with the above, Queue.reset_monitors, Resource.reset_monitors, State.reset_monitors now have a stats_only parameter, with which all monitors can set/reset the stats_only status. So, if you want all eight monitors belonging to a resource, but the requesters' to be stats_only, you can write r.reset_monitors(stats_only=True) r.requesters.monitors(stats_only=False)
The current stats_only mode can be queried with Monitor.stats_only() .

New functionality (1)

Queue.all_monitors() returns all (2) monitors associated with the queue Resource.all_monitors() returns all (8) monitors associated with the resource State.all_monitors() returns all (3) monitors associated with the state

Change of functionality (0)

Monitor.t is no longer a property, but a method. That means that to get the last tally time and value of monitor m, you can write last_tally_t = m.t() last_tally = m() # or last_tally = m.get()

Bug fix (0)

Non-level monitors did not always calculate the ex0=True mean and ex0=True std correctly when weights other than one were in the monitor. Fixed.

version 20.0.5 2020-11-24

Added functionality (0)

The methods Component.request() and Component.wait() now also support the urgent and priority parameters, which make it possible to fine-tune race conditions.

Added functionality (1)

ComponentGenerator can now 'propagate' keyword parameters to the components generated. In line with this, the name parameter is changed to generator_name, in order to make it possible to define the name of the generated components. Example:

Added functionality (2)

sim.Environment() has now an extra parameter, blind_animation that can be used to create videos without showing the animation during production. This is particularly useful when running a simulation on a platform where tkinter is not supported, such a server. This functionality can also be used to slightly increase the performance of video production.
Example usage: try: import tkinter blind_animation = False except ImportError: blind_animation = True env = sim.Environment(blind_animation = blind_animation)

Improved functionality (0)

Monitor.print_histogram() can now show a user specified collection of values. This can be done by giving an iterable to the values parameter. If not all values present in the monitor are given, a value will be shown at the bottom. Like: x0.status.print_histogram(values=["scheduled", "current", "passive"]) may result in Histogram of x.0.status duration 50

By default, the values are shown in the given order, but can also be sorted on value by specifying sort_on_value=True.

Improved functionality (1)

Monitor.print_histogram() with values now supports sorting on weight (=number of entries if all weights are 1) (for non-level monitors) and sorting on duration (for level monitors). Therefore, two parameters have been added to print_histogram(): sort_on_weight and sort_on_duration.

So, x0.status.print_histogram(values=["scheduled", "current", "passive"], sort_on_duration=True) may result in Histogram of x.0.status duration 50

Improved functionality (2)

Monitor.values() now supports sorting on weight (=number of entries if all weights are 1) (for non-level monitors) and sorting on duration (for level monitors). Therefore, two parameters have been added to values(): sort_on_weight and sort_on_duration. So, in the above example, print(x0.status.values()) may result in ['data', 'interrupted', 'passive', 'scheduled'] , whereas print(x0.status.values(sort_on_duration)) may result in ['passive', 'scheduled', data', 'interrupted']

Internal change (0)

The Component.del now explicitly checks for the existence of an _animation_children attribute to prevent an error when deleting a component which is not completely initialized yet.

version 20.0.4 2020-10-26

New functionality (0)

A component now supports a level monitor 'status' that keeps track of all the statuses a component has been in. For instance, component.status.print_histogram(values=True) will print something like Histogram of x.1.status duration 40

And of course, it is possible to get the duration a component was in a certain status, like passive_time = component.status.value_duration(sim.passive). You can even get the status of the component at a moment in the past, with e.g. status_4_units_ago = c1.status(env.now() - 4)

In line with this change, the various statutes (passive, scheduled, ...) are no longer functions, but just strings. So now, therefore it is also possible to say passive_time = component.status.value_duration("passive") This will be quite transparent from the user. Only if the text representation of a status was required, status() had to be called, like print(f"car status={car.status()()}") From this version on, this should read print(f"car status={car.status()}") , which is also more intuitive. Alternatively, you can now also write print(f"car status={car.status.value}")

This makes the methods Component.ispassive(), Component.isscheduled, etc. less required as component.status() == "passive" and component.status() == "scheduled" are arguably easier to understand.

The package salabim now has a function statuses() that returns a tuple with all statuses a component can be in. So print(sim.statuses()) will print ('current', 'data', 'interrupted', 'passive', 'requesting', 'scheduled', 'standby', 'waiting')

New functionality (1)

Component.mode is now a monitor. That makes it possible to get an overview of all modes a component has been in, either as a histogram or an animated monitor. And it is possible to get the duration a component was in a certain mode, with e.g. red_time = c1.mode.value_duration("red") It is even possible to get the mode a component was in at a given moment, like mode_4_units_ago = c1.mode(env.now() - 4) Because of this you can't use mode(x) anymore to set the mode directly. In order to do that, you have to use the new method set_mode: c1.set_mode("green") or c1.mode.value = "green" Both options do store the modetime correctly. Please observe that the initial value for mode is now the null string and not None.

New_functionality (3)

New method: Monitor.values() to assess all values in a monitor. The values returned will be alphabetically sorted (not case sensitive), just as in Monitor.print_histogram(). The method supports both ex0 and force_numeric parameter flags. Example (with same monitor as above): print(x1.status.values()) will print ['data', 'interrupted', 'passive', 'requesting', 'scheduled']

New functionality (4)

The values parameter of print_histogram() can now also be an iterable (i.e. tuple, list or set). In that case, the statistics of only these values will be shown. Example (with same monitor as above): x1.status.print_histogram(values = sim.statuses()) will print Histogram of x.1.status duration 40

Any not shown values will be shown at the bottom as ''. So, again with the same monitor as above: x1.status.print_histogram(values = ("passive", "requesting", "error")) will print Histogram of x.1.status duration 40

New functionality (5)

AnimateMonitor and Monitor.animate has got a new parameter: vertical_map. This parameter should be a function that accepts one argument (the value to be plotted). By default vertical_map is float. The function vertical_map should result in a float or raise a TypeError or ValueError. E.g. to map "red" to the value 1, "blue" to 2, etc., you could provide a mapping function like: vertical_map = "unknown red blue green yellow".split().index Note that in this example any value other than red, blue, green or yellow would map to 0 (via a ValueError exception). This vertical_map function can also be used to map to a logarithmic scale: vertical_map = lambda value_y: math.log(value_y) * 10

New functionality (6)

AnimateMonitor and Monitor.animate can now show labels and corresponding lines. There is full control of the colours, linewidth, fonts, placement, etc. See the docstring or documentation for details. There is a sample model (demo animation of labeled monitors.py) in the sample model folder to illustrate the usage of this new functionality with the new Component.status and Component.mode monitors.

Changed functionality (0)

The tests for which value(s) to include in Montitor.value_number_of_entries() Monitor.value_duration() Monitor.value_weight() are now direct and do not involve string conversion anymore. Normally, this won't cause any compatibility issues.

Distribution change (0)

The setup information (used in PyPI) now includes a set of classifiers as well as requires_python information.

Compatibility change (0)

Salabim requires Python 3.6 from now on.

Internal change (0)

The statuses requesting and waiting were internally stored as scheduled and 'translated' in the status() method. Now, these statuses are natively stored as such (in the status monitor).

Version 20.0.3 2020-08-06

New functionality (0)

The specification of sim.Distribution now also accepts a time_unit parameter. Note that if the specification string contains a time_unit parameter as well, the time_unit parameter of Distribution is ignored. Examples d = sim.Distribution('uniform(1, 2)', time_unit='minutes')) # 1-2 minutes d = sim.Distribution('uniform(1, 2, time_unit='hours')')) # 1-2 hours, same as before d = sim.Distribution('uniform(1, 2, time_unit='hours')', time_unit='minutes')) # 1-2 hours, ignore minutes

New functionality (1)

Monitor.freeze() returns a 'frozen' monitor that can be used to store the results not depending on the current environment. This is particularly useful for pickling a monitor. E.g. use with open("mon.pickle", "wb") as f: pickle.dump(f, mon.freeze()) to save the monitor mon, and with open("mon.pickle", "rb") as f: mon_retrieved = pickle.load(f) to retrieve the monitor, later.

Both level and non-level monitors are supported. Frozen monitors get the name of the original monitor padded with '.frozen' unless specified differently.

New functionality (2)

All Component methods that support urgent to schedule a component now also support a priority parameter. With this it is possible to sort a component before or after other components, scheduled for the same time. Note that urgent only applies to components scheduled with the same time and same priority. The priority is 0 by default. This is particularly useful for race conditions. It is possible to change the priority of a component by cancelling it prior to activating it with another priority.

The priority can be accessed with the new Component.scheduled_priority() method.

Improved functionality (0)

Video production can now be done with a context manager, thus making an explicit final call to video_close obsolete: with env.video('myvideo.mp4'): ... env.run(10)
This will automatically close the file myvideo.mp4 upon leaving the with block.

Change in functionality (0)

In sim.reset(), which is always called at startup, random_seed() will be called without any parameters, causing the random_seed to be set to 1234567. That makes reproducibility even possible when calling env = sim.Environment(random_seed="") # no change in seed

If 'real' random behaviour (dependent on clock ticks) is required, just do: env = sim.Environment(random_seed="*")

Changes in functionality (1)

Queue.extend() does return None now instead of a copy of self, for consistency reasons.

Now queues support all comparisons, i.e. ==, !=, <, <=, >, >= These comparsons are on membership only, i.e they ignore, the order, priorities and name. It is possible to compare a quueu with any object supporting the iter protocol, most notably sets, lists and tuples.

Internal changes /change in functionality (0)

Upon termination of a process, salabim did check a list to see whether there were any animation objects that had this component as it parent. Particularly with many animation objects defined that could take a long time, as reported by Hugo Huges. From this version on, a component itself has a set of its 'animation children'. Also changed is the moment the animation child is removed automatically. When a parent (component) is no longer accessible, it will remove all of its animation children if any. That means we now rely on the automatic garbage collection, which can be slightly delayed. This change means that an animation object with a parent that terminates its process is not necessarily removed, as it can be still in a queue, or even just referenced by a variable. If you use the parent parameter in an Animate, AnimateQueue or AnimateMonitor this might change the behaviour.

Added support files (0)

For the development there's is now a set of pytest files that will result in a more stable product. Indeed several bugs (see below) were detected during this test development. Future versions will include more test scripts.

Bug fix (0)

Minor bug in 'install salabim from github.py' and 'install salabim.py' fixed.

Bug fix (1)

Bug in Component.wait() fixed. Thank you, Alexander Kaiblinger, for detecting the bug and proposing the solution.

Bug fix (2)

Upon honouring an anonymous resource, the statistics of the available_quantity, claimed_quantity and occupancy were not updated. Bug fixed. Thank you, Lukas Hollenstein, for detecting the bug and proposing the solution.

Bug fix (3)

The algorithm used to calculate weighted percentiles in Monitor.percentile() from a stackoverflow.com article was found to be incorrect, thus sometimes resulting in wrong percentiles. Bug fixed.

Bug fix (4)

Minor changes in Queue.iter and Queue.reversed to make iterating when components are added or removed during the iteration more consistent.

version 20.0.2 2020-05-18

New functionality (0)

Component.request() has a new parameter 'oneof'. If oneof=True, the request has to be honoured by just one of the given resources. So, this essentially an or condition. Note that the order of the mentioned resources is the order in which the request will be honoured. It is possible to check which resource has claimed with Component.claimers()

Example: c.request(r1, r2, r3, oneof=True) The request will be honoured if either r1, r2 OR r3 has at least a quantity of one available. This contrast to c.request(r1, r2, r3) , which will be honoured if r1, r2 AND r3 have at least a quantity of one available.

The trace of request and honouring request has been updated accordingly.

Changes in video production (0)

With this version, animated PNGs are supported as well. In contrast to animated GIFs, the background can be transparent (i.e. have an alpha <255).

For the production of individual video frames in jpg, png, bmp, tiff or gif format, the filename now has to contain one asterisk (), which will be replaced by a 6 digit zero-padded serial number at run time, for each frame. E.g. video('.\videos\car.jpg) Filenames without an asteriks are only allowed for real videos or animated gif/png files: videos('.videos\car.png') Note that the directory is now automatically created. Because of the changes, the Environment.delete_video() is now deprecated.

Internally, the video production is now more consistent and easier to maintain.

Improved functionality (0)

Under Linux and Android many more fonts will be available because salabim now searches (recursively) in /usr/share/fonts and /system/fonts for .ttf files as well. As a reminder: it is possible to show all available fonts with sim.show_fonts()

Utility files (0)

The utility 'install.py' is now called 'install salabim.py' and is now fully compatible with pip uninstall and pip update, because a salabim-.dist-info directory is written correctly to the site-packages folder.

New is 'install salabim from github.py' which installs salabim directly from github instead of PyPI.

Support for iPadOS/iOS PyTo (0)

When running under PyTo, a NotImplementedError was raised. Now, salabim can be run on this platform, albeit animation is not supported.

Compatibility (0)

From now on, only Python >=3.4 is supported, particularly because salabim now uses pathlib internally.

Bugfix (0)

In a number of methods, e.g. Queue.init(), the environment of an implied Monitor was set to the default environment instead of the overruled environment.

Bugfix (1)

PeriodMonitor lacked an env parameter. Fixed.

Bugfix (2)

Under certain conditions, an animated GIF was not written correctly as a result of a bug in the optimization of PIL save. From now on, salabim disables the optimization, possibly resulting in slightly larger .GIF files. This change does not apply to Pythonista, which uses another technique to save animated GIFs.

Bugfix (3)

Under Pythonista, video production (animated gif only) did not start at the right time. Fixed.

Internal changes (0)

In order to support the new oneof parameter of request, the data structure of _requests and _claims is now collections.OrderedDict instead of collections.defaultdict.

version 20.0.1 2020-02-27

New functionality (0)

A new class ComponentGenerator is introduced. With this, component can be generated according to a given inter arrival time (distribution) or a random spread over a given interval. Examples: sim.ComponentGenerator(Car, iat=sim.Exponential(2)) # generates Cars according to a Poisson arrival

ComponentGenerator is a subclass of Component and therefore has all the normal properties and methods of an ordinary component, altough it is not recommended to use any the process methods, apart from cancel.

Added functionality (0)

It is now possible to suppress line numbers in the trace. Particularly when the trace output is written to a file, this can result in dramatic (up to 50 times!) performance increase, because the calculation of line numbers is very demanding. Now, therefore a method Environment.suppress_trace_linenumbers() has been introduced. Like: env = sim.Environment(trace=True) env.suppress_trace_linenumbers(True) By default, line numbers are not suppressed in the trace. The current status can be queried with print(env.suppress_trace_linenumbers())

version 20.0.0 2020-02-10

Announcement (0)

Salabim now runs fully also on Android platforms under the excellent Pydroid3 app https://play.google.com/store/apps/details?id=ru.iiec.pydroid3&hl=en. In order to use animation on that platform, it is required to import tkinter in the main program , otherwise you will get a salabim error message if you try and start an animation.

With the PyDroid3 Premium version, your can also produce animation videos, albeit only .avi files.

AnimateButton does not properly yet and therefore the animation navigation buttons are not placed correctly and in fact not usable. We plan a fix for this in a future version of salabim.

New functionality (0)

An AnimateQueue object now has a queue() method that returns the queue it refers to. This is useful in animation_objects() that want to check the queue (in the id parameter).

Improved functionality (0)

Normal video production (i.e. not .gif, .jpg, .bmp, .tiff, .png) is now done via a temporary file. That has the effect that if the video production is not closed properly, the video is not written at all, thus making the production process more stable. It is necessary to always close the video production with env.video("")

Changed functionality (0)

If a video name with extension .avi is to be produced (with env.video()), the default codec is now MJPG. For all other extensions, the default codec remains MP4V.

Utility update (0)

The install.py utility is changed internally. Also, it shows now the location where salabim was installed.

Documentation (0)

The width parameter of AnimateImage was not documented. Fixed.

Documentation (1)

Closing a video is now documented (see also above).

Bug fix (0)

Minor bug in video production, when animation was turned off and on during the simulation. Fixed.

version 19.0.10 2019-12-18

New functionality (0)

Map distribution allows sampled values from a distribution to be mapped to a given function. E.g. round_normal = sim.Map(sim.Normal(10, 4), lambda x: round(x)) or, equivalently: round_normal = sim.Map(sim.Normal(10, 4), round) The sampled values from the normal distribution will be rounded. Another example: positive_normal = sim.Map(sim.Normal(10, 4), lambda x: x if x > 0 else 0) Negative sampled values will be set to zero, positive values are not affected. Note that this is different from sim.Bounded(sim.Normal(10, 4), lowerbound=0) as in the latter case resampling will be done when a negative value is sampled, in other words, virtually no zero samples.

New functionality (1)

Introduced an AnimateCombined class with which it is possible to combine several instances of AnimateText, AnimateCircle, AnimateRectangle, AnimatePolygon, AnimateLine, AnimatePoints, AnimateImage or another AnimateCombined. AnimateCombined works as a list, so can be filled by initializing with a list of Animatexxx instances, like a = sim.AnimateCombined((a0, a1, a2)) , but can also be build up with a.append(a3) a.extend((a4, a5)) a += a6 etc. It is then possible to set an attribute of the combined animation objects, like a.x = 100 This will set the x for all animation objects to 100. It is required that each of animation objects in an AnimatCombined instance supports the attribute to be set! That means that you cannot set the radius for an AnimateCombined instance with an` AnimateCircle and an AnimateRectangle, as the latter does not support radius. If an attribute of an AnimateCombined instance is queried, the value for the first animation object will be returned. It is possible to put AnimateCombined instances in another AnimateCombined object.

The remove method applied to AnimateCombined objects will remove all the included animation objects.

Changed functionality (0

In version 19.0.6 the following functionality was introduced: If an error occurred in the make_pil_image method during an animation, it was sometimes difficult to find out the reason for that error. From this version on, the line number (and the filename) where the animation object was created will be added to the error message, which makes debugging hopefully easier. However, this feature sometimes appears to significantly slow down animations. Therefore, this feature is now disabled by default. If required (particularly when finding a complicated error), it is possible to enable the source location tracking. The method Environment.animation_parameters() has therefore now a new parameter: animate_debug, which is False by default. Alternatively, the method Environment.animate_debug() can be used.

Changed functionality (1)

sim.Environment() now automatically resets the simulation environment when run under Pythonista. A parameter 'do_reset' to sim.Environment() allows the user to force a reset as well, or to indicate that no reset should be executed under Pythonista.

Internal change (0)

The install.py file has been internally changed to support other single source packages. No functional changes.

version 19.0.9 2019-10-08

New functionality (0)

Introduced preemptive resources

It is now possible to specify that a resource is to be preemptive, by adding preemptive=True when the resource is created. If a component requests from a preemptive resource, it may bump component(s) that are claiming from the resource, provided these have a lower priority = higher value). If component is bumped, it releases the resource and is the activated, thus essentially stopping the current action (usually hold or passivate). Therefore, it is necessary that a component claiming from a preemptive resource should check whether the component is bumped or still claiming at any point where they can be bumped. This can be done with the method Component.isclaiming which is True if the component is claiming from the resource, or the opposite (Component.isbumped) which is True is the component is not claiming from te resource.

E.g. if the component has to start all over again (hold(1)) if it is bumped: def process(self): prio = sim.Pdf((1,2,3), 1) while True: yield self.request((preemptive_resource, 1, prio) yield self.hold(1) if self.isclaiming(preemptive_resource): break self.release(preemptive_resource)
E.g. if the component just has to 'complete' the hold time: def process(self): prio = sim.Pdf((1,2,3), 1) remaining = 1 while True: yield self.request((preemptive_resource, 1, prio) yield self.hold(remaining, mode='') if self.isclaiming(preemptive_resource): break remaining -= (env.now() - self.mode_time()) self.release(preemptive_resource)

Note that a component that hasn't requested at all from a resource, it is considered as bumped, therefore the above example can be rewritten as: def process(self): prio = sim.Pdf((1,2,3), 1) remaining = 1 while isbumped(preemptive_resource): yield self.request((preemptive_resource, 1, prio) yield self.hold(remaining, mode='') remaining -= (env.now() - self.modetime()) self.release(preemptive_resource)
Finally, if the component is dealing with just one preemptive resource (which is very likely), the isbumped() and isclaiming() methods can be used without an argument: prio = sim.Pdf((1,2,3), 1) remaining = 1 while isbumped(): yield self.request((preemptive_resource, 1, prio) yield self.hold(remaining, mode='') remaining -= (env.now() - self.modetime()) self.release(preemptive_resource)
If a request for a preemptive resource is made, it is not possible to combine that request with any other resource.

The method Resource.ispreemptive() can be used to check the whether a resource is preemptive.

There is an animated demo showing nicely the difference between preemptive and non preemptive resources See salabim./sample models/Demo preemptive resources animated.py.

New functionality (1)

In the trace, the time of rescheduling of Component initialization, activate, hold, request, wait and run the time was always shown in the information field of the trace as 'scheduled for xxx'. From this version, also the delta time (if not 0 of inf) is shown as +xxx behind the action, e.g.

75+ 1.000 main current 76 client.0 create 76 client.0 activate scheduled for 1.000 @ 59 process=process 77 client.1 create 77 client.1 activate +1.000 scheduled for 2.000 @ 59 process=process 78 client.2 create 78 client.2 activate +3.000 scheduled for 4.000 @ 59 process=process

New functionality (2)

The trace parameter in Environment() and Environment.trace() can now be a file handle. If so, the trace output will be directed to the file given, provided it is opened for output. When the trace parameter is not an 'open for write' file handle, but is 'Truthy' (usually True), the trace output is sent to stdout, as before. When the parameter is 'Falsy', no trace output will be generated, as before.

Example: import salabim as sim

After execution, the file output.txt contains: line# time current component action information ------ ---------- -------------------- ----------------------------------- ------------------------------- line numbers refers to test.py 8 default environment initialize 8 main create 8 0.000 main current 9 x.0 create data component

And the following output is generated: 12 x.1 create data component
Note that by issueing env.trace(False), the file is not closed, so that has to be done explicitely or with a context manager.

Changed functionality (0)

The default priority of a requested resource is now inf, which means the lowest possible priority. In practice, this will hardly ever make a difference with the former behaviour that if no priority was given, it was added to the tail of the requesters

When a request is honoured, the component now enters the claimers queue with priority as it had in the requesters queue. Again, in practice this will hardly make any difference.

Bugfix (0)

A bug with autonumbering components, queues, etc. by ending the name with a comma, like for _ in range(3): Car(name='car,') fixed. Now, the cars are correctly named car.1, car.2 and car.3 .

Bugfix (1)

On some platforms PIL does not accept a new image with a width or height of 0. Therefore, salabim now sets the dimensions for a dummy image to (1, 1).

Bugfix (2)

As a leftover from a test, the seed was printed when an Environment was created. Removed.

version 19.0.8 2019-09-17

New functionality (0)

The time related parameters of the methods methods mentioned below can now be called with a distribution instead of a float value ('auto sampling ').

Method parameters that can be auto sampled


sim.Component at, delay Component.activate at, delay Component.hold duration, till Component.request fail_at, fail_delay Component.wait fail_at, fail_delay Environment.run duration, till Environment.reset_now new_now Environment.years, ... t Environment.to_time_unit t Environment.to_years, ... t

If a distribution is given, the distribution will be sampled. So, car = Car(delay=sim.Normal(100,10)) is equivalent to car = Car(delay=sim.Normal(100,10).sample()) and car = Car(delay=sim.Normal(100,10)())
This makes it possible to intermix floats/ints and distributions, without having to worry about sampling, e.g. set_up_time = 6 processing_time = Normal(10, 1) reaction_time = sim.Constant(10) ... yield self.hold(set_up_time) yield self.hold(processing_time) yield self.hold(env.hours(reaction_time))
Note that salabim also supports basic expressions for distributions, so it is even possible to do something like yield self.hold(2 * setup_time + processing_time)

New functionality (1)

Introduced a new distribution, External, that makes it possible to specify an 'external' statistical distribution from the modules

Examples: d = sim.External(random.lognormvariate, mu=5, sigma=1) d = sim.External(numpy.random.laplace, loc=5, scale=1) d = sim.SciPyDis(SciPy.beta, a=1, loc=4, scale=1) Then sampling with d.sample() or d()
If the size is given (only for numpy.random and scipy.stats distributions), salabim will return the sampled values successively. This can be useful to increase performance.

The mean() method returns the proper mean for scipy.stats distributions, nan otherwise.

If a time_unit parameter is given, the sampled values will be multiplied by the applicable factor, e.g. env = sim.Environment(time_unit='seconds') d = sim.ScyPyDis(SciPy.norm, loc=2, time_unit='minutes') print(d.mean()) will print out 120

New functionality (2)

Bounded distribution now has a time_unit parameter to specify the lowerbound or upperbound.

New functionality (3)

IntUniform distribution now also supports a time_unit. If used, the returned value will be scaled accordingly. E.g. env = sim.Environment('seconds') d = sim.IntUniform(1, 2, time_unit='minutes') print(d.sample()) will print either 60 or 120.

Improvement (0)

When trying to animate text for a None object, salabim issued an error, about absence of .strip(). Now, None will be handled as the null string ("")

Improvement (1)

When a distribution with a time unit was specified without an Environment was instantiated, a rather obscure error message was shown. Now, a clear error message will be shown, in that case.

Implementation note (0)

Provided numpy is installed, now numpy.random is seeded at init of Environment() and when calling random_seed, unless the set_numpy_random_seed parameter is False. This is particularly useful when using External distributions with numpy.random or scypi.stats distributions. Therefore, unless explicitely overriden, also those distribution sampling will be reproducable. Related to this is an internal change in the way Environment seeds random.

Implementation note (1)

Change in the way time_units are handled internally.

version 19.0.7 2019-08-12

Bug fix (0)

Rare error in multiplication of monitors (and thus Monitor.to_hours, etc) fixed.

version 19.0.6 2019-07-09

Improved functionality (0)

AnimateMonitor and Monitor.animate() now allow rotation of an animated monitor. This is particularly useful for animating a monitor from top to bottom (angle = 270), instead of from left to right (angle=0) The rotation angle is specified with the angle parameter, which defaults to 0. Also, offsetx and offsety are now supported for AnimateMonitor and Monitor.animate().

Improved functionality (1)

The spec parameter of AnimateLine, AnimatePoints, AnimateRectangle and AnimatePolygon can now use None to repeat a previous x or y-coordinate. The same holds for the line0/line1, rectangle0/rectangle1, polygon0/polygon1 parameters of Animate and Animate.update(). From now on it is therefore possible to say for instance: sim.AnimateLine(spec=(100, 0, 900, None, None, 600)) which is equivalent to sim.AnimateLine(spec=(100, 0, 900, 0, 900, 600)) And it even possible to say: sim.AnimatePoints(spec=100, 400, 150, None, 200, None, 300, None) which is equivalent to sim.AnimatePoints(spec=100, 400, 150, 400, 200, 400, 300, 400)

Improved functionality (2)

Applications can now retrieve the salabim version consistenty with sim.version. Previously, this had to be done with

Improved functionality (3)

If an error occured in the make_pil_image method during an animation, it was sometimes difficult to find out the reason for that error. From this version on, the line number (and the filename) where the animation object was created will be added to the error message, which makes debugging hopefully easier.

Bug fix (0)

Creating a video with audio did crash sometimes. Fixed.

Bug fix (1)

Under Pythonista (iOS), animation objects were not properly sorted, thus causing layer values sometimes to be ignored. Fixed.

Bug fix (2)

When querying a tallied value from a level monitor, either with mon.get(t) or mon(t), the values were not correct when a Environment.reset_time() was applied. Fixed.

version 19.0.5 2019-06-20

New functionality (0)

Level monitors have two new properties:

Combining these two is for instance useful to calculate the cost of storage over a period, such as costs += (env.now() - inventory.t) * inventory.value * cost_per_day inventory.value -= consume Both value and t are available can be used even if the monitor is turned off.

Note that these two properties are only available for level monitors.

Note that this functionality is also available for the capacity of a resource, which means that increasing the capacity of a resource by 1 can now be done with res.set_capacity(res.capacity() + 1) or res.capacity.value += 1
The following standard salabim level monitors do support only getting the value and (for obvious reasons) not setting the value:

New functionality (1)

Anonymous resources can now request for a negative quantity. In that case, the request gets honoured if the claimed quantity is greater than or equal to minus the requested quantity. In case of these anonymous resources, it might be easier to think in terms of get and put, like in SimPy.Container. Therefore, salabim introduces to new methods:

Note that you can still 'refill' an anonymous resource without checking for exceeding the capacity, with a call to Resource.release(). In many cases that will be sufficient.

New functionality (2)

On Windows and Pythonista platforms, an audio track (usually an mp3 file) may be played during animation. This particularly during the development of lip synchronized videos. Therefore, a new method Environment.audio() has been introduced. Alternatively, the new parameter audio of Environment.animation_parameters() can be used to specify which mp3 to be played. It is recommended not to use variable bit rate (vbr) mp3 files, as the length can't be detected correctly. Note that audio is only played when the animation speed is equal to audio_speed, which is 1 by default. The audio_speed may be changed with Environment.audio_speed() or the new parameter audio_speed of Environment.animation_parameters(). On other platforms than Windows or Pythonista, the audio functions are ignored.

The duration of an audio file (usually an mp3 file) can be retrieved with sim.audio_duration(filename). On other platforms than Windows or Pythonista, a length of zero will be returned when sim.audio_duration() is called.

New functionality (3)

On Windows platforms, an audio track can now be added to a video animation. The audio is controlled by Environment.audio() commands (see above). In order to add audio to a video, ffmpeg must be installed and included in the environment path. See www.ffmpeg.org for download and installation instructions.

New functionality (4)

When animating images, the alpha (transparency channel) can now be specified. Therefore, Animation() and Animation.update() have two extra arguments: alpha0 and alpha1. AnimateImage() has an extra parameter alpha. The alpha value should be between 0 (fully transparent) and 255 (not transparent). Note that if an image has alpha values other than 255, these are scaled accordingly. Images with an alpha values of less than 255 are rendered quicker when numpy is installed.

Improved performance (0)

Animation objects that are completely out of the frame are suppressed now. This results in better performance when many animation objects are not visible anyway.

Improved performance (1)

Rendering speed of animated text improved, particularly when numpy is installed.

Experimental functionality (0)

Experimental support for retina screens on supported iOS devices (Pythonista only). By adding retina=True to the Environment() call, iOS devices will double the number of pixels, both in height and width. As of now, buttons and sliders are not shown in retina mode.

Bug fixes (0)

In some Ubuntu environments, install.py could not install salabim correctly. This has been fixed with this release.

version 19.0.4 2019-05-03

New functionality (0)

Queue.extend() has an extra parameter, clear_source. If clear_source = True, the given source will be cleared after copying the elements to self. This means that q0.extend(q1, clear_source=True) effectively transfers all elements of q1 into q0, prior to emptying q1. Note that clear_source cannot be applied if source is a list or a tuple.

New functionality (1)

non-level monitors can now be filled from a list or tuple, like m = Monitor("my monitor", level=False, fill=(1,2,5,7,2,3)) which is functionally equivalent to m = Monitor("my monitor", level=False) for el in (1,2,5,7,2,3): m.tally(el)

Bug fix (0)

Autoscaling of histograms was incorrectly always enabled. Now histograms are autoscaled only if neither number_of_bins, nor lowerbound nor bin_width is specified.

Bug fix (1)

Due to a problem with the Black formatter, version 19.0.3 did not run under Python versions prior to 3.6. This is fixed with this release.

version 19.0.3 2019-04-21

New method (0)

Monitor.rename() can be used to rename a monitor in a chained way. This is particularly useful when merging with the + operator, merging with sum or slicing with [], multiplication and division as well as the unit conversion methods (to_years, ...), because the resulting monitors get automatically a name, that might not be appropriate. The Monitor.rename() method is essentially the same as Monitor.name(), but will return the monitor itself. Examples: (mon0 + mon1 + mon2 + mon3).rename('mon0 - mon3').print_histogram() sum(mon for mon in (mon0, mon1, mon2, mon3)).rename('mon0 - mon3').print_histogram() mon0[1000:2000].rename('mon0 between t=1000 and t=2000').print_statistics() mon0.to_years().rename('mon0 in hours').print_statistics()

New method (1)

Queue.rename() can be used to rename a queue in a chained way. This particularly useful when the queues are combined with +, -, |, & and ^ operator or the sum function, because the resulting queue get automatically a name, that might not be appropriate. The Queue.rename() method is essentially the same as Queue.name(), but will return the queue itself. Examples: (q0 + q1 + q2 + q3).rename('q0 - q3').print_statistics() (q1 - q0).rename('difference of q1 and q0)').print_histograms()

New functionality

It is now possible to get the union of several queues by means of the sum function. Example: rows = [Queue('row.') for _ in range(10)] ... sum(rows).print_info()

Improved functionality (0)

When animating a large number of objects, it was possible that tkinter crashed because there were too many tkinter bitmaps aka canvas objects, sometimes by issuing a 'Fail to allocate bitmap', sometimes without any message. From this version on, salabim limits the number of bitmap automatically by combining animation objects in one aggregated bitmap if the number of bitmaps exceeds a given maximum. Unfortunately it is not possible to detect this 'Fail to allocate bitmap', so it may take some experimentation to find a workable maximum (maybe going as low as 1000). By default, salabim sets the maximum number of bitmaps to 4000, but may be changed with the Environment.maximum_number_of_bitmaps() method, or the maximum_number_of_bitmaps parameter of Environment.animation_parameters(). Choosing a too low maximum (particularly 0), may result in a performance degradation. The bitmap aggregation process is transparent to the user, but improves the usability of salabim. Note that does this not apply to the Pythonista implementation, where bitmaps are always aggregated.

Improved functionality (1)

When using the new style Animate classes (AnimateLine, AnimateRectangle, ...), texts are optional. Up to this version, even a blank text (which is the default), resulted in a small 'empty' bitmap to be 'displayed'. From this version, these blank texts are ignored automatically , which is transparent to the user but can result in better performance and reduces the probability of a tkinter crash.

Changed functionality (0)

The method Environment.animation_parameters() does no longer automatically enable animate, but instead leaves the animate status unchanged. So, if the user now wants to start the animation as well when specifying other parameters, it is necessary to add animate=True. Or, better yet, specify the various parameters with their corresponding method and use env.animate(True). E.g. instead of env.animation_parameters(x0=100, modelname="My model") use now env.animation_parameters(x0=100, modelname="My model", animate=True) or env.x0(100) env.modelname("My model") env.animate(True)

Changed functionality (1)

When creating an animated video, the default codec is now mp4v instead of MP4V. If this causes a problem in an appication, just add +MP4V to the filename, like env.video("my_video.mp4+MP4V")

Changed functionality (2)

The function random_seed is defined in a slightly different way and is now in line with the random_seed parameter of Environment(). The docstring and the documentation have been updated accordingly.

Bug fixes

Minor error in AnimateMonitor and Monitor.animate() for non-level monitors fixed.

version 19.0.2 2019-03-25

New functionality

It is now possible to scale the output of non-level monitor, which is most useful for the automatically registered length_of_stay monitor of queues. For instance, if the time unit of the simulation is days, the duration in the queue (q) is registered in days. Buf if a histogram in minutes is more appropriate, it is possible to say q.length_of_stay.to_hours().print_histogram() Equivalent methods are available for year, weeks, days, minutes, seconds, milliseconds and microseconds. Alternatively the method Monitor.to_time_unit() might be used as in q.length_of_stay.to_time_unit('minutes').print_histogram() Finally, a monitor may be scaled with a given factor, e.g. q.length_of_stay.multiply(24 * 60).print_histogram() or even (q.length_of_stay * 24 * 60).print_histogram()

Here is a list of all the new Monitor methods:

Monitor.multiply() Monitor.to_years() Monitor.to_weeks() Monitor.to_days() Monitor.to_hours() Monitor.to_minutes() Monitor.to_seconds() Monitor.to_milliseconds() Monitor.to_microseconds() Monitor.to_time_unit()

On top of Environment.to_years, Environment.to_weeks, etc., there is now a generic method Environment.to_time_unit() For instance, env.to_minutes(env.now) is equivalent to env._to_time_unit('minutes', env.now)

Bug fixes

Minor error in run without a duration or till parameter fixed (was not correctly fixed in version 19.0.1) Bug in Queue.extend() fixed. Bug in Queue.clear() fixed.

version 19.0.1 2019-03-02

Added functionality

The methods Queue.add, Queue.append, Queue.add_at_head, Queue.add_sorted, Queue.add_in_front_of, Queue.add_behind, Queue.insert, Queue.remove now return the the queue itself (self), in order to allow chaining,like waitingline.add(car1).add(car2)

Documentation update

The documentation has been enhanced. A section on using monitors in other packages, like matplotlib has been added. This includes a hint to use plt.plot(*waitingline.length.tx(), drawstyle="steps-post") to generate a plot from a level monitor.

Bug fixes

Minor error in run without a duration or till parameter fixed.

version 19.0.0 2019-01-01

New functionality

Queues now register the arrival rate and the departure rate, defined as number of arrivals since last reset / duration since last reset number of departures since last reset / duration since last reset The following methods are available: Queue.arrival_rate() Queue.departure_rate() The registration can be reset with Queue.arrival_rate(reset=True) Queue.departure_rate(reset=True) Note that this functionality is completely independent of the monitoring.

Added functionality

Video production now supports also the creation of a series of individual frames, in .jpg, .png, .tiff or .bmp format. By specifying video with one of these extension, the filename will be padded with 6 increasing digits, e.g. env.video('test.jpg') will write individual autonumbered frames named test000000.jpg test000001.jpg test000002.jpg ... Prior to creating the frames, all files matching the specification will be removed, in order to get only the required frames, most likely for post processing with ffmpeg or similar.

Note that individual frame video production is available on all platforms, including Pythonista.

The method Environment.delete_video() can be used to delete all autonumbered files, like env.delete_video('test.jpg') will delete test000000.jpg test000001.jpg test000002.jpg ... If this method is used with any other file type, like .mp4, the file does not need to exist (i.e. no action is taken then).

Announcements

From this version, salabim uses the CalVer (see www.calver.org) standard for version numbering. This means that versions are numbered YY.major.minor, where YY is the year of the release minus 2000 major is the major version number (0 based) minor is the minor version number (0 based)

From this version, legacy Python (<= 2.7) is not longer officially supported.

version 2.4.2 2018-11-01

Added functionality

Sampling from a Pdf distribution now also supports getting a number of samples without replacement. In order to be able to use that, all probabilities have to be the same, like colors_dis = sim.Pdf(("red", "green", "blue", "yellow"), 1) Then we can get a random list of all colors with colors_dis.sample(4) # e.g. ["yellow", "green", "blue", "red"] or two randomly choosen colors, without replacement with colors_dis.sample(2) # e.g. ["green", "blue"] Note that if n=1, a list of one value will be returned colors_dis,sample(1) # e.g. ["blue"]

Bug fixes

Under some conditions, Monitor.merge() did not work properly. Fixed. Under rare conditions Pdf did not handle timeunits properly. Fixed.

Internal changes

The code is now 'blackened', causing neater and more consistent formatting. This has not any effect on the functionality.

Sampling from a Pdf distribution has been optimized.

version 2.4.0 2018-10-23

Added functionality

Apart from Monitor.merge(), monitors can now be merged also with the + operator, e.g. print((m0 + m1 + m2).mean()) is equivalent to print(m0.merge(m1, m2).mean()) It is also possible to use the sum function to merge a number of monitors. So, print(sum((m0, m1, m2)).mean()) is equivalent to the constructs above. And if ms = (m0, m1, m2), it is also possible to use: print(sum(ms).mean()) A practical example of this, is the case where the list waitinglines contains a number of queues. Then to get the aggregated statistics of the length of all these queues, use: sum(waitingline.length for waitingline in waitinglines).print_statistics()

Distributions can now be used in expressions. This creates an _Expression distribution which can be sampled from. Examples: d0 = 5 - sim.Uniform(1, 2) # equivalent to Uniform (3, 4) d1 = sim.Normal(4, 1) // 1 # integer samples of a normal distribution d2 = sim.IntUniform(1,5) * 10) # to return 10, 20, 30, 40 or 50. d3 = (1 / sim.Uniform(1, 2))() # to return values between 0.5 and 1 (not uniform!) d4 = sim.Pdf((0, 1, 2, 3, 4, 5, 6), (18, 18, 18, 18, 18, 8,2), 'days') + sim.Cdf((0,0, 8,10, 17, 90, 24, 100), 'hours') # this generates an arrival moment during a week, with emphasis on day 0-4. # The distribution over the day concentrates between hour 8 and 17. d5 = sim.Uniform(1,2) * (10 + sim.Triangular(1,3,2) + sim.Normal(2,1)) ** 3 # over the top or what?

An instance of the class _Expresssion can be used as any other distribution. For example, each expression below returns 10, 20, 30, 40 or 50. d2.sample() sim.IntUniform(1,5) * 10).sample() sim.IntUniform(1,5) * 10)()
Like all distributions, the _Expression class supports the mean(), sample(), bounded_sample() and print_info() methods. If the mean can't be calculated, mean() will return nan. (sim.Uniform(1, 2) / 10).mean() # 0.15 (10 / sim.Uniform(1, 2)).mean() # nan (sim.Uniform(1, 2) / sim.Uniform(1, 2)).mean() # nan

Added a special distribution class: Bounded. This class can be used as a replacement of a distribution's bounded_sample method(), with the advantage that the boundaries can now be specified at time of definition. Examples: dis = sim.Bounded(sim.Normal(3, 1), lowerbound=0) sample = dis.sample() # normal distribution, non negative sim.Bounded(sim.Exponential(6, upperbound=20).sample() # exponential distribution <= 20 sim.Bounded(sim.Exponential(6, upperbound=20)() # exponential distribution <= 20 The bounded_sample() method of distributions is still available, but not preferred. Like all distributions, the Bounded class supports the mean(), sample() and print_info() methods.

Modified exception handling

Salabim does not generate SalabimError exceptions anymore. Instead, the appropriate standard exceptions (primarily ValueError and TypeError) are raised, if required. Also several error messages contain more (useful) information.

Added validation checks

Check for tallying 'off' in a level monitor added. If so, a SalabimError exception is raised. Check for enabling merged and sliced monitors added. If so, a SalabimError exception is raised.

Bug fixes

Bug in Monitor.merge() fixed.

Documentation

The documentation is updated considerably, particularly with respect to recent features. Also, a section on interpretation and controlling trace output has been added (cf. Miscellaneous).

Compatibility

Salabim has been tested against Python 3.7, with success. Legacy Python (aka version 2.7) is still supported (till at least 31 December 2018).

version 2.4.0 2018-10-07

New Functionality

Complete overhaul of Monitor/MonitorTimestamp internals, resulting in more consistent operation. The documentation has been updated accordingly. MonitorTimestamp is phased out and replaced by level monitors, which are instances of Monitor. Timestamped monitors are now called level monitors, whereas non timestamped monitors are now called non-level monitors. non-level monitors now always timestamp the entry, which makes slicing and more useful animation possible. It is no longer required (and possible) to indicate that a non-level monitor is weighted. If a weight is required, just tally with a weight != 1.

Animation of non-level monitors is now also shown on a timescale, thus making it easier to see the relation with a level monitor, e.g. length and length_of_stay of a queue.

This has a number of consequences for the API:

MonitorTimestamp ==> Monitor(level=True) Merging of monitors not anymore part of the init, but is a separate method: m = Monitor(merge=(m1, m2, m3)) ==> m = m1.merge(m2, m3)

It is no longer necessary (and possible) to specify weighted=True in init of Monitor. All non-level monitors now support weighted, if required. m = Monitor(weighted=True) ==> m = Monitor()
The (level monitor only) method .get() or direct call can now also be used to get the value of the level at a specified time within the monitored period, e.g. print('queue length at time 100 was', q.length.get(100)) or alternatively: print('queue length at time 100 was', q.length(100))

It is now possible to slice a monitor with Monitor.slice(), which has two applications:

Instead of slice(), a monitor can be sliced as well with the standard slice operator [], like: q.length[1000:2000].print_histogram() q.length[2:3:24].print_histogram()

It is now possible to change the format of times in trace, animation, etc. with the method Environment.time_to_str_format() For instance, if 5 decimals in the trace are to be shown instead of the default 3, issue: env.time_to_str_format('{:10.5f}') Make sure that the format represents 10 characters. See the docstring / documentation for more information.

From now on it is possible, but definitely not required, to set the dimension of the time unit when defining an environment, like env = sim.Environment(time_unit='hours') Salabim supports 'years', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds' and 'n/a' as dimension.

If the time unit is set, times can be specified in any dimension, like yield self.hold(env.seconds(20)) The following methods for conversion to the current time unit are available: Environment.years() Environment.weeks() Environment.days() Environment.hours() Environment.minutes() Environment.seconds() Environment.milliseconds() Environment.microseconds() Example: env = sim.Environment(time_unit='hours') env.run(env.days(10)) This effectively let the simulation run for 240 (hours).
The following methods for conversion from time current time unit are available: Environment.to_years() Environment.to_weeks() Environment.to_days() Environment.to_hours() Environment.to_minutes() Environment.to_seconds() Environment.to_milliseconds() Environment.to_microseconds() Example: env = sim.Environment(time_unit='hours') env.run(env.days(14)) print('it is now', env.to_days(env.now()), 'weeks')

output: it is now 2 weeks

Finally, the current time unit dimension can be queried with Environment.get_time_unit(). Example: env = sim.Environment(time_unit='hours') env.run(env.days(10)) print('it is now', env.now()), env.get_time_unit())

output: it is now 240 hours

All distributions, apart from IntUniform, Poisson and Beta now have an additional parameter, time_unit. If the time_unit is specified at initialization of Environment(), the time_unit of the distribution can now be specified. As an example, suppose env has been initialized with env = sim.Environment(time_unit='hours'). If we then define a duration distribution as: duration_dis = sim.Uniform(10, 20, 'days') , the distribution is effectively uniform between 240 and 480 (hours). This facility makes specification of duration distribution easier and more intuitive. Refer to the docstrings or documentation for details.
By default the time unit dimension is 'n/a', which means that conversions are not possible.

The documentation has a new chapter Miscellaneous/Time units covering this topic.

Announcement

The class PeriodMonitor that was added in version 2.3.4 will be phased out in a future release. Slicing monitors (see above) is much more reliable and versatile.

Reminder

Python 2.7 will no longer be supported as from 31 December 2018. Please upgrade to Python 3.x as soon as possible.

version 2.3.4.2 2018-09-29

Bug fixes

Serious bug in MonitorTimestamp caching mechanism resulted in sometimes not updating the statistics, when animation is off. Fixed.

version 2.3.4.1 2018-09-22

Bug fixes

Joren van Lindert showed that the percentile calculation for (timestamped) monitors was incorrect. A new algorithm is implemented.

Bug in merging (timestamped)monitors fixed.

version 2.3.4 2018-09-20

New functionality

From this version, salabim supports so called period monitors with the class PeriodMonitor. These period monitors can be used to get statistics about (timestamped) monitors per specific period. For instance if the time unit is hours a period monitor with 24 periods of 1, will give 24 monitors containing data about hour 0-1, 1-2, ... 23-24 (modulo 24). It is not necessary that the durations are all the same. The most obvious use case of this new functionality is to get period information about the builtin queue monitors length and length_of_stay. qlength_per_hours= sim.PeriodMonitor(q.length, periods=24 * [1]) q_length_of_stay_per_hour = sim.PeriodMonitor(q.length_of_stay, periods=24 * [1]) Then after a run, the histogram of the length of stay for hour 0-1 (modulo 24) can be printed with q_length_of_stay_per_hour[0].print_histogram() And the mean length for hour 2-3 (modulo 24) can be retrieved with q_length_per_hour[2].mean() Have a look at the sample program 'Demo periodmonitor.py' for an example, and the docstring or the latest documentation for the exact API specification. Please note that this class is still experimental functionality that might change in a future version.

It is now possible to indicate that after a specific component returns control to the event scheduler, standby component should not be activated. This is particularly useful when a data collection component becomes current every so often. In that case, activating standby component is not useful and will just consume processing power. There are two ways to indicate that a component should skip standby activation:

New methods

Added the methods

Improved functionality

It is no longer required that a process of a component contains a yield statement. So now ordinary methods (non generators) can now be activated. This is particularly useful for decision processes that do not consume any time. Note that these non generator methods can also be called with (keyword) parameters.

Implementation notes

Check for hasprocess() changed. Substantial refactoring to allow for non generator processes to be supported, particularly with respect to proper tracing.

Process as defined in Component inititalization and Component.activate are no longer assessed by eval(), but by getattr(), resulting in more stable code.

Bug fixes

Bug in MonitorTimestamp.xduration() fixed.

Announcement

Python 2.7 will no longer be supported as from 31 December 2018. Please upgrade to Python 3.x as soon as possible.

version 2.3.3.2 2018-08-25

Changed functionality

If Environment.run() is given a till or a duration, main will become active at the specified time. If not, main will become active when there are no more events on the event list. This will be shown in the trace with 'ends on no events left'. So, if you don't want a simulation to stop at the moment the event list becomes empty, issue env.run(sim.inf) This can particularly be useful if you don't want to stop an animation when there are no events left.

The placement of the title of AnimateQueue() and Queue.animate() when the direction is 'n' or 's' has been changed. Now the title is always displayed horizontally.

Implementation note

Error reporting for AnimateEntry on Pythonista now only when actually animating.

Big fix

On Pythonista, setting env.video parameter without animating, caused a crash. Fixed.

version 2.3.3.1 2018-08-23

Implementation note

Not documented tkinter_init and tkinit_exit code removed.

Bug fix

From version 2.3.3 main will be reactivated once there are no more events. This functionality was not supported when running animated. Fixed.

Sometimes an animation object was not removed correctly (under tkinter). Fixed.

version 2.3.3 2018-08-22

New functionality

The class AnimateEntry adds a new UI element to salabim. With this, it is possible to ask for the user to enter a text. The Enter key will call an action function, which can be used to get the entered text. In most cases, the entry animation will have to be removed then. See the manual or docstring for details. AnimateEntry is not supported under Pythonista. Note that the API of this class is still experimental and might change in future releases.

Improved functionality

AnimateQueue() and Queue.animate() can now display a title near the start of the queue animation. This makes it very easy to show the contents of a queue, with just one line of code By default, the name of the queue will be displayed next to the queue. There are several parameters to control the text placement and appearance. See the manual or docstring for details.

Changed functionality

When there are no more events on the event chain, main was activated at time=inf, which made it difficult to get useful time stamped statistics. Therefore, from this version on main will become active at the time of the last event, when there are no more events. This condition is also properly traced now.

Bug fixes

When a timestamped monitor was run till inf, the print_histogram method did not work properly. Fixed.

The linenumber in trace for 'request honor' or 'wait honor' was not correct. Fixed.

version 2.3.2.6 2018-08-09

Bug fix

Under Python 2.7 autoscaling histogram sometimes did result into a TypeError. Fixed.

The wrong line number was displayed when tracing request honor and request wait. Fixed.

version 2.3.2.5 2018-08-08

New functionality

The functionality to print to another file rather than stdout in all methods print_histogram() print_histograms() print_statistics() print_info() as introduced in version 2.3.2.4 has been changed into a more logical way.

Now each of these methods has an additional parameter 'file' which can be used to direct the output to that file. If file is omitted, the output goes to stdout. If as_str is True, the methods always return as a string rather than writing to a file.

Example: q=sim.Queue(name='queue') with open('test.txt', 'w') as f: q.print_statistics(file=f)

Bug fixes

A bug when Pillow (PIL) was not installed (correctly) and running without animation fixed.

version 2.3.2.4 2018-08-07

New functionality

The as_str parameter in the methods print_histogram() print_histograms() print_statistics() print_info() can now also be used to direct the output to the file as mentioned in as_str, like in q=sim.Queue(name='queue') with open('test.txt', 'w') as f: q.print_statistics(as_str=f) If as_str is True, the output will be returned as a string. If as_str is False (the default for all methods), the output will go to stdout.

Bug fixes

Problem when printing 'values' histograms fixed.

Bug with drawing a circle with old style Animate() fixed.

A syntax error when run under Python 2.7 with 'yield from' fixed. Be advised that salabim will drop Python 2.7 support in the future.

version 2.3.2.3 2018-07-31

Bug fixes

ItemFile.read_item(), ItemFile.read_item_int(), ItemFile.read_item_float() and ItemFile.read_bool() did ignore tabs completely. From this version on, tabs are treated as whitespace, just like blanks.

Error in scaling of lines, rectangles, polygons and points fixed. Line widths are now always rounded, which has the consequence that lines with a line width < 0.5 are not displayed.

version 2.3.2.2 2018-07-24

Bug fixes

Circles were scaled twice. Fixed.

version 2.3.2.1 2018-07-22

Bug fixes

Fatal error in Component.cancel() when tracing standby components fixed.

version 2.3.2 2018-07-21

New functionality

The methods AnimateText AnimateRectangle AnimateLine AnimatePolygon AnimatePoints AnimateCircle AnimateImage AnimateQueue AnimateMonitor AnimateMonitorTimestamp now have an additional parameter, parent. When a process finishes, either by reaching the end or by a cancel, all animation objects with that component as its parent will be automatically removed.

Bug fixes

Fatal error when tracing standby components fixed.

After a reset of a timestamped monitor, the animation of that monitor did not start at the left hand side of the graph. Fixed.

Some minor error handling problems with checking for types fixed.

Demonstration of animation features

In order to demonstrate the new style animation classes, the following files (with comments) are included in the GitHub distribution: Demo animation classes.py (requires the file Pas un pipe.py) Demo queue animation.py Demo animation dynamic.py Demo animation dynamic lambda.py

version 2.3.1 2018-07-09

New functionality

Monitors can now be weighted. Initializing a Monitor instance has therefore a new parameter, weighted. If weighted is set to True, the tally method can be used to specify the weight. Monitor.weight() returns the sum of all weights. Monitor.value_weight() returns the sum of all weights equal to or in value Monitor.bin_weight() returns the sum of all weights > lowerbound and <= upperbound. The name of the weight (used in Print_histogram and print_statistics) can be specified by the user (default is weight).

sim.Animate(circle0=) now supports also ellipses and circle arcs. Please consult the documentation for the correct parameter usage. It is highly recommended to use the new style AnimateCircle, which has some more parameters now: radius1 is the height of the ellipse (if omitted, a circle will be drawn) arc_angle0 is the start angle of the arc (default 0) arc_angle1 is the end angle of the arc (default 360, thus a full circle) draw_arc indicates whether the arcs should be drawn (default False) Note that all the parameters may be a scalar, a function with 0, 1 or 2 arguments or a method with one argument (t). Note also that the text is always positioned relative to the full circle/ellipse, regardless of the arc_angles. Example: sim.AnimateCircle(radius=100, radius1=50, arc_angle1=lambda t: t*10)

Bug fixes

Bug in closing an animation under Spyder or Idle fixed. Bug in Environment.snaphot() under Pythonista fixed.

Internal

Major overhaul of monitor off values and handling of duration for MonitorTimestamped. Timestamped monitors now share most of their functionality with weighted monitors.

version 2.3.0 2018-06-28

New functionality

As from this version, animation is more powerful and easier to use. Although the old style Animate class is still available, it is recommended to use the new style classes.

The documentation is not yet completely up-to-date. Please read these release notes carefully to get more information.

All the docstrings (and therefore the reference section of the manual) are however up-to-date. It is planned to publish a number of tutorial videos or guides, both for basic and advanced animation.

To visualize rectangles, lines, points, polygon, texts, circles and images salabim offers the new classes

The main difference with the Animate class is that no automatic linear interpolation over time is supported. But, each of the characteristics may be still changed over time easily! All visualizations (apart from AnimateText) have an attached text field that will be displayed relative to the shape. Thus, for instance, it possible to say: vis = sim.AnimateRectangle(spec=(100, 100, 300, 50), text='some text') and then a rectangle with the text 'some text' in the middle will be displayed. In contrast to Animate, updating any of the specifying fields does not require the update method, but can be done directly. In the above example you can just say vis.text='yet another text' or vis.x=100

One of the key features of this new visualization is that all the specifying fields can now be functions or methods. This make is possible to automatically update fields, e.g. vis = sim.AnimateText(lambda:'mean of histogram = ' + str(hist1.mean()), x=100, y=100) which will show and update the current mean of the histogram or vis = sim.AnimateRectangle(spec=(0, 0, 60, 20), x=100, y=lambda t:t+10) which results in a rectangle, moving from bottom to top. The animation_objects method of Component now accepts any of the new visualization class instances as well as Animate instances.

Animation of queues is now specified with the class AnimateQueue, although Queue.animate() is still supported. One queue can now be animated in several ways, whereas previously one queue could be animated only once. See Demo queue animation.py for an example. It possible to restrict the number of components shown (max_length). Is possible to change all the parameters of the queue animation and the shown components dynamically. See for instance Elevator animated.py where the queue position moves up and down. Or see Machine shop animated.py where the shape of the components changes dynamically. Internally, the animation of queues uses a new, more efficient, algorithm.

Most examples have been updated to use this new visualization functionality.

Texts can now spawn multiple lines (lines separated by linefeeds). Also, a list or tuple of strings may be used instead, in which case each element of the list/tuple will be treated as another line. This is particularly useful to present (dynamic) monitor values. With AnimateText, it is possible to restrict the number of lines (parameter max_lines) shown.

Class Animate has a new animation parameter, as_points that applies to lines, rectangles and polygons. If as_points is False (the default), all lines will be drawn. If as_points is True, only the end point will be drawn as a square with a width equal to the linewidth. Technical remark: the advantage of using as_points this instead of a series of individual squares is that there is only one bitmap to be placed on the canvas, which may lead to better performance in many cases. Also this is used internally for AnimateMonitor() (see below). Points are also available in the new AnimatePoints class.

Class AnimateMonitor() can be used to visualize the value of a timestamped monitor over time. It is particularly useful for visualizing the length of a queue, the various monitors of a resource or the value of a state. It is possible to connect the lines (very useful for 'duration' monitors, like queue length) or just show the individual points. This class can also visualize the relationship between the index and the value of a non time stamped monitor. The points can be just shown or connected with a line. It is possible to use Monitor.animate() and MonitorTimestamp.animate() as an alternative, although not recommended.

The MMc animated.py model demonstrates the use of the (timestamped) monitor animation.

Monitor and MonitorTimestamp can now be used to create a merged (timestamped) monitor. This is done by providing a list of (timestamped) monitors (all have to have the same type), like mc = MonitorTimestamp(name='m1 and m2', merge=(m1, m2)) For monitors, just all of the tallied x-values are copied from the to be merged monitors. For timestamped monitors, the x-values are summed, for all the periods where all the monitors were on. Periods where one or more monitors were off, are excluded. Note that the merge only takes place at creation of the (timestamped) monitor and not dynamically later.

Sample usage: Suppose we have three types of products, that each have a queue for processing, so a.processing, b.processing, c.processing. If we want to print the histogram of the combined (=summed) length of these queues: MonitorTimestamp(name='combined processing length', merge=(a.processing.length, b.processing.length, c.processing.length)).print_histogram() and to print the histogram of the length_of_stay for all entries: Monitor(name='combined processing length of stay', merge=(a.processing.length_of_stay, b.processing.length_of_stay, c.processing.length_of_stay)).print_histogram()

CumPdf is a new distribution type that is similar to Pdf, but where cumulative probability values are used. This is particularly useful for dichotomies, like failing probabilities: failrate = 0.1 if CumPdf(True, failrate, False,1) print('failed!')

All methods print_histogram() print_histograms() print_statistics() print_info() now have an additional parameter as_str, that allows the output to be returned as a string, rather than print the information (the default is False, so just print): This is particularly useful for animation of that information (see demo queue animation.py) or to write directly to a file.

sim.Random() is a new class that makes a randomstream. It is essentially the same as sim.random.Random().

Queue.name(value), Resource.name(value) and State.name(value) now also update the derived names.

API changes

The API of Component has changed slightly. The parameter process now defaults to None, which means that it tries to run the process generator method, if any. If you don't want to start the process generator method, even if it exists, now set process='' (this was None).

The API of Environment had changed slightly. The parameter random_seed now defaults to None, which means that 1234567 will be used as the random seed value. If random_seed is '*', a system generated, non reproducable, random seed will be used.

The API of Environment.random_seed has changed slightly. If the argument seed is '*', a system generated, non reproducable random seed will be used.

State.animate() is phased out. Use the standard visualization classes, like AnimateRectangle, AnimateCircle and AnimateTex instead.

Future changes

Python 2.7 will not be supported in a future version. Please upgrade to Python 3.x as soon as possible .

Internal changes

Most default parameters are now None, instead of omitted, which is completely phased out. This makes it easier to specify default arguments, like: myname = None sim.Component(name=myname) This internal change required a couple of changes to the API (see above). Apart from that, the user shouldn't notice this rather dramatic internal change (>500 replacements in the code!).

Animating lines and polygons without any points is now supported.

version 2.2.23 2018-05-28

New functionality

Component.leave() can now be called without a queue, in which case, the component will leave all queues it is in, apart from internal queues (Resource.claimers(), Resource.requesters and State.waiters()). It is not necessary that the component is in any queue. The best use case is to leave the one and only queue it is in without having to specify the queue, like self.enter(q1) ... self.leave()

Also, Component.leave() now returns the component (self). This is useful for daisy chaining, like: def process(self): while True: self.leave().enter(q1) yield self.hold(p1()) self.leave().enter(q2) yield self.hold(p2()) self.leave().enter(q3) yield self.hold(p3()) With this code, the component 'hops' from queue to queue, with minimal effort.

In line with this daisy chaining of leave, the methods Component.enter(), Component.enter_at_head() Component.enter_sorted() Component.enter_in_front_of() Component.enter_behind() now return the component (self).

With this new functionality, it is possible to do things like self.enter(q1).enter(q2).enter(q3) for a component to enter three queues on just one line.

Monitor.print_histograms() introduced as an alias for Monitor.print_histogram() MonitorTimestamped.print_histograms() introduced as an alias for MonitorTimestamped.print_histogram() Monitor.reset_monitors() introduced as an alias for Monitor.reset() MonitorTimestamped.reset_monitors() introduced as an alias for MonitorTimestamped.reset()

These four new methods make it possible to intermix resources, states, queues, (timestamped) monitors when printing histograms or resetting the monitors, like: for obj in [my_queue, my_resource, my_monitor, my_timestamped_monitor]: obj.print_histograms() obj.reset_monitors()

Introduced methods register and deregister for classes: Component Monitor MonitorTimestamped Queue Resource State This makes it easier to do collective actions on a number of component, queues, (timestamped)monitors, queues, resources and/or states. A registry is just a list of objects, which can for instance be used as: monitors = [] m1 = sim.Monitor(name='m1').register(monitors) m2 = sim.Monitor(name='m2').register(monitors) queues = [] q1 = sim.Queue(name='q1').register(queues) q2 = sim.Queue(name='q2').register(queues) ... for obj in monitors + queues: obj.print_histograms() obj.reset_histograms()
Another example: components = [] while(...): MyComp().register(components) ... print('all components in system:') for component in components: print(component.name())

Make sure to deregister any objects that are not used anymore, otherwise these will not be garbage collected!

Note that it is possible to mix several types of class in a registry (list).

Documentation update

In contrast to documentation so far, the priority parameter in Component.enter_sorted, Component.priority() and Queue.add_sorted() does not have to be float. It can be any type as long as it can be compared with the other priorities in the queue. Example 1: q=sim.Queue('q') X().enter_sorted(q, (1,1)) X().enter_sorted(q, (0,2)) X().enter_sorted(q, (1,0)) X().enter_sorted(q, (1,3)) q.print_info() will print Queue 0x11a0136d8 name=q component(s): x.1 enter_time 0.000 priority=(0, 2) x.2 enter_time 0.000 priority=(1, 0) x.0 enter_time 0.000 priority=(1, 1) x.3 enter_time 0.000 priority=(1, 3)
Example 2: q=sim.Queue('q') X().enter_sorted(q, 'one') X().enter_sorted(q, 'two') X().enter_sorted(q, 'three') X().enter_sorted(q, 'four') q.print_info() will print Queue 0x1279a82b0 name=q component(s): x.3 enter_time 0.000 priority=four x.0 enter_time 0.000 priority=one x.2 enter_time 0.000 priority=three x.1 enter_time 0.000 priority=two
Note: Avoid mixing enter_sorted or add_sorted with enter, enter_at_head, add, append or add_at_head when using non float priorities.

Bug fixes

Bug in State introduced in version 2.2.22 fixed.

version 2.2.22 2018-05-21

New functionality

Monitor.print_histogram() and MonitorTimestamp.print_histogram() now support auto scaling of the bin_width, lowerbound and number_of_bins, when none of these parameters are specified. The autoscaling algorithm method can be overridden if required (see Histogram.histogram_autoscale). For example, the following code m = sim.Monitor('normal distribution') for i in range(100000): m.tally(sim.Normal(10,2)()) m.print_histogram()

will print:

Monitor.print_histogram() and MonitorTimestamp.print_histogram() now supports the presentation of individual values, by specifying values=True. This is especially useful when collecting the status of a component over time, like:

Monitor.value_number_of_entries() introduced. The method can be used to check how many entries have an x equal to value or an x that is in value.

MonitorTimestamp.value_duration() introduced. The method can be used to check the duration of an x equal to value or an x that is in value.

MonitorTimestamp.value_number_of_entries() introduced. The method can be used to check how many entries have an x equal to value or an x that is in value.

Introduced Queue.print_histograms(). This method prints autoscaled histograms of the

Introduced Resource.print_histograms(). This method prints autoscaled histograms of the

Introduced State.print_histograms(). This method prints autoscaled histograms of the

MonitorTimestamp.number_of_entries() introduced, to be used to retrieve the number of entries.

Component.name(), Environment.name(), Monitor.name(), MonitorTimestamp.name(), Queue.name() Resource.name() and State.name() now can be used to change the name of the object. Note that the base_name and the sequence_number will not change upon a name change.

Function regular_polygon introduced.

Renamed

MonitorTimestamp.bin_count() is now called MonitorTimestamp.bin_duration() Monitor.bin_count() is now called Monitor.bin_number_entries()

Bug fix

Problem when PIL was not installed (correctly).

version 2.2.21 2018-05-01

New functionality

Component.interrupt() can now be called also when a component is interrupted. In that case, the 'interrupt_level' will be incremented. This allows interrupts to be 'stacked'. When resuming with Component.resume(), the interrupt_level will be decremented. If it reaches zero, the component will return to the status at the moment of the first interrupt. If c.resume(all=True) is issued, the component c will return to its original status, regardless of the interrupt_level. The trace shows the level in interrupt and resume for interrupt_levels >= 2. The file 'demo interrupt resume.py' demonstrates the application of stacked interrupts for breakdowns of several parts of a machine.

The new method Component.interrupt_level() can be used to query the current interrupt_level. It is zero for non interrupted components.

Function Component.isinterrupted() can be used to check whether a component is interrupted. This is equivalent to c.status() == sim.interrupted

The new method Environment.snapshot() can be used to write an animated frame (at time=now()) to a file. The method accepts .png, .jpg, .bmp, .gif and .tiff files. For all extensions but .jpg, the image can have a semi transparent background (i.e. an alpha < 255). The animation does not have to be started to use snapshot().

Bug fixes

Component.resume() did not handle mode correctly. Fixed. It was not possible to use sim.spec_to_image() if the animation was not started. Fixed.

version 2.2.20 2018-04-28

New functionality

New process interaction methods Component.interrupt() and Component.resume() introduced to interrupt a component. This is particularly useful for simulating breakdowns.

When a component is interrupted, the component is removed from the event chain (if applicable) and the status becomes interrupted. For scheduled, waiting and requesting component, the remaining duration will be calculated. Upon resume, the action depends on the original status:

The functionality of method Component.remaining_duration() has been extended. Now the method has a value parameter which allows setting the remaining_duration. The action depends on the status where the component is in:

Environment.run() now allows main to be scheduled urgent, by specifying urgent=True.

Improvement

When a process ends the trace now shows the line number of the last line in the process, postpended with a plus symbol. Previously no line number was shown. Also, any auto resource releases at the end of a process will show the same information now.

version 2.2.19 2018-04-13

New functionality

Support for animated GIF production. If Environment.animation.parameters(video=...) of Environment.video() gets a filename with .gif as extension, an animated GIF is created. Note that GIF production does not require numpy nor opencv, and therefore Environment.can_video() does not have to be True to produce an animated GIF. This feature is particularly useful on Pythonista platforms, where opencv is not supported.

For animated GIFs, Environment.animation_parameters() has two extra parameters: video_repeat ==> how many times the animated GIF will be repeated (default 1) video_pingpong ==> whether all frames should be appended in reverse order at the end of the animated GIF resulting in a smooth repeating video (default: False)

Two methods have been added to Environment to support GIF file production: Environment.video_repeat() Environment,video_pingpong()

For ordinary video files (non GIF), it is now possible to specify a codec, by adding a plus sign and the name of the codec after the extension of the file, like video='myvideo.avi+DIVX'

Introduced Resource.occupancy timestamped monitor. The occupancy is defined as the claimed quantity divided by the capacity. Note that when the capacity of r changes over time, r.occupancy.mean() may differ from r.claimed_quantity.mean() / r.capacity.mean(). Also, in that case, the occupancy may be even greater than 1. If the capacity is <=0, the occupancy is assumed to be 0.

Improvements

The time in the upper right-hand corner is now displayed with the mono font, which is better legible and not as 'nervous' as the narrow font that was used previously. Also, on Pythonista, the text is moved a bit to the left in order not to coincide with the closing symbol X.

Clarification

When a process ends, all claimed resources will be automatically released. If that functionality is not desired, the process should be prematurely cancelled, with yield self.cancel()
Compare these two components: class X(sim.Component): def process(self): yield self.request(r) yield self.hold(1) # automatically releases r at the end of the process

Also, all animation objects that have set the parent field to a component will be removed automatically when the process of that component ends. Again, this can be prevented by yield self.cancel().

This information will be included in the documentation.

Bug fixes

Bugs in AnimateSlider fixed (thanks to John Hutchinson).

Bug when honoring a resource request of a component that was already claiming that resource fixed.

Bug in the line number of the trace when auto releasing claimed resources at end of a process fixed.

Bug in wait with a fail_at parameter fixed.

version 2.2.18 2018-03-31

New functionality

Function reset() resets all global variables and closes a video recording, if any It can be useful to start a script with sim.reset() when used in REPLs and under Pythonista (iPad).

Release notes 2.2.17 corrections

Method Environment.scale() introduced, returning the scale of the animation, i.e. width / (x1 - x0) (not (x1 - x0) / width).

Function arrow_polygon added (not polygon_arrow). Function centered_rectangle() added (not rectangle_centered).

Bug fixes

Bug in AnimateSlider corrected.

version 2.2.17 2018-03-31

New functionality

Normal distribution now supports specification of coefficient_of_variation as an alternative to standard_deviation. Note that it is not allowed to specify both standard_deviation and cooeficient_of_variation. The coefficient_of_variation is now also shown in Normal.print_info(). The coefficient_of_variation is defined as the standard deviation divided by the mean.

Method Environment.scale() introduced, returning the scale of the animation, i.e. (x1 - x0) / width

sim.Animate and sim.update now allows and prefers circle0 or circle1 to be specified as a scalar. The functionality to the specify the radius as a one element tuple/list is still supported. E.g. sim.Animate(circle0=(30,)) is equivalent to sim.Animate(circle0=30) now. Note that Animate.circle may also return a one item tuple/list or a scalar.

sim.Animate() and sim.update() now allows the specification of line0, line1, rectangle0, rectangle1, poloygon0 and polygon1 to include None values The None values will repeat the previous x or y value. E.g. sim.Animate(line0=(10, 20, None, 30, 40, None)) is equivalent to sim.Animate(line0=(10, 20, 10, 30, 40, 30))

Function polygon_arrow() added. Function rectangle_centered() added.

Video production now supports .MP4 and .AVI extensions. Other extensions are not accepted.

Environment.is_dark(colorspec) now returns the is_dark value of the background color if the alpha value of the colorspec is 0.

Changed API

The parameter lambda_ for the Poisson distribution is renamed to mean, in order to avoid problems with the online documentation.

Bug fixes

Bug in handling width/height of images when using a redefined coordinate system fixed. Bug in method Queue.add_at_head() fixed. Bug with default font handling on Pythonista fixed. Bug in Environment.animation_parameters() and Environment.video() fixed. Work around a PIL bug where rendering the first letter of some italic font texts chopped the lefthand serif.

Sample files

Show colornames.py shows all available colors. Demo using process interaction in method.py gives an example of how to use hold in a separate method. Dining philsophers animated.py updated to use the new circle specification.

Documentation

Documentation updated and improved. Although still work in progress ...

 

version 2.2.16 2018--03-01

From this version, neither animation modules (PIL, tkinter) nor video modules (cv2, numpy) will be imported unless these are required at runtime (with animation_parameters).

A user program can now check whether animation is supported with a call to sim.can_animate(). A user program can now check whether video is supported with a call to sim.can_video().

Bug fix

Minor bug when ImageTk could not be imported corrected.

version 2.2.15 2018-02-28

Animation updates

Overhaul of the way animation is organized. Now, the animation can be started and stopped during a run. When animation is off, the simulation model runs full speed without any overhead. It is possible to use different environmnent for the animation although not at the same time.

The API for animation has changed slightly: center is now refered to as 'c' (although 'center' is still accepted) xy_anchor allows x0, x1, y0 and y1 in Animate and x and y in AnimateButton or AnimateSlider object, to be relative to each of the wind directions 'n', 'nw', 'w', 'sw', 's', 'se', 'e' or 'ne' or 'c' (for center). This makes it, for instance, possible to define a button relative to the top right hand corner of the animation frame: b = sim.AnimateButton(text='My button', x=-100, y=-20, xy_anchor='ne')

All arguments of Environment.animation_parameters have now a corresponding function that can be used to set or query one of the animation parameters: Environment.x0() to set/query x-coordinate of lower left corner of animation frame Environment.x1() to set/query x-coordinate of upper right corner of animation frame Environment.y0() to set/query y-coordinate of lower left corner of animation frame Environment.y1() to query y-coordinate of upper right corner of animation frame Environment.width() to set/query width of animation frame Environment.height() to set/query height of animation frame Environment.fps() to set/query the number of frames per second Environment.show_time() to set/query whether time should be shown Environment.show_fps() to set/query whether fps should be shown Environment.modelname() to set/query the model name ('' to show nothing) Environment.animate() to start/stop the animation and to query the current status Environment.speed() to set/query the speed of the animation Environment.video() to set/query the name of the video ('' for no video)

New functionality

The Poisson distribution is now supported.

Enhancements

Text alignment in text Animate is significantly improved. Now the text is always aligned according to the (estimated) 'cap line', which is derived from a capital A. So, when aligning south, the descender of the g is below the baseline. When aligning north, top of the capline is the given y-position. Finally, aligning w, c or e means given y-position is the middle of the cap line.

Functionality updates

Updated animated sample files

Please not that the animated sample models have been updated to use the new xy_anchor functionality.

Bug fix

Collected tallies for monitors were not cached properly, resulting in non optimal performance of Monitor.x() and querying the monitor, e.g. print_histogram.

version 2.2.14 2018-02-02

New functionality

Standby components just getting current and go into standby again can now be excluded from the trace. This can be controlled with the method Environment.suppress_trace_standby(). By default, standby is excluded from the trace.

Added functionality to read item based input files (inspired by TomasRead). Therefore, the class ItemFile is added to salabim.

Example usage: with sim.ItemFile(filename) as f: run_length = f.read_item_float() run_name = f.read_item()
Or (not recommended): f = sim.InputFile(filename) run_length = f.read_item_float() run_name = f.read_item() f.close()

The input file is read per item, where blanks, linefeeds, tabs are treated as separators. Any text on a line after a # character is ignored. Any text within curly brackets ( {} ) is ignored (and treated as an item separator). Note that this strictly on a per line basis. If a blank is to be included in a string, use single or double quotes. The recommended way to end a list of values is //

So, a typical input file is:

Instead of the filename as a parameter to ItemFile, also a string with the content can be given. In that case, at least one linefeed has to be in the content string. Usually, the content string will be triple quoted. This can be very useful during testing as the input is part of the source file and not external, e.g.


version 2.2.13A 2018-01-25

Bug fix

The just introduced functionality to use parameters for processes did not work under Python prior to version 3.4, so also not under Python 2.7|.

This intermediate version fixes that.

version 2.2.13 2018-01-25

New functionality

It is now possible to use arguments for the process generator of a component. Only keyword arguments are supported. Parameters can either be set at initialization of a component or the call to activate. E.g.:

Note that the keywords used by the process generator are not passed to setup(), at initialization of a component. That means that setup() can't have the same parameters as the process called at initialization (usually process()). Furthermore, neither the process generator nor setup() can use at, delay, urgent, process, keep_request, keep_wait when called from activate() name, suppress_trace, suppress_pause_at_step, mode, env, at, delay, urgent, process at initialization of a component as parameters.

Bug fix

Corrected bug when tracing a standby component.

Optimization

Optimized animation perfomance by improving the interpolate function.

version 2.2.12 2018-01-18

In the trace the line numbers are now prefixed by a letter to indicate in which file the line is in. The file from which the environment is created is not prefixed. The trace will issue a line when a not yet used source file is referenced.

New method Environment.print_trace_header() will print a header. If an Environment is initialized, the trace_header is also printed provided trace=True.

Now MonitorTimestamped.xt() and MonitorTimestampled.tx() add the last tallied value along with the current time as the last x- and t-value. This can be turned off by specifying add_now=False.

Defining MonitorTimestamp is now much simpler as it is no longer required to use a getter function. Instead, the caller has now to provide the value to be tallied directly to the tally() method. When initializing a timestamped monitor, an initial_tally can be provided (by default 0).

Added IntUniform distribution to sample integer values in a given range, for example: die = sim.IntUniform(1,6)

Added method bounded_sample to all distributions, to force sampling of a distribution within given bounds. This is, for instance, useful when sampling from a normal distribution, where the sample has be positive: s = Normal(8,5).bounded_sample(lowerbound=0)

Added Component.queues() which returns a set of all queues where the component is in. Added Component.count() which returns the number of queues the component is in or 1 if the component is in the queue, 0 otherwise

Added a method Environment.beep() which can be useful to attract attention, etc. Note that this works only under Windows or iOS (Pythonista). For all other platforms, this is just a dummy function.

Component.index_in_queue() renamed to Component.index() to be consistent with Queue.index()

Significant updates to the documentation (structure).

Optimizations by checking _trace flag before calling print_trace.

version 2.2.11 2018-01-14

Enhanced trace output

The trace output now also shows the line number in the source code. This can be extremely useful when debugging or learning.

E.g. the following code

10|class Y(sim.Component): 11| def process(self): 12| yield self.request((res, 4), fail_at=15) 13| x1.activate() 14| 15|env=sim.Environment(trace=True) 16|res = sim.Resource() 17| 18|x0=X() 19|x1=X() 20|Y() 21| 22|env.run(50)

prints:

line# time current component action information


15 main create 15 0.000 main current 16 resource.0 create capacity=1 18 x.0 create 18 x.0 activate scheduled for 0.000 @ 4 process=process 19 x.1 create 19 x.1 activate scheduled for 0.000 @ 4 process=process 20 y.0 create 20 y.0 activate scheduled for 0.000 @ 11 process=process 22 main run scheduled for 50.000 @ 22+ 4 0.000 x.0 current 5 x.0 hold scheduled for 10.000 @ 5+ mode=one 4 0.000 x.1 current 5 x.1 hold scheduled for 10.000 @ 5+ mode=one 11 0.000 y.0 current 12 y.0 request for 4 from resource.0 12 y.0 request scheduled for 15.000 @ 12+ 5+ 10.000 x.0 current 8 x.0 hold scheduled for 20.000!@ 8+ mode=two 5+ 10.000 x.1 current 7 x.1 passivate mode=one 12+ 15.000 y.0 current 22 y.0 request failed 13 x.1 activate scheduled for 15.000 @ 7+ mode=one y.0 ended 7+ 15.000 x.1 current 8 x.1 hold scheduled for 25.000!@ 8+ mode=two 8+ 20.000 x.0 current x.0 ended 8+ 25.000 x.1 current x.1 ended 22+ 50.000 main current

Note that output line now starts with the line number in the source. If a + is behind the line number, that means the statement following that line. Also, for components to be scheduled the line number where the component will start execution is shown following the @ sign. Urgent scheduling is now indicated with an ! sign behind the time.

Distributions

New distributions: Beta Erlang Gamma Weibull
Exponential distributions can now be specified with a mean (beta) or a rate (lambda).

Normal distributions can now be specified to use the alternative random.gauss method.

New parameter for class Component

suppress_pause_at_trace

New methods

Component.suppress_pause_at_trace

Sampling from a distribution is now also possible by just calling the distribution, like: yield self.hold(inter_arrival_time()) , which is equivalent to yield self.hold(inter_arrival_time.sample())

Documentation update

The online documentation is now better structured and more accessible. Also, a lot of content added, although still not complete. As always, volunteers are welcome to help in improving the manual!

The documentation makes clear now that the time stamps as used in timestamped monitors are not adjusted for reset_now().

version 2.2.10 2018-01-03

New methods: Component.remaining_duration() This method returns the duration left of a hold, request or wait at the time a passivate was given. For components that are scheduled, the remaining time to the scheduled time is returned. This is very handy to interrupt a component's hold for a, like in class Machine(sim.Component): def process(self): while True: yield self.hold(produce_one_part) number_of_parts += 1

Environment.reset_now() This method can be used to reset now, by default to 0. All times communicated to/from the application will be according to the new time. Be sure to adjust any user defined times as these will not be updated automatically!

Naming of object changed: In previous versions when initializing an object (Environment, Component, Queue, Resource, State, Monitor or MonitorTimestamped) where the name ended with a period , the sequence number (0) was suppressed for the first object. When a second object with the same name was initialized, that first object was renamed and got a 0 as sequence number. Now, an object with a name ending with a period is always serialized. If the name ends with a comma, the sequence starts at 1 (and the , is replaced by a .). E.g. for i in range(2): a = Airplane(name='airplane') b = Boat() c = Car(name='car,') print(a.name()) print(b.name()) print(c.name())

Change of name: Queue.intersect has been renamed to Queue.intersection.

New queue functionality: The intersection of two queues can now be assessed also with the & operator, e.g. q1 & q2. The union of two queues can now be assessed also with | operator, e.g. q1 | q2. The difference of two queues can now be assessed also with the - operator, e.g. q1 - q2. The symmetric_difference of two queues (new method) can be assessed also with the ^ operator, e.g. q1 ^ q2.

New color functionality: The method Environment.animation_parameters now has an additional parameter, foregroundcolor. If not specified, salabim automatically chooses the most contrasting color (white or black). This foreground color is used to show the system button, the time, modelname. Besides, several colors in Animate, AmimateButton and AnimateSliders now defaults to this foreground_color.

Internal: Several optimizations. Better checks for validity of colors.

version 2.2.9 2017-12-20

From this version the following classes: Environment Monitor MonitorTimestamp Queue Resource State support automatic naming according to the class where it is defined, like Component. Also, all these classes now call a setup method as the last statement in init . By default, this setup method is dummy.

Classes Monitor and MonitorTimestamp now contain base_name() and sequence_number() methods.

The image method of the class Animate now just returns an image and not anymore a tuple of an image and a serial number.

The function spec_to_image now supports the null string, in which case a dummy picture will be returned.

Animation of images is now correctly handled if width0 is omitted. When overriding Animate.width, None can be used to disable scaling.

Improved error handling (no more asserts).

Bug in Resource.release fixed.

version 2.2.8 2017-12-06

New features / major changes: Animation can now run synced (i.e. in real time, with a speed factor) or not synced. In the latter case, the animation will step from event to event. In that case also single stepping is now supported. Synced on/off can be set with animation_parameters and/or with the menu system.

The menu system is completely redesigned. It now shows only a 'Menu' button at start up. When this button is pressed, several buttons are shown. Here, the user can select Synced on/off, Trace on/off and Stop. When Synced is on, the user can increase or decrease the animation speed. When Synced is off, the user can single step through the simulation (with Step). Finally, with Go the simulation will run again. The animation speed is no longer shown in the right hand upper corner. And frames per second (fps) is disabled by default now, in order to get a less cluttered top line and more space for user information.

Minor changes and bug fixes: A major bug caused Pythonista to crash frequently and rapidly. This is now fixed.

The function show_speed of Environment.animation_parameters is no longer available.

The functions clocktext is are now a method of Environment, thus making it overridable.

Sample models are updated.

version 2.2.7 2017-11-28

New features: Animation of queues is now a standard feature, which makes visualisation of queue contents much simpler. In order to realize that, a component now has an animation_objects method, which defines how a component is to be visualized. The default method shows a black square of 40x40 with the sequence_number in white in this square. But, it is possible and quite likely necessary to override the animation_objects method. The method should return a list or tuple containing the x-size, y-size and one or more animation objects. In order to animate a queue, the method Queue.animate should be called with the position of the first component and the direction for subsequent components. Please have a look at the MMc model for a demonstration.

Animation of states is now a standard feature, which facilitates in visualization of a changing state. In order to realize that, a state now has an animation_object method, which defines how a state is displayed for each possible value. The default method shows a black square of 40x40 with the value dispayed in white in this square. If the value is a valid colour, an emmpty square with that colour is displayed. But, it is possible and quite likely necessary to override the animation_objects method. The method should return a list or tuple containing one or more animation objects. In order to animate a state, the method State.animate should be called once with the position of the animation object.

The animation now calls a method Environment.animation_pre_tick(t) just before starting the animation objects display loop. And Environment.animation_post_tick(t) just after the loop. Both methods are by default dummy (they just return). Overriding/monkey patching these methods (particularly animation_pre_tick) can be useful for advanced animations, e.g. for a queue where the y position changes over time (cf. the Elevator animated model).

Reintroduced functionality to remove animation objects belonging to a component that becomes a data component. The 'belonging to' has to be indicated with parent in the call to Animate.

Minor changes and bug fixes: Default arguments are now handled with an omitted value rather than None (or '*' in some cases).

All error handling is now via SalabimError.

Internal handling of Monitor.x, MonitorTimestamped.xduration, MonitorTimestamped.xt and MonitorTimestamped.tx improved.

Code optimized with several list comprehensions.

Bug fixes.

version 2.2.6 2017-10-07

Salabim now supports Python 2.7. The biggest advantage of this is, that models can now be run under PyPy.

The module numpy is no longer required (although still required for video production). This makes it easier to run under PyPy, where installing numpy can be complicated. When PIL is installed, even animation is now supported under PyPy.

All animated examples were updated to support Python 2.7, particularly by changing super().proc(...) into sim.Component.proc(self,...) and sim.Animate.proc(self, ...)

Internal: font searching and build up of font tables improved. Now salabim also searches the current directory for any .ttf files.

Several bug fixes.

version 2.2.5 2017-09-27

Queue.reset() has been renamed to Queue.reset_monitors() Resource.reset() has been renamed to Resource.reset_monitors() New method: State.reset_monitors()

In Component.wait(), if the test value contains a $, the $ sign is now replaced by state.value() instead of str(state.value()) This means that you can now say self.wait('light','$ in ("green","yellow")')
Monitor can now store the tallied values in an array instead of a list. this results in less memory usage and faster execution speed. The array may be either integer, unsigned integer or float. Integer and unsigned integer is available in 1, 2, 4 or 8 byte versions. When type='any' is given (default), the tallied values will be stored in a list. Note that the monitor for Queue.length_of_stay is a float array now. For list monitors the x-value in x() returns a numpy array where the values are converted to a numeric (value 0 if not possible) unless overridden with force_numeric=False.

MonitorTimestamp will now store the timestamps in a float array. The tallied values can be stored in an array instead of a list. This results in less memory usage and faster execution speed. The array may be either integer, unsigned integer or float. Integer and unsigned integer is available in 1, 2, 4 or 8 byte versions. When type='any' is given, the tallied values will be stored in a list. Monitor off is now tallied now with the attribute off of the timestamped monitor, which is dependent on the type. Note that the tallied values for Queue.length are in an uint32 array. The tallied values for Resource.capacity, Resource.claimed_quantity and Resource.available_quantity are in a float array, The tallied values for State.value are in a list, unless a type is specified at init time. The MonitorTimestamp.x() method returns a numpy array for integer and float monitors. For 'any' timestamped monitors the x-value in xduration() is a numpy array where the values are converted to a numeric (value 0 if not possible) unless overridden with force_numeric=False.

The Monitor.x() and MonitorTimestamped.xduration() methods now uses caching to impove performance.

The function type in Component.wait now uses three arguments instead of a tuple of value, component and state.

Redesigned font searching in order to support animation on Linux and to guarantee a consistent appearance over different platforms. Therefore, a small set of ttf fonts is included in the distribution. These should reside in the same directory where salabim.py is located (PyPI automatically takes care of that). These fonts will be first searched. As of this moment, salabim is shipped with:

Internal optimizations by using defaultdict instead of dict where useful.

Outphased: salabim.run() salabim.main() salabim.trace() salabim.now() salabim.animation_parameters() salabim.current_component() Use the equivalent default environment method instead, like env.run()

version 2.2.4 2017-09-12

Automatic naming of components, queues, etc. results in shorter names now. E.g., instead of 'client............11' the name is now 'client.11'. Also the name is not shortened anymore (was: 20 characters).

The methods Monitor.print_statistics(), MonitorTimestamp.print_statistics(), Queue.print_statistics, State.print_statistics and Resource.print_statistics() have an improved layout.

The method Monitor.print_histogram() and MonitorTimestamp.print_histogram() have an improved layout.

Monitor and MonitorTimestamp names are now serialized.

Please have a look at the much improved manual.

version 2.2.3 2017-09-05

PIL 4.2.1 (shipped with the latest WinPython distribution), has a bug when trying to animate text with the null string. Therefore, salabim now has special code to handle null strings correctly.

Minor bug with importing PIL under Pythonista fixed.

version 2.2.2 2017-09-02

Component.wait() now allows to check for":

Component.activate() has an additional parameter keep_wait, which controls whether waits are to be kept upon an activate, thus allowing an update of a timeout.

When salabim detects that PIL or tkinter is not installed when trying to animate, the error message now provided instructions on how to install the relevant package.

When salabim detects that cv2 is not installed when trying to produce a video, the error message now provided instructions on how to install the relevant package.

version 2.2.1 2017-08-31

Bug in Component.request() corrected. This prevented Machine shop animated to work properly.

version 2.2.0 2017-08-30

Introduced a new process control method: wait. This functionality is similar but not equal to the waitevent and queueevent methods in SimPy2. The method allows a process to wait for a any if all of (number of) certain value(s) of a so called state. A state has a value and each time this value change, all components waiting for this state are checked. The class State has a number of methods, which allow to control the state: set(value) sets the value. Normally used without argument, in which case True will be used. reset() resets the value to False= trigger(value) sets the value (default True), triggers any components waiting, and then immediately resets to a given value (default False). optionally, the number of components to be honored with the trigger value may be limited (if used, most like 1). The current value of a state can be retrieved with get() or by directly calling the state. So, e.g. dooropen.get() or dooropen() On top of that, the queue of waiters may be accessed with State.waiters() And there is a monitor to register the the value over time, called State.value . . The waiters queue and value will be monitored by default.

Components can wait for a certain value of a state (or states) by yield self.wait(dooropen) or yield self.wait((dooropen,False)) And for several states at one time: yield self.wait(frontdooropen,backdooropen) to test for fontdooropen OR backdooropen or yield self.wait(dooropen,lighton,all=True) to test for dooropen AND lighton It is also possible to check for several values of one state: yield self.wait((light,'green'),(light,'yellow')) tests for lighth is green of yellow

The method wait can have an optional timeout parameter. If they are timed out, Component.fail() is True.

If a component is in a wait state, the status waiting will be returned. In order to test for this state, use either c.state() == waiting or c.iswaiting() See the example script demo wait.py for a demonstration of the trigger and time out functionality.

Deprecated functionality: In method Component.request(), it is no longer allowed to specify greedy behaviour. Also strict_order is not anymore supported. The reason for this is its limited use and stability.

Technical note: The process of honouring requests is now optimized by keeping track of the minimal requested quantity for each resource.

Method request_failed() changed to failed(). This method now also refers to fails of wait.

For Monitor and MonitorTimestamp, the tallied values are now converted to a numeric equivalent, if not yet int/float. Values of type bool are converted to 0 for False and 1 for True (as usual). For all other types a conversion to int/float is tried. If that's not possible, 0 is used. So after m=sim.Monitor(name='m') m.tally(2) m.tally(True) m.tally('12') m.tally('red') m.x() is array([2,1,12,0]) This automatic conversion is used in all monitor methods, apart from getting the current value. So, after the above code, m() will return 'red' The unprocessed values are available in the lists Monitor.x end MonitorTimestamp.x (along with the coresponding MonitorTimestamp.t) So, after the above code m.x is (2,True,'12','red') .

The repr methods of Environment, Queue, Component, Monitor, Resource and the distributions, now return Environment(name), Queue(name), ... E.g. now q=sim.Queue('visitors') print(q) returns Queue(visitors) Getting information about one of the above class instances is now provided by the method print_info (formerly available via repr). E.g. q=sim.Queue('visitors') print(q) prints something like Queue 0x26e8e78ada0 name=visitors no components

The default random stream when initializing Environment is now 1234567. If a purely, not reproducable, stream by by specifying Environment(random_seed=None) or sim.random_seed(None). If you don't want any action when calling Environment (thats usually for subsequent run) (cf. Elevator animated.py specify the null string, so Environment(random_seed='') All sample models have been updated to reflect this change, i.e. no more random_seed=1234567 in the call to Environment, there.

Convention change: Instead of naming the default environment de, from now env is prefered. All sample models have been updated accordingly.

Documentation change only: Package numpy is required. The order of components in Queue.union() and Queue.intersect() is now specified.

Packaging remarks: Release notes.txt is now called changelog.txt, to be more in line with PyPI standards.

salabim is now on PyPI. So now you can install salabim also with pip install salabim!

version 2.1.3 2017-08-24

Upto now it was a requirement to build an init method with a call to super().init if the component had to be initialized in some way. This is a rather awkward construction and difficult to grasp for beginners. Although this construction is still accepted, there is now a more elegant method: setup. If this method is present in the definition of a component, it will be called after salabim has done all its initialization and even after the activate statement, if applicable. The method may have arguments. If so, whencreating the component, it is required to use keyword arguments.

salabim any version salabim>=2.1.3

class Car(sim.Component): class Car(sim.Component): def init(self,name,color): def setup(self,color): super().init(name=name) self.color=color self.color=color Car(name='BMW',color='red') Car(name='BMW',color=red)
Note that the init construction is still available.

All examples that used the init construction have been updated accordingly.

Both salabim and all examples now conform to PEP8 (with the exception of requirements on line length and visual indent).
Bug in print_statistics when monitoring was False fixed.

version 2.1.0

Not released

version 2.1.2 2017-08-21

New functionality: When a Component, Queue or Reseource is created, and the name ends with a period to signify auto serializing, the first created object with that name does not get serialized. So after wait0=sim.Queue('wait.') , the name of wait0 is 'wait' . When a second object with that name is created, the first object is serialized as yet. So after wait0=sim.Queue('wait.') wait1=sim.Queue('wait.') , the name of wait0 is 'wait..............0' This also holds for Components that are named after their class, like after class TrafficLight(sim.Component): pass t=TrafficLight() , the name of t is 'trafficlight' When more traffic lights are created, the name of t will become 'trafficlight.......0'

Name changes for a Component, Queue and Resource are now traced.

Creation of a Component, Queue and Resource is now traced.

Bug in Component.hold() fixed.

version 2.1.1 2017-08-19

Method Queue.print_statistics() now shows more information. New: Resource.print_statistics()

If a component now terminates (no more events), all claimed resources are now automatically released. Note, that this does not hold for cancellation of a component.

version 2.1.0 2017-08-18

At creation of a component, it is now possible to specify a process other than self.process to start. The new process parameter for the init of a component is a string containing the name of the (generation)function. Now, there also a check to see if process is a generator (i.e. contains at least one yield statement).

If there is a process attribute in a component, but you do not want that to be activated, specify process=None. The parameter auto_start is phased out, as there is now process=None.

The process parameter of Component.activate() has been changed. It is now a string containing the name of the process to be started. If None, no change.

With the function running_process, the name of the currently running process can be retrieved. For data components, None will be returned.

New to salabim are monitors, which are very useful for collecting (statistical) data. It is possible to tally values based on occurence, like the length of a ship, each time one is created. Or the length of stay for components leaving a queue. Another feature is a timestamped value. This is useful when collecting data on a time axis, like the length of a queue. Each time the queue length changes, the new length, together with time is then collected. This is a much more accurate and usually more efficient way of collecting data than just sampling on a regular basis.

Queue now supports monitoring of key statistics: length * length_of_stay

Resource now supports monitoring of key statistics: requesters.length * claimers.length * requesters.length_of_stay claimers.length_of_stay claimed_quantity * available_quantity * capacity *

The timestamped monitors (marked with a *) can be also used to query the current value, like c=myresource.claimed_quantity() or l=myqueue.length() although len(myqueue) is preferred

Monitors can be disabled/enabled with monitor (on the level of a Resource, a Queue or an individual Monitor or MonitorTimeStamp).

salabim <2.1.0 salabim >= 2.1.0

Queue.reset_statistics() Queue.reset() / Queue.monitor() Queue.minimum_length() Queue.length.minimum() Queue.maximum_length() Queue.length.maximum() Queue.number_passed() Queue.length_of_stay.number_entries() Queue.number_direct_passed() Queue.length_of_stay.number_entries_zero() Queue.mean_length() Queue.length.mean() Queue.mean_length_of_stay() Queue.length_of_stay.mean() Queue.minimum_length_of_stay() Queue.length_of_stay.minimum() Queue.maximum_length_of_stay() Queue.length_of_stay.maximum() Queue.length() Queue.length() / len() Queue.print_statistics() Queue.print_histogram() Queue.length.std() Queue.length.median() Queue.length.percentile() Queue.length.bin_count() Queue.length_of_stay.std() Queue.length_of_stay.median() Queue.length_of_stay.percentile() Queue.length_of_stay.bin_count() Resource.availabe_quantity ==> MonitorTimeStamp Resource.claimed_quantity ==> MonitorTimeStamp Resource.capacity ==> MonitorTimeStamp Resource.capacity(cap) Resource.set_capacity(cap) Component.requested_resources() Component.requested_quantity(resource)

All monitors can be used with MatPlotLib. For Monitor, there is Monitor.x() to get an array of all tallied values. For MonitorTimestamp, MonitorTimestamp.xduration() and MonitorTimestamp.xt() are available. Note that for the time a MonitorTimestamp is disabled, x will be nan.

Defining a non timestamped monitor (class Monitor) is fairly straightforward. Defining a timestamped monitor is a bit more complicated. The Lock with monitor script shows an example. Furthermore, refer to the manual for details.

As a consequence of this, Resource.capacity() can no longer be used to change the capacity. A new method Resource.set_capacity() is provided, instead.

All examples have been updated to support or show the new functionality.

Thanks to Frans Sopers: Bug in Component.release() fixed. Thanks to Jan Knoop: arialmt font is now included in getfont, which is required for iPad without additional fonts.

version 2.0.2 2017-08-08

Internal event handling changed, in order to be able to use all event methods (i.e. activate, hold, request, standby, cancel and passivate regardless whether the component is current or not). For the current component, always use yield ... Also, now it is possible to activate, hold or passivate main.

Component.reschedule() and Component.reactivate had been phased out. Both are replaced by the more versatile Component.activate() method.

Component.activate() has an additional parameter keep_request, which controls whether pending requests are to be kept upon an activate.

Environment.stop_run() and stop_run() has been phased out. Use the much more logical Environment.main().activate() or env.main().activate() instead. This allows the user to specify the time (including inf) of the main reactivation. The models 'Dining philosophers animated.py' and 'Elevator animated.py' have been updated accordingly.

Component.request() has now an additional parameter fail_delay.

When a request is pending, the status of the component used to be scheduled, but is now requesting. Also, a new method Component.isrequesting() is provided to test for this new status

The methods Component.get_priority and Component.set_priority are replaced by one method: Component.priority.

'salabim release notes.txt' is now called 'Release notes.txt'.

The github distribution now also contains the example scripts as used in the manual.

version 2.0.1

In the specification of Distribution, it is now also possible to use: c1 ==> Constant(c1) c1,c2, ==> Uniform(c1,c2) c1,c2,c3 ==> Triangular (c1,c2,c3)

It is not required to use the exact name of the distribution anymore. If you specify only the first letters of the distribution name, the correct distribution will be selected, regardless of casing.

Examples of these new features Uniform(13) ==> Uniform(13) Uni(12,15) ==> Uniform(12,15) UNIF(12,15) ==> Uniform(12,15) N(12,3) ==> Normal(12,3) Tri(10,20). ==> Triangular(10,20,15) 10 ==> Constant(10) 12,15 ==> Uniform(12,15) (12,15) ==> Uniform(12,15)

Normal distribution now has a default value for standard_deviation : 0, so effectively constant Uniform distribution now has a default value for upperbound : lowerbound, so effectively constant Trianguar distribution now has a default value for high : low, so effectively constant Trianguar distribution now has a default value for mode : (low+high)/2, so a symmetric distribution

version 2.0.0 2017-07-24

Major reorganization of the interface.

All properties has been phased out and made place for a method. This is done in order to be more Pythonic and consistent. Also, overriding is now easier.

Version 2.0.0 Version 2.0.0

Environment.peek Environment.peek() Environment.main Environment.main() Environment.now Environment.now() Environment.trace Environment.trace(value) Environment.current_component Environment.current_component() Environment.name Environment.name(txt) Environment.base_name Environment.base_name() Environment.sequence_number Environment.sequence_number()

Component.claimed_resources Component.claimed_resources() Component.request_failed Component.request_failed() Component.name Component.name(txt) Component.base_name Component.base_name() Component.sequence_number Componnt.sequence_number() Component.suppress_trace Component.suppress_trace(value) Component.mode Component.mode(value) Component.ispassive Component.ispassive() Component.iscurrent Component.iscurrent() Component.isscheduled Component.isscheduled() Component.isstandby Component.isstandby() Component.isdata Component.isdate() Component.creation_time Component.creation_time() Component.scheduled_time Component.scheduled_time() Component.mode_time Component.mode_time() Component.status Component.status() Component.now phased out, use Component.env.now() Component.main phased out, use Component.env.main() Component.trace phased out, use Component.env.trace(value) Component.current_component phased out, use Component.env.current_component()

Resource.requesters Resource.requesters() Resource.claimers Resource.claimers() Resource.capacity Resource.capacity(cap) Resource.claimed_quantity Resource.claimed_quantity() Resource.strict_order Resource.strict_order(value)

Queue.name Queue.name(txt) Queue.base_name Queue.base_name() Queue.sequence_number Queue.sequence_number() Queue.head Queue.head() Queue.tail Queue.tail() Queue.pop Queue.pop() Queue.length Queue.length(), len() recommended Queue.minimum_length Queue.minimum_length() Queue.maximum_length Queue.maximum_length() Queue.minimum_length_of_stay Queue.minimum_length_of_stay() Queue.maximum_length_of_stay Queue.maximum_length_of_stay() Queue.number_passed Queue.number_passed() Queue.mean_length_of_stay Queue.mean_length_of_stay() Queue.mean_length Queue.mean_length()

Uniform.mean Uniform.mean() Triangular.mean Triangular.mean() Constant.mean Constant.mean() Exponential.mean Exponential.mean() Normal.mean Normal.mean() Cdf.mean Cdf.mean() Pdf.mean Pdf.mean() Distribution.mean Distribution.mean()

Uniform.sample Uniform.sample() Triangular.sample Triangular.sample() Constant.sample Constant.sample() Exponential.sample Exponential.sample() Normal.sample Normal.sample() Cdf.sample Cdf.sample() Pdf.sample Pdf.sample() Distribution.sample Distribution.sample()

AnimateSlider.v AnimateSlider.v(value)

ATTENTION Be very careful with adding the parentheses, particularly when using in logical tests, like: if c.ispassive: #always True ! if c.status==passive #always False ! if trace: #always True and never assign to one of these methods,like env.trace=True #overrides the trace method of env c.name='Machine' # overrides the name method of the component c
Nearly all existing models will need be updated. All sample files are now version 2 compatible.

Nearly only internal change: the possible values of the status of a component were up to version 2.0.0 global variables, containing the name of the status. From this version the values of status are actually references to methods that return the name of the status. This affects passive, data, current, standby, scheduled and suspended. From an application point of view this does not change anything. Testing of the status is still done with (e.g.) if c.status()=sim.passive: or, better yet, if c.ispassive(): Only when the status has to be printed, be sure to call the status() method, thus print('current status of c=', c.status()())

If the distribution Pdf gets now a distribution as an x-value, not the distribution, but a sample of that this distribution will be returned. Example: d=sim.Pdf((Uniform(10,20),10,Uniform(20,30),80,Uniform(30,40),10)) d.sample() # 10% between 10 and 20, 80% between 20 and 30 and 10% between 30 and 40. d.mean() # is 25.

The argument list if Component.request() is redefined. See the manual or docstring for details. The argument list if Component.release() is redefined. See the manual or docstring for details.

The method Queue.index_of() has been renamed Queue.index() to be more in line with Python standards.

The script to install salalabim in site-packages is now called install.py

There are two new sample models, 'Lock with resources' and 'Lock with resources animated', showing a different approach of the Lock model, involving resources.

Major improvements of the docstrings and manual (at www.salabim.org/manual). Just a reminder: you can get help on all methods of salabim (pages and pages of information) with import salabim as sim help(sim) or just one method or class by (e.g.) import salabim as sim help(Animate) help(Component.enter) help(env.now)
Bug fixes.

version 1.1.3 2017-07-22

Pythonista could not handle the long one line statement defining the std_fonts. Therefore the definition of the dictionary is now in several lines of maximal 80 characters. The table is now also sorted (only for cosmetic reasons). The colornames dictionary is now sorted (only for cosmetic reasons).

version 1.1.2 2017-07-20

Under the hood restructuring of classes, thus enabling overriding.

In order to support packaging (i.e make salabim a site_package), the simulation environment is no longer automatically defined. It is now necessary to make an environment in the application, by: Environment() The Environment class has now an extra argument, is_default_env, which is True by default. Also, the global variable random has been phased out. Instead, salabim uses now the random class directly in all statistical sampling functions. It is possible to set the seed of the random stream by means of

Other than in previous versions, reproducibility is only available if a random seed value is specified, usually when defining an Environment.

All sample programs are defined in such a way that they give reproducible results.

main is no longer a global variable. You can use either the main property in Component or Environment classes or just main() from the salabim module, e.g. with env.main()

The default environment can be queried with the function default_env(), e.g. de=sim.default_env().

All sample filea are updated according to the new interface.

Font handling has been improved. It is now allowed to specify a font by the file name or the descriptor, e.g. 'timesi' or 'Times New Roman Italic'. The case is not important any more. The function show_fonts() can be used to retrieve all currently available font names (on this machine).

The salabim_install script now installs salabim in a more Pythonic way in the site-packages folder.

Bug in slider function under Pythonista fixed.

version 1.1.1 2017-07-08

Bug in video production fixed. Minor changes in the sample programs.

version 1.1.0 2017-07-08

The animation of all sample models is completely restructured in order to separate the simulation model code from the animation code. This leads to much easier to read and debug models. Also the same code can now be used for animation and production (non animated) applications.

Introduced an attribute 'mode' for components, which can be set either with an assignment or as a parameter of: initialization of a component passivate activate reactivate hold activate standby request cancel This attribute can be very useful for animations which run more or less separate from the simulation code. The mode parameter may be any type, although str is possibly the most useful. Also, the mode is now shown in a trace.

The attribute passive_reason has been phased out, as mode offers similar functionality for passive components. Be aware that mode is not reset automatically!

When the mode is set, either by an assignment or one of the above methods, the property mode_time is set to now. This is particularly useful for hold, in order to assess when the component started the hold, like in fraction=sim.interpolate(env.now,comp.mode_time,comp.scheduled_time,0,1) This technique is used in the sample models 'Lock animated' and 'Elevator animated'.

A bug in PIL caused non black texts to have a kind of outline around the characters in Animate(text=...). This was especially visible with white texts on a dark background. Code has been added to correct this behaviour.

Introduced a parameter visible for Animate and Animate.update. The method visible of animation objects can be overridden. This is especially useful in dashboard like animations.

AnimateButton and AnimateSlider no longer have a layer parameter, as these UI elements are now always on top of the Animate objects.

The animation parameters of run() are all removed. Now, a new animation_parameters method (in class Environment) is used to set all parameters. The function animation_speed has been phased out. It is now in animation_parameters.

Pythonista animation performance is increased by building up the image completely with PIL, before putting it into a scene. This also results in higher quality images (essentially the same as under tkinter).

On Pythonista, the animation size is now automatically set to the dimension of the screen. This can be overridden with a call to animation_parameters.

In the animation, the Quit button has been renamed to Stop.

The color of the text in Animate(text=) is now set with textcolor0 and textcolor1 instead of fillcolor0 and fillcolor1.

If a specified font in Animate cannot be found, the system now searches for calibri and next for arial fonts.

Bug fix: after a yield self.request call, the status of the component was still current, instead of scheduled.

version 1.0.6 2017-06-16

Animation on CPython platform is now slightly smoother, because there was a bug in the timing routine, which caused that the animation was doomed to be slower than 30 fps.

The upper right hand corner now shows, optionally, the number of frames per second fps). The run command has three new parameters, which control what is shown in the upper right hand corner, in case of animation: show_fps : bool if True, show the number of frames per second (default)|n| if False, do not show the number of frames per second

The text of an AnimateButton is now set via function text(), which can be overridden.

The function str_or_function had been phased out, as this can be achieved now by overriding methods.

version 1.0.5 2017-06-14

Complete overhaul of the tkinter way objects are shown. Instead of deleting all canvas objects for each animation tick, salabim now tries to minimize the number of create_image calls, by using itemconfig and coords and reusing canvas elements, if possible and necessary. The result is a much smoother animation, particularly at higher animation speeds. The above doesn't, unfortunately, apply to Pythonista.

Now all of the following methods of the class Animate may be overridden: def x(self,t=None) def y(self,t=None) def offsetx(self,t=None) def offsety(self,t=None) def angle(self,t=None) def linewidth(self,t=None) def linecolor(self,t=None) def fillcolor(self,t=None) def circle(self,t=None) def line(self,t=None) def polygon(self,t=None) def rectangle(self,t=None) def width(self,t=None) def fontsize(self,t=None) def text(self,t=None) def anchor(self,t=None) def image(self,t=None) def layer(self,t=None) , thus giving the possibility to use non linear movement, special effects, etc.

The distribution now also contains a script called salabim_install, which will install salabim in the appropriate site-packages folder. Once installed, salabim can be used from whatever location!

version 1.0.4

Animation interpolation can now be overridden (monkey patched) to allow for non linear interpolation or advanced animation techniques. For this make a new class based on Animation and redefine the necessary functions, like x,y, angle.

As illustration of the possibilities, have a look at demo_override.py

Duck typing implemented for more Pythonic behaviour.

version 1.0.3 2017-05-26

Minor bug with stop_run in animation mode fixed.

version 1.0.2 2017-05-07

The animation part of salabim has been redesigned, to make it more reliable and easier to access. Animation is not initialized with Animation() any more. Instead, the animation (and creation of a tkinter canvas in case of CPython) is now started by the run function. Therefore, the run function has now a number of additional parameters to set the size of the canvas, define scaling and transformation, etc.

An animation object is now a class. One of the attributes is the environment where it belongs to. There are three classes defined: Animate() Animate_button() Animate_slider() If you don't need animation, PIL does not need to be imported any more.

Also, the run function now supports the creation of an .mp4 video. For video production, numpy and cv2 (opencv) are required. This feature is not supported on Pythonista.

The animation_speed can now be get/set by calling animation_speed().

A number of function have been renamed to be more in line with Python naming (like isinstance): is_passive -> ispassive is_current -> iscurrent is_scheduled -> isscheduled is_standby -> isstandby is_data -> isdata

Bug when in a call to Animate() t0 was specified fixed.

version 1.0.1 2017-03-13

Iteration of components in a queue is completely reimplemented. Instead of the function components, iteration is now done the Pythonic way, like for comp in myqueue: Also, the algorithm now allows for very fast iteration even if components are leaving the queue during the iteration. If there is a change (enter or leave or change of order) during the iteration, the iteration is guaranteed to deliver all components in the queue after the change correctly. Therefore, there is no need for specification of static and/or removals_possible as in the out phased components function.

It is also possible to traverse a queue from tail to head, by using reversed as in: for comp in reversed(myqueue): This is also a generator where updates are possible during the iteration.

If you want to get a specific element in a queue, this can be now achieved with comp=myqueue[1] to access the second element in the queue. So, therefore myqueue[0] is equivalent to q.head myqueue[-1] is equivalent to q.tail Nevertheless head and tail are still supported, to be more in line with Tomas/Must terminology. The function Queue.component_with_index has been phased-out.

Slicing of queues is now supported. So for c in myqueue[1:3]: print(c.name) will print the names of the second and third element of the queue (provided they are available). Also, for c in myqueue[::-1]: print(c.name) will list the contents of myqueue, in reverse order. In contrast with the above mentioned reversed() this is just a static snapshot.
If a static snapshot of the queue contents is required, use myqueue[:] now. In contrast to previous versions of salabim, this is not faster than the standard iteration.

The functions Queue.contains(c) and Component.is_in_queue(q) have been phased out. Instead salabim now support the much more legible and Pythonic in construction, for example if comp in myqueue: or if comp not in queue:

The length of a queue can be found with len(q). The old style Queue.length is still available.

Automatic numbering of components, queues, resources and environments now starts with 0 (in contrast to 1 in previous versions). This is done to be more in line with Python.

All sample models are updated to accommodate the above changes.

Bug fixed: Changing a priority with set_priority, did affect the queue statistics. Now works as intended.

Bug fixed: The parameter use_toplevel in Animation() works as intended.

version 1.0.0 2017-03-01

The following names have been changed: SalabimComponent Component SalabimQueue Queue SalabimEnvironment Environment SalabimResource Resource salabim_random random reset_env (method) reset Distribution_from_string Distribution

The global function reset_env() (not the method of Environment) has been phased out. Use default_env.reset() instead.

The default process to be executed when creating a component is now called process, instead of action. The proc argument of activate has been changed to process. The proc argument of reschedule has been changed to process.

It is highly recommended to use the pythonistic import salabim or import salabim as sim instead of from salabim import * In all examples, we now use import salabim as sim If you use the latter form, all salabim items have to be preceeded by sim. , like sim.Component, sim.Resource, env.main, sim.passive, env.now(), sim.inf. If you want to use a salabim item without prefixing, use something like from salabim import inf,main along with import salabim as sim

In order to avoid having to specifiy sim.passive, sim.scheduled, etc. when importing as recommended, a number of new properties are introduced: is_passive (equivalent to status=sim.passive) is_current (eqivalent to status==sim.current) is_scheduled (equivalent to status==sim.scheduled) is_standby (equivalent to status==sim.standby) is_data (equivalent to status==sim.data)

Technical note: The test routines, which were present in the salabim source, are now in a separate module, called salabim_test.py.

Style note: the name salabim is now all lowercase, to be more Pythonistic.

The utility program salabim0to1.py translates existing version 0 models into the new version 1 style. The program translates ALL version 0 programs in the current directory, adding a 1 to the name, e.g. Lock.py will be translated to Lock1.py Please note, that the translation is not guaranteed to be 100% accurate, and some fine tuning might be necessary.

All sample models are updated accordingly.

version 0.9.17 (not released)

Salabim animations can now work in parallel with other modules using tkinter, like graphics.py. If so, set the parameter use_toplevel to True. If a user would like to start its own tkinter window(s) next to salabim's window (initialized with tkiniter.Tk(), just use root=tkinter.Toplevel() instead of root=tkinter.Tk()

version 0.9.16 2017-02-15

Bug when using Salabim without animation fixed.

version 0.9.15 2017-01-14

Improved way of animating circles. Also, now support for offset (most useful in combination with angle). Better support for transparency(alpha blending) for fill and line colors.

Bugs fixed.

version 0.9.14 2017-02-02

The classes AnimateCircle, AnimateLine, AnimatePolygon, AnimateRectangle, AnimateImage and AnimateText are now all combined in one class: Animate. All animations, including images now support scaling and rotation. Also, under Pythonista, convave polygons are now available. All animation objects support semi-transparency (also called alpha blending or opacity). Salabim now requires PIL (Pillow) for animations.

Bugs fixed.

version 0.9.13 2017-01-14

Animation on iPad / Pythonista now supported. Button and slider animation objects now available without the need to use tkinter functions. Button and slider animation objects are now also available under Pythonista. All functions are now documented as docstrings.

version 0.9.12 2016-12-16

SalabimQueue, SalabimComponent, SalabimResource and SalabimEnvironment as well as distribution now feature repr, in order to print the contents of the object. Thus,e.g., print(q1) now print information of the contents of q1

Now any environment may be reset to initial condition, with reset_env(). The function salabim_reset is renamed into reset_env, which is equivalent to default_env.reset_env() Environments now have a (optionally serialized) name.

In order to make the usage of the default environment, a number of functions have been changed into global variables: default_env main

Please observe that these variables should NEVER be set by the user. Tracing of the default environment is still controlled via the trace function or the setter/getter default_env.trace. Also, now() and current_component() are still to be used to access now and current_component for the default_env. Alternatively, now, current_component and trace can be accessed directly via the setter/getter of any SalabimComponent.

The function show() of a SalabimQueue has been renamed to print_statistics() and will no longer show the contents of a queue. In order to print the contents of a queue just use print(q).

Introduced a parameter 'reason' for passivate(). A program can now check why a component is passive by means of the property passive_reason. If a component is not passive, passive_reason is None.

Extensive support for animation with tkinter.

Bugs fixed.

version 0.9.11 2016-11-29

Bug in standby fixed.

When the name of a component is derived from the class name, the name is now serialized, e.g. s=Ship() print(s.name) will print Ship...............1

version 0.9.10 2016-11-28

Overhaul of the way randomstreams are used with the Salabim distributions. Now, instead of a seed, all distibutions can be given a randomstream. If this parameter is omitted, the default randomstream called salabim_random will be used. This salabim_randomstream will be automatically started with a reproducable seed, i.e. -1. The user may reseed this distribution with salabim_random.seed(seed) or salabim_random.seed() for a system time dependent (not reproducable) stream. If the user does not want to use the salabim_random stream, all distributions may be called with something like randomstream=random.Random(11000) or randomstream=mystream. Please note that the randomstream salabim_random may be (and is recommended to be) used by the native Random sampling routines, like random, shuffle, gauss and sample.

This version contains an extensive animation engine, that is not yet fully tested nor documented. This functionality will be soon available, along with several examples. The animation engine will be fully available on CPython and Pythonista platforms with tkinter and PIL (Pillow) installed and and with limited functionality when PIL (Pillow) is not available like in PyPy.

version 0.9.8 2016-11-13

Introduced property base_name and sequence_number for components, queues and resources

sorting_parameter renamed priority to be compatible with resource terminology.

SalabimComponents, SalabimQueue and SalabimResource now have a seqence_number and a base_name attribute (property). The base_name is the string given at initialization time. Even for components, queues and resources whose base name does not end with a period, the sequence_number is available. When assigning a new name to a component, queue or resource, base_name and sequence_number will be set according to the new name.

Bug fix for method release.

version 0.9.7 2016-11-10

The queue requests has been renamed to requesters.

Properties that do not return a value, are now functions (again): yield passivate ==> yield passivate() yield cancel ==> yield cancel() yield standby ==> yield standby() step ==> step() clear ==> clear() reset_statistics ==> reset_statistics() run_stop ==> run_stop()

Please be sure to update older applications, particularly with respect to passivate and stop_run, as the old syntax is still valid Python code, but the functions calls without parenthesis will have no action!

Added comments to resource functionality.

version 0.9.6 2016-11-06

Specification of a pdf now allows the x value to be a pair in the form of a list or tuple. If so, uniform sampling between these will be used if 'selected'. Example d=Pdf((10,(5,10),80,(10,50),10,(50,100)) In this case 10% will be 5-10, 80% between 10 and 50 and 10% from 50 to 100. There is not any restriction on the value pairs, so they may be overlapping or be completely separate. Also, scalars may be used in the same pdf specification. So the following is allowed, although not very likely to be useful: d=Pdf((10,0, 20,(-5,50), 30,(100,500), 1,2000)

The method mean() is now available for all distributions.

If no name is specified when initializing a SalabimComponent, the name is now derived from the class name (lowercased), e.g. mycar=Car() makes car the name of mycar.

Queue methods union, intersect and difference optimized for performance.

Introduced support for resources requesting/releasing resources. The functionality is similar to resources and levels in SimPy and semaphores in Tomas. This is a concept, with which a component can claim a quantity from a resource. A component is able to request from more than one resource at a time ('and' clause). Once all the requested quantities are available, the program continues with the next statement. The requested quantities are then claimed. Anyone (but most of the time it will be the requesting component) can then release (part of) the claimed quantities. The resource can be also anonymous, which means that the claimed quantities are not connected to a component, but just treated as one heap. This is similar to levels in SimPy. to be described in more detail

The following methods are now properties and are to be used without (): SalabimQueue name *) head tail pop length minimum_length maximum_length minimum length_of_stay maximum_length_of_stay number_passed number_passed_direct mean_length_of_stay mean_length clear reset_statistics

SalabimEnvironment step peek main now trace *) current_component

SalabimComponent passivate cancel standby stop_run granted_resources request_failed name *) main now trace *) creation_time scheduled_time current_component status

Distributions_from_string mean sample

SalabimResource requests claimers claimed_quantity strict_order *) name *)

*) means that these are also setters, so for instance c.name='box'.

Finally, the following functions, which are not part of a class still require (): main() now() trace() or trace(val) step() peek()

version 0.9.5 2016-10-27

It is now possible to use a default environment. In most cases there will be no need to use the concept of environments at all, which results in an easier to understand and read program. Therefore, upon startup, Salabim automatically creates an environment, which is available via default_env() if required. The default environment is an excellent placeholder for pseudo global variables. A new default environment (e.g. for a new run or experiment), can be set via salabim_reset(). Note that also the pseudo globals will be 'lost' then.

For the (few) methods/properties that required an environment as parameter, the following alternatives are available: env.run() run() env.peek() peek() env.step() step() env.now() now() env.main() main() Furthermore tracing control is implemented differently and may be set/retrieved now via env.trace() trace() Both with SalabimEnvironent.init as well as salabim_reset the trace parameter can be set (it is False by default).

It is now possible to schedule a component from the initialization by including an action() method in the class for this component. If so, it will be auto started, optionally at a later moment, specified by the at, delay and urgent parameters as in activate.

All properties have been removed to be more consistent (reversing the changes of one of the previous versions). Therefore, now all values to be retrieved, like q.length() and c.name() need a pair of parentheses.

Retrieving and setting of the sorting_parameter of a component in a queue is now done with one function: sorting_parameter.

The trace now also shows the name of the action generator, in case it's not action()?

Distributions are now documented. Please observe that Table is renamed to Cdf and Discrete if renamed to Pdf.

version 0.9.4 2016-10-25

The method components to iterate over all components in a queue now has an additional parameter 'removals_possible'. If it is guaranteed that no components will leave the queue during the iteration, this parameter may be set to False. Also in that case, components that do not enter the queue at the tail, might be excluded from the iteration. The result is a speed increase (particularly with large queues).

Internal: Optimalisations for several queue methods, by using the result of the check for membership of a queue in subsequent actions.

version 0.9.3 2016-10-24

The method stop_run has been added.

version 0.9.2 2016-10-23

Major bug in the activate, hold, reactivate and reschedule fixed

Now supports also older versions of Python, where NumPy does not feature nan and inf.

Now Python 2.7 compatible (not fully tested yet).

Optimized the method components, which is particularly useful if the queues is large. The method was O(n^2) and is now O(n).

Beautified the trace output.

version 0.9.0 2016-10-23

This version has a number of changes compared to previous versions.

Salabim does not rely on SimPy anymore.

The statuses now comply more with Must and Prosim than with Tomas, in order to achieve a more consistent interface.

State transition diagram

from\to | data| current| scheduled| passive| standby| ---------+------------+----------+----------------+---------------+-------------+ data | - | activate| activate| -| -| | | 1)| | | | ---------+------------+----------+----------------+---------------+-------------+ current | action end| -| yield hold|yield passivate|yield standby| |yield cancel| |yield reschedule| | | ---------+------------+----------+----------------+---------------+-------------+ scheduled| cancel|next event| reschedule| passivate| -| | | | | | | ---------+------------+----------+----------------+---------------+-------------+ passive | cancel|reactivate| reactivate| -| -| | | 1)| reschedule| | | ---------+------------+----------+----------------+---------------+-------------+ standby | cancel|next event| reschedule| -| -| | | | | | | ---------+------------+----------+----------------+---------------+-------------+

  1. via scheduled

The method activate now has an optional parameter for the action. If omitted, action will be assumed. So if the action of the component description is just called action, then the component can be started with component.activate(), which is functionally the equivalent to component.activate(component.action()).

The method hold now supports 'duration' as well as 'till' parameters (even both are permitted) activate, reactivate and reschedule now support 'at' as well 'delay' (even both are permitted) All transitions to current are via scheduled now.

It is now possible to issue a new action in activate, reactivate and reschedule, even for the current component via reschedule.

The methods reactivate, activate, reschedule and hold now have an 'urgent' parameter, which, if True, will schedule this component before each component with the same scheduled time. When urgent=False (default), component are scheduled after all components with the same scheduled time.

The method run now supports 'duration' as well as 'till' parameters (even both are permitted).

The scheduled time is now accessible for all component via scheduled_time. If the component is data or passive, inf will be returned. The creation time is now available via creation_time.

Many functions without parameters are now a property.

New naming: q.components (was q.iterate) q.mean_length (was q.average_length) q.mean_length_of_stay (was q.average_length_of_stay) q.add_in_front_of (was q.add_before) q.add_behind (was q.add_after) c.enter_in_front_of (was c.enter_before) c.enter_behind (was c.enter_after)

Names for queues and components are serialized separately now, if the name end with a period.

Introduced statistical distribution sampling. Exponential Normal Uniform Triangular Constant Table Discrete Distribution_from_string

Note that these distributions are not yet fully documented.

Improved trace functionality.

All functions and classes are now fully documented in the source code. Use help(...) to obtain information.