class IndexedOrderedStore(Store):
    """Resource with *capacity* slots for storing objects in order and
    retrieving multiple items simultaneously from arbitrary positions in the
    :class:`IndexedOrderedStore`. All items in an *IndexedOrderedStore*
    instance must be orderable, which is to say that items must implement
    :meth:`~object.__lt__()`.

    """

    put = BoundClass(StorePut)
    """Request to put an *item* into the store."""

    get = BoundClass(IndexedOrderedStoreGet)
    """Request to get *items* at *indices* out of the store."""
    def _do_put(self, event):
        if len(self.items) < self._capacity:
            insort(self.items, event.item)
            event.succeed()

    def _do_get(self, event):
        if len(event.indices) == 1:
            event.succeed([self.items.pop(event.indices[0])])
            return

        result = []

        for idx, val in enumerate(event.indices):
            result.append(self.items[val])
            self.items[val] = self.items[-1 - idx]

        if len(event.indices) > 0:
            del self.items[-len(event.indices):]
            self.items.sort()

        event.succeed(result)
Ejemplo n.º 2
0
class FilterStore(Store):
    """Resource with *capacity* slots for storing arbitrary objects supporting
    filtered get requests. Like the :class:`Store`, the *capacity* is unlimited
    by default and objects are put and retrieved from the store in a first-in
    first-out order.

    Get requests can be customized with a filter function to only trigger for
    items for which said filter function returns ``True``.

    .. note::

        In contrast to :class:`Store`, get requests of a :class:`FilterStore`
        won't necessarily be triggered in the same order they were issued.

        *Example:* The store is empty. *Process 1* tries to get an item of type
        *a*, *Process 2* an item of type *b*. Another process puts one item of
        type *b* into the store. Though *Process 2* made his request after
        *Process 1*, it will receive that new item because *Process 1* doesn't
        want it.

    """

    put = BoundClass(StorePut)
    """Request a to put *item* into the store."""

    get = BoundClass(FilterStoreGet)
    """Request a to get an *item*, for which *filter* returns ``True``, out of
    the store."""
    def _do_get(self, event):
        for item in self.items:
            if event.filter(item):
                self.items.remove(item)
                event.succeed(item)
                break
        return True
Ejemplo n.º 3
0
class Store(base.BaseResource):
    """Resource with *capacity* slots for storing arbitrary objects. By
    default, the *capacity* is unlimited and objects are put and retrieved from
    the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    """
    def __init__(self, env, capacity=float('inf')):
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')

        super(Store, self).__init__(env, capacity)

        self.items = []
        """List of the items available in the store."""

    put = BoundClass(StorePut)
    """Request to put *item* into the store."""

    get = BoundClass(StoreGet)
    """Request to get an *item* out of the store."""

    def _do_put(self, event):
        if len(self.items) < self._capacity:
            self.items.append(event.item)
            event.succeed()

    def _do_get(self, event):
        if self.items:
            event.succeed(self.items.pop(0))
Ejemplo n.º 4
0
class Resource(base.BaseResource):
    """Resource with *capacity* of usage slots that can be requested by
    processes.

    If all slots are taken, requests are enqueued. Once a usage request is
    released, a pending request will be triggered.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    resource is bound to.

    """

    def __init__(self, env: Environment, capacity: int = 1):
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')

        super().__init__(env, capacity)

        self.users: List[Request] = []
        """List of :class:`Request` events for the processes that are currently
        using the resource."""
        self.queue = self.put_queue
        """Queue of pending :class:`Request` events. Alias of
        :attr:`~simpy.resources.base.BaseResource.put_queue`.
        """

    @property
    def count(self) -> int:
        """Number of users currently using the resource."""
        return len(self.users)

    if TYPE_CHECKING:

        def request(self) -> Request:
            """Request a usage slot."""
            return Request(self)

        def release(self, request: Request) -> Release:
            """Release a usage slot."""
            return Release(self, request)

    else:
        request = BoundClass(Request)
        release = BoundClass(Release)

    def _do_put(self, event: Request) -> None:
        if len(self.users) < self.capacity:
            self.users.append(event)
            event.usage_since = self._env.now
            event.succeed()

    def _do_get(self, event: Release) -> None:
        try:
            self.users.remove(event.request)  # type: ignore
        except ValueError:
            pass
        event.succeed()
Ejemplo n.º 5
0
class Container(base.BaseResource):
    """Models the production and consumption of a homogeneous, undifferentiated
    bulk. It may either be continuous (like water) or discrete (like apples).

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    The *capacity* defines the size of the container and must be a positive
    number (> 0). By default, a container is of unlimited size.  You can
    specify the initial level of the container via *init*. It must be >=
    0 and is 0 by default.

    Raise a :exc:`ValueError` if ``capacity <= 0``, ``init < 0`` or
    ``init > capacity``.

    """
    def __init__(self, env, capacity=float('inf'), init=0):
        super(Container, self).__init__(env)
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')
        if init < 0:
            raise ValueError('"init" must be >= 0.')
        if init > capacity:
            raise ValueError('"init" must be <= "capacity".')

        self._capacity = capacity
        self._level = init

    @property
    def capacity(self):
        """The maximum capacity of the container."""
        return self._capacity

    @property
    def level(self):
        """The current level of the container (a number between ``0`` and
        ``capacity``).

        """
        return self._level

    put = BoundClass(ContainerPut)
    """Creates a new :class:`ContainerPut` event."""

    get = BoundClass(ContainerGet)
    """Creates a new :class:`ContainerGet` event."""

    def _do_put(self, event):
        if self._capacity - self._level >= event.amount:
            self._level += event.amount
            event.succeed()

    def _do_get(self, event):
        if self._level >= event.amount:
            self._level -= event.amount
            event.succeed()
Ejemplo n.º 6
0
class Resource(base.BaseResource):
    """A resource has a limited number of slots that can be requested by
    a process.

    If all slots are taken, requesters are put into a queue. If a process
    releases a slot, the next process is popped from the queue and gets one
    slot.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    resource is bound to.

    The *capacity* defines the number of slots and must be a positive integer.

    """
    def __init__(self, env, capacity=1):
        super(Resource, self).__init__(env)
        self._capacity = capacity
        self.users = []
        """List of :class:`Request` events for the processes that are currently
        using the resource."""
        self.queue = self.put_queue
        """Queue/list of pending :class:`Request` events that represent
        processes waiting to use the resource."""

    @property
    def capacity(self):
        """Maximum capacity of the resource."""
        return self._capacity

    @property
    def count(self):
        """Number of users currently using the resource."""
        return len(self.users)

    request = BoundClass(Request)
    """Create a new :class:`Request` event."""

    release = BoundClass(Release)
    """Create a new :class:`Release` event."""

    def _do_put(self, event):
        if len(self.users) < self.capacity:
            self.users.append(event)
            event.succeed()

    def _do_get(self, event):
        try:
            self.users.remove(event.request)
        except ValueError:
            pass
        event.succeed()
