Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
6
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
simpy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
18
Issues
18
List
Boards
Labels
Service Desk
Milestones
Iterations
Merge Requests
6
Merge Requests
6
Requirements
Requirements
List
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Test Cases
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Package Registry
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issue
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
team-simpy
simpy
Commits
2ca6158e
Commit
2ca6158e
authored
Sep 10, 2013
by
Stefan Scherfke
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Moved all events from simpy.core to simpy.events and adjusted the tests and docs.
parent
587eedfb
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
676 additions
and
627 deletions
+676
-627
docs/api_reference/index.rst
docs/api_reference/index.rst
+1
-0
docs/api_reference/simpy.core.rst
docs/api_reference/simpy.core.rst
+32
-31
docs/api_reference/simpy.events.rst
docs/api_reference/simpy.events.rst
+34
-0
docs/index.rst
docs/index.rst
+2
-1
docs/simpy_intro/basic_concepts.rst
docs/simpy_intro/basic_concepts.rst
+13
-13
docs/simpy_intro/process_interaction.rst
docs/simpy_intro/process_interaction.rst
+19
-18
docs/topical_guides/porting_from_simpy2.rst
docs/topical_guides/porting_from_simpy2.rst
+6
-5
simpy/__init__.py
simpy/__init__.py
+7
-5
simpy/core.py
simpy/core.py
+19
-544
simpy/events.py
simpy/events.py
+532
-0
simpy/resources/base.py
simpy/resources/base.py
+6
-5
simpy/resources/resource.py
simpy/resources/resource.py
+2
-2
simpy/test/test_single_process.py
simpy/test/test_single_process.py
+1
-1
simpy/util.py
simpy/util.py
+2
-2
No files found.
docs/api_reference/index.rst
View file @
2ca6158e
...
...
@@ -11,6 +11,7 @@ components.
simpy
simpy.core
simpy.events
simpy.monitoring
simpy.resources
simpy.resources.base
...
...
docs/api_reference/simpy.core.rst
View file @
2ca6158e
...
...
@@ -4,56 +4,57 @@
.. automodule:: simpy.core
Environments
============
.. autoclass:: BaseEnvironment
:members:
.. autoclass:: Environment
:members:
:inherited-members:
.. autoattribute:: now
.. autoattribute:: active_process
Events
======
.. method:: start(generator)
.. autoclass:: Event(env, value=PENDING, name=None)
:members:
Start a new process from *generator*. Alias of :meth:`process`.
.. autoclass:: Process
:members:
.. method:: process(generator)
.. autoclass:: Timeout
:members:
Return a new :class:`~simpy.events.Process` instance for *generator*.
.. autoclass:: Condition
:members:
.. method:: timeout(delay, value=None)
.. autoclass:: AllOf
:members:
Return a new :class:`~simpy.events.Timeout` event with a *delay* and,
optionally, a *value*.
.. autoclass:: AnyOf
:members:
.. autoclass:: Initialize
:members:
.. method:: suspend()
Alias of :meth:`event()`. Yielding this event suspends a process until
another process triggers the event.
.. method:: event()
Return a new :class:`~simpy.events.Event` instance.
.. method:: all_of(events)
Miscellaneous (Interrupt and constants)
=======================================
Return a new :class:`~simpy.events.AllOf` condition for a list of
*events*.
.. method:: any_of(events)
Return a new :class:`~simpy.events.AnyOf` condition for a list of
*events*.
.. automethod:: exit
.. automethod:: schedule
.. automethod:: peek
.. automethod:: step
.. automethod:: run
.. autoclass:: BoundClass
:members:
.. autoclass:: EmptySchedule
.. autoclass:: Interrupt
:members: cause
.. autodata:: Infinity
.. autodata:: PENDING
.. autodata:: HIGH_PRIORITY
.. autodata:: DEFAULT_PRIORITY
.. autodata:: LOW_PRIORITY
docs/api_reference/simpy.events.rst
0 → 100644
View file @
2ca6158e
=====================================
``simpy.events`` --- Core event types
=====================================
.. automodule:: simpy.events
.. autoclass:: Event
:members:
.. autoclass:: Process
:members:
.. autoclass:: Timeout
:members:
.. autoclass:: Condition
:members:
.. autoclass:: AllOf
:members:
.. autoclass:: AnyOf
:members:
.. autoclass:: Initialize
:members:
.. autoclass:: Interrupt
:members: cause
.. autodata:: PENDING
.. autodata:: HIGH_PRIORITY
.. autodata:: DEFAULT_PRIORITY
.. autodata:: LOW_PRIORITY
docs/index.rst
View file @
2ca6158e
...
...
@@ -3,4 +3,5 @@ SimPy home
==========
.. _templates/index.html contains the content for this page.
..
_templates/index.html contains the content for this page.
docs/simpy_intro/basic_concepts.rst
View file @
2ca6158e
...
...
@@ -22,12 +22,12 @@ the process, when the event occurs (we say that the event is *triggered*).
Multiple processes can wait for the same event. SimPy resumes them in the same
order in which they yielded that event.
An important event type is the :class:`
Timeout`. Events of this type are
t
riggered after a certain amount of (simulated) time has passed. They allow
a
process to sleep (or hold its state) for the given time. A :class:`Timeout`
and all other events can be created by calling the appropriate method of the
:class:`Environment` that the process lives in (:meth:`Environment.timeout()`
for example).
An important event type is the :class:`
~simpy.events.Timeout`. Events of this
t
ype are triggered after a certain amount of (simulated) time has passed. They
a
llow a process to sleep (or hold its state) for the given time.
A :class:`~simpy.events.Timeout` and all other events can be created by calling
the appropriate method of the :class:`Environment` that the process lives in
(:meth:`Environment.timeout()`
for example).
Our First Process
...
...
@@ -59,10 +59,10 @@ will resume the function at this statement.
As I said before, our car switches between the states *parking* and *driving*.
It announces its new state by printing a message and the current simulation
time (as returned by the :attr:`Environment.now` property). It then calls the
:meth:`Environment.timeout()` factory function to create
a :class:`Timeout`
event. This event describes the point in time the car is done *parking* (or
*driving*, respectively). By yielding the event, it signals the simulation that
it wants to wait for the event to occur.
:meth:`Environment.timeout()` factory function to create
a :class:`~simpy.events.Timeout` event. This event describes the point in time
the car is done *parking* (or *driving*, respectively). By yielding the event,
it
signals the simulation that it
wants to wait for the event to occur.
Now that the behaviour of our car has been modelled, lets create an instance of
it and see how it behaves::
...
...
@@ -86,9 +86,9 @@ a *process generator* that needs to be started and added to the environment via
Note, that at this time, none of the code of our process function is being
executed. It's execution is merely scheduled at the current simulation time.
The :class:`
Process` returned by :meth:`~Environment.start()` can be used for
process interactions (we will cover that in the next section, so we will ignor
e
it for now).
The :class:`
~simpy.events.Process` returned by :meth:`~Environment.start()` can
be used for process interactions (we will cover that in the next section, so w
e
will ignore
it for now).
Finally, we start the simulation by calling meth:`Environment.simulate()` and
passing the environment as well as an end time to it.
...
...
docs/simpy_intro/process_interaction.rst
View file @
2ca6158e
...
...
@@ -4,20 +4,20 @@ Process Interaction
.. currentmodule:: simpy.core
The :class:`
Process` instance that is returned by :meth:`Environment.start()`
can be utilized for process interactions. The two most common examples for this
are to wait for another process to finish and to interrupt another process
while it is waiting for an event.
The :class:`
~simpy.events.Process` instance that is returned by
:meth:`Environment.start()` can be utilized for process interactions. The two
most common examples for this are to wait for another process to finish and to
interrupt another process
while it is waiting for an event.
Waiting for a Process
=====================
As it happens, a SimPy :class:`
Process` can be used like an event (technically,
a process actually *is* an event). If you yield it, you are resumed once th
e
process has finished. Imagine a car-wash simulation where cars enter the
car-wash and wait for the washing process to finish. Or an airport simulation
where passengers have to wait until a security check finishes.
As it happens, a SimPy :class:`
~simpy.events.Process` can be used like an event
(technically, a process actually *is* an event). If you yield it, you ar
e
resumed once the process has finished. Imagine a car-wash simulation where cars
enter the car-wash and wait for the washing process to finish. Or an airport
simulation
where passengers have to wait until a security check finishes.
Lets assume that the car from our last example magically became an electric
vehicle. Electric vehicles usually take a lot of time charing their batteries
...
...
@@ -30,8 +30,9 @@ Therefore, we refactor our car to be a class with two process methods:
The ``run`` process is automatically started when ``Car`` is instantiated.
A new ``charge`` process is started every time the vehicle starts parking. By
yielding the :class:`Process` instance that :meth:`Environment.start()`
returns, the ``run`` process starts waiting for it to finish::
yielding the :class:`~simpy.events.Process` instance that
:meth:`Environment.start()` returns, the ``run`` process starts waiting for it
to finish::
>>> class Car(object):
... def __init__(self, env):
...
...
@@ -79,7 +80,7 @@ Imagine, you don't want to wait until your electric vehicle is fully charged
but want to interrupt the charging process and just start driving instead.
SimPy allows you to interrupt a running process by calling its
:meth:`~Process.interrupt()` method::
:meth:`~
simpy.events.
Process.interrupt()` method::
>>> def driver(env, car):
... yield env.timeout(3)
...
...
@@ -88,10 +89,10 @@ SimPy allows you to interrupt a running process by calling its
The ``driver`` process has a reference to the car's ``run`` process. After
waiting for 3 time steps, it interrupts that process.
Interrupts are thrown into process functions as :exc:`
Interrupt` exceptions
that can (should) be handled by the interrupted process. The process can than
decide what to do next (e.g., continuing to wait for the original event or
yielding a new event)::
Interrupts are thrown into process functions as :exc:`
~simpy.events.Interrupt`
exceptions that can (should) be handled by the interrupted process. The process
can than decide what to do next (e.g., continuing to wait for the original
event or
yielding a new event)::
>>> class Car(object):
... def __init__(self, env):
...
...
@@ -138,8 +139,8 @@ What's Next
We just demonstrated two basic methods for process interactions---waiting for
a process and interrupting a process. Take a look at the
:doc:`../topical_guides/index` or the :class:`
Process` API reference for more
details.
:doc:`../topical_guides/index` or the :class:`
~simpy.events.Process` API
reference for more
details.
In the :doc:`next section <shared_resources>` we will cover the basic usage of
shared resources.
docs/topical_guides/porting_from_simpy2.rst
View file @
2ca6158e
...
...
@@ -22,8 +22,9 @@ simulation (``SimPy.Simulation``), a real-time simulation
keywords (``hold`` or ``passivate``, for example) from that package.
In SimPy 3, you usually need to import much less classes and modules (e.g., you
don't need direct access to :class:`~simpy.core.Process` and the SimPy keywords
anymore). In most use cases you will now only need to import :mod:`simpy`.
don't need direct access to :class:`~simpy.events.Process` and the SimPy
keywords anymore). In most use cases you will now only need to import
:mod:`simpy`.
**SimPy 2**
...
...
@@ -176,7 +177,7 @@ additional parameters (at least ``self``). These keywords had to be import from
to a function that generated the according event.
SimPy 3 directly exposes these event-generating functions via the
:class:`~simpy.core.Environment`, :class:`~simpy.
core
.Process` or resource
:class:`~simpy.core.Environment`, :class:`~simpy.
events
.Process` or resource
types, depending on were they make most sense. You don't need to import
something separately anymore.
...
...
@@ -240,8 +241,8 @@ of the victim.
Explicitly checking for an interrupt is obviously error prone as it is too easy
to be forgotten.
In SimPy 3, you call :meth:`~simpy.
core
.Process.interrupt()` on the victim
process. You can optionally pass a cause. An :exc:`~simpy.
core
.Interrupt` is
In SimPy 3, you call :meth:`~simpy.
events
.Process.interrupt()` on the victim
process. You can optionally pass a cause. An :exc:`~simpy.
events
.Interrupt` is
then thrown into the victim process, which has to handle the interrupt via
``try: ... except Interrupt: ...``.
...
...
simpy/__init__.py
View file @
2ca6158e
...
...
@@ -12,9 +12,9 @@ Core classes and functions
- :class:`Environment`: SimPy's central class. It contains
the simulation's state and lets the PEMs interact with it (i.e.,
schedule events).
- :class:`Process`: This class represents a process function while
it is executed in an environment. An instance of it is returned by
:meth:`Environment.start()`. It inherits :class:`Event`.
.. currentmodule:: simpy.events
- :class:`Interrupt`: This exception is thrown into a process if it gets
interrupted by another one.
...
...
@@ -66,7 +66,8 @@ Other
.. - :func:`test`: Run the test suite on the installed copy of Simpy.
"""
from
simpy.core
import
Environment
,
Interrupt
,
Process
from
simpy.core
import
Environment
from
simpy.events
import
Interrupt
from
simpy.resources.resource
import
(
Resource
,
PriorityResource
,
PreemptiveResource
)
from
simpy.resources.container
import
Container
...
...
@@ -75,7 +76,8 @@ from simpy.resources.store import Store, FilterStore
__all__
=
[
'test'
,
'Environment'
,
'Interrupt'
,
'Process'
,
'Environment'
,
'Interrupt'
,
'Resource'
,
'PriorityResource'
,
'PreemptiveResource'
,
'Container'
,
'Store'
,
'FilterStore'
,
...
...
simpy/core.py
View file @
2ca6158e
This diff is collapsed.
Click to expand it.
simpy/events.py
0 → 100644
View file @
2ca6158e
"""
This *events* module contains the various event type used by the SimPy core.
The base class for all events is :class:`Event`. Though it can be directly
used, there are several specialized subclasses of it:
- :class:`Timeout`: is scheduled with a certain delay and lets processes hold
their state for a certain amount of time.
- :class:`Initialize`: Initializes a new :class:`Process`.
- :class:`Process`: Processes are also modeled as an event so other processes
can wait until another one finishes.
- :class:`Condition`: Events can be concatenated with ``|`` an ``&`` to either
wait until one or both of the events are triggered.
- :class:`AllOf`: Special case of :class:`Condition`; wait until a list of
events has been triggered.
- :class:`AnyOf`: Special case of :class:`Condition`; wait until one of a list
of events has been triggered.
This module also defines the :exc:`Interrupt` exception.
"""
from
inspect
import
isgenerator
from
simpy._compat
import
PY2
if
PY2
:
import
sys
PENDING
=
object
()
#: Unique object to identify pending values of events
HIGH_PRIORITY
=
0
#: Priority of interrupts and Initialize events
DEFAULT_PRIORITY
=
1
#: Default priority used by events
LOW_PRIORITY
=
2
#: Priority of timeouts
class
Event
(
object
):
"""Base class for all events.
Every event is bound to an environment *env* (see
:class:`~simpy.core.BaseEnvironment`) and has an optional *value*.
An event has a list of :attr:`callbacks`. A callback can be any callable
that accepts a single argument which is the event instances the callback
belongs to. This list is not exclusively for SimPy internals---you can also
append custom callbacks. All callbacks are executed in the order that they
were added when the event is processed.
This class also implements ``__and__()`` (``&``) and ``__or__()`` (``|``).
If you concatenate two events using one of these operators,
a :class:`Condition` event is generated that lets you wait for both or one
of them.
"""
def
__init__
(
self
,
env
):
self
.
env
=
env
"""The :class:`~simpy.core.Environment` the event lives in."""
self
.
callbacks
=
[]
"""List of functions that are called when the event is
processed."""
self
.
_value
=
PENDING
def
__repr__
(
self
):
"""Return the description of the event (see :meth:`_desc`) with the id
of the event."""
return
'<%s object at 0x%x>'
%
(
self
.
_desc
(),
id
(
self
))
def
_desc
(
self
):
"""Return a string *Event()*."""
return
'%s()'
%
self
.
__class__
.
__name__
@
property
def
triggered
(
self
):
"""Becomes ``True`` if the event has been triggered and its callbacks
are about to be invoked."""
return
self
.
_value
is
not
PENDING
@
property
def
processed
(
self
):
"""Becomes ``True`` if the event has been processed (e.g., its
callbacks have been invoked)."""
return
self
.
callbacks
is
None
@
property
def
value
(
self
):
"""The value of the event if it is available.
The value is available when the event has been triggered.
Raise a :exc:`RuntimeError` if the value is not yet available.
"""
if
self
.
_value
is
PENDING
:
raise
RuntimeError
(
'Value of %s is not yet available'
%
self
)
return
self
.
_value
def
trigger
(
self
,
event
):
"""Triggers the event with the state and value of the provided *event*.
This method can be used directly as a callback function.
"""
self
.
ok
=
event
.
ok
self
.
_value
=
event
.
_value
self
.
env
.
schedule
(
self
,
DEFAULT_PRIORITY
)
def
succeed
(
self
,
value
=
None
):
"""Schedule the event and mark it as successful. Return the event
instance.
You can optionally pass an arbitrary ``value`` that will be sent into
processes waiting for that event.
Raise a :exc:`RuntimeError` if this event has already been scheduled.
"""
if
self
.
_value
is
not
PENDING
:
raise
RuntimeError
(
'%s has already been triggered'
%
self
)
self
.
ok
=
True
self
.
_value
=
value
self
.
env
.
schedule
(
self
,
DEFAULT_PRIORITY
)
return
self
def
fail
(
self
,
exception
):
"""Schedule the event and mark it as failed. Return the event instance.
The ``exception`` will be thrown into processes waiting for that event.
Raise a :exc:`ValueError` if ``exception`` is not an :exc:`Exception`.
Raise a :exc:`RuntimeError` if this event has already been scheduled.
"""
if
self
.
_value
is
not
PENDING
:
raise
RuntimeError
(
'%s has already been triggered'
%
self
)
if
not
isinstance
(
exception
,
Exception
):
raise
ValueError
(
'%s is not an exception.'
%
exception
)
self
.
ok
=
False
self
.
_value
=
exception
self
.
env
.
schedule
(
self
,
DEFAULT_PRIORITY
)
return
self
def
__and__
(
self
,
other
):
"""Return ``True`` if this event and *other* are triggered."""
return
Condition
(
self
.
env
,
Condition
.
all_events
,
[
self
,
other
])
def
__or__
(
self
,
other
):
"""Return ``True`` if this event or *other is triggered, or both."""
return
Condition
(
self
.
env
,
Condition
.
any_events
,
[
self
,
other
])
class
Timeout
(
Event
):
"""An :class:`Event` that is scheduled with a certain *delay* after its
creation.
This event can be used by processes to wait (or hold their state) for
*delay* time steps. It is immediately scheduled at ``env.now + delay`` and
has thus (in contrast to :class:`Event`) no *success()* or *fail()* method.
"""
def
__init__
(
self
,
env
,
delay
,
value
=
None
):
if
delay
<
0
:
raise
ValueError
(
'Negative delay %s'
%
delay
)
# NOTE: The following initialization code is inlined from
# Event.__init__() for performance reasons.
self
.
env
=
env
self
.
callbacks
=
[]
self
.
_value
=
value
# NOTE: The succeed() call is inlined for performance reasons.
self
.
_delay
=
delay
self
.
ok
=
True
env
.
schedule
(
self
,
LOW_PRIORITY
,
delay
)
def
_desc
(
self
):
"""Return a string *Timeout(delay[, value=value])*."""
return
'%s(%s%s)'
%
(
self
.
__class__
.
__name__
,
self
.
_delay
,
''
if
self
.
_value
is
None
else
(
', value=%s'
%
self
.
_value
))
class
Initialize
(
Event
):
"""Initializes a process. Only used internally by :class:`Process`."""
def
__init__
(
self
,
env
,
process
):
# NOTE: The following initialization code is inlined from
# Event.__init__() for performance reasons.
self
.
env
=
env
self
.
callbacks
=
[
process
.
_resume
]
self
.
_value
=
None
# Note: The succeed() call is inlined for performance reasons.
self
.
ok
=
True
env
.
schedule
(
self
,
HIGH_PRIORITY
)
class
Process
(
Event
):
"""A *Process* is a wrapper for the process *generator* (that is returned
by a *process function*) during its execution.
It also contains internal and external status information and is used for
process interaction, e.g., for interrupts.
``Process`` inherits :class:`Event`. You can thus wait for the termination
of a process by simply yielding it from your process function.
An instance of this class is returned by
:meth:`simpy.core.Environment.start()`.
"""
def
__init__
(
self
,
env
,
generator
):
if
not
isgenerator
(
generator
):
raise
ValueError
(
'%s is not a generator.'
%
generator
)
# NOTE: The following initialization code is inlined from
# Event.__init__() for performance reasons.
self
.
env
=
env
self
.
callbacks
=
[]
self
.
_value
=
PENDING
self
.
_generator
=
generator
# Schedule the start of the execution of the process.
self
.
_target
=
Initialize
(
env
,
self
)
def
_desc
(
self
):
"""Return a string *Process(process_func_name)*."""
return
'%s(%s)'
%
(
self
.
__class__
.
__name__
,
self
.
_generator
.
__name__
)
@
property
def
target
(
self
):
"""The event that the process is currently waiting for.
May be ``None`` if the process was just started or interrupted and did
not yet yield a new event.
"""
return
self
.
_target
@
property
def
is_alive
(
self
):
"""``True`` until the process generator exits."""
return
self
.
_value
is
PENDING
def
interrupt
(
self
,
cause
=
None
):
"""Interupt this process optionally providing a *cause*.
A process cannot be interrupted if it already terminated. A process
can also not interrupt itself. Raise a :exc:`RuntimeError` in these
cases.
"""
if
self
.
_value
is
not
PENDING
:
raise
RuntimeError
(
'%s has terminated and cannot be interrupted.'
%
self
)
if
self
is
self
.
env
.
active_process
:
raise
RuntimeError
(
'A process is not allowed to interrupt itself.'
)
# Schedule interrupt event
# NOTE: The succeed() call is inline for performance reasons and to
# set a HIGH_PRIORITY.
event
=
self
.
env
.
event
()
event
.
_value
=
Interrupt
(
cause
)
event
.
ok
=
False
# Interrupts do not cause the simulation to crash.
event
.
defused
=
True
event
.
callbacks
.
append
(
self
.
_resume
)
self
.
env
.
schedule
(
event
,
HIGH_PRIORITY
)
def
_resume
(
self
,
event
):
"""Resume the execution of the process.
Send the result of the event the process was waiting for into the
process generator and retrieve a new event from it. Register this
method as callback for that event.
If the process generator exits or raises an exception, terminate this
process. Also schedule this process to notify all registered callbacks,
that the process terminated.
"""
# Ignore dead processes. Multiple concurrently scheduled interrupts
# cause this situation. If the process dies while handling the first
# one, the remaining interrupts must be discarded.
if
self
.
_value
is
not
PENDING
:
return
# If the current target (e.g. an interrupt) isn't the one the process
# expects, remove it from the original events joiners list.
if
self
.
_target
is
not
event
:
self
.
_target
.
callbacks
.
remove
(
self
.
_resume
)