Commit 4831f5d2 authored by Ontje Lünsdorf's avatar Ontje Lünsdorf

Fixed documentation and renamed simulate to run.

parent c7b3cfe9
......@@ -5,15 +5,19 @@
.. automodule:: simpy.core
Performing the simulation of an environment
===========================================
Environments
============
.. autoclass:: Environment
:members:
:show-inheritance:
:inherited-members:
.. autoclass:: BaseEnvironment
:members:
Events and helpers
==================
Events
======
.. autoclass:: Event(env, value=PENDING, name=None)
......@@ -88,8 +92,8 @@ Events and helpers
Optional name for this event. Used for :class:`str` / :func:`repr` if
not ``None``.
.. autofunction:: all_events
.. autofunction:: any_event
.. automethod:: all_events
.. automethod:: any_events
.. autoclass:: Initialize
......@@ -103,6 +107,10 @@ Events and helpers
The :class:`Environment` the event lives in.
.. autoclass:: AllOf
.. autoclass:: AnyOf
Miscellaneous (Scheduling, Interrupt, constants)
================================================
......
......@@ -6,6 +6,7 @@
.. autoclass:: BaseResource
:members:
:private-members:
.. autoclass:: Put
:members:
......
==============================
``simpy`` --- The end-user API
``simpy`` --- The user API
==============================
......
......@@ -62,7 +62,7 @@ print('Bank renege')
random.seed(RANDOM_SEED)
env = simpy.Environment()
# Start processes and simulate
# Start processes and run
counter = simpy.Resource(env, capacity=1)
env.start(source(env, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter))
env.simulate()
env.run()
......@@ -93,5 +93,5 @@ random.seed(RANDOM_SEED) # This helps reproducing the results
env = simpy.Environment()
env.start(setup(env, NUM_MACHINES, WASHTIME, T_INTER))
# Simulate!
env.simulate(until=SIM_TIME)
# Execute!
env.run(until=SIM_TIME)
......@@ -100,5 +100,5 @@ fuel_pump = simpy.Container(env, GAS_STATION_SIZE, init=GAS_STATION_SIZE)
env.start(gas_station_control(env, fuel_pump))
env.start(car_generator(env, gas_station, fuel_pump))
# Simulate!
env.simulate(until=SIM_TIME)
# Execute!
env.run(until=SIM_TIME)
......@@ -68,4 +68,4 @@ cable = Cable(env, 10)
env.start(sender(env, cable))
env.start(receiver(env, cable))
env.simulate(until=SIM_DURATION)
env.run(until=SIM_DURATION)
......@@ -130,8 +130,8 @@ machines = [Machine(env, 'Machine %d' % i, repairman)
for i in range(NUM_MACHINES)]
env.start(other_jobs(env, repairman))
# Simulate!
env.simulate(until=SIM_TIME)
# Execute!
env.run(until=SIM_TIME)
# Analyis/results
print('Machine shop results after %s weeks' % WEEKS)
......
......@@ -93,9 +93,9 @@ num_renegers = {movie: 0 for movie in movies}
theater = Theater(counter, movies, available, sold_out, when_sold_out,
num_renegers)
# Start process and simulate
# Start process and run
env.start(customer_arrivals(env, theater))
env.simulate(until=SIM_TIME)
env.run(until=SIM_TIME)
# Analysis/results
for movie in movies:
......
......@@ -122,7 +122,7 @@ env.start(message_generator('Generator A', env, pipe))
env.start(message_consumer('Consumer A', env, pipe))
print('\nOne-to-one pipe communication\n')
env.simulate(until=SIM_TIME)
env.run(until=SIM_TIME)
# For one-to many use BroadcastPipe
# (Note: could also be used for one-to-one,many-to-one or many-to-many)
......@@ -134,4 +134,4 @@ env.start(message_consumer('Consumer A', env, bc_pipe.get_output_conn()))
env.start(message_consumer('Consumer B', env, bc_pipe.get_output_conn()))
print('\nOne-to-many pipe communication\n')
env.simulate(until=SIM_TIME)
env.run(until=SIM_TIME)
......@@ -3,4 +3,5 @@ SimPy home
==========
.. _templates/index.html contains the content for this page.
..
_templates/index.html contains the content for this page.
......@@ -214,9 +214,9 @@ class Condition(Event):
The ``evaluate`` function receives the list of target events and the
dictionary with all values currently available. If it returns
``True``, the condition is scheduled. SimPy provides the
:func:`all_events()` and :func:`any_event()` functions that are used
for the implementation of *and* (``&``) and *or* (``|``) of all
SimPy event types.
:func:`Condition.all_events()` and :func:`Condition.any_events()` functions
that are used for the implementation of *and* (``&``) and *or* (``|``) of
all SimPy event types.
Since condition are normal events, too, they can also be used as
sub- or nested conditions.
......@@ -312,10 +312,14 @@ class Condition(Event):
@staticmethod
def all_events(events, values):
"""A condition function that returns ``True`` if all ``events`` have
been triggered."""
return len(events) == len(values)
@staticmethod
def any_events(events, values):
"""A condition function that returns ``True`` if there is at least one
of ``events`` has been triggered."""
return len(values) > 0 or len(events) == 0
......@@ -565,14 +569,77 @@ class Scheduler(object):
class BaseEnvironment(object):
"""Base class for an environment, which schedules and executes events and
processes.
"""The abstract definition of an environment. An implementation must at
least provide the means to access the current time in the environment (see
:attr:`now`), to schedule (see :meth:`schedule()`) and execute (see
:meth:`step()` and :meth:`run()`) events.
The class is meant to be subclassed for different execution environments.
:class:`Environment` is for example a simulation environment with a virtual
time concept, whereas the :class:`~simpy.rt.RealtimeEnvironment` is
schedules and executes events in real (e.g. wallclock) time."""
@property
def now(self):
"""Property that returns the current time in the environment."""
raise NotImplemented(self)
def schedule(self, event, priority=DEFAULT_PRIORITY, delay=0):
"""Schedule an *event* with a given *priority* and a *delay*."""
raise NotImplemented(self)
def step(self):
"""Process the next event."""
raise NotImplemented(self)
def run(self, until=None):
"""Executes :meth:`step()` until the given criterion *until* is met.
- If it is ``None`` (which is the default) this method will return if
there are no further events to be processed.
- If it is an :class:`Event` the method will continue stepping until
this event has been triggered and returns its value.
- If it can be converted to a number the method will continue stepping
until the time in the environment reaches *until*.
"""
if until is None:
until = Event(self)
elif not isinstance(until, Event):
at = float(until)
if at <= self.now:
raise ValueError('until(=%s) should be > the current '
'simulation time.' % at)
# Schedule the event with before all regular timeouts.
until = Event(self)
until._value = None
self.schedule(until, HIGH_PRIORITY, at - self.now)
until.callbacks.append(_stop_simulate)
try:
while True:
self.step()
except EmptySchedule:
pass
return until.value if until.triggered else None
class Environment(BaseEnvironment):
"""The simulation *environment* which simulates the passing of time by
stepping from event to event.
This class also provides aliases for common event types, for example:
:attr:`process`, :attr:`timeout` and :attr:`event`.
"""
:attr:`process`, :attr:`timeout` and :attr:`event`."""
def __init__(self, scheduler):
def __init__(self, initial_time=0, scheduler=None):
if scheduler is None:
scheduler = Scheduler(self, initial_time)
self.scheduler = scheduler
self._active_proc = None
......@@ -605,17 +672,21 @@ class BaseEnvironment(object):
The ``value`` is sent to processes waiting for the current
process.
From Python 3.3, you can use ``return value`` instead.
.. note::
From Python 3.3, you can use ``return value`` instead.
"""
raise StopIteration(value)
def step(self):
"""Process the next event for the Environment ``env``.
Raise an :exc:`EmptySchedule` if no valid event is on the heap.
def peek(self):
"""Return the time at which the next event is scheduled or ``inf`` if
there are no futher events."""
return self.scheduler.peek()
"""
def step(self):
"""Process the next event. If there are no further events an
:exc:`EmptySchedule` will be risen."""
event = self.pop()
# Process callbacks of the event.
......@@ -629,64 +700,6 @@ class BaseEnvironment(object):
raise event._value
class Environment(BaseEnvironment):
"""The *environment* contains the simulation state and provides a
basic API for processes to interact with it.
"""
def __init__(self, initial_time=0, scheduler=None):
if scheduler is None:
scheduler = Scheduler(self, initial_time)
super(Environment, self).__init__(scheduler)
def peek(self):
"""Return the time at which the next event is scheduled or ``inf`` if
the event queue is empty.
"""
return self.scheduler.peek()
def simulate(self, until=None):
"""Executes events until the given criterion *until* is met.
- If it is ``None`` (which is the default) the execution will only
stop if there are no further events.
- If it is an :class:`Event` the execution will stop once this
event has been triggered.
- If it can be converted to a number the execution will stop when the
simulation time reaches *until*. (*Note:* Internally, an event is
created, so the simulation time will be exactly *until* afterwards.
No other events scheduled for *until* will be processed, though---as
it is at the very beginning of the simulation.)
"""
if until is None:
until = Event(self)
elif not isinstance(until, Event):
at = float(until)
if at <= self.now:
raise ValueError('until(=%s) should be > the current '
'simulation time.' % at)
# Schedule the event with before all regular timeouts.
until = Event(self)
until._value = None
self.schedule(until, HIGH_PRIORITY, at - self.now)
until.callbacks.append(_stop_simulate)
try:
while True:
self.step()
except EmptySchedule:
pass
return until.value if until.triggered else None
def _describe_frame(frame):
"""Prints filename, linenumber and function name of a stackframe."""
filename, name = frame.f_code.co_filename, frame.f_code.co_name
......
"""
Helpers for real-time (aka *wallclock time*) simulations.
Helpers for real-time (aka *wallclock time*) environments.
"""
try:
......@@ -22,9 +22,9 @@ class RealtimeScheduler(Scheduler):
The arguments *env* and an *initial_time* are passed to
:class:`~simpy.core.Scheduler`.
A simulation time step will take *factor* seconds of real time (one second
by default), e.g. if you simulate from ``0`` until ``3`` with
``factor=0.5``, the :meth:`~simpy.core.Environment.simulate()` call will
A time step will take *factor* seconds of real time (one second
by default), e.g. if you step from ``0`` until ``3`` with
``factor=0.5``, the :meth:`simpy.core.BaseEnvironment.run()` call will
take at least 1.5 seconds.
If the processing of the events for a time step takes too long,
......@@ -45,8 +45,8 @@ class RealtimeScheduler(Scheduler):
The call is delayed corresponding to the real-time *factor* of the
scheduler.
If the events of a simulation time step are processed to slowly for the
given *factor* and if *strict* is enabled, raise a :exc:`RuntimeError`.
If the events of a time step are processed too slowly for the given
*factor* and if *strict* is enabled, raise a :exc:`RuntimeError`.
"""
event = super(RealtimeScheduler, self).pop()
......@@ -67,8 +67,8 @@ class RealtimeScheduler(Scheduler):
class RealtimeEnvironment(Environment):
"""This :class:`~simpy.core.Environment` uses a :class:`RealtimeScheduler`
by default, so a simulation time step will take *factor* seconds of real
time (see :class:`RealtimeScheduler` for more information).
by default, so a time step will take *factor* seconds of real time (see
:class:`RealtimeScheduler` for more information).
"""
def __init__(self, initial_time=0, factor=1.0, strict=True):
......
......@@ -13,7 +13,7 @@ def test_operator_and(env):
}
env.start(process(env))
env.simulate()
env.run()
def test_operator_or(env):
......@@ -26,7 +26,7 @@ def test_operator_or(env):
}
env.start(process(env))
env.simulate()
env.run()
def test_operator_nested_and(env):
......@@ -41,7 +41,7 @@ def test_operator_nested_and(env):
assert env.now == 1
env.start(process(env))
env.simulate()
env.run()
def test_operator_nested_or(env):
......@@ -57,7 +57,7 @@ def test_operator_nested_or(env):
assert env.now == 2
env.start(process(env))
env.simulate()
env.run()
def test_nested_cond_with_error(env):
......@@ -73,7 +73,7 @@ def test_nested_cond_with_error(env):
assert err.args == ('Onoes!',)
env.start(process(env))
env.simulate()
env.run()
def test_cond_with_error(env):
......@@ -89,7 +89,7 @@ def test_cond_with_error(env):
assert err.args == ('Onoes, failed after 0!',)
env.start(process(env))
env.simulate()
env.run()
def test_cond_with_nested_error(env):
......@@ -105,7 +105,7 @@ def test_cond_with_nested_error(env):
assert err.args == ('Onoes, failed after 0!',)
env.start(process(env))
env.simulate()
env.run()
def test_cond_with_uncaught_error(env):
......@@ -120,7 +120,7 @@ def test_cond_with_uncaught_error(env):
env.start(process(env))
try:
env.simulate()
env.run()
assert False, 'There should have been an exception.'
except ValueError:
pass
......@@ -139,7 +139,7 @@ def test_iand_with_and_cond(env):
assert sorted(results.values()) == [0, 1, 2]
env.start(process(env))
env.simulate()
env.run()
def test_iand_with_or_cond(env):
......@@ -154,7 +154,7 @@ def test_iand_with_or_cond(env):
assert sorted(results.values()) == [0, 1]
env.start(process(env))
env.simulate()
env.run()
def test_ior_with_or_cond(env):
......@@ -169,7 +169,7 @@ def test_ior_with_or_cond(env):
assert sorted(results.values()) == [0]
env.start(process(env))
env.simulate()
env.run()
def test_ior_with_and_cond(env):
......@@ -184,7 +184,7 @@ def test_ior_with_and_cond(env):
assert sorted(results.values()) == [0]
env.start(process(env))
env.simulate()
env.run()
def test_immutable_results(env):
......@@ -205,7 +205,7 @@ def test_immutable_results(env):
assert results == {timeout[0]: 0}
env.start(process(env))
env.simulate()
env.run()
def test_shared_and_condition(env):
......@@ -223,7 +223,7 @@ def test_shared_and_condition(env):
env.start(p1(env, c1))
env.start(p2(env, c2))
env.simulate()
env.run()
def test_shared_or_condition(env):
......@@ -241,4 +241,4 @@ def test_shared_or_condition(env):
env.start(p1(env, c1))
env.start(p2(env, c2))
env.simulate()
env.run()
......@@ -25,7 +25,7 @@ def test_error_forwarding(env):
assert err.args[0] == 'Onoes!'
env.start(parent(env))
env.simulate()
env.run()
def test_no_parent_process(env):
......@@ -45,7 +45,7 @@ def test_no_parent_process(env):
pytest.fail('There should be no error (%s).' % err)
env.start(parent(env))
pytest.raises(ValueError, env.simulate)
pytest.raises(ValueError, env.run)
def test_crashing_child_traceback(env):
......@@ -70,7 +70,7 @@ def test_crashing_child_traceback(env):
assert 'raise RuntimeError(\'Oh noes,' in stacktrace
env.start(root(env))
env.simulate()
env.run()
@pytest.mark.skipif('sys.version_info[0] < 3')
......@@ -93,7 +93,7 @@ def test_exception_chaining(env):
env.start(grandparent(env))
try:
env.simulate()
env.run()
pytest.fail('There should have been an exception')
except RuntimeError:
import traceback
......@@ -111,7 +111,7 @@ def test_invalid_event(env):
env.start(root(env))
try:
env.simulate()
env.run()
pytest.fail('Hey, this is not allowed!')
except RuntimeError as err:
assert err.args[0].endswith('Invalid yield value "None"')
......@@ -124,7 +124,7 @@ def test_exception_handling(env):
event = env.event()
event.fail(RuntimeError())
try:
env.simulate(until=1)
env.run(until=1)
assert False, 'There must be a RuntimeError!'
except RuntimeError:
pass
......@@ -140,7 +140,7 @@ def test_callback_exception_handling(env):
event.callbacks.append(callback)
event.fail(RuntimeError())
assert not hasattr(event, 'defused'), 'Event has been defused immediately'
env.simulate(until=1)
env.run(until=1)
assert event.defused, 'Event has not been defused'
......@@ -158,5 +158,5 @@ def test_process_exception_handling(env):
event.fail(RuntimeError())
assert not hasattr(event, 'defused'), 'Event has been defuseed immediately'
env.simulate(until=1)
env.run(until=1)
assert event.defused, 'Event has not been defused'
......@@ -28,7 +28,7 @@ def test_interruption(env):
child_process.interrupt('interrupt!')
env.start(interruptor(env))
env.simulate()
env.run()
def test_concurrent_interrupts(env, log):
......@@ -51,7 +51,7 @@ def test_concurrent_interrupts(env, log):
for name in ('boggis', 'bunce', 'beans'):
env.start(farmer(env, name, fantastic_mr_fox))
env.simulate(20)
env.run(20)
assert log == [(0, 'boggis'), (0, 'bunce'), (0, 'beans')]
......@@ -72,7 +72,7 @@ def test_init_interrupt(env):
yield env.timeout(1)
env.start(root(env))
env.simulate()
env.run()
def test_interrupt_terminated_process(env):
......@@ -91,7 +91,7 @@ def test_interrupt_terminated_process(env):
yield env.timeout(1)
env.start(parent(env))
env.simulate()
env.run()
def test_multiple_interrupts(env):
......@@ -116,7 +116,7 @@ def test_multiple_interrupts(env):
assert result == 1
env.start(parent(env))
env.simulate()
env.run()