Ejemplo n.º 7
0
class Container(base.BaseResource):
    """Resource containing up to *capacity* of matter which may either be
    continuous (like water) or discrete (like apples). It supports requests to
    put or get matter into/from the container.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    The *capacity* defines the size of the container. By default, a container
    is of unlimited size. The initial amount of matter is specified by *init*
    and defaults to ``0``.

    Raise a :exc:`ValueError` if ``capacity <= 0``, ``init < 0`` or
    ``init > capacity``.

    """
    def __init__(self, env, capacity=float('inf'), init=0):
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')
        if init < 0:
            raise ValueError('"init" must be >= 0.')
        if init > capacity:
            raise ValueError('"init" must be <= "capacity".')

        super(Container, self).__init__(env, capacity)

        self._level = init

    @property
    def level(self):
        """The current amount of the matter in the container."""
        return self._level

    put = BoundClass(ContainerPut)
    """Request to put *amount* of matter into the container."""

    get = BoundClass(ContainerGet)
    """Request to get *amount* of matter out of the container."""

    def _do_put(self, event):
        if self._capacity - self._level >= event.amount:
            self._level += event.amount
            event.succeed()
            return True

    def _do_get(self, event):
        if self._level >= event.amount:
            self._level -= event.amount
            event.succeed()
            return True
Ejemplo n.º 8
0
class StampedStore(base.BaseResource):
    """Models the production and consumption of concrete Python objects.

    Items put into the store can be of any type.  By default, they are put and
    retrieved from the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    The *capacity* defines the size of the Store and must be a positive number
    (> 0). By default, a Store is of unlimited size. A :exc:`ValueError` is
    raised if the value is negative.

    """
    def __init__(self, env, capacity=float('inf')):
        super(StampedStore, self).__init__(env, capacity=float('inf'))
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')
        self._capacity = capacity
        self.items = []  # we are keeping items sorted by stamp
        self.event_count = 0  # Used to break ties with python heap implementation
        # See: https://docs.python.org/3/library/heapq.html?highlight=heappush#priority-queue-implementation-notes
        """List of the items within the store."""

    @property
    def capacity(self):
        """The maximum capacity of the store."""
        return self._capacity

    put = BoundClass(StampedStorePut)
    """Create a new :class:`StorePut` event."""

    get = BoundClass(StampedStoreGet)
    """Create a new :class:`StoreGet` event."""

    # We assume the item is a tuple: (stamp, packet). The stamp is used to
    # sort the packet in the heap.
    def _do_put(self, event):
        self.event_count += 1  # Needed this to break heap ties
        if len(self.items) < self._capacity:
            heappush(self.items,
                     [event.item[0], self.event_count, event.item[1]])
            event.succeed()

    # When we return an item from the stamped store we do not
    # return the stamp but only the content portion.
    def _do_get(self, event):
        if self.items:
            event.succeed(heappop(self.items)[2])
Ejemplo n.º 9
0
class PriorityPool(Pool):
    """Pool with prioritizied put() and get() requests.

    A priority is provided with `put()` and `get()` requests. This priority
    determines the strict order in which requests are fulfilled. Requests of
    the same priority are serviced in strict FIFO order.

    """
    def __init__(self,
                 env,
                 capacity=float('inf'),
                 init=0,
                 hard_cap=False,
                 name=None):
        super(PriorityPool, self).__init__(env, capacity, init, hard_cap, name)
        self._event_count = 0

    #: Put amount in the pool.
    put = BoundClass(PriorityPoolPutEvent)

    #: Get amount from the pool.
    get = BoundClass(PriorityPoolGetEvent)

    def _trigger_put(self, _=None):
        while self._put_waiters:
            put_ev = self._put_waiters[0]
            if self.capacity - self.level >= put_ev.amount:
                heapq.heappop(self._put_waiters)
                self.level += put_ev.amount
                put_ev.succeed()
                if self._put_hook:
                    self._put_hook()
            elif self._hard_cap:
                raise OverflowError()
            else:
                break

    def _trigger_get(self, _=None):
        while self._get_waiters:
            get_ev = self._get_waiters[0]
            if get_ev.amount <= self.level:
                heapq.heappop(self._get_waiters)
                self.level -= get_ev.amount
                get_ev.succeed(get_ev.amount)
                if self._get_hook:
                    self._get_hook()
            else:
                break
Ejemplo n.º 10
0
class ReadableFilterStore(FilterStore):
    """Extends simpy.resources.store.FilterStore with a non-destructive read()
    method."""

    get = BoundClass(FilterStoreGetWithRemove)

    read = BoundClass(FilterStoreGetWithNoRemove)

    def _do_get(self, event):
        for item in self.items:
            if event.filter(item):
                if event.remove_item:
                    self.items.remove(item)
                event.succeed(item)
                break
        return True
Ejemplo n.º 11
0
class FilterQueue(Queue[ItemType]):
    """Specialized queue where items are dequeued in priority order.

    Items in `PriorityQueue` must be orderable (implement
    :meth:`~object.__lt__`). Unorderable items may be used with `PriorityQueue`
    by wrapping with :class:`~PriorityItem`.

    Items that evaluate less-than other items will be dequeued first.

    """


    if TYPE_CHECKING:
        def get(self,filter: Callable[[Any], bool] = lambda item: True) -> FilterQueueGet:
            """Dequeue an item from the queue."""
            ...
    else:
        get = BoundClass(FilterQueueGet)

    def _trigger_get(self, _: Optional[Event] = None,*args,**kwargs) -> None:

        idx = 0
        while self._get_waiters and idx < len(self._get_waiters):
            get_ev = self._get_waiters[idx]
            for i,item in enumerate(self.items):
                if get_ev.filter(item):
                    self.items.pop(i)
                    self._get_waiters.pop(idx)
                    get_ev.succeed(item,*args,**kwargs)
                    if self._get_hook:
                        self._get_hook()
                    break
            else:
                idx +=1
