Commit bdff073f authored by Ontje Lünsdorf's avatar Ontje Lünsdorf
Browse files

Drop schedulers again, the BaseEnvironment provides enough abstraction to...

Drop schedulers again, the BaseEnvironment provides enough abstraction to implement all known environment types (simulation, realtime and distributed).
parent 4831f5d2
......@@ -111,23 +111,8 @@ Events
.. autoclass:: AnyOf
Miscellaneous (Scheduling, Interrupt, constants)
================================================
.. autoclass:: Scheduler
:members:
.. attribute:: env
The :class:`Environment` that the scheduler is associated with.
.. attribute:: now
The current simulation time.
.. attribute:: queue
A list with all currently scheduled events.
Miscellaneous (Interrupt and constants)
=======================================
.. autoclass:: EmptySchedule
......
......@@ -6,6 +6,3 @@
.. autoclass:: RealtimeEnvironment
:members:
.. autoclass:: RealtimeScheduler
:members:
......@@ -524,50 +524,11 @@ class Process(Event):
class EmptySchedule(Exception):
"""Thrown by a :class:`Scheduler` if its :attr:`~Scheduler.queue` is
empty."""
"""Thrown by the :class:`Environment` if there are no further events to be
processed."""
pass
class Scheduler(object):
"""Schedulers manage the event queue of an :class:`Environment`.
They schedule/enqueue new events and pop events from the queue. They also
manage the current simulation time.
"""
def __init__(self, env, initial_time):
self.env = env
self.now = initial_time
self.queue = []
self._eid = count()
def schedule(self, event, priority=DEFAULT_PRIORITY, delay=0):
"""Schedule an *event* with a given *priority* and a *delay*."""
heappush(self.queue, (self.now + delay, priority, next(self._eid),
event))
def peek(self):
"""Get the time of the next scheduled event. Return ``Infinity`` if the
event queue is empty."""
try:
return self.queue[0][0]
except IndexError:
return Infinity
def pop(self):
"""Remove and return the next event from the queue as ``(now, event)``.
Raise :exc:`EmptySchedule` if the schedule is empty.
"""
try:
self.now, _, _, event = heappop(self.queue)
return event
except IndexError:
raise EmptySchedule()
class BaseEnvironment(object):
"""The abstract definition of an environment. An implementation must at
least provide the means to access the current time in the environment (see
......@@ -637,21 +598,19 @@ class Environment(BaseEnvironment):
This class also provides aliases for common event types, for example:
:attr:`process`, :attr:`timeout` and :attr:`event`."""
def __init__(self, initial_time=0, scheduler=None):
if scheduler is None:
scheduler = Scheduler(self, initial_time)
self.scheduler = scheduler
def __init__(self, initial_time=0):
self._now = initial_time
self._queue = []
"""A list with all currently scheduled events."""
self._eid = count()
self._active_proc = None
self.schedule = self.scheduler.schedule
self.pop = self.scheduler.pop
BoundClass.bind_early(self)
@property
def now(self):
"""Property that returns the current simulation time."""
return self.scheduler.now
return self._now
@property
def active_process(self):
......@@ -667,27 +626,34 @@ class Environment(BaseEnvironment):
start = process
def exit(self, value=None):
"""Stop the current process, optionally providing a ``value``.
The ``value`` is sent to processes waiting for the current
process.
"""Convenience function provided for Python versions prior to 3.3. Stop
the current process, optionally providing a ``value``.
.. note::
From Python 3.3, you can use ``return value`` instead.
"""
From Python 3.3, you can use ``return value`` instead."""
raise StopIteration(value)
def schedule(self, event, priority=DEFAULT_PRIORITY, delay=0):
"""Schedule an *event* with a given *priority* and a *delay*."""
heappush(self._queue, (self._now + delay, priority, next(self._eid),
event))
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()
"""Get the time of the next scheduled event. Return ``Infinity`` if
there is no further event."""
try:
return self._queue[0][0]
except IndexError:
return Infinity
def step(self):
"""Process the next event. If there are no further events an
:exc:`EmptySchedule` will be risen."""
event = self.pop()
try:
self._now, _, _, event = heappop(self._queue)
except IndexError:
raise EmptySchedule()
# Process callbacks of the event.
for callback in event.callbacks:
......
"""
Helpers for real-time (aka *wallclock time*) environments.
"""Provides an environment whose time passes according to the (scaled)
real-time (aka *wallclock time*)."""
"""
try:
# Python >= 3.3
from time import monotonic as time, sleep
......@@ -9,49 +8,46 @@ except ImportError:
# Python < 3.3
from time import time, sleep
from simpy.core import Environment, Scheduler
from simpy.core import Environment, Infinity
Infinity = float('inf')
class RealtimeScheduler(Scheduler):
"""This :class:`~simpy.core.Scheduler` delays the :meth:`pop()` operation
to adjust to the wallclock time.
The arguments *env* and an *initial_time* are passed to
:class:`~simpy.core.Scheduler`.
class RealtimeEnvironment(Environment):
"""An environment which uses the real (e.g. wallclock) time.
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.
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,
a :exc:`RuntimeError` is raised by :meth:`pop()`. You can disable this
behavior by setting *strict* to ``False``.
a :exc:`RuntimeError` is raised in :meth:`step()`. You can disable this
behavior by setting *strict* to ``False``."""
def __init__(self, initial_time=0, factor=1.0, strict=True):
Environment.__init__(self, initial_time)
"""
def __init__(self, env, initial_time, factor=1.0, strict=True):
Scheduler.__init__(self, env, initial_time)
self.sim_start = initial_time
self.env_start = initial_time
self.real_start = time()
self.factor = factor
"""Scaling factor of the realtime."""
self.strict = strict
"""Running mode of the environment. :meth:`step()` will raise a
:exc:`RuntimeError` if this is set to ``True`` and the processing of
events took too long."""
def pop(self):
"""Return the next event from the schedule.
def step(self):
"""Waits until enough realtime has passed for the next event to happen.
The call is delayed corresponding to the real-time *factor* of the
scheduler.
The delay is scaled according to the real-time :attr:`factor`. If the
events of a time step are processed too slowly for the given
:attr:`factor` and if :attr:`strict` is enabled, a :exc:`RuntimeError`
is risen."""
evt_time = self.peek()
If the events of a time step are processed too slowly for the given
*factor* and if *strict* is enabled, raise a :exc:`RuntimeError`.
if evt_time is Infinity:
raise EmptySchedule()
"""
event = super(RealtimeScheduler, self).pop()
sim_delta = self.now - self.sim_start
sim_delta = evt_time - self.env_start
real_delta = time() - self.real_start
delay = sim_delta * self.factor - real_delta
......@@ -62,15 +58,5 @@ class RealtimeScheduler(Scheduler):
# for their computation, before an error is raised.
raise RuntimeError(
'Simulation too slow for real time (%.3fs).' % -delay)
return event
class RealtimeEnvironment(Environment):
"""This :class:`~simpy.core.Environment` uses a :class:`RealtimeScheduler`
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):
Environment.__init__(self, initial_time, RealtimeScheduler(
self, initial_time, factor, strict))
return Environment.step(self)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment