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)
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
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))
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()
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()
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()
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
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])
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
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
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
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
class Pkt(simpy.resources.store.Store): get = BoundClass(PktRX) def _do_get(self, event): if self.items: event.succeed(self.items) self.items = []
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
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
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."""
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)
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:]
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
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."""
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
class HdlEnvironmentCore(Environment): """ Simpy Environment with patched processes to allow propcess start priotiry tweaks """ process = BoundClass(HdlProcess)
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)
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
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
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
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})' )
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
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)))