Ejemplo n.º 12
0
class DPStore(store.Store):
    if TYPE_CHECKING:

        def get(
            self, epsilon: float, block_nr: int
        ) -> DPStoreGet:
            """Request to get an *item*, for which *filter* returns ``True``,
            out of the store."""
            return DPStoreGet(self, epsilon, block_nr )

    else:
        get = BoundClass(DPStoreGet)

    def _do_get(  # type: ignore[override] # noqa: F821
        self, event: DPStoreGet
    ) -> Optional[bool]:
        if len(self.items) >= event.block_nr:
            return_blocks = []
            for i in random.sample(range(len(self.items)), k=event.block_nr):
                block = self.items[i]
                if block["epsilon"] - event.epsilon <0:
                    block["epsilon"] = -1
                else:
                    block["epsilon"] = block["epsilon"] - event.epsilon
                return_blocks.append(block)

            event.succeed(return_blocks)
        return True
Ejemplo n.º 13
0
class Pkt(simpy.resources.store.Store):

    get = BoundClass(PktRX)

    def _do_get(self, event):
        if self.items:
            event.succeed(self.items)
            self.items = []
Ejemplo n.º 14
0
class ModifiedContainer(simpy.Container):

    def __init__(self, env, capacity, init, name, owner, resource):
        super().__init__(env,capacity, init)
        self.name = name
        self.owner = owner
        self.resource = resource

    if TYPE_CHECKING:

        def put(  # type: ignore[override] # noqa: F821
            self, amount: ContainerAmount, name: str, owner, resource: str
        ) -> ModifiedContainerPut:
            """Request to put *amount* of matter into the container."""
            return ModifiedContainerPut(self, amount, self.name, self.owner, self.resource)

        def get(  # type: ignore[override] # noqa: F821
            self, amount: ContainerAmount, name: str, owner, resource: str
        ) -> ModifiedContainerGet:
            """Request to get *amount* of matter out of the container."""
            return ModifiedContainerGet(self, amount, self.name, self.owner, self.resource)

    else:
        put = BoundClass(ModifiedContainerPut)
        get = BoundClass(ModifiedContainerGet)

    def _do_put(self, event: ModifiedContainerPut) -> Optional[bool]:
        if self._capacity - self._level >= event.amount:
            self._level += event.amount
            event.succeed()
            evnt_logger.info(f"\t {self.name} of {self.owner}'s level increased by {event.amount} {self.resource}'",extra={"sim_time":self._env.now})
            return True
        else:
            evnt_logger.info(f"\t {self.name} of {self.owner}'s put failed.'",extra={"sim_time":self._env.now})
            return None

    def _do_get(self, event: ModifiedContainerGet) -> Optional[bool]:
        if self._level >= event.amount:
            self._level -= event.amount
            event.succeed()
            evnt_logger.info(f"\t {self.name} of {self.owner}'s level decreased by {event.amount} {self.resource}'",extra={"sim_time":self._env.now})
            return True
        else:
            evnt_logger.info(f"\t {self.name} of {self.owner}'s put failed.'",extra={"sim_time":self._env.now})
            return None
Ejemplo n.º 15
0
class Store(base.BaseResource):
    """Resource with *capacity* slots for storing arbitrary objects. By
    default, the *capacity* is unlimited and objects are put and retrieved from
    the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    """
    def __init__(self,
                 env: Environment,
                 capacity: Union[float, int] = float('inf')):
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')

        super().__init__(env, capacity)

        self.items: List[Any] = []
        """List of the items available in the store."""

    if TYPE_CHECKING:

        def put(  # type: ignore[override] # noqa: F821
                self, item: Any) -> StorePut:
            """Request to put *item* into the store."""
            return StorePut(self, item)

        def get(self) -> StoreGet:  # type: ignore[override] # noqa: F821
            """Request to get an *item* out of the store."""
            return StoreGet(self)

    else:
        put = BoundClass(StorePut)
        get = BoundClass(StoreGet)

    def _do_put(self, event: StorePut) -> Optional[bool]:
        if len(self.items) < self._capacity:
            self.items.append(event.item)
            event.succeed()
        return None

    def _do_get(self, event: StoreGet) -> Optional[bool]:
        if self.items:
            event.succeed(self.items.pop(0))
        return None
Ejemplo n.º 16
0
class Store(base.BaseResource):
    """Models the production and consumption of concrete Python objects.

    Items put into the store can be of any type.  By default, they are put and
    retrieved from the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    The *capacity* defines the size of the Store and must be a positive number
    (> 0). By default, a Store is of unlimited size. A :exc:`ValueError` is
    raised if the value is negative.

    """
    def __init__(self, env, capacity=float('inf')):
        super(Store, self).__init__(env)
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')
        self._capacity = capacity
        self.items = []
        """List of the items within the store."""

    @property
    def capacity(self):
        """The maximum capacity of the store."""
        return self._capacity

    put = BoundClass(StorePut)
    """Create a new :class:`StorePut` event."""

    get = BoundClass(StoreGet)
    """Create a new :class:`StoreGet` event."""

    def _do_put(self, event):
        if len(self.items) < self._capacity:
            self.items.append(event.item)
            event.succeed()

    def _do_get(self, event):
        if self.items:
            event.succeed(self.items.pop(0))
class PriorityResource(Resource):
    """A :class:`~simpy.resources.resource.Resource` supporting prioritized
    requests.

    Pending requests in the :attr:`~Resource.queue` are sorted in ascending
    order by their *priority* (that means lower values are more important).

    """
    PutQueue = SortedQueue
    """Type of the put queue. See
    :attr:`~simpy.resources.base.BaseResource.put_queue` for details."""
    GetQueue = list
    """Type of the get queue. See
    :attr:`~simpy.resources.base.BaseResource.get_queue` for details."""
    def __init__(self, env, capacity=1):
        super(PriorityResource, self).__init__(env, capacity)

    request = BoundClass(PriorityRequest)
    """Request a usage slot with the given *priority*."""

    release = BoundClass(Release)
    """Release a usage slot."""
Ejemplo n.º 18
0
class PriorityResource(Resource):
    """A :class:`~simpy.resources.resource.Resource` supporting prioritized
    requests.

    Pending requests in the :attr:`~Resource.queue` are sorted in ascending
    order by their *priority* (that means lower values are more important).

    """

    PutQueue = SortedQueue
    """Type of the put queue. See
    :attr:`~simpy.resources.base.BaseResource.put_queue` for details."""

    GetQueue = list
    """Type of the get queue. See
    :attr:`~simpy.resources.base.BaseResource.get_queue` for details."""

    def __init__(self, env: Environment, capacity: int = 1):
        super().__init__(env, capacity)

    if TYPE_CHECKING:

        def request(
            self, priority: int = 0, preempt: bool = True
        ) -> PriorityRequest:
            """Request a usage slot with the given *priority*."""
            return PriorityRequest(self, priority, preempt)

        def release(  # type: ignore[override] # noqa: F821
            self, request: PriorityRequest
        ) -> Release:
            """Release a usage slot."""
            return Release(self, request)

    else:
        request = BoundClass(PriorityRequest)
        release = BoundClass(Release)
Ejemplo n.º 19
0
class Pkt(simpy.resources.store.Store):

    get = BoundClass(PktRX)

    def _do_get (self, event):
        if self.items:
            
            # if no limit - fetch all
            if event.limit is None:
                event.succeed(self.items)
                self.items = []
                
            else:
            # if limit was set - slice the list
                event.succeed(self.items[:event.limit])
                self.items = self.items[event.limit:]
Ejemplo n.º 20
0
class FilterStore(Store):
    """Resource with *capacity* slots for storing arbitrary objects supporting
    filtered get requests. Like the :class:`Store`, the *capacity* is unlimited
    by default and objects are put and retrieved from the store in a first-in
    first-out order.

    Get requests can be customized with a filter function to only trigger for
    items for which said filter function returns ``True``.

    .. note::

        In contrast to :class:`Store`, get requests of a :class:`FilterStore`
        won't necessarily be triggered in the same order they were issued.

        *Example:* The store is empty. *Process 1* tries to get an item of type
        *a*, *Process 2* an item of type *b*. Another process puts one item of
        type *b* into the store. Though *Process 2* made his request after
        *Process 1*, it will receive that new item because *Process 1* doesn't
        want it.

    """

    if TYPE_CHECKING:

        def get(
            self,
            filter: Callable[[Any],
                             bool] = lambda item: True) -> FilterStoreGet:
            """Request to get an *item*, for which *filter* returns ``True``,
            out of the store."""
            return FilterStoreGet(self, filter)

    else:
        get = BoundClass(FilterStoreGet)

    def _do_get(  # type: ignore[override] # noqa: F821
            self, event: FilterStoreGet) -> Optional[bool]:
        for item in self.items:
            if event.filter(item):
                self.items.remove(item)
                event.succeed(item)
                break
        return True
Ejemplo n.º 21
0
class PriorityResource(Resource):
    """This class works like :class:`Resource`, but requests are sorted by
    priority.

    The :attr:`~Resource.queue` is kept sorted by priority in ascending order
    (a lower value for *priority* results in a higher priority), so more
    important request will get the resource earlier.

    """
    PutQueue = SortedQueue
    """The type to be used for the
    :attr:`~simpy.resources.base.BaseResource.put_queue`."""
    GetQueue = list
    """The type to be used for the
    :attr:`~simpy.resources.base.BaseResource.get_queue`."""
    def __init__(self, env, capacity=1):
        super(PriorityResource, self).__init__(env, capacity)

    request = BoundClass(PriorityRequest)
    """Create a new :class:`PriorityRequest` event."""
Ejemplo n.º 22
0
class FilterStore(Store):
    """The *FilterStore* subclasses :class:`Store` and allows you to only get
    items that match a user-defined criteria.

    This criteria is defined via a filter function that is passed to
    :meth:`get()`. :meth:`get()` only considers items for which this function
    returns ``True``.

    .. note::

        In contrast to :class:`Store`, processes trying to get an item from
        :class:`FilterStore` won't necessarily be processed in the same order
        that they made the request.

        *Example:* The store is empty. *Process 1* tries to get an item of type
        *a*, *Process 2* an item of type *b*. Another process puts one item of
        type *b* into the store. Though *Process 2* made his request after
        *Process 1*, it will receive that new item because *Process 1* doesn't
        want it.

    """
    GetQueue = FilterQueue
    """The type to be used for the
    :attr:`~simpy.resources.base.BaseResource.get_queue`."""

    def __init__(self, env, capacity=float('inf')):
        super(FilterStore, self).__init__(env, capacity)
        self.get_queue.store = self

    get = BoundClass(FilterStoreGet)
    """Create a new :class:`FilterStoreGet` event."""

    def _do_get(self, event):
        for item in self.items:
            if event.filter(item):
                self.items.remove(item)
                event.succeed(item)
                break
Ejemplo n.º 23
0
class HdlEnvironmentCore(Environment):
    """
    Simpy Environment with patched processes to allow propcess start priotiry tweaks
    """
    process = BoundClass(HdlProcess)
Ejemplo n.º 24
0
class Pool(object):
    """Simulation pool of discrete or continuous resources.

    `Pool` is similar to :class:`simpy.resources.Container`.
    It provides a simulation-aware container for managing a shared pool of
    resources. The resources can be either discrete objects (like apples) or
    continuous (like water).

    Resources are added and removed using :meth:`put()` and :meth:`get()`.

    :param env: Simulation environment.
    :param capacity: Capacity of the pool; infinite by default.
    :param hard_cap:
        If specified, the pool overflows when the `capacity` is reached.
    :param init_level: Initial level of the pool.
    :param name: Optional name to associate with the queue.

    """
    def __init__(self,
                 env,
                 capacity=float('inf'),
                 init=0,
                 hard_cap=False,
                 name=None):
        self.env = env
        #: Capacity of the pool (maximum level).
        self.capacity = capacity
        #: Current fill level of the pool.
        self.level = init
        self._hard_cap = hard_cap
        self.name = name
        self._put_waiters = []
        self._get_waiters = []
        self._at_most_waiters = []
        self._at_least_waiters = []
        self._put_hook = None
        self._get_hook = None
        BoundClass.bind_early(self)

    @property
    def remaining(self):
        """Remaining pool capacity."""
        return self.capacity - self.level

    @property
    def is_empty(self):
        """Indicates whether the pool is empty."""
        return self.level == 0

    @property
    def is_full(self):
        """Indicates whether the pool is full."""
        return self.level >= self.capacity

    #: Put amount in the pool.
    put = BoundClass(PoolPutEvent)

    #: Get amount from the pool.
    get = BoundClass(PoolGetEvent)

    #: Return and event triggered when the pool has at least `amount` items.
    when_at_least = BoundClass(PoolWhenAtLeastEvent)

    #: Return and event triggered when the pool has at most `amount` items.
    when_at_most = BoundClass(PoolWhenAtMostEvent)

    #: Return an event triggered when the pool is non-empty.
    when_any = BoundClass(PoolWhenAnyEvent)

    #: Return an event triggered when the pool becomes full.
    when_full = BoundClass(PoolWhenFullEvent)

    #: Return an event triggered when the pool becomes not full.
    when_not_full = BoundClass(PoolWhenNotFullEvent)

    #: Return an event triggered when the pool becomes empty.
    when_empty = BoundClass(PoolWhenEmptyEvent)

    def _trigger_put(self, _=None):
        idx = 0
        while self._put_waiters and idx < len(self._put_waiters):
            put_ev = self._put_waiters[idx]
            if self.capacity - self.level >= put_ev.amount:
                self._put_waiters.pop(idx)
                self.level += put_ev.amount
                put_ev.succeed()
                if self._put_hook:
                    self._put_hook()
            elif self._hard_cap:
                raise OverflowError()
            else:
                idx += 1

    def _trigger_get(self, _=None):
        idx = 0
        while self._get_waiters and idx < len(self._get_waiters):
            get_ev = self._get_waiters[idx]
            if get_ev.amount <= self.level:
                self._get_waiters.pop(idx)
                self.level -= get_ev.amount
                get_ev.succeed(get_ev.amount)
                if self._get_hook:
                    self._get_hook()
            else:
                idx += 1

    def _trigger_when_at_least(self, _=None):
        while (self._at_least_waiters
               and self.level >= self._at_least_waiters[0].amount):
            when_at_least_ev = heapq.heappop(self._at_least_waiters)
            when_at_least_ev.succeed()

    def _trigger_when_at_most(self, _=None):
        while (self._at_most_waiters
               and self.level <= self._at_most_waiters[0].amount):
            at_most_ev = heapq.heappop(self._at_most_waiters)
            at_most_ev.succeed()

    def __repr__(self):
        return ('{0.__class__.__name__}(name={0.name!r} level={0.level}'
                ' capacity={0.capacity})').format(self)
Ejemplo n.º 25
0
class BaseResource(object):
    """This is the abstract base class for all SimPy resources.

    All resources are bound to a specific :class:`~simpy.core.Environment`
    *env*.

    You can :meth:`put()` something into the resources or :meth:`get()`
    something out of it. Both methods return an event that the requesting
    process has to ``yield``.

    If a put or get operation can be performed immediately (because the
    resource is not full (put) or not empty (get)), that event is triggered
    immediately.

    If a resources is too full or too empty to perform a put or get request,
    the event is pushed to the *put_queue* or *get_queue*. An event is popped
    from one of these queues and triggered as soon as the corresponding
    operation is possible.

    :meth:`put()` and :meth:`get()` only provide the user API and the general
    framework and should not be overridden in subclasses. The actual behavior
    for what happens when a put/get succeeds should rather be implemented in
    :meth:`_do_put()` and :meth:`_do_get()`.

    """

    PutQueue = list
    """The type to be used for the :attr:`put_queue`. This can either be
    a plain :class:`list` (default) or a subclass of it."""

    GetQueue = list
    """The type to be used for the :attr:`get_queue`. This can either be
    a plain :class:`list` (default) or a subclass of it."""
    def __init__(self, env):
        self._env = env
        self.put_queue = self.PutQueue()
        """Queue/list of events waiting to get something out of the resource.
        """
        self.get_queue = self.GetQueue()
        """Queue/list of events waiting to put something into the resource."""

        # Bind event constructors as methods
        BoundClass.bind_early(self)

    put = BoundClass(Put)
    """Create a new :class:`Put` event."""

    get = BoundClass(Get)
    """Create a new :class:`Get` event."""

    def _do_put(self, event):
        """Actually perform the *put* operation.

        This methods needs to be implemented by subclasses. It receives the
        *put_event* that is created at each request and doesn't need to return
        anything.

        """
        raise NotImplementedError(self)

    def _trigger_put(self, get_event):
        """Trigger pending put events after a get event has been executed."""
        self.get_queue.remove(get_event)

        for put_event in self.put_queue:
            if not put_event.triggered:
                self._do_put(put_event)
                if not put_event.triggered:
                    break

    def _do_get(self, event):
        """Actually perform the *get* operation.

        This methods needs to be implemented by subclasses. It receives the
        *get_event* that is created at each request and doesn't need to return
        anything.

        """
        raise NotImplementedError(self)

    def _trigger_get(self, put_event):
        """Trigger pending get events after a put event has been executed."""
        self.put_queue.remove(put_event)

        for get_event in self.get_queue:
            if not get_event.triggered:
                self._do_get(get_event)
                if not get_event.triggered:
                    break
Ejemplo n.º 26
0
class Store(BaseResource):
    """Resource with *capacity* slots for storing arbitrary objects. By
    default, the *capacity* is unlimited and objects are put and retrieved from
    the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    """
    PeekQueue: ClassVar[Type[MutableSequence[PeekType]]] = list
    GetTriggeredQueue: ClassVar[Type[MutableSequence[GetTriggeredType]]] = list
    PopQueue: ClassVar[Type[MutableSequence[PopType]]] = list
    PushQueue: ClassVar[Type[MutableSequence[PushType]]] = list
    """The type to be used for the :attr:`get_queue`. It is a plain
    :class:`list` by default. The type must support index access (e.g.
    ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and
    ``pop()`` operations."""
    def __init__(self,
                 env: Environment,
                 capacity: int = -1,
                 length: int = 2,
                 speed: int = 1,
                 acceleration: int = 0,
                 deceleration: int = 0,
                 distance: int = -1):
        super().__init__(env, capacity)
        self.peek_queue = self.PeekQueue()
        self.get_triggered_queue = self.GetTriggeredQueue()
        self.pop_queue = self.PopQueue()
        self.push_queue = self.PushQueue()

        self.length = length
        self.speed = speed
        self.acceleration = acceleration
        self.deceleration = deceleration
        self.distance = distance
        self.items: List[Any] = []
        """List of the items available in the store."""
        self.processing_queue: List[Any] = []
        self.ready_queue: List[Any] = []
        """List of the items available in the store."""

    if TYPE_CHECKING:

        def put(  # type: ignore[override] # noqa: F821
                self, item: Any) -> StorePut:
            """Request to put *item* into the store."""
            return StorePut(self, item)

        def get(self,
                item: Any) -> StoreGet:  # type: ignore[override] # noqa: F821
            """Request to get an *item* out of the store."""
            return StoreGet(self, item)

        def peek(self) -> StorePeek:
            return StorePeek(self)

        def get_triggered(self) -> StoreGetTriggered:
            return StoreGetTriggered(self)

        def pop(self) -> StorePop:
            return StorePop(self)

        def push(self) -> StorePush:
            return StorePush(self)

    else:
        put = BoundClass(StorePut)
        get = BoundClass(StoreGet)
        peek = BoundClass(StorePeek)
        get_triggered = BoundClass(StoreGetTriggered)
        pop = BoundClass(StorePop)
        push = BoundClass(StorePush)

    def _get_duration(self):
        return self.length / self.speed

    def _check_distance(self):
        if self.distance == -1:
            return 0
        elif len(self.processing_queue) == 0:
            return 0
        else:
            return (self.distance -
                    (self._env.now - self.processing_queue[-1]["startTime"]) *
                    self.speed) / self.speed

    def _check_capacity(self):
        if self.capacity == -1:
            return 0
        elif len(self.processing_queue) < self.capacity:
            return 0
        else:
            return self._get_duration() - (
                self._env.now - self.processing_queue[0]["startTime"])

    def _capacity_available(self):
        if self.capacity == -1:
            return True
        elif len(self.processing_queue) < self.capacity:
            return True
        else:
            return False

    def _check_available(self):
        return self._check_distance() <= 0 and self._check_capacity(
        ) <= 0 and self._capacity_available()

    def _do_put(self, event: StorePut) -> Optional[bool]:
        if self._check_available():
            self.processing_queue.append({
                "data": event.item,
                "startTime": self._env.now
            })
            event.succeed(True)
        return None

    def _do_get(self, event: StoreGet) -> Optional[bool]:
        if self.processing_queue[event.item]:
            event.succeed(self.processing_queue[event.item])
        return None

    def _do_peek(self, event: StorePeek) -> Optional[bool]:
        if self.items:
            event.succeed(self.items[0])
        return None

    def _do_get_triggered(self, event: StoreGetTriggered) -> Optional[bool]:
        if self._check_available():
            event.succeed(True)
        return None

    def _do_pop(self, event: StorePop) -> Optional[bool]:
        if self.ready_queue:
            event.succeed(self.ready_queue.pop(0))
            self.processing_queue.pop(0)
        return None

    def _do_push(self, event: StorePush) -> Optional[bool]:
        self.ready_queue.append(event.item)
        event.succeed(True)
        return None

    def _trigger_peek(self, put_event: Optional[PutType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.peek_queue):
            peek_event = self.peek_queue[idx]
            proceed = self._do_peek(peek_event)
            if not peek_event.triggered:
                idx += 1
            elif self.peek_queue.pop(idx) != peek_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break

    def _trigger_get_triggered(self, get_event: Optional[GetType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.get_triggered_queue):
            get_triggered_event = self.get_triggered_queue[idx]
            proceed = self._do_get_triggered(get_triggered_event)
            if not get_triggered_event.triggered:
                idx += 1
            elif self.get_triggered_queue.pop(idx) != get_triggered_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break

    def _trigger_pop(self, get_event: Optional[GetType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.pop_queue):
            pop_event = self.pop_queue[idx]
            proceed = self._do_pop(pop_event)
            if not pop_event.triggered:
                idx += 1
            elif self.pop_queue.pop(idx) != pop_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break

    def _trigger_push(self, get_event: Optional[GetType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.push_queue):
            push_event = self.push_queue[idx]
            proceed = self._do_push(push_event)
            if not push_event.triggered:
                idx += 1
            elif self.push_queue.pop(idx) != push_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break
Ejemplo n.º 27
0
class Store(BaseResource):
    """Resource with *capacity* slots for storing arbitrary objects. By
    default, the *capacity* is unlimited and objects are put and retrieved from
    the store in a first-in first-out order.

    The *env* parameter is the :class:`~simpy.core.Environment` instance the
    container is bound to.

    """
    PeekQueue: ClassVar[Type[MutableSequence[PeekType]]] = list
    GetTriggeredQueue: ClassVar[Type[MutableSequence[GetTriggeredType]]] = list
    """The type to be used for the :attr:`get_queue`. It is a plain
    :class:`list` by default. The type must support index access (e.g.
    ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and
    ``pop()`` operations."""
    def __init__(self,
                 env: Environment,
                 capacity: Union[float, int] = float('inf')):
        if capacity <= 0:
            raise ValueError('"capacity" must be > 0.')
        self.peek_queue = self.PeekQueue()
        self.get_triggered_queue = self.GetTriggeredQueue()
        super().__init__(env, capacity)
        self.items: List[Any] = []
        """List of the items available in the store."""

    if TYPE_CHECKING:

        def put(  # type: ignore[override] # noqa: F821
                self, item: Any) -> StorePut:
            """Request to put *item* into the store."""
            return StorePut(self, item)

        def get(self) -> StoreGet:  # type: ignore[override] # noqa: F821
            """Request to get an *item* out of the store."""
            return StoreGet(self)

        def peek(self) -> StorePeek:
            return StorePeek(self)

        def get_triggered(self) -> StoreGetTriggered:
            return StoreGetTriggered(self)

    else:
        put = BoundClass(StorePut)
        get = BoundClass(StoreGet)
        peek = BoundClass(StorePeek)
        get_triggered = BoundClass(StoreGetTriggered)

    def _do_put(self, event: StorePut) -> Optional[bool]:
        if len(self.items) < self._capacity:
            self.items.append(event.item)
            event.succeed(True)
        return None

    def _do_get(self, event: StoreGet) -> Optional[bool]:
        if self.items:
            event.succeed(self.items.pop(0))
        return None

    def _do_peek(self, event: StorePeek) -> Optional[bool]:
        if self.items:
            event.succeed(self.items[0])
        return None

    def _do_get_triggered(self, event: StoreGetTriggered) -> Optional[bool]:
        if not self.items:
            event.succeed(True)
        return None

    def _trigger_peek(self, put_event: Optional[PutType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.peek_queue):
            peek_event = self.peek_queue[idx]
            proceed = self._do_peek(peek_event)
            if not peek_event.triggered:
                idx += 1
            elif self.peek_queue.pop(idx) != peek_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break

    def _trigger_get_triggered(self, get_event: Optional[GetType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.get_triggered_queue):
            get_triggered_event = self.get_triggered_queue[idx]
            proceed = self._do_get_triggered(get_triggered_event)
            if not get_triggered_event.triggered:
                idx += 1
            elif self.get_triggered_queue.pop(idx) != get_triggered_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break

    def _trigger_put(self, get_event: Optional[GetType]) -> None:
        """This method is called once a new put event has been created or a get
        event has been processed.

        The method iterates over all put events in the :attr:`put_queue` and
        calls :meth:`_do_put` to check if the conditions for the event are met.
        If :meth:`_do_put` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All put requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.put_queue):
            put_event = self.put_queue[idx]
            proceed = self._do_put(put_event)
            if not put_event.triggered:
                idx += 1
            elif self.put_queue.pop(idx) != put_event:
                raise RuntimeError('Put queue invariant violated')

            if not proceed:
                break

    def _trigger_get(self, put_event: Optional[PutType]) -> None:
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.get_queue):
            get_event = self.get_queue[idx]
            proceed = self._do_get(get_event)
            if not get_event.triggered:
                idx += 1
            elif self.get_queue.pop(idx) != get_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break
Ejemplo n.º 28
0
class Queue(Generic[ItemType]):
    """Simulation queue of arbitrary items.

    `Queue` is similar to :class:`simpy.Store`. It provides a simulation-aware
    first-in first-out (FIFO) queue useful for passing messages between
    simulation processes or managing a pool of objects needed by multiple
    processes.

    Items are enqueued and dequeued using :meth:`put()` and :meth:`get()`.

    :param env: Simulation environment.
    :param capacity: Capacity of the queue; infinite by default.
    :param hard_cap:
        If specified, the queue overflows when the `capacity` is reached.
    :param items: Optional sequence of items to pre-populate the queue.
    :param name: Optional name to associate with the queue.

    """

    def __init__(
        self,
        env: Environment,
        capacity: Union[int, float] = float('inf'),
        hard_cap: bool = False,
        items: Iterable[ItemType] = (),
        name: Optional[str] = None,
    ) -> None:
        self.env = env
        #: Capacity of the queue (maximum number of items).
        self.capacity = capacity
        self._hard_cap = hard_cap
        self.items: List[ItemType] = list(items)
        self.name = name
        self._put_waiters: List[QueuePutEvent] = []
        self._get_waiters: List[QueueGetEvent] = []
        self._at_most_waiters: List[QueueWhenAtMostEvent] = []
        self._at_least_waiters: List[QueueWhenAtLeastEvent] = []
        self._put_hook: Optional[Callable[[], Any]] = None
        self._get_hook: Optional[Callable[[], Any]] = None
        BoundClass.bind_early(self)

    @property
    def size(self) -> int:
        """Number of items in queue."""
        return len(self.items)

    @property
    def remaining(self) -> Union[int, float]:
        """Remaining queue capacity."""
        return self.capacity - len(self.items)

    @property
    def is_empty(self) -> bool:
        """Indicates whether the queue is empty."""
        return not self.items

    @property
    def is_full(self) -> bool:
        """Indicates whether the queue is full."""
        return len(self.items) >= self.capacity

    def peek(self) -> ItemType:
        """Peek at the next item in the queue."""
        return self.items[0]

    if TYPE_CHECKING:

        def put(self, item: ItemType) -> QueuePutEvent:
            """Enqueue an item on the queue."""
            ...

        def get(self) -> QueueGetEvent:
            """Dequeue an item from the queue."""
            ...

        def when_at_least(self, num_items: int) -> QueueWhenAtLeastEvent:
            """Return an event triggered when the queue has at least n items."""
            ...

        def when_at_most(self, num_items: int) -> QueueWhenAtMostEvent:
            """Return an event triggered when the queue has at most n items."""
            ...

        def when_any(self) -> QueueWhenAnyEvent:
            """Return an event triggered when the queue is non-empty."""
            ...

        def when_full(self) -> QueueWhenFullEvent:
            """Return an event triggered when the queue becomes full."""
            ...

        def when_not_full(self) -> QueueWhenNotFullEvent:
            """Return an event triggered when the queue becomes not full."""
            ...

        def when_empty(self) -> QueueWhenEmptyEvent:
            """Return an event triggered when the queue becomes empty."""
            ...

    else:
        put = BoundClass(QueuePutEvent)
        get = BoundClass(QueueGetEvent)
        when_at_least = BoundClass(QueueWhenAtLeastEvent)
        when_at_most = BoundClass(QueueWhenAtMostEvent)
        when_any = BoundClass(QueueWhenAnyEvent)
        when_full = BoundClass(QueueWhenFullEvent)
        when_not_full = BoundClass(QueueWhenNotFullEvent)
        when_empty = BoundClass(QueueWhenEmptyEvent)

    def _enqueue_item(self, item: ItemType) -> None:
        self.items.append(item)

    def _dequeue_item(self) -> ItemType:
        return self.items.pop(0)

    def _trigger_put(self, _: Optional[Event] = None) -> None:
        while self._put_waiters:
            if len(self.items) < self.capacity:
                put_ev = self._put_waiters.pop(0)
                self._enqueue_item(put_ev.item)
                put_ev.succeed()
                if self._put_hook:
                    self._put_hook()
            elif self._hard_cap:
                raise OverflowError()
            else:
                break

    def _trigger_get(self, _: Optional[Event] = None) -> None:
        while self._get_waiters and self.items:
            get_ev = self._get_waiters.pop(0)
            item = self._dequeue_item()
            get_ev.succeed(item)
            if self._get_hook:
                self._get_hook()

    def _trigger_when_at_least(self, _: Optional[Event] = None, *args, **kwargs) -> None:
        while (
            self._at_least_waiters and self.size >= self._at_least_waiters[0].num_items
        ):
            when_at_least_ev = heappop(self._at_least_waiters)
            when_at_least_ev.succeed(*args,**kwargs)

    def _trigger_when_at_most(self, _: Optional[Event] = None) -> None:
        while self._at_most_waiters and self.size <= self._at_most_waiters[0].num_items:
            at_most_ev = heappop(self._at_most_waiters)
            at_most_ev.succeed()

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__}('
            f'name={self.name!r} size={self.size} capacity={self.capacity})'
        )
Ejemplo n.º 29
0
class BaseResource(object):
    """Abstract base class for a shared resource.

    You can :meth:`put()` something into the resources or :meth:`get()`
    something out of it. Both methods return an event that is triggered once
    the operation is completed. If a :meth:`put()` request cannot complete
    immediately (for example if the resource has reached a capacity limit) it
    is enqueued in the :attr:`put_queue` for later processing. Likewise for
    :meth:`get()` requests.

    Subclasses can customize the resource by:

    - providing custom :attr:`PutQueue` and :attr:`GetQueue` types,
    - providing custom :class:`Put` respectively :class:`Get` events,
    - and implementing the request processing behaviour through the methods
      ``_do_get()`` and ``_do_put()``.

    """
    PutQueue = list
    """The type to be used for the :attr:`put_queue`. It is a plain
    :class:`list` by default. The type must support index access (e.g.
    ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and
    ``pop()`` operations."""

    GetQueue = list
    """The type to be used for the :attr:`get_queue`. It is a plain
    :class:`list` by default. The type must support index access (e.g.
    ``__getitem__()`` and ``__len__()``) as well as provide ``append()`` and
    ``pop()`` operations."""

    def __init__(self, env, capacity):
        self._env = env
        self._capacity = capacity
        self.put_queue = self.PutQueue()
        """Queue of pending *put* requests."""
        self.get_queue = self.GetQueue()
        """Queue of pending *get* requests."""

        # Bind event constructors as methods
        BoundClass.bind_early(self)

    @property
    def capacity(self):
        """Maximum capacity of the resource."""
        return self._capacity

    put = BoundClass(Put)
    """Request to put something into the resource and return a :class:`Put`
    event, which gets triggered once the request succeeds."""

    get = BoundClass(Get)
    """Request to get something from the resource and return a :class:`Get`
    event, which gets triggered once the request succeeds."""

    def _do_put(self, event):
        """Perform the *put* operation.

        This method needs to be implemented by subclasses. If the conditions
        for the put *event* are met, the method must trigger the event (e.g.
        call :meth:`Event.succeed()` with an apropriate value).

        This method is called by :meth:`_trigger_put` for every event in the
        :attr:`put_queue`, as long as the return value does not evaluate
        ``False``.
        """
        raise NotImplementedError(self)

    def _trigger_put(self, get_event):
        """This method is called once a new put event has been created or a get
        event has been processed.

        The method iterates over all put events in the :attr:`put_queue` and
        calls :meth:`_do_put` to check if the conditions for the event are met.
        If :meth:`_do_put` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All put requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.put_queue):
            put_event = self.put_queue[idx]
            proceed = self._do_put(put_event)
            if not put_event.triggered:
                idx += 1
            elif self.put_queue.pop(idx) != put_event:
                raise RuntimeError('Put queue invariant violated')

            if not proceed:
                break

    def _do_get(self, event):
        """Perform the *get* operation.

        This method needs to be implemented by subclasses. If the conditions
        for the get *event* are met, the method must trigger the event (e.g.
        call :meth:`Event.succeed()` with an apropriate value).

        This method is called by :meth:`_trigger_get` for every event in the
        :attr:`get_queue`, as long as the return value does not evaluate
        ``False``.
        """
        raise NotImplementedError(self)

    def _trigger_get(self, put_event):
        """Trigger get events.

        This method is called once a new get event has been created or a put
        event has been processed.

        The method iterates over all get events in the :attr:`get_queue` and
        calls :meth:`_do_get` to check if the conditions for the event are met.
        If :meth:`_do_get` returns ``False``, the iteration is stopped early.
        """

        # Maintain queue invariant: All get requests must be untriggered.
        # This code is not very pythonic because the queue interface should be
        # simple (only append(), pop(), __getitem__() and __len__() are
        # required).
        idx = 0
        while idx < len(self.get_queue):
            get_event = self.get_queue[idx]
            proceed = self._do_get(get_event)
            if not get_event.triggered:
                idx += 1
            elif self.get_queue.pop(idx) != get_event:
                raise RuntimeError('Get queue invariant violated')

            if not proceed:
                break
Ejemplo n.º 30
0
class Queue(object):
    """Simulation queue of arbitrary items.

    `Queue` is similar to :class:`simpy.Store`. It provides a simulation-aware
    first-in first-out (FIFO) queue useful for passing messages between
    simulation processes or managing a pool of objects needed by multiple
    processes.

    Items are enqueued and dequeued using :meth:`put()` and :meth:`get()`.

    :param env: Simulation environment.
    :param capacity: Capacity of the queue; infinite by default.
    :param hard_cap:
        If specified, the queue overflows when the `capacity` is reached.
    :param items: Optional sequence of items to pre-populate the queue.
    :param name: Optional name to associate with the queue.

    """
    def __init__(self,
                 env,
                 capacity=float('inf'),
                 hard_cap=False,
                 items=(),
                 name=None):
        self.env = env
        #: Capacity of the queue (maximum number of items).
        self.capacity = capacity
        self._hard_cap = hard_cap
        self.items = list(items)
        self.name = name
        self._putters = []
        self._getters = []
        self._new_waiters = []
        self._any_waiters = []
        self._full_waiters = []
        self._put_hook = None
        self._get_hook = None
        BoundClass.bind_early(self)

    @property
    def size(self):
        """Number of items in queue."""
        return len(self.items)

    @property
    def remaining(self):
        """Remaining queue capacity."""
        return self.capacity - len(self.items)

    @property
    def is_empty(self):
        """Indicates whether the queue is empty."""
        return not self.items

    @property
    def is_full(self):
        """Indicates whether the queue is full."""
        return len(self.items) >= self.capacity

    def peek(self):
        """Peek at the next item in the queue."""
        return self.items[0]

    #: Enqueue an item on the queue.
    put = BoundClass(QueuePutEvent)

    #: Dequeue an item from the queue.
    get = BoundClass(QueueGetEvent)

    #: Return an event triggered when a new item is put into the queue.
    when_new = BoundClass(QueueWhenNewEvent)

    #: Return an event triggered when the queue is non-empty.
    when_any = BoundClass(QueueWhenAnyEvent)

    #: Return an event triggered when the queue becomes full.
    when_full = BoundClass(QueueWhenFullEvent)

    def _enqueue_item(self, item):
        self.items.append(item)

    def _dequeue_item(self):
        return self.items.pop(0)

    def _trigger_put(self, _=None):
        if self._putters:
            if len(self.items) < self.capacity:
                put_ev = self._putters.pop(0)
                put_ev.succeed()
                self._enqueue_item(put_ev.item)
                self._trigger_when_new()
                self._trigger_when_any()
                self._trigger_when_full()
                if self._put_hook:
                    self._put_hook()
            elif self._hard_cap:
                raise OverflowError()

    def _trigger_get(self, _=None):
        if self._getters and self.items:
            get_ev = self._getters.pop(0)
            item = self._dequeue_item()
            get_ev.succeed(item)
            if self._get_hook:
                self._get_hook()

    def _trigger_when_new(self):
        for when_new_ev in self._new_waiters:
            when_new_ev.succeed()
        del self._new_waiters[:]

    def _trigger_when_any(self):
        if self.items:
            for when_any_ev in self._any_waiters:
                when_any_ev.succeed()
            del self._any_waiters[:]

    def _trigger_when_full(self):
        if len(self.items) == self.capacity:
            for when_full_ev in self._full_waiters:
                when_full_ev.succeed()
            del self._full_waiters[:]

    def __str__(self):
        return ('Queue: name={0.name}'
                ' size={1}'
                ' capacity={0.capacity}'
                ')'.format(self, len(self.items)))