API Reference

factorysimpy

base

belt_store

BeltStore(env, capacity=float('inf'), speed=1, accumulation_mode_indicator=True)

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a reservable store for processes to reserve space for storing and retrieving items.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservableReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes.

get and put are two methods that can be used for item storing and retrieval from ReservableReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservableReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservableReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

When an item is added into the BeltStore using put(), it is associated with a delay representing the time it takes for the item to move through the belt. The item is not immediately available for retrieval after being put into the store. Instead, it becomes available only after the specified delay has elapsed, simulating the movement of items on a conveyor belt.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Initializes a reservable store with reservations.

Args:

capacity (int, optional): The maximum number of items the store can hold. Defaults to infinity. mode (str, optional): The mode of the store ('FIFO' or 'LIFO'). Defaults to 'FIFO'. speed (float, optional): The speed of the conveyor belt. Defaults to 1. accumulation_mode_indicator (bool, optional): Indicates if the belt is in accumulation mode. Defaults to True. capacity (int, optional): The maximum number of items the store can hold. Defaults to infinity.

Source code in src/factorysimpy/base/belt_store.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __init__(self, env, capacity=float('inf'), speed=1, accumulation_mode_indicator=True):
    """
    Initializes a reservable store with reservations.

    Args:

     capacity (int, optional): The maximum number of items the store can hold.
                                Defaults to infinity.
     mode (str, optional): The mode of the store ('FIFO' or 'LIFO'). Defaults to 'FIFO'.
     speed (float, optional): The speed of the conveyor belt. Defaults to 1.
     accumulation_mode_indicator (bool, optional): Indicates if the belt is in accumulation mode.
                                                   Defaults to True.
     capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
    """
    super().__init__(env, capacity)
    self.env = env

    self.speed = speed  # Speed of the conveyor belt (units per time)
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self.ready_items=[]  #Maintains the items ready to be taken out
    self.reserved_items   = []   # parallel list of the exact items reserved
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
    # Process tracking for interrupt functionality
    self.active_move_processes = {}  # Dictionary to track active move_to_ready_items processes
    self.active_delayed_interrupt_processes = {}  # Dictionary to track active delayed interrupt processes
    self.resume_event = self.env.event()  # Event to signal when to resume processes
    self.noaccumulation_mode_on = False # to control if the belt is in noaccumulation mode
    self.accumulation_mode_indicator = accumulation_mode_indicator # to indicate if the belt is in accumulation mode or not
    self.one_item_inserted = False # to control insertion of only one item in noaccumulation mode
    self.ready_item_event= self.env.event()
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/belt_store.py
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      #self._update_time_averaged_level()
      return item
handle_new_item_during_interruption(item)

Handle new item added during STALLED_ACCUMULATING_STATE with accumulation=1.

Computes delay using same logic as for existing items.

Source code in src/factorysimpy/base/belt_store.py
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
def handle_new_item_during_interruption(self, item):
    """
    Handle new item added during STALLED_ACCUMULATING_STATE with accumulation=1.

    Computes delay using same logic as for existing items.
    """


    # Build the updated belt pattern with new item
    current_pattern = self._get_belt_pattern()
    print(f"T={self.env.now:.2f} Current belt pattern after adding new item: {current_pattern[0]} and items {current_pattern[1]}")
    updated_pattern = list(current_pattern[0])




    # Recompute item positions
    item_positions = [i for i, c in enumerate(updated_pattern) if c == '*']
    print("item_positions with new item", item_positions)
    # Get interruption plan
    interruption_plan = self._calculate_gap_based_interruptions(''.join(updated_pattern), item_positions, current_pattern[1])
    print(f"T={self.env.now:.2f} Interruption plan with new item: {interruption_plan}")
    # The new item is the last one added in pattern
    delay_for_new_item = interruption_plan[0]['delay']

    item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
    item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
    item_length = item[0].length if hasattr(item[0], 'length') else 1.0
    delay_for_new_item = delay_for_new_item * (item_length / self.speed)
    if delay_for_new_item > 0:
        print(f"T={self.env.now:.2f} New item {item_id} will be interrupted after {delay_for_new_item} time units")
        interrupt_process= self.env.process(self._delayed_interrupt(item_id, delay_for_new_item, "New item during interruption"))
        item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
        self.active_delayed_interrupt_processes[item_id] = interrupt_process
    else:
        print(f"T={self.env.now:.2f} New item {item_id} {interruption_plan[0]['item_index']} {interruption_plan[-1]['delay']} interrupted immediately")
        self._interrupt_specific_item(item_id, "New item during interruption")
interrupt_all_move_processes(reason='External interrupt')

Interrupt all active move_to_ready_items processes.

Parameters:
  • reason (str, default: 'External interrupt' ) –

    Reason for the interrupt

Source code in src/factorysimpy/base/belt_store.py
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
def interrupt_all_move_processes(self, reason="External interrupt"):
    """
    Interrupt all active move_to_ready_items processes.

    Args:
        reason (str): Reason for the interrupt
    """
    print(f"T={self.env.now:.2f} Belt_Store interrupting {len(self.active_move_processes)} move processes - {reason}")

    for item_id, process_info in self.active_move_processes.items():
        process = process_info['process']
        if process and not process.processed:
            try:
                process.interrupt(reason)
                print(f"T={self.env.now:.2f} Interrupted move process for item {item_id}")
            except RuntimeError:
                # Process might already be finished
                pass
interrupt_and_resume_all_delayed_interrupt_processes(reason='State change interrupt')

Interrupt all active delayed interrupt processes and clear them.

Parameters:
  • reason (str, default: 'State change interrupt' ) –

    Reason for the interrupt

Source code in src/factorysimpy/base/belt_store.py
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
def interrupt_and_resume_all_delayed_interrupt_processes(self, reason="State change interrupt"):
    """
    Interrupt all active delayed interrupt processes and clear them.

    Args:
        reason (str): Reason for the interrupt
    """
    print(f"T={self.env.now:.2f} Belt_Store interrupting {len(self.active_delayed_interrupt_processes)} delayed interrupt processes - {reason}")

    for item_id, process in list(self.active_delayed_interrupt_processes.items()):
        if process and not process.processed:
            try:
                process.interrupt(reason)
                print(f"T={self.env.now:.2f} Interrupted delayed interrupt process for item {item_id}")
            except RuntimeError:
                # Process might already be finished
                pass
        # Remove from tracking regardless
        self.active_delayed_interrupt_processes.pop(item_id, None)
move_to_ready_items(item)

Move items from the store to the ready_items list after a put operation. This method is called as a process to ensure that items are moved asynchronously. Handles interrupts when state changes to stalled and resumes when state changes back. Movement is split into two phases: 1. First phase: item[0].length/self.speed time (time for item to fully enter belt) 2. Second phase: remaining time (time for item to reach exit)

Source code in src/factorysimpy/base/belt_store.py
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
def move_to_ready_items(self, item):
    """
    Move items from the store to the ready_items list after a put operation.
    This method is called as a process to ensure that items are moved asynchronously.
    Handles interrupts when state changes to stalled and resumes when state changes back.
    Movement is split into two phases:
    1. First phase: item[0].length/self.speed time (time for item to fully enter belt)
    2. Second phase: remaining time (time for item to reach exit)
    """
    item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
    event=self.env.event()
    total_interruption_time=0
    #self.delay=item[0].length/self.speed
    #print(f"created Added event suceeded{self.delay}")
    event.callbacks.append(self._trigger_reserve_put)# after putting an item, an event is created and will be triggered ater delay amount of time to allow waiting get calls to succeed in a stalled belt
    #event.callbacks.append(self._trigger_put)# this may not be needed

    #print(f"{self.env.now}Added event suceed        ed")

    # Calculate the two phases of movement
    phase1_time = item[0].length / self.speed  # Time for item to fully enter belt
    phase2_time = item[1] - phase1_time        # Remaining time to reach exit
    item[0].total_interruption_time= total_interruption_time
    item[0].interruption_start_time=None
    try:
        # Move items to the ready_items list
        if self.items:
            print(f"T={self.env.now:.2f} beltstore received an item {item[0].id, item[1]} . Item started moving in belt")

            # Phase 1: Item entering the belt (length/speed time)
            remaining_phase1_time = phase1_time
            print(f"T={self.env.now:.2f} Item {item_id} starting Phase 1 (entering belt): {phase1_time:.2f} time")

            while remaining_phase1_time > 0:
                try:
                    start_time = self.env.now
                    yield self.env.timeout(remaining_phase1_time)
                    # If we reach here, phase 1 completed without interruption
                    event.succeed()
                    remaining_phase1_time = 0
                    break
                except simpy.Interrupt as interrupt:
                    # Calculate how much time has passed
                    interruption_start_time_phase1 = self.env.now
                    item[0].interruption_start_time= interruption_start_time_phase1
                    print(f"{self.env.now} for item {item_id} Phase 1 started interruption at {interruption_start_time_phase1}")
                    elapsed_time = self.env.now - start_time
                    remaining_phase1_time -= elapsed_time

                    print(f"T={self.env.now:.2f} Move process Phase 1 for item {item_id} interrupted: {interrupt.cause}")
                    print(f"T={self.env.now:.2f} Remaining Phase 1 time for item {item_id}: {remaining_phase1_time:.2f}")

                    # Wait for resume signal
                    print(f"T={self.env.now:.2f} Item {item_id} waiting for resume signal with {remaining_phase1_time:.2f} time left to complete(Phase 1)...")
                    yield self.resume_event
                    total_interruption_time += self.env.now - interruption_start_time_phase1
                    item[0].interruption_start_time=None
                    item[0].total_interruption_time= total_interruption_time
                    print(f"T={self.env.now:.2f} Item {item_id} resuming Phase 1 movement with {remaining_phase1_time:.2f} time remaining")

            print(f"T={self.env.now:.2f} Item {item_id} completed Phase 1 (fully entered belt)")

            # Phase 2: Item moving through the belt to exit
            remaining_phase2_time = phase2_time
            print(f"T={self.env.now:.2f} Item {item_id} starting Phase 2 (moving to exit): {phase2_time:.2f} time")

            while remaining_phase2_time > 0:
                try:
                    start_time = self.env.now
                    yield self.env.timeout(remaining_phase2_time)
                    # If we reach here, phase 2 completed without interruption
                    remaining_phase2_time = 0
                    break
                except simpy.Interrupt as interrupt:
                    # Calculate how much time has passed
                    interruption_start_time_phase2 = self.env.now
                    item[0].interruption_start_time= interruption_start_time_phase2
                    print(f"{self.env.now} for item {item_id} Phase 2 started interruption at {interruption_start_time_phase2}")
                    elapsed_time = self.env.now - start_time
                    remaining_phase2_time -= elapsed_time

                    print(f"T={self.env.now:.2f} Move process Phase 2 for item {item_id} interrupted: {interrupt.cause}")
                    print(f"T={self.env.now:.2f} Remaining Phase 2 time for item {item_id}: {remaining_phase2_time:.2f}")

                    # Wait for resume signal
                    print(f"T={self.env.now:.2f} Item {item_id} waiting for resume signal (Phase 2) time left- {remaining_phase2_time:.2f}...")
                    yield self.resume_event
                    item[0].interruption_start_time = None
                    total_interruption_time += self.env.now - interruption_start_time_phase2
                    item[0].total_interruption_time= total_interruption_time
                    print(f"T={self.env.now:.2f} Item {item_id} resuming Phase 2 movement with {remaining_phase2_time:.2f} time remaining")

            print(f"T={self.env.now:.2f} Item {item_id} completed Phase 2 (reached exit)")
            #print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} going to ready_items")

            item_index = self.items.index(item)
            item_to_put = self.items.pop(item_index)  # Remove the item

            if len(self.ready_items) + len(self.items) < self.capacity:
                self.ready_items.append(item_to_put[0])
                item_to_put[0].conveyor_ready_item_entry_time = self.env.now
                print("Total items on belt",len(self.ready_items)+len(self.items))

                if not self.ready_item_event.triggered:
                    self.ready_item_event.succeed()

                print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} moved to ready_items")
                self._trigger_reserve_get(None)
                self._trigger_reserve_put(None)
            else:
                raise RuntimeError("Total number of items in the store exceeds capacity. Cannot move item to ready_items.")


    except simpy.Interrupt as interrupt:
        # Handle any uncaught interrupts
        print(f"T={self.env.now:.2f} Item {item_id} move process was interrupted: {interrupt.cause}")   

    finally:
        # Clean up the process tracking when done
        if item_id in self.active_move_processes:
            del self.active_move_processes[item_id]
            print(f"T={self.env.now:.2f} Removed tracking for completed move process of item {item_id}")
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/belt_store.py
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """
    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)



    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      #self._update_time_averaged_level()
      return proceed
reserve_get()

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in the order in which the requests are made, and _trigger_reserve_get() is called to process pending reservations if items are available.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/belt_store.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def reserve_get(self):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as
    the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    the order in which the requests are made, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.



    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process




    self.reserve_get_queue.append(event)


    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

Source code in src/factorysimpy/base/belt_store.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def reserve_get_cancel(self, get_event_to_cancel):
    """
    Cancel a previously made `reserve_get` request.
    """
    # Case 1: still waiting
    if get_event_to_cancel in self.reserve_get_queue:
        self.reserve_get_queue.remove(get_event_to_cancel)
        self._trigger_reserve_get(None)
        return True

    # Case 2: already yielded reservation
    if get_event_to_cancel in self.reservations_get:
        # 1) Remove from active reservations
        self.reservations_get.remove(get_event_to_cancel)

        # 2) Find its index in the parallel lists
        ev_idx = self.reserved_events.index(get_event_to_cancel)

        # 3) Pop out the exact item reference
        item = self.reserved_items.pop(ev_idx)
        # 4) Drop the event token
        self.reserved_events.pop(ev_idx)

        # 5) Remove it from ready_items wherever it currently is
        try:
            self.ready_items.remove(item)
        except ValueError:
            raise RuntimeError(f"Item {item!r} not found in ready_items during cancel.")

        # 6) Compute new insertion index

        insert_idx = len(self.ready_items) - len(self.reserved_events) - 1

        # 7) Re‑insert it
        self.ready_items.insert(insert_idx, item)

        # 8) Trigger any other pending reservations
        self._trigger_reserve_get(None)
        return True

    # No such reservation
    raise RuntimeError(
        "No matching event in reserve_get_queue or reservations_get"
    )
reserve_put()

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in the order in which the request is made.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/belt_store.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def reserve_put(self):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in the order in which the 
    request is made.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.


    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation

    self.reserve_put_queue.append(event)


    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/belt_store.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed
resume_all_move_processes()

Resume all interrupted move_to_ready_items processes.

Source code in src/factorysimpy/base/belt_store.py
774
775
776
777
778
779
780
781
782
783
def resume_all_move_processes(self):
    """
    Resume all interrupted move_to_ready_items processes.
    """
    print(f"T={self.env.now:.2f} Belt_Store resuming move processes")

    # Create a new resume event and trigger it
    old_resume_event = self.resume_event
    self.resume_event = self.env.event()
    old_resume_event.succeed()
selective_interrupt(reason='Selective interrupt')

Perform selective interruption based on belt occupancy patterns and mode.

When noaccumulation_mode_on=True (STALLED_NONACCUMULATING_STATE): - Interrupt all items immediately

When noaccumulation_mode_on=False (STALLED_ACCUMULATING_STATE): - Use pattern-based interruption with delays based on item positions

Pattern-based rules for accumulating mode: - For patterns like '_*', interrupt all items. - For patterns like '___', interrupt item in second last position after 1 delay, and second item after 2 delays. - For patterns like '___*', interrupt first item and second item after 3 delays. - For patterns like '__*', interrupt first item, second after 2 delays, and third after 2 delays.

Parameters:
  • reason (str, default: 'Selective interrupt' ) –

    Reason for the interrupt

Source code in src/factorysimpy/base/belt_store.py
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
def selective_interrupt(self, reason="Selective interrupt"):
    """
    Perform selective interruption based on belt occupancy patterns and mode.

    When noaccumulation_mode_on=True (STALLED_NONACCUMULATING_STATE):
    - Interrupt all items immediately

    When noaccumulation_mode_on=False (STALLED_ACCUMULATING_STATE):
    - Use pattern-based interruption with delays based on item positions

    Pattern-based rules for accumulating mode:
    - For patterns like '_****', interrupt all items.
    - For patterns like '_*_*_', interrupt item in second last position after 1 delay,
      and second item after 2 delays.
    - For patterns like '*___*', interrupt first item and second item after 3 delays.
    - For patterns like '**__*', interrupt first item, second after 2 delays,
      and third after 2 delays.

    Args:
        reason (str): Reason for the interrupt
    """
    if not self.items:
        print(f"T={self.env.now:.2f} No items on belt to interrupt")
        return

    # If noaccumulation_mode_on is True (STALLED_NONACCUMULATING_STATE), interrupt all items immediately
    if self.noaccumulation_mode_on == True:
        print(f"T={self.env.now:.2f} Noaccumulation_mode_on: interrupting all items immediately")
        for i, item in enumerate(self.items):
            item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
            self._interrupt_specific_item(item_id, f"{reason} - immediate (no accumulation)")
        return

    # For accumulating mode (STALLED_ACCUMULATING_STATE), use pattern-based interruption
    if self.accumulation_mode_indicator == True:
    #if self.noaccumulation_mode_on == False:
        print(f"T={self.env.now:.2f} Accumulating mode: using pattern-based interruption")

        # Get current belt pattern
        all_patterns = self._get_belt_pattern()
        pattern = all_patterns[0]
        beltitems = all_patterns[1]
        print(f"T={self.env.now:.2f} Current belt pattern: {pattern} and items {beltitems}")

        # Analyze pattern and determine interruption strategy
        interruption_plan = self._analyze_pattern_for_interruption(all_patterns)

        if not interruption_plan:
            print(f"T={self.env.now:.2f} No interruption needed for current pattern")
            return

        print(f"T={self.env.now:.2f} Executing selective interruption plan: {interruption_plan}")

        # Execute the interruption plan
        self._execute_interruption_plan(interruption_plan, reason)

buffer_store

BufferStore(env, capacity=float('inf'), mode='FIFO')

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a reservable store for processes to reserve space for storing and retrieving items.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservableReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes.

get and put are two methods that can be used for item storing and retrieval from ReservableReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservableReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservableReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

An item that is added into the BufferStore using put(), it is associated with a delay representing the time it takes for the item to be available for retrieval. The delay can also be 0. The items can be retrieved in FIFO or LIFO manner based on the mode of operation of the BufferStore.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Args:

capacity (int, optional): The maximum number of items the store can hold.
                          Defaults to infinity.
mode (str, optional): The mode of the store ('FIFO' or 'LIFO'). Defaults to 'FIFO'.
Source code in src/factorysimpy/base/buffer_store.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(self, env, capacity=float('inf'),mode='FIFO'):
    """
    Args:

        capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
        mode (str, optional): The mode of the store ('FIFO' or 'LIFO'). Defaults to 'FIFO'.
    """
    super().__init__(env, capacity)
    self.env = env
    self.mode=mode
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self.ready_items=[]  #Maintains the items ready to be taken out
    self.reserved_items   = []   # parallel list of the exact items reserved
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/buffer_store.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      #self._update_time_averaged_level()
      return item
move_to_ready_items(item)

Move items from the store to the ready_items list after a put operation. This method is called as a process to ensure that items are moved asynchronously.

Source code in src/factorysimpy/base/buffer_store.py
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
def move_to_ready_items(self,item):
    """
    Move items from the store to the ready_items list after a put operation.
    This method is called as a process to ensure that items are moved asynchronously.
    """

    # Move items to the ready_items list
    if self.items:
        #print(f"T={self.env.now:.2f} beltstore received an item {item[0].id, item[1]} . Item started moving in belt")
        yield self.env.timeout(item[1])
        #print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} going to ready_items")

        item_index = self.items.index(item)
        item_to_put = self.items.pop(item_index)  # Remove the first item
        #print(item_to_put, item)
        if len(self.ready_items)+ len(self.items) < self.capacity:
            self.ready_items.append(item_to_put[0])
            #print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} moved to ready_items")
            self._trigger_reserve_get(None)
            self._trigger_reserve_put(None)
            #print(f"T={self.env.now:.2f} bufferstore is moving item {item[0].id, item[1]} to ready_items. Total items in buffer is {len(self.items)+len(self.ready_items)}"   )
        else:
            raise RuntimeError("Total number of items in the store exceeds capacity. Cannot move item to ready_items.")
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/buffer_store.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """
    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)



    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      #self._update_time_averaged_level()
      return proceed
reserve_get()

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in the order in which the requests are made, and _trigger_reserve_get() is called to process pending reservations if items are available.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/buffer_store.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def reserve_get(self):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as the resource 
    it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    the order in which the requests are made, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.


    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process


    self.reserve_get_queue.append(event)


    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

Source code in src/factorysimpy/base/buffer_store.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def reserve_get_cancel(self, get_event_to_cancel):
    """
    Cancel a previously made `reserve_get` request.
    """
    # Case 1: still waiting
    if get_event_to_cancel in self.reserve_get_queue:
        self.reserve_get_queue.remove(get_event_to_cancel)
        self._trigger_reserve_get(None)
        return True

    # Case 2: already yielded reservation
    if get_event_to_cancel in self.reservations_get:
        # 1) Remove from active reservations
        self.reservations_get.remove(get_event_to_cancel)

        # 2) Find its index in the parallel lists
        ev_idx = self.reserved_events.index(get_event_to_cancel)

        # 3) Pop out the exact item reference
        item = self.reserved_items.pop(ev_idx)
        # 4) Drop the event token
        self.reserved_events.pop(ev_idx)

        # 5) Remove it from ready_items wherever it currently is
        try:
            self.ready_items.remove(item)
        except ValueError:
            raise RuntimeError(f"Item {item} not found in ready_items during cancel.")

        # 6) Compute new insertion index as a get call is cancelled and item that is reserved and associated to an event is now freely available to be assigned to a new incoming event
        if self.mode == "FIFO":
            # one slot before the remaining reserved block
            insert_idx = len(self.ready_items) - len(self.reserved_events) - 1
        else:  # LIFO
            # top of stack
            insert_idx = len(self.ready_items)

        # 7) Re‑insert it
        self.ready_items.insert(insert_idx, item)

        # 8) Trigger any other pending reservations
        self._trigger_reserve_get(None)
        return True

    # No such reservation
    raise RuntimeError(
        "No matching event in reserve_get_queue or reservations_get"
    )
reserve_put()

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in the order in which the requests are made.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/buffer_store.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def reserve_put(self):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in the order in which the requests are made.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.



    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation

    self.reserve_put_queue.append(event)


    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/buffer_store.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed

fleet_store

FleetStore(env, capacity=float('inf'), delay=1, transit_delay=0)

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a priority-based reservable store for processes to reserve space for storing and retrieving items with priority-based access.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservablePriorityReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes. Processes can also pass a priority as argument in the request. Lower values indicate higher priority.

get and put are two methods that can be used for item storing and retrieval from ReservablePriorityReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservablePriorityReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservablePriorityReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Initializes a reservable store with priority-based reservations.

Parameters:
  • env (Environment) –

    The simulation environment.

  • capacity (int, default: float('inf') ) –

    target quantity of items after which the fleet will be activated Defaults to infinity.

  • delay ((int, float), default: 1 ) –

    Delay after which fleet activates to move items incase the target capacity is not reached.

  • transit_delay ((int, float), default: 0 ) –

    Time to move the items after which the item becomes available. Can be a constant, generator, or callable. Defaults to 0.

Source code in src/factorysimpy/base/fleet_store.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(self, env, capacity=float('inf'),delay=1, transit_delay=0):
    """
    Initializes a reservable store with priority-based reservations.

    Args:
     env (simpy.Environment): The simulation environment.
     capacity (int, optional): target quantity of items after which the fleet will be activated
                               Defaults to infinity.
     delay (int, float, optional): Delay after which fleet activates to move items incase the target capacity is not reached.
     transit_delay (int, float, optional): Time to move the items after which the item becomes available.
                                                 Can be a constant, generator, or callable. Defaults to 0.
    """
    super().__init__(env, capacity)
    self.env = env
    self.delay = delay
    self.transit_delay = transit_delay
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self.ready_items=[]  #Maintains the items ready to be taken out
    self.reserved_items   = []   # parallel list of the exact items reserved
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
    self.activate_fleet= self.env.event()  # Event to activate the fleet when items are available

    self.env.process(self.fleet_activation_process())  # Start the fleet activation process
fleet_activation_process()

Process to activate the fleet when items are available but not equivalent to self.capacity(level not achieved). This process waits for the activate_fleet event to be triggered.

Source code in src/factorysimpy/base/fleet_store.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def fleet_activation_process(self,):
    """
    Process to activate the fleet when items are available but not equivalent to self.capacity(level not achieved).
    This process waits for the activate_fleet event to be triggered.
    """
    while True:
        timeout_event = self.env.timeout(self.delay)
        event_list= [timeout_event, self.activate_fleet]
        yield self.env.any_of(event_list)

        print(f"T={self.env.now:.2f}: Fleet activation process triggered.")

        if self.items:
            print(f"T={self.env.now:.2f}: Fleet activated with {len(self.items)} items ready.")
            self.env.process(self.move_to_ready_items(self.items))
            #self.env.process(self.move_to_ready_items(self.items))
            if self.activate_fleet.triggered:
                #print("yes")
                self.activate_fleet = self.env.event()  # Reset the event for next activation
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/fleet_store.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      #self._update_time_averaged_level()
      return item
move_to_ready_items(items)

Move items from the store to the ready_items list after a put operation. This method is called as a process to ensure that items are moved asynchronously.

Source code in src/factorysimpy/base/fleet_store.py
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def move_to_ready_items(self, items):
    """
    Move items from the store to the ready_items list after a put operation.
    This method is called as a process to ensure that items are moved asynchronously.
    """

    # Move items to the ready_items list
    if items:

        print(f"T={self.env.now:.2f}: Moving items to ready_items.")
        #START=self.env.now
        yield self.env.timeout(self.transit_delay)
        #print("WAITED FOR TRANSIT_DELAY BEFORE MOVING", self.env.now-START)
        yield self.env.timeout(self.transit_delay)

        for item in items:

            item_index = self.items.index(item)
            item_to_put = self.items.pop(item_index)  # Remove the first item

            if len(self.ready_items) < self.capacity:
                self.ready_items.append(item_to_put)
                #print(f"T={self.env.now:.2f}: {self.id} moved item {item_to_put.id} to ready_items.")
                self._trigger_reserve_get(None)
                self._trigger_reserve_put(None)
                #print(f"T={self.env.now:.2f} bufferstore is moving item {item[0].id, item[1]} to ready_items. Total items in buffer is {len(self.items)+len(self.ready_items)}"   )
            else:
                raise RuntimeError("Total number of items in the store exceeds capacity. Cannot move item to ready_items.")

        print(f"T={self.env.now:.2f}: Fleetstore moved items to ready_items.")
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/fleet_store.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """

    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)



    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      #self._update_time_averaged_level()
      return proceed
reserve_get(priority=0)

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as priority, the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in priority order, and _trigger_reserve_get() is called to process pending reservations if items are available.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/fleet_store.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def reserve_get(self,priority=0):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as priority,
    the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    priority order, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process

    #event.priority_to_get = (priority, self._env.now)
    event.priority_to_get = priority

    #sorting the list based on priority after appending the new event
    self.reserve_get_queue.append(event)
    self.reserve_get_queue.sort(key=lambda e: e.priority_to_get)

    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

Source code in src/factorysimpy/base/fleet_store.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def reserve_get_cancel(self, get_event_to_cancel):
    """
    Cancel a previously made `reserve_get` request.
    """
    # Case 1: still waiting
    if get_event_to_cancel in self.reserve_get_queue:
        self.reserve_get_queue.remove(get_event_to_cancel)
        self._trigger_reserve_get(None)
        return True

    # Case 2: already yielded reservation
    if get_event_to_cancel in self.reservations_get:
        # 1) Remove from active reservations
        self.reservations_get.remove(get_event_to_cancel)

        # 2) Find its index in the parallel lists
        ev_idx = self.reserved_events.index(get_event_to_cancel)

        # 3) Pop out the exact item reference
        item = self.reserved_items.pop(ev_idx)
        # 4) Drop the event token
        self.reserved_events.pop(ev_idx)

        # 5) Remove it from ready_items wherever it currently is
        try:
            self.ready_items.remove(item)
        except ValueError:
            raise RuntimeError(f"Item {item!r} not found in ready_items during cancel.")

        # 6) Compute new insertion index
        # "FIFO":
            # one slot before the remaining reserved block
        insert_idx = len(self.ready_items) - len(self.reserved_events) - 1


        # 7) Re‑insert it
        self.ready_items.insert(insert_idx, item)

        # 8) Trigger any other pending reservations
        self._trigger_reserve_get(None)
        return True

    # No such reservation
    raise RuntimeError(
        "No matching event in reserve_get_queue or reservations_get"
    )
reserve_get_cancel1(get_event_to_cancel)

Cancel a previously made reserve_get request.

This method allows a process to cancel its reservation for retrieving an item from the store. If the reservation exists in the reserve_get_queue, it is removed, and _trigger_reserve_get() is called to process any remaining reservations.

If the reservation is already in reservations_get, it is removed, and the corresponding item is repositioned in the store to maintain order. _trigger_reserve_get() is then triggered to handle pending reservations.

Parameters:
  • get_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_get_queue or reservations_get.

Source code in src/factorysimpy/base/fleet_store.py
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def reserve_get_cancel1(self, get_event_to_cancel):

  """
    Cancel a previously made `reserve_get` request.

    This method allows a process to cancel its reservation for retrieving an item
    from the store. If the reservation exists in the `reserve_get_queue`, it is removed,
    and `_trigger_reserve_get()` is called to process any remaining reservations.

    If the reservation is already in `reservations_get`, it is removed, and the corresponding
    item is repositioned in the store to maintain order. `_trigger_reserve_get()` is then
    triggered to handle pending reservations.

    Args:
        get_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
        proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_get_queue`
                      or `reservations_get`.
    """

  proceed = False
  #checking and removing the event if it is not yielded and is present in the reserve_get_queue
  if get_event_to_cancel in self.reserve_get_queue:
    self.reserve_get_queue.remove(get_event_to_cancel)
    self._trigger_reserve_get(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  #checking and removing the event if it is already yielded and is present in the reservations_queue.
  # 1-to-1 association with items done to preserve item order should also be removed.
  elif get_event_to_cancel in self.reservations_get:
    self.reservations_get.remove(get_event_to_cancel)

    #deleting the associated event in the reserved_events list to preserve the order of the items
    #finding index of the item
    event_in_index = self.reserved_events.index(get_event_to_cancel)
    delta_position = len(self.reserved_events)
    #shifting the item
    item_to_shift = self.items.pop(event_in_index)
    self.items.insert(delta_position-1, item_to_shift)
    #deleting the event
    self.reserved_events.pop(event_in_index)#if t is removed, then a waiting event can be succeeded, if any

    self._trigger_reserve_get(None)
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_get_queue or reservations_get for this process")

  return proceed
reserve_put(priority=0)

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as priority, resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in priority order.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/fleet_store.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def reserve_put(self, priority=0):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as priority, resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in priority order.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation
    event.priority_to_put = priority  # Priority for sorting reservations

    # Add the event to the reservation queue and sort by priority
    self.reserve_put_queue.append(event)
    self.reserve_put_queue.sort(key=lambda e: e.priority_to_put)

    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/fleet_store.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed

priority_req_store

PriorityGet(resource, priority=0)

Bases: Get

Source code in src/factorysimpy/base/priority_req_store.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __init__(self, resource, priority=0, ):
   self.priority = priority
   """The priority of this request. A smaller number means higher
   priority."""


   self.time = resource._env.now
   """The time at which the request was made."""

   self.key = (self.priority, self.time)
   #print("Resource is ", resource)


   super().__init__(resource)
priority = priority instance-attribute

The priority of this request. A smaller number means higher priority.

time = resource._env.now instance-attribute

The time at which the request was made.

PriorityPut(resource, item, priority=0)

Bases: Put

Source code in src/factorysimpy/base/priority_req_store.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(self, resource, item, priority=0, ):
   self.priority = priority
   """The priority of this request. A smaller number means higher
   priority."""


   self.time = resource._env.now
   """The time at which the request was made."""

   self.key = (self.priority, self.time)
   self.item = item
   #print("Resource is ", resource)


   super().__init__(resource)
priority = priority instance-attribute

The priority of this request. A smaller number means higher priority.

time = resource._env.now instance-attribute

The time at which the request was made.

PriorityReqStore(env, capacity=1)

Bases: Store

This is a class derived from SimPy's Store class and has extra capabilities that makes it a priority-based store for put and get.

Processes can pass a priority as argument in the put and get request. Request with lower values of priority yields first among all get(or put) requests. If two requests with same priority are placed from two processes then FIFO order is followed to yield the requests.

Source code in src/factorysimpy/base/priority_req_store.py
87
88
def __init__(self, env, capacity=1):
    super().__init__(env, capacity)
get = BoundClass(PriorityGet) class-attribute instance-attribute

yields a get request with the given priority.

put = BoundClass(PriorityPut) class-attribute instance-attribute

yields a put request with the given priority

SortedQueue(maxlen=None)

Bases: list

Queue for sorting events by their :attr:~PriorityRequest.key attribute.

Source code in src/factorysimpy/base/priority_req_store.py
15
16
17
18
def __init__(self, maxlen = None):
    super().__init__()
    self.maxlen = maxlen
    """Maximum length of the queue."""
maxlen = maxlen instance-attribute

Maximum length of the queue.

append(item)

Sort item into the queue.

Raise a :exc:RuntimeError if the queue is full.

Source code in src/factorysimpy/base/priority_req_store.py
20
21
22
23
24
25
26
27
28
29
30
def append(self, item) -> None:
    """Sort *item* into the queue.

    Raise a :exc:`RuntimeError` if the queue is full.

    """
    if self.maxlen is not None and len(self) >= self.maxlen:
        raise RuntimeError('Cannot append event. Queue is full.')

    super().append(item)
    super().sort(key=lambda e: e.key)

reservable_priority_req_filter_store

ReservablePriorityReqFilterStore(env, capacity=float('inf'), trigger_delay=0)

Bases: FilterStore

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a priority-based reservable store for processes to reserve space for storing and retrieving items with priority-based access. It supports user tp only get items that match a user criteria

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservablePriorityReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes. Processes can also pass a priority as argument in the request. Lower values indicate higher priority. Filter to be used while using "get" can be passed in the reserve_get request.

get and put are two methods that can be used for item storing and retrieval from ReservablePriorityReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservablePriorityReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservablePriorityReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

  • trigger_delay (int) –

    Delay time after which a trigger_reserve_get is called to allow waiting get calls to succeed.

Initializes a reservable store with priority-based reservations.

Args:

capacity (int, optional): The maximum number of items the store can hold.
                          Defaults to infinity.
Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(self, env, capacity=float('inf'),trigger_delay=0):
    """
    Initializes a reservable store with priority-based reservations.

    Args:

        capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
    """
    super().__init__(env, capacity)
    self.env = env

    self.trigger_delay = trigger_delay
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      return item
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
def put(self,put_event,item,):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """

    proceed = False
    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)

    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      return proceed
reserve_get(priority=0, filter=None)

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as priority, the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in priority order, and _trigger_reserve_get() is called to process pending reservations if items are available.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

  • filter (filter=lambdaitem=True, default: None ) –

    Filter to be used while using "reserve_get

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def reserve_get(self,priority=0,filter= None):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as priority,
    the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    priority order, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.
        filter (filter=lambdaitem=True, optional):  Filter to be used while using "reserve_get

    Returns:
        event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get

    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process
    #event.priority_to_get = (priority, self._env.now)
    event.priority_to_get = priority

    # Check if 'filter' is provided, if not, assign a default filter
    if filter is None:
        #print(f"T={self.env.now} filter is None so making it true for all items")
        #filter = lambda item: True  # Default filter that accepts all items
        filter=lambda x : self.env.now>= x.put_time + self.trigger_delay
        event.filter = filter
    else:
        #print(f"T={self.env.now} filter is not None ")
        event.filter = filter

    #sorting the list based on priority after appending the new event
    self.reserve_get_queue.append(event)
    self.reserve_get_queue.sort(key=lambda e: e.priority_to_get)

    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

This method allows a process to cancel its reservation for retrieving an item from the store. If the reservation exists in the reserve_get_queue, it is removed, and _trigger_reserve_get() is called to process any remaining reservations.

If the reservation is already in reservations_get, it is removed, and the corresponding item is repositioned in the store to maintain order. _trigger_reserve_get() is then triggered to handle pending reservations.

Parameters:
  • get_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_get_queue or reservations_get.

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
def reserve_get_cancel(self, get_event_to_cancel):

  """
    Cancel a previously made `reserve_get` request.

    This method allows a process to cancel its reservation for retrieving an item
    from the store. If the reservation exists in the `reserve_get_queue`, it is removed,
    and `_trigger_reserve_get()` is called to process any remaining reservations.

    If the reservation is already in `reservations_get`, it is removed, and the corresponding
    item is repositioned in the store to maintain order. `_trigger_reserve_get()` is then
    triggered to handle pending reservations.

    Args:
        get_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
        proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_get_queue`
                      or `reservations_get`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_get_queue
  proceed = False
  if get_event_to_cancel in self.reserve_get_queue:
    self.reserve_get_queue.remove(get_event_to_cancel)
    self._trigger_reserve_get(None)#if t is removed, then a waiting event can be succeeded, if any
    return True

  #checking and removing the event if it is already yielded and is present in the reservations_queue.
  # 1-to-1 association with items done to preserve item order should also be removed.
  elif get_event_to_cancel in self.reservations_get:
    self.reservations_get.remove(get_event_to_cancel)

    #deleting the associated event in the reserved_events list to preserve the order of the items
    #finding index of the item
    event_in_index = self.reserved_events.index(get_event_to_cancel)
    delta_position = len(self.reserved_events)
    #shifting the item
    item_to_shift = self.items.pop(event_in_index)
    self.items.insert(delta_position-1, item_to_shift)
    #deleting the event
    self.reserved_events.pop(event_in_index)#if t is removed, then a waiting event can be succeeded, if any

    self._trigger_reserve_get(None)
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_get_queue or reservations_get for this process")

  return proceed
reserve_put(priority=0)

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as priority, resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in priority order.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def reserve_put(self, priority=0):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as priority, resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in priority order.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.
    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation
    event.priority_to_put = priority  # Priority for sorting reservations

    # Add the event to the reservation queue and sort by priority
    self.reserve_put_queue.append(event)
    self.reserve_put_queue.sort(key=lambda e: e.priority_to_put)

    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/reservable_priority_req_filter_store.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
        proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed =  True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed =  True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed

reservable_priority_req_store

ReservablePriorityReqStore(env, capacity=float('inf'))

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a priority-based reservable store for processes to reserve space for storing and retrieving items with priority-based access.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservablePriorityReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes. Processes can also pass a priority as argument in the request. Lower values indicate higher priority.

get and put are two methods that can be used for item storing and retrieval from ReservablePriorityReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservablePriorityReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservablePriorityReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Initializes a reservable store with priority-based reservations.

Args:

capacity (int, optional): The maximum number of items the store can hold.
                          Defaults to infinity.
Source code in src/factorysimpy/base/reservable_priority_req_store.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __init__(self, env, capacity=float('inf')):
    """
    Initializes a reservable store with priority-based reservations.

    Args:

        capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
    """
    super().__init__(env, capacity)
    self.env = env
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/reservable_priority_req_store.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      self._update_time_averaged_level()
      return item
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/reservable_priority_req_store.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """
    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)

    #if the put operation is not successful, then raise an erro

    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      self._update_time_averaged_level()
      return proceed
reserve_get(priority=0)

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as priority, the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in priority order, and _trigger_reserve_get() is called to process pending reservations if items are available.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/reservable_priority_req_store.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def reserve_get(self,priority=0):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as priority,
    the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    priority order, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process

    #event.priority_to_get = (priority, self._env.now)
    event.priority_to_get = priority

    #sorting the list based on priority after appending the new event
    self.reserve_get_queue.append(event)
    self.reserve_get_queue.sort(key=lambda e: e.priority_to_get)

    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

This method allows a process to cancel its reservation for retrieving an item from the store. If the reservation exists in the reserve_get_queue, it is removed, and _trigger_reserve_get() is called to process any remaining reservations.

If the reservation is already in reservations_get, it is removed, and the corresponding item is repositioned in the store to maintain order. _trigger_reserve_get() is then triggered to handle pending reservations.

Parameters:
  • get_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_get_queue or reservations_get.

Source code in src/factorysimpy/base/reservable_priority_req_store.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def reserve_get_cancel(self, get_event_to_cancel):

  """
    Cancel a previously made `reserve_get` request.

    This method allows a process to cancel its reservation for retrieving an item
    from the store. If the reservation exists in the `reserve_get_queue`, it is removed,
    and `_trigger_reserve_get()` is called to process any remaining reservations.

    If the reservation is already in `reservations_get`, it is removed, and the corresponding
    item is repositioned in the store to maintain order. `_trigger_reserve_get()` is then
    triggered to handle pending reservations.

    Args:
        get_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
        proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_get_queue`
                      or `reservations_get`.
    """

  proceed = False
  #checking and removing the event if it is not yielded and is present in the reserve_get_queue
  if get_event_to_cancel in self.reserve_get_queue:
    self.reserve_get_queue.remove(get_event_to_cancel)
    self._trigger_reserve_get(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  #checking and removing the event if it is already yielded and is present in the reservations_queue.
  # 1-to-1 association with items done to preserve item order should also be removed.
  elif get_event_to_cancel in self.reservations_get:
    self.reservations_get.remove(get_event_to_cancel)

    #deleting the associated event in the reserved_events list to preserve the order of the items
    #finding index of the item
    event_in_index = self.reserved_events.index(get_event_to_cancel)
    delta_position = len(self.reserved_events)
    #shifting the item
    item_to_shift = self.items.pop(event_in_index)
    self.items.insert(delta_position-1, item_to_shift)
    #deleting the event
    self.reserved_events.pop(event_in_index)#if t is removed, then a waiting event can be succeeded, if any

    self._trigger_reserve_get(None)
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_get_queue or reservations_get for this process")

  return proceed
reserve_put(priority=0)

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as priority, resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in priority order.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/reservable_priority_req_store.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def reserve_put(self, priority=0):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as priority, resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in priority order.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation
    event.priority_to_put = priority  # Priority for sorting reservations

    # Add the event to the reservation queue and sort by priority
    self.reserve_put_queue.append(event)
    self.reserve_put_queue.sort(key=lambda e: e.priority_to_put)

    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/reservable_priority_req_store.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed

reservable_req_store

ReservableReqStore(env, capacity=float('inf'))

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a reservable store for processes to reserve space for storing and retrieving items.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservableReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes.

get and put are two methods that can be used for item storing and retrieval from ReservableReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservableReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservableReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Initializes a reservable store with reservations.

Args:

capacity (int, optional): The maximum number of items the store can hold.
                          Defaults to infinity.
Source code in src/factorysimpy/base/reservable_req_store.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(self, env, capacity=float('inf')):
    """
    Initializes a reservable store with reservations.

    Args:

        capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
    """
    super().__init__(env, capacity)
    self.env = env
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/reservable_req_store.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      self._update_time_averaged_level()
      return item
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/reservable_req_store.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """
    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)

    #if the put operation is not successful, then raise an erro

    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      self._update_time_averaged_level()
      return proceed
reserve_get()

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in the order in which it has added, and _trigger_reserve_get() is called to process pending reservations if items are available.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/reservable_req_store.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def reserve_get(self):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    the order in which it has added, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.



    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process




    self.reserve_get_queue.append(event)


    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

This method allows a process to cancel its reservation for retrieving an item from the store. If the reservation exists in the reserve_get_queue, it is removed, and _trigger_reserve_get() is called to process any remaining reservations.

If the reservation is already in reservations_get, it is removed, and the corresponding item is repositioned in the store to maintain order. _trigger_reserve_get() is then triggered to handle pending reservations.

Parameters:
  • get_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_get_queue or reservations_get.

Source code in src/factorysimpy/base/reservable_req_store.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def reserve_get_cancel(self, get_event_to_cancel):

  """
    Cancel a previously made `reserve_get` request.

    This method allows a process to cancel its reservation for retrieving an item
    from the store. If the reservation exists in the `reserve_get_queue`, it is removed,
    and `_trigger_reserve_get()` is called to process any remaining reservations.

    If the reservation is already in `reservations_get`, it is removed, and the corresponding
    item is repositioned in the store to maintain order. `_trigger_reserve_get()` is then
    triggered to handle pending reservations.

    Args:
        get_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
        proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_get_queue`
                      or `reservations_get`.
    """

  proceed = False
  #checking and removing the event if it is not yielded and is present in the reserve_get_queue
  if get_event_to_cancel in self.reserve_get_queue:
    self.reserve_get_queue.remove(get_event_to_cancel)
    self._trigger_reserve_get(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  #checking and removing the event if it is already yielded and is present in the reservations_queue.
  # 1-to-1 association with items done to preserve item order should also be removed.
  elif get_event_to_cancel in self.reservations_get:
    self.reservations_get.remove(get_event_to_cancel)

    #deleting the associated event in the reserved_events list to preserve the order of the items
    #finding index of the item
    event_in_index = self.reserved_events.index(get_event_to_cancel)
    delta_position = len(self.reserved_events)
    #shifting the item
    item_to_shift = self.items.pop(event_in_index)
    self.items.insert(delta_position-1, item_to_shift)
    #deleting the event
    self.reserved_events.pop(event_in_index)#if t is removed, then a waiting event can be succeeded, if any

    self._trigger_reserve_get(None)
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_get_queue or reservations_get for this process")

  return proceed
reserve_put()

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as the resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in the order in which the request has come.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/reservable_req_store.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def reserve_put(self):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as the resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in the order in which the request has come.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.




    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation

    self.reserve_put_queue.append(event)


    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/reservable_req_store.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed

slotted_belt_store

BeltStore(env, capacity=float('inf'), mode='FIFO', delay=1)

Bases: Store

This is a class that is derived from SimPy's Store class and has extra capabilities that makes it a priority-based reservable store for processes to reserve space for storing and retrieving items with priority-based access.

Processes can use reserve_put() and reserve_get() methods to get notified when a space becomes available in the store or when an item gets available in the ReservablePriorityReqStore. These methods returns a unique event (SimPy.Event) to the process for every reserve requests it makes. Processes can also pass a priority as argument in the request. Lower values indicate higher priority.

get and put are two methods that can be used for item storing and retrieval from ReservablePriorityReqStore. Process has to make a prior reservation and pass the associated reservation event as argument in the get and put requests. ReservablePriorityReqStore maintains separate queues for reserve_put and reserve_get operations to ensures that only processes with valid reservations can store or retrieve items.

ReservablePriorityReqStore preserves item order by associating an unreserved item in the store with a reservation event by index when a reserve_get() request is made. As a result, it maintains a list of reserved events to preserve item order.

It also allows users to cancel an already placed reserve_get or reserve_put request even if it is yielded. It also handles the dissociation of the event and item done at the time of reservation when an already yielded event is canceled.

Attributes:
  • reserved_events (list) –

    Maintains events corresponding to reserved items to preserve item order by index

  • reserve_put_queue (list) –

    Queue for managing reserve_put reservations

  • reservations_put (list) –

    List of successful put reservations

  • reserve_get_queue (list) –

    Queue for managing reserve_get reservations

  • reservations_get (list) –

    List of successful get reservations

Initializes a reservable store with priority-based reservations.

Args:

capacity (int, optional): The maximum number of items the store can hold.
                          Defaults to infinity.
Source code in src/factorysimpy/base/slotted_belt_store.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(self, env, capacity=float('inf'),mode='FIFO', delay=1):
    """
    Initializes a reservable store with priority-based reservations.

    Args:

        capacity (int, optional): The maximum number of items the store can hold.
                                  Defaults to infinity.
    """
    super().__init__(env, capacity)
    self.env = env
    self.mode=mode
    self.delay = delay  # Speed of the conveyor belt (units per time)
    self.reserve_put_queue = []  # Queue for managing reserve_put reservations
    self.reservations_put = []   # List of successful put reservations
    self.reserve_get_queue = []  # Queue for managing reserve_get reservations
    self.reservations_get = []   # List of successful get reservations
    self.reserved_events = []     # Maintains events corresponding to reserved items to preserve item order
    self.ready_items=[]  #Maintains the items ready to be taken out
    self.reserved_items   = []   # parallel list of the exact items reserved
    self._last_level_change_time = self.env.now
    self._last_num_items = 0
    self._weighted_sum = 0.0
    self.time_averaged_num_of_items_in_store = 0.0  # Time-averaged number of items in the store
    # Process tracking for interrupt functionality
    self.active_move_processes = {}  # Dictionary to track active move_to_ready_items processes
    self.resume_event = self.env.event()  # Event to signal when to resume processes
    self.noaccumulation_mode_on = False # to control if the belt is in noaccumulation mode
    self.one_item_inserted = False # to control insertion of only one item in noaccumulation mode
    self.ready_item_event= self.env.event()
get(get_event)

Retrieve an item from the store after a successful reservation.

This method attempts to retrieve an item associated with a reserve_get event. If the reservation exists, it triggers _trigger_get to retrieve the item. If successful, _trigger_reserve_put is called to process any pending reserve_put requests. If the item retrieval fails, an error message is raised.

Parameters:
  • get_event (Event) –

    The reservation event associated with the request.

Returns:
  • item( Object ) –

    The retrieved item if successful, otherwise raises an error

Raises:
  • RuntimeError

    If no reservations are available in the reservations_get

  • RuntimeError

    If item returned is None

Source code in src/factorysimpy/base/slotted_belt_store.py
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
def get(self,get_event):
    """
    Retrieve an item from the store after a successful reservation.

    This method attempts to retrieve an item associated with a `reserve_get`
    event. If the reservation exists, it triggers `_trigger_get` to retrieve
    the item. If successful, `_trigger_reserve_put` is called to process any
    pending `reserve_put` requests. If the item retrieval fails, an error
    message is raised.

    Args:
        get_event (simpy.Event): The reservation event associated with the request.

    Returns:
        item (Object): The retrieved item if successful, otherwise raises an error

    Raises:
        RuntimeError: If no reservations are available in the reservations_get
        RuntimeError: If item returned is None
    """

    item = None
    #if there are reservations, then call _trigger_get
    if self.reservations_get:
      item= self._trigger_get(get_event)
    #else raise an error
    else:
      raise RuntimeError("No matching reservation found for process: reservations_get is empty")
    #if an item is returned then call _trigger_reserve_put to process pending requests
    if item is not None:
      self._trigger_reserve_put(None)

    if item is None:
      raise RuntimeError(f"No item found in the store for {get_event.requesting_process} and get request failed")
    else:
      #self._update_time_averaged_level()
      return item
handle_new_item_during_interruption(item)

Handle new items arriving during selective interruption.

For new items, the delay before interruption is: capacity - num_items

Parameters:
  • item

    The new item being added

Source code in src/factorysimpy/base/slotted_belt_store.py
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
def handle_new_item_during_interruption(self, item):
    """
    Handle new items arriving during selective interruption.

    For new items, the delay before interruption is: capacity - num_items

    Args:
        item: The new item being added
    """
    if self.noaccumulation_mode_on:
        num_items = len(self.items) + len(self.ready_items)
        delay_before_interrupt = max(self.capacity - num_items, 0)

        item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))

        if delay_before_interrupt > 0:
            print(f"T={self.env.now:.2f} New item {item_id} will be interrupted after {delay_before_interrupt} time units")
            self.env.process(self._delayed_interrupt(item_id, delay_before_interrupt, "New item during interruption"))
        else:
            print(f"T={self.env.now:.2f} New item {item_id} interrupted immediately")
            self._interrupt_specific_item(item_id, "New item during interruption")
interrupt_all_move_processes(reason='External interrupt')

Interrupt all active move_to_ready_items processes.

Parameters:
  • reason (str, default: 'External interrupt' ) –

    Reason for the interrupt

Source code in src/factorysimpy/base/slotted_belt_store.py
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
def interrupt_all_move_processes(self, reason="External interrupt"):
    """
    Interrupt all active move_to_ready_items processes.

    Args:
        reason (str): Reason for the interrupt
    """
    print(f"T={self.env.now:.2f} BufferStore interrupting {len(self.active_move_processes)} move processes - {reason}")

    for item_id, process_info in self.active_move_processes.items():
        process = process_info['process']
        if process and not process.processed:
            try:
                process.interrupt(reason)
                print(f"T={self.env.now:.2f} Interrupted move process for item {item_id}")
            except RuntimeError:
                # Process might already be finished
                pass
move_to_ready_items(item)

Move items from the store to the ready_items list after a put operation. This method is called as a process to ensure that items are moved asynchronously. Handles interrupts when state changes to stalled and resumes when state changes back. Movement is split into two phases: 1. First phase: item[0].length/self.speed time (time for item to fully enter belt) 2. Second phase: remaining time (time for item to reach exit)

Source code in src/factorysimpy/base/slotted_belt_store.py
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
def move_to_ready_items(self, item):
    """
    Move items from the store to the ready_items list after a put operation.
    This method is called as a process to ensure that items are moved asynchronously.
    Handles interrupts when state changes to stalled and resumes when state changes back.
    Movement is split into two phases:
    1. First phase: item[0].length/self.speed time (time for item to fully enter belt)
    2. Second phase: remaining time (time for item to reach exit)
    """
    item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
    event=self.env.event()
    #self.delay=self.delay
    #print(f"created Added event suceeded{self.delay}")
    event.callbacks.append(self._trigger_reserve_put)# after putting an item, an event is created and will be triggered ater delay amount of time to allow waiting get calls to succeed in a stalled belt
    #event.callbacks.append(self._trigger_put)# this may not be needed

    #print(f"{self.env.now}Added event suceed        ed")

    # Calculate the two phases of movement
    phase1_time = self.delay  # Time for item to fully enter belt
    phase2_time = item[1] - phase1_time        # Remaining time to reach exit

    try:
        # Move items to the ready_items list
        if self.items:
            print(f"T={self.env.now:.2f} beltstore received an item {item[0].id, item[1]} . Item started moving in belt")

            # Phase 1: Item entering the belt (length/speed time)
            remaining_phase1_time = phase1_time
            print(f"T={self.env.now:.2f} Item {item_id} starting Phase 1 (entering belt): {phase1_time:.2f} time")

            while remaining_phase1_time > 0:
                try:
                    start_time = self.env.now
                    yield self.env.timeout(remaining_phase1_time)
                    # If we reach here, phase 1 completed without interruption
                    event.succeed()
                    remaining_phase1_time = 0
                    break
                except simpy.Interrupt as interrupt:
                    # Calculate how much time has passed
                    elapsed_time = self.env.now - start_time
                    remaining_phase1_time -= elapsed_time

                    print(f"T={self.env.now:.2f} Move process Phase 1 for item {item_id} interrupted: {interrupt.cause}")
                    print(f"T={self.env.now:.2f} Remaining Phase 1 time for item {item_id}: {remaining_phase1_time:.2f}")

                    # Wait for resume signal
                    print(f"T={self.env.now:.2f} Item {item_id} waiting for resume signal (Phase 1)...")
                    yield self.resume_event
                    print(f"T={self.env.now:.2f} Item {item_id} resuming Phase 1 movement with {remaining_phase1_time:.2f} time remaining")

            print(f"T={self.env.now:.2f} Item {item_id} completed Phase 1 (fully entered belt)")

            # Phase 2: Item moving through the belt to exit
            remaining_phase2_time = phase2_time
            print(f"T={self.env.now:.2f} Item {item_id} starting Phase 2 (moving to exit): {phase2_time:.2f} time")

            while remaining_phase2_time > 0:
                try:
                    start_time = self.env.now
                    yield self.env.timeout(remaining_phase2_time)
                    # If we reach here, phase 2 completed without interruption
                    remaining_phase2_time = 0
                    break
                except simpy.Interrupt as interrupt:
                    # Calculate how much time has passed
                    elapsed_time = self.env.now - start_time
                    remaining_phase2_time -= elapsed_time

                    print(f"T={self.env.now:.2f} Move process Phase 2 for item {item_id} interrupted: {interrupt.cause}")
                    print(f"T={self.env.now:.2f} Remaining Phase 2 time for item {item_id}: {remaining_phase2_time:.2f}")

                    # Wait for resume signal
                    print(f"T={self.env.now:.2f} Item {item_id} waiting for resume signal (Phase 2)...")
                    yield self.resume_event
                    print(f"T={self.env.now:.2f} Item {item_id} resuming Phase 2 movement with {remaining_phase2_time:.2f} time remaining")

            print(f"T={self.env.now:.2f} Item {item_id} completed Phase 2 (reached exit)")
            print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} going to ready_items")

            item_index = self.items.index(item)
            item_to_put = self.items.pop(item_index)  # Remove the item

            if len(self.ready_items) + len(self.items) < self.capacity:
                self.ready_items.append(item_to_put[0])
                if not self.ready_item_event.triggered:
                    self.ready_item_event.succeed()  # Notify that a new item is ready
                print(f"T={self.env.now:.2f} bufferstore finished moving item {item[0].id, item[1]} moved to ready_items")
                self._trigger_reserve_get(None)
                self._trigger_reserve_put(None)
            else:
                raise RuntimeError("Total number of items in the store exceeds capacity. Cannot move item to ready_items.")




    finally:
        # Clean up the process tracking when done
        if item_id in self.active_move_processes:
            del self.active_move_processes[item_id]
            print(f"T={self.env.now:.2f} Removed tracking for completed move process of item {item_id}")
put(put_event, item)

Perform a put operation on the store and trigger any pending reserve_get requests.

Ensures that only processes with a valid reservation can put items into the store. If the put operation succeeds, it triggers _trigger_reserve_get to process pending get requests.

Parameters:
  • put_event (Event) –

    The event corresponding to the reservation.

  • item (object) –

    The item to be added to the store.

Returns:
  • proceed( bool ) –

    True if the put operation succeeded, False otherwise.

Raises:
  • RuntimeError

    If no reservations are available in the reservations_put

  • RuntimeError

    If proceed is False after put operation

Source code in src/factorysimpy/base/slotted_belt_store.py
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def put(self,put_event,item):
    """
    Perform a `put` operation on the store and trigger any pending `reserve_get` requests.

    Ensures that only processes with a valid reservation can put items into the store.
    If the put operation succeeds, it triggers `_trigger_reserve_get` to process pending get requests.

    Args:
        put_event (simpy.Event): The event corresponding to the reservation.
        item (object): The item to be added to the store.

    Returns:
        proceed (bool): True if the put operation succeeded, False otherwise.

    Raises:
        RuntimeError: If no reservations are available in the reservations_put
        RuntimeError: If proceed is False after put operation
    """
    proceed = False

    if self.reservations_put:
      proceed = self._trigger_put(put_event,item)
    else:
      raise RuntimeError("No matching reservation found for process: reservations_put is empty")

    if proceed:
      #print(f"{self.env.now} proceed")
      #self._trigger_get(None)
      self._trigger_reserve_get(None)



    if not proceed:

      raise RuntimeError(f"No matching put_event found in the reservations and put failed for{item}")
    else:
      #self._update_time_averaged_level()
      return proceed
reserve_get(priority=0)

Create a reservation request to retrieve an item from the store.

This method generates a SimPy event representing a request to reserve an item for retrieval (get). The event is assigned attributes such as priority, the resource it belongs to, and the process making the request.

The event is then added to reserve_get_queue, which is maintained in priority order, and _trigger_reserve_get() is called to process pending reservations if items are available.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when an item becomes available.

Source code in src/factorysimpy/base/slotted_belt_store.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def reserve_get(self,priority=0):
    """
    Create a reservation request to retrieve an item from the store.

    This method generates a SimPy event representing a request to reserve an item
    for retrieval (`get`). The event is assigned attributes such as priority,
    the resource it belongs to, and the process making the request.

    The event is then added to `reserve_get_queue`, which is maintained in
    priority order, and `_trigger_reserve_get()` is called to process pending
    reservations if items are available.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
       event (simpy.Event): A reservation event that will succeed when an item becomes available.
    """
    #adding attributes to the newly created event for reserve_get
    event = self.env.event()
    event.resourcename=self
    event.requesting_process = self.env.active_process  # Associate event with the current process

    #event.priority_to_get = (priority, self._env.now)
    event.priority_to_get = priority

    #sorting the list based on priority after appending the new event
    self.reserve_get_queue.append(event)
    self.reserve_get_queue.sort(key=lambda e: e.priority_to_get)

    self._trigger_reserve_get(event)
    return event
reserve_get_cancel(get_event_to_cancel)

Cancel a previously made reserve_get request.

Source code in src/factorysimpy/base/slotted_belt_store.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def reserve_get_cancel(self, get_event_to_cancel):
    """
    Cancel a previously made `reserve_get` request.
    """
    # Case 1: still waiting
    if get_event_to_cancel in self.reserve_get_queue:
        self.reserve_get_queue.remove(get_event_to_cancel)
        self._trigger_reserve_get(None)
        return True

    # Case 2: already yielded reservation
    if get_event_to_cancel in self.reservations_get:
        # 1) Remove from active reservations
        self.reservations_get.remove(get_event_to_cancel)

        # 2) Find its index in the parallel lists
        ev_idx = self.reserved_events.index(get_event_to_cancel)

        # 3) Pop out the exact item reference
        item = self.reserved_items.pop(ev_idx)
        # 4) Drop the event token
        self.reserved_events.pop(ev_idx)

        # 5) Remove it from ready_items wherever it currently is
        try:
            self.ready_items.remove(item)
        except ValueError:
            raise RuntimeError(f"Item {item!r} not found in ready_items during cancel.")

        # 6) Compute new insertion index
        if self.mode == "FIFO":
            # one slot before the remaining reserved block
            insert_idx = len(self.ready_items) - len(self.reserved_events) - 1
        else:  # LIFO
            # top of stack
            insert_idx = len(self.ready_items)

        # 7) Re‑insert it
        self.ready_items.insert(insert_idx, item)

        # 8) Trigger any other pending reservations
        self._trigger_reserve_get(None)
        return True

    # No such reservation
    raise RuntimeError(
        "No matching event in reserve_get_queue or reservations_get"
    )
reserve_put(priority=0)

Create a reservation request to put an item into the store.

This function generates a SimPy event representing a reservation request. The event is assigned attributes such as priority, resource name, and the process making the request. The event is then added to reserve_put_queue, which is maintained in priority order.

After adding the event to the queue, _trigger_reserve_put is called to process any pending reservations.

Parameters:
  • priority (int, default: 0 ) –

    The priority level of the reservation request. Lower values indicate higher priority. Defaults to 0.

Returns:
  • event( Event ) –

    A reservation event that will succeed when space is available.

Source code in src/factorysimpy/base/slotted_belt_store.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def reserve_put(self, priority=0):
    """
    Create a reservation request to put an item into the store.

    This function generates a SimPy event representing a reservation request. The event is
    assigned attributes such as priority, resource name, and the process making the request.
    The event is then added to `reserve_put_queue`, which is maintained in priority order.

    After adding the event to the queue, `_trigger_reserve_put` is called to process
    any pending reservations.

    Args:
        priority (int, optional): The priority level of the reservation request.
                                  Lower values indicate higher priority. Defaults to 0.

    Returns:
        event (simpy.Event): A reservation event that will succeed when space is available.

    """
    event = self.env.event()
    event.resourcename = self  # Store reference
    event.requesting_process = self.env.active_process  # Process making the reservation
    event.priority_to_put = priority  # Priority for sorting reservations

    # Add the event to the reservation queue and sort by priority
    self.reserve_put_queue.append(event)
    self.reserve_put_queue.sort(key=lambda e: e.priority_to_put)

    # Attempt to process reservations
    self._trigger_reserve_put(event)

    return event
reserve_put_cancel(put_event_to_cancel)

Cancel a previously made reserve_put request.

This method allows a process to cancel its reservation for putting an item into the store. If the reservation exists in the reserve_put_queue, it is removed before triggering _trigger_reserve_put to process any pending reservations. If the reservation is already in reservations_put, it is also removed and _trigger_reserve_put is triggered.

Parameters:
  • put_event_to_cancel (Event) –

    The reservation event that needs to be canceled.

Returns:
  • proceed( bool ) –

    True if the reservation was successfully canceled.

Raises:
  • RuntimeError

    If the specified event does not exist in reserve_put_queue or reservations_put.

Source code in src/factorysimpy/base/slotted_belt_store.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def reserve_put_cancel(self, put_event_to_cancel):
  """
    Cancel a previously made `reserve_put` request.

    This method allows a process to cancel its reservation for putting an item
    into the store. If the reservation exists in the `reserve_put_queue`, it is
    removed before triggering `_trigger_reserve_put` to process any pending reservations.
    If the reservation is already in `reservations_put`, it is also removed and
    `_trigger_reserve_put` is triggered.

    Args:
        put_event_to_cancel (simpy.Event): The reservation event that needs to be canceled.

    Returns:
       proceed (bool): True if the reservation was successfully canceled.

    Raises:
        RuntimeError: If the specified event does not exist in `reserve_put_queue`
                      or `reservations_put`.
    """

  #checking and removing the event if it is not yielded and is present in the reserve_put_queue
  proceed = False
  if put_event_to_cancel in self.reserve_put_queue:
    self.reserve_put_queue.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True
  #checking and removing the event if it is already yielded and is present in the reservations_put
  elif put_event_to_cancel in self.reservations_put:
    self.reservations_put.remove(put_event_to_cancel)
    self._trigger_reserve_put(None)#if t is removed, then a waiting event can be succeeded, if any
    proceed = True

  else:
    raise RuntimeError("No matching event in reserve_put_queue or reservations_put for this process")
  return proceed
resume_all_move_processes()

Resume all interrupted move_to_ready_items processes.

Source code in src/factorysimpy/base/slotted_belt_store.py
731
732
733
734
735
736
737
738
739
740
def resume_all_move_processes(self):
    """
    Resume all interrupted move_to_ready_items processes.
    """
    print(f"T={self.env.now:.2f} BufferStore resuming move processes")

    # Create a new resume event and trigger it
    old_resume_event = self.resume_event
    self.resume_event = self.env.event()
    old_resume_event.succeed()
selective_interrupt(reason='Selective interrupt')

Perform selective interruption based on belt occupancy patterns and mode.

When noaccumulation_mode_on=True (STALLED_NONACCUMULATING_STATE): - Interrupt all items immediately

When noaccumulation_mode_on=False (STALLED_ACCUMULATING_STATE): - Use pattern-based interruption with delays based on item positions

Pattern-based rules for accumulating mode: - For patterns like '_*', interrupt all items. - For patterns like '___', interrupt item in second last position after 1 delay, and second item after 2 delays. - For patterns like '___*', interrupt first item and second item after 3 delays. - For patterns like '__*', interrupt first item, second after 2 delays, and third after 2 delays.

Parameters:
  • reason (str, default: 'Selective interrupt' ) –

    Reason for the interrupt

Source code in src/factorysimpy/base/slotted_belt_store.py
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
def selective_interrupt(self, reason="Selective interrupt"):
    """
    Perform selective interruption based on belt occupancy patterns and mode.

    When noaccumulation_mode_on=True (STALLED_NONACCUMULATING_STATE):
    - Interrupt all items immediately

    When noaccumulation_mode_on=False (STALLED_ACCUMULATING_STATE):
    - Use pattern-based interruption with delays based on item positions

    Pattern-based rules for accumulating mode:
    - For patterns like '_****', interrupt all items.
    - For patterns like '_*_*_', interrupt item in second last position after 1 delay,
      and second item after 2 delays.
    - For patterns like '*___*', interrupt first item and second item after 3 delays.
    - For patterns like '**__*', interrupt first item, second after 2 delays,
      and third after 2 delays.

    Args:
        reason (str): Reason for the interrupt
    """
    if not self.items:
        print(f"T={self.env.now:.2f} No items on belt to interrupt")
        return

    # If noaccumulation_mode_on is True (STALLED_NONACCUMULATING_STATE), interrupt all items immediately
    if self.noaccumulation_mode_on:
        print(f"T={self.env.now:.2f} No accumulation mode: interrupting all items immediately")
        for i, item in enumerate(self.items):
            item_id = item[0].id if hasattr(item[0], 'id') else str(id(item))
            self._interrupt_specific_item(item_id, f"{reason} - immediate (no accumulation)")
        return

    # For accumulating mode (STALLED_ACCUMULATING_STATE), use pattern-based interruption
    print(f"T={self.env.now:.2f} Accumulating mode: using pattern-based interruption")

    # Get current belt pattern
    pattern = self._get_belt_pattern()
    print(f"T={self.env.now:.2f} Current belt pattern: {pattern}")

    # Analyze pattern and determine interruption strategy
    interruption_plan = self._analyze_pattern_for_interruption(pattern)

    if not interruption_plan:
        print(f"T={self.env.now:.2f} No interruption needed for current pattern")
        return

    print(f"T={self.env.now:.2f} Executing selective interruption plan: {interruption_plan}")

    # Execute the interruption plan
    self._execute_interruption_plan(interruption_plan, reason)

constructs

chain

connect_nodes_with_buffers(machines, buffers, src, sink)

Connects source, machines, buffers, and optionally a sink in the following order: src -> buffer1 -> machine1 -> buffer2 -> machine2 -> ... -> bufferN -> sink

Parameters:
  • src

    Source node

  • machines

    List of machine nodes

  • buffers

    List of buffer edges (should be len(machines) - 1) inlcuding source and sink

  • sink

    Optional sink node

Returns:
  • List of all nodes and buffers in connection order

Source code in src/factorysimpy/constructs/chain.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def connect_nodes_with_buffers(machines, buffers, src, sink):
    """
    Connects source, machines, buffers, and optionally a sink in the following order:
    src -> buffer1 -> machine1 -> buffer2 -> machine2 -> ... -> bufferN -> sink

    Args:
        src: Source node
        machines: List of machine nodes
        buffers: List of buffer edges (should be len(machines) - 1) inlcuding source and sink
        sink: Optional sink node

    Returns:
        List of all nodes and buffers in connection order
    """
    assert len(buffers) == len(machines) - 1, "Number of buffers must be one more than number of machines"


    # Connect intermediate machines and buffers
    for i in range(1, len(machines)):
        buffers[i-1].connect(machines[i-1], machines[i])




    # Return all nodes and buffers for reference

    return machines, buffers

mesh

connect_mesh(env, rows, cols, node_cls, edge_cls, node_kwargs=None, edge_kwargs=None, node_kwargs_grid=None, edge_kwargs_grid=None, prefix='M', edge_prefix='B', source_cls=None, sink_cls=None, source_kwargs=None, sink_kwargs=None)

Builds a mesh/grid of nodes connected with buffers. Each node is connected to its right and down neighbor.

Returns:
  • mesh_nodes

    2D list of nodes (rows x cols)

  • edge_dict

    dict of buffers by (from_node.id, to_node.id)

Source code in src/factorysimpy/constructs/mesh.py
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def connect_mesh(env, rows, cols, node_cls, edge_cls,
                                  node_kwargs=None, edge_kwargs=None,
                                  node_kwargs_grid=None, edge_kwargs_grid=None,
                                  prefix="M", edge_prefix="B",
                                  source_cls=None, sink_cls=None,
                                  source_kwargs=None, sink_kwargs=None):
    """
    Builds a mesh/grid of nodes connected with buffers.
    Each node is connected to its right and down neighbor.

    Returns:
        mesh_nodes: 2D list of nodes (rows x cols)
        edge_dict: dict of buffers by (from_node.id, to_node.id)
    """
    node_kwargs = node_kwargs or {}
    edge_kwargs = edge_kwargs or {}

    mesh_nodes = []
    edge_dict = {}

    def get_kwargs(grid, r, c, default):
        if grid and r < len(grid) and c < len(grid[r]):
            return grid[r][c]
        return default

    # Create nodes (machines)
    for r in range(rows):
        row = []
        for c in range(cols):
            kwargs = get_kwargs(node_kwargs_grid, r, c, node_kwargs)
            node_id = f"{prefix}_{r+1}_{c+1}"
            node = node_cls(env=env, id=node_id, **kwargs)
            row.append(node)
        mesh_nodes.append(row)

    # Helper to create and connect edges
    def create_edge(from_node, to_node, r, c):
        kwargs = get_kwargs(edge_kwargs_grid, r, c, edge_kwargs)
        edge_id = f"{edge_prefix}_{from_node.id}_{to_node.id}"
        edge = edge_cls(env=env, id=edge_id, **kwargs)
        edge.connect(from_node, to_node)
        edge_dict[(from_node.id, to_node.id)] = edge

    # Create buffers (edges) to right and down
    for r in range(rows):
        for c in range(cols):
            from_node = mesh_nodes[r][c]
            if c + 1 < cols:
                create_edge(from_node, mesh_nodes[r][c+1], r, c)
            if r + 1 < rows:
                create_edge(from_node, mesh_nodes[r+1][c], r, c)

    return mesh_nodes,edge_dict
connect_mesh_with_source_sink(env, rows, cols, node_cls, edge_cls, node_kwargs=None, edge_kwargs=None, node_kwargs_grid=None, edge_kwargs_grid=None, prefix='M', edge_prefix='B', source_cls=None, sink_cls=None, source_kwargs=None, sink_kwargs=None)

Builds a mesh/grid of nodes connected with buffers. Source sends to first row, Sink collects from last row.

Returns:
  • mesh_nodes

    2D list of nodes (rows x cols)

  • edge_dict

    dict of buffers by (from_node.id, to_node.id)

  • source, sink: optional

Source code in src/factorysimpy/constructs/mesh.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def connect_mesh_with_source_sink(env, rows, cols, node_cls, edge_cls,
                                  node_kwargs=None, edge_kwargs=None,
                                  node_kwargs_grid=None, edge_kwargs_grid=None,
                                  prefix="M", edge_prefix="B",
                                  source_cls=None, sink_cls=None,
                                  source_kwargs=None, sink_kwargs=None):
    """
    Builds a mesh/grid of nodes connected with buffers.
    Source sends to first row, Sink collects from last row.

    Returns:
        mesh_nodes: 2D list of nodes (rows x cols)
        edge_dict: dict of buffers by (from_node.id, to_node.id)
        source, sink: optional
    """
    node_kwargs = node_kwargs or {}
    edge_kwargs = edge_kwargs or {}
    source_kwargs = source_kwargs or {}
    sink_kwargs = sink_kwargs or {}

    mesh_nodes = []
    edge_dict = {}

    # Create nodes (machines)
    for r in range(rows):
        row = []
        for c in range(cols):
            kwargs = node_kwargs_grid[r][c] if node_kwargs_grid else node_kwargs
            node_id = f"{prefix}_{r+1}_{c+1}"
            node = node_cls(env=env, id=node_id, **kwargs)
            row.append(node)
        mesh_nodes.append(row)

    # Create buffers (edges) to right and down
    for r in range(rows):
        for c in range(cols):
            from_node = mesh_nodes[r][c]

            # Connect right
            if c + 1 < cols:
                to_node = mesh_nodes[r][c+1]
                edge_id = f"{edge_prefix}_{from_node.id}_{to_node.id}"
                kwargs = edge_kwargs_grid[r][c] if edge_kwargs_grid else edge_kwargs
                edge = edge_cls(env=env, id=edge_id, **kwargs)
                edge.connect(from_node, to_node)
                edge_dict[(from_node.id, to_node.id)] = edge

            # Connect down
            if r + 1 < rows:
                to_node = mesh_nodes[r+1][c]
                edge_id = f"{edge_prefix}_{from_node.id}_{to_node.id}"
                kwargs = edge_kwargs_grid[r][c] if edge_kwargs_grid else edge_kwargs
                edge = edge_cls(env=env, id=edge_id, **kwargs)
                edge.connect(from_node, to_node)
                edge_dict[(from_node.id, to_node.id)] = edge

    # Optional source
    source = None
    if source_cls:
        source = source_cls(env=env, id="Source", **source_kwargs)
        for c in range(cols):
            to_node = mesh_nodes[0][c]
            edge_id = f"{edge_prefix}_SRC_{to_node.id}"
            edge = edge_cls(env=env, id=edge_id, **edge_kwargs)
            edge.connect(source, to_node)
            edge_dict[(source.id, to_node.id)] = edge

    # Optional sink
    sink = None
    if sink_cls:
        sink = sink_cls(env=env, id="Sink", **sink_kwargs)
        for c in range(cols):
            from_node = mesh_nodes[rows-1][c]
            edge_id = f"{edge_prefix}_{from_node.id}_SINK"
            edge = edge_cls(env=env, id=edge_id, **edge_kwargs)
            edge.connect(from_node, sink)
            edge_dict[(from_node.id, sink.id)] = edge

    return mesh_nodes, edge_dict, source, sink

edges

buffer

Buffer(env, id, capacity=1, delay=0, mode='FIFO')

Bases: Edge

Buffer class representing a FIFO queue. Inherits from the Edge class. This buffer can have a single input edge and a single output edge.

Attributes:
  • state (str) –

    The current state of the buffer.

  • capacity (int) –

    The capacity of the buffer's internal storage.

  • mode (str) –

    Mode of operation for the buffer, It can be - "FIFO" (First In First Out) - "LIFO" (Last In First Out).

  • delay ((int, float)) –

    Delay after which the item becomes available. It Can be

    • int or float: Used as a constant delay.

Behavior: The Buffer is a type of edge represents components that holds the items that are waiting to be accepted by the destination node. Items that are added in buffer becomes available for use after delay amount of time. It operates in two modes- 1. FIFO: It prioritizes items in the order they were added, with the oldest items being available for the destination node first. 2. LIFO: It prioritizes items in the reverse order of their arrival, items that newly added are available to use by the destination node first Incoming edges can use reserve_get and reserve_put calls on the store in the buffer to reserve an item or space and after yielding the requests, an item can be put and obtained by using put and get methods.

Raises:
  • AssertionError

    If the buffer does not have at least one source node or one destination node.

Output performance metrics

The key performance metrics of the buffer edge are captured in the stats attribute (dict) during a simulation run.

last_state_change_time                      : Time when the state was last changed.
time_averaged_num_of_items_in_buffer        : Time-averaged number of items available in the buffer.
total_time_spent_in_states                  : Dictionary with total time spent in each state.
Source code in src/factorysimpy/edges/buffer.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(self, env, id,  capacity=1, delay=0,  mode="FIFO"):
      super().__init__( env, id, capacity)
      self.state = "IDLE_STATE"
      self.mode=mode
      self.delay = delay
      self.capacity =  capacity
      self.stats = {
        "last_state_change_time": None,
        "time_averaged_num_of_items_in_buffer": 0,
        "total_time_spent_in_states":{"IDLE_STATE": 0.0, "RELEASING_STATE": 0.0, "BLOCKED_STATE": 0.0}
    }



      if self.mode not in ["FIFO", "LIFO"]:
        raise ValueError("Invalid mode. Choose either 'FIFO' or 'LIFO'.")


      # Initialize the buffer store

      self.inbuiltstore = BufferStore(env, capacity=self.capacity,  mode=self.mode)






      if callable(delay) or hasattr(delay, '__next__') or isinstance(delay, (int, float)) or delay is None:
        self.delay = delay

      else:
        raise ValueError("delay must be None, int, float, generator, or callable.")
can_get()

Check if the buffer can accept an item.

Returns

bool True if the buffer can give an item, False otherwise.

Source code in src/factorysimpy/edges/buffer.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def can_get(self):
    """
    Check if the buffer can accept an item.

    Returns
    -------
    bool
        True if the buffer can give an item, False otherwise.
    """
    if not self.inbuiltstore.ready_items:
        return False
    #only return items that are older than the delay. Count such items
    #count = sum(1 for item in self.inbuiltstore.items if item.time_stamp_creation + self.delay <= self.env.now)
    # count should be greater than the number of reservations that are already there
    return len(self.inbuiltstore.ready_items) > len(self.inbuiltstore.reservations_get)
can_put()

Check if the buffer can accept an item.

Returns

bool True if the buffer can accept an item, False otherwise.

Source code in src/factorysimpy/edges/buffer.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def can_put(self):
    """
    Check if the buffer can accept an item.

    Returns
    -------
    bool
        True if the buffer can accept an item, False otherwise.
    """
    # Check if the buffer has space for new items
    if  len(self.inbuiltstore.items)+len(self.inbuiltstore.ready_items)== self.capacity:
        return False
    # return True if the number of items in the buffer is less than the store capacity minus the number of reservations
    # reservations_put is the number of items that are already reserved to be put in the buffer
    return (self.capacity-len(self.inbuiltstore.items)-len(self.inbuiltstore.ready_items)) >len(self.inbuiltstore.reservations_put)
get(event)

Get an item from the buffer.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Returns

item : object The item retrieved from the buffer.

Source code in src/factorysimpy/edges/buffer.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def get(self, event):
    """
    Get an item from the buffer.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.

    Returns
    -------
    item : object
        The item retrieved from the buffer.
    """
    item = self.inbuiltstore.get(event)
    self._buffer_stats_collector()
    return item
reserve_get_cancel(event)

Cancel a reserved get event.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Source code in src/factorysimpy/edges/buffer.py
182
183
184
185
186
187
188
189
190
191
def reserve_get_cancel(self,event):
    """
    Cancel a reserved get event.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.
    """
    return self.inbuiltstore.reserve_get_cancel(event)
reserve_put_cancel(event)

Cancel a reserved put event.

Parameters

event : simpy.Event The event that was reserved for putting an item.

Source code in src/factorysimpy/edges/buffer.py
193
194
195
196
197
198
199
200
201
202
def reserve_put_cancel(self,event):
    """
    Cancel a reserved put event.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for putting an item.
    """
    return self.inbuiltstore.reserve_put_cancel(event)

continuous_conveyor

ConveyorBelt(env, id, conveyor_length, speed, item_length, accumulating)

Bases: Edge

A conveyor belt system with optional accumulation.

Attributes:

capacity (int): Maximum capacity of the belt.
state (str): state of the conveyor belt.
length (float): Length of the item.
speed (float): Speed of the conveyor belt.
accumulating (bool): Whether the belt supports accumulation (1 for yes, 0 for no).
belt (BeltStore): The belt store object.
Source code in src/factorysimpy/edges/continuous_conveyor.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def __init__(self, env, id, conveyor_length, speed,item_length,accumulating):
    capacity = int(np.ceil(conveyor_length)/item_length)
    super().__init__(env, id, capacity)

    self.state = "IDLE_STATE"
    self.length= item_length #length of the item
    self.conveyor_length = conveyor_length

    self.accumulating = accumulating
    self.speed=speed
    self.delay = int(self.conveyor_length/self.speed)*capacity
    #self.delay = (self.length*self.speed)/capacity
    self.belt = BeltStore(env, capacity, self.speed, self.accumulating)



    #self.time_per_item = self.length/self.speed
    #self.inp_buf=ReservablePriorityReqStore(env, capacity=1)
    #self.out_buf=ReservablePriorityReqStore(env, capacity=1)
    self.stats = {
        "last_state_change_time": None,
        "time_averaged_num_of_items_in_conveyor": 0,
        "total_time_spent_in_states":{"IDLE_STATE": 0.0,"MOVING_STATE":0.0, "ACCUMULATING_STATE": 0.0, "STALLED_NONACCUMULATING_STATE": 0.0}
    }
    self.item_arrival_event = self.env.event()
    # self.item_get_event=self.env.event()
    self.get_events_available = self.env.event()
    self.put_events_available = self.env.event()


    self.noaccumulation_mode_on=False


    # self.get_request_queue = []
    # self.put_request_queue = []
    # self.active_events = []
    # self.get_dict = {}
    # self.put_dict = {}


    self.env.process(self.behaviour())
can_get()

Check if an item can be retrieved from the belt.

Source code in src/factorysimpy/edges/continuous_conveyor.py
123
124
125
126
127
128
129
def can_get(self):
    """Check if an item can be retrieved from the belt."""
    #first_item_to_go_out = self.items[0] if self.items else None
    if not self.out_buf.items:
        return False
    else:
       return True
can_put()

Check if an item can be added to the belt.

Source code in src/factorysimpy/edges/continuous_conveyor.py
138
139
140
141
142
143
def can_put(self):
    """Check if an item can be added to the belt."""
    if not self.inp_buf.items:
        return True
    else:
        return False
get(event)

Get an item from the belt.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Returns

Item The item retrieved from the belt.

Source code in src/factorysimpy/edges/continuous_conveyor.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def get(self, event):
    """
    Get an item from the belt.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.

    Returns
    -------
    Item
        The item retrieved from the belt.
    """
    print(f"T={self.env.now:.2f}: {self.id }:get: getting item from belt")
    item = self.belt.get(event)
    item.conveyor_exit_time = self.env.now
    self._conveyor_stats_collector()
    event= self.env.event()
    self.get_events_available.succeed()
    print(f"{self.env.now} {item.id} time in conveyor {item.conveyor_entry_time} and {item.conveyor_exit_time} - time spend in conveyor {item.conveyor_exit_time - item.conveyor_entry_time if item.conveyor_exit_time and item.conveyor_entry_time else 'N/A'}")
    return item
is_empty()

Check if the belt is completely empty.

Source code in src/factorysimpy/edges/continuous_conveyor.py
105
106
107
108
def is_empty(self):
  """Check if the belt is completely empty."""
  return (
      len(self.belt.items)+len(self.belt.ready_items) == 0  )
is_full()

Check if the belt is full.

Source code in src/factorysimpy/edges/continuous_conveyor.py
119
120
121
def is_full(self):
      """Check if the belt is full."""
      return len(self.belt.items)+len(self.belt.ready_items) == self.belt.capacity
is_stalled()

Check if the belt is stalled due to time constraints.

Source code in src/factorysimpy/edges/continuous_conveyor.py
131
132
133
134
135
136
def is_stalled(self):
      """Check if the belt is stalled due to time constraints."""
      if self.belt.ready_items and len(self.belt.reservations_get)==0 :
        return True
      else:
        return False
put(event, item)

Put an item into the belt.

Parameters

event : simpy.Event The event that was reserved for putting an item. item : Item The item to be put on the belt.

Returns

simpy.Event An event that will be triggered when the item is successfully put on the belt.

Source code in src/factorysimpy/edges/continuous_conveyor.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def put(self, event, item):
    """
    Put an item into the belt.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for putting an item.
    item : Item
        The item to be put on the belt.

    Returns
    -------
    simpy.Event
        An event that will be triggered when the item is successfully put on the belt.
    """
    #delay=self.get_delay(self.delay)
    print(f"T={self.env.now:.2f}: Conveyor:put: putting item {item.id} ")
    delay = self.length * self.capacity/self.speed
    item.conveyor_entry_time = self.env.now
    item_to_put = (item, delay)
    print(f"T={self.env.now:.2f}: {self.id }:put: putting item {item_to_put[0].id} on belt with delay {item_to_put[1]} {self.state}")
    return_val = self.belt.put(event, item_to_put)
    self._conveyor_stats_collector()
    if len(self.belt.items)==1 and self.state=="IDLE_STATE":
        self.item_arrival_event.succeed()
        print(f"T={self.env.now:.2f}: {self.id }:put: item arrival event succeeded")
    else: 
        event= self.env.event()
        self.put_events_available.succeed()
        if self.accumulating==0:
            print(f"T={self.env.now:.2f}: {self.id }: attempting to put an item while non accumulating mode on and {self.state} and {self.belt.noaccumulation_mode_on}")
        print(f"T={self.env.now:.2f}: {self.id }:put: item arrival event else succeeded")

    if self.state=="STALLED_ACCUMULATING_STATE" and self.accumulating==1 or self.state=="STALLED_NONACCUMULATING_STATE" and self.accumulating==0:
        print(f"T={self.env.now:.2f}: {self.id }:put: handling new item during interruption {item_to_put[0].id} on belt")
        self.belt.handle_new_item_during_interruption(item_to_put)




    return return_val
set_conveyor_state(new_state)

Set the conveyor state and manage belt store interrupts/resumes.

Parameters:
  • new_state (str) –

    The new conveyor state

Source code in src/factorysimpy/edges/continuous_conveyor.py
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
def set_conveyor_state(self, new_state):
    """
    Set the conveyor state and manage belt store interrupts/resumes.

    Args:
        new_state (str): The new conveyor state
    """
    old_state = self.state
    self.state = new_state

    print(f"T={self.env.now:.2f}: {self.id} state changed from {old_state} to {new_state}")

    # Control belt store based on conveyor state changes
    if old_state in ["MOVING_STATE", "IDLE_STATE"] and new_state in ["STALLED_ACCUMULATING_STATE", "STALLED_NONACCUMULATING_STATE"]:
        # When conveyor becomes stalled (either accumulating or non-accumulating), apply selective interruption
        if not self.accumulating:
            self.belt.noaccumulation_mode_on = True

        self.belt.selective_interrupt(f"Conveyor {self.id} selective interruption - {new_state}")
    elif old_state in ["STALLED_ACCUMULATING_STATE", "STALLED_NONACCUMULATING_STATE"] and new_state in ["MOVING_STATE", "IDLE_STATE"]:
        # When conveyor resumes to moving or becomes idle, resume all belt store processes
        #self.belt.interrupt_and_resume_all_delayed_interrupt_processes()
        if not self.accumulating:
            self.belt.noaccumulation_mode_on = False
        self.belt.resume_all_move_processes()
        self.belt.interrupt_and_resume_all_delayed_interrupt_processes()
    else:
        print("state changes from",old_state,"to",  new_state)

edge

Edge(env, id, capacity)

Edge represents the passive components. It used to connect two nodes and helps to move items between them. It is the base class used to model buffers, conveyors, fleets, etc in manufacturing system.

Parameters:
  • id (str) –

    unique identifier for the edge

  • src_node (Node) –

    reference to the source node connected to this edge.

  • dest_node (Node) –

    reference to the destination node connected to this edge.

Raises:
  • TypeError

    If the type of env or id is incorrect.

  • ValueError

    If the delay parameter is not a valid type (int, float, generator, or callable).

  • ValueError

    If the edge is already connected to a source or destination node and reconnect is False.

  • ValueError

    If the source or destination nodes are not valid Node instances.

Source code in src/factorysimpy/edges/edge.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def __init__(self, env, id, capacity):
    self.env = env
    self.id = id
    self.src_node = None
    self.dest_node = None
    self.capacity = capacity

     # Type checks
    if not isinstance(env, simpy.Environment):
        raise TypeError("env must be a simpy.Environment instance")
    if not isinstance(id, str):
        raise TypeError("id must be a string")
    if not isinstance(self.capacity, int) or self.capacity <= 0:
        raise ValueError("capacity must be a positive integer")

    assert self.src_node is None, f"Edge '{self.id}' must have a source node."
    assert self.dest_node is None, f"Edge '{self.id}' must have a destination node."
    assert self.id is not None, "Edge id cannot be None."
    assert self.capacity is not None, "Edge capacity cannot be None."
connect(src, dest, reconnect=False)

Connects this edge to a source node and a destination node.

This method checks that both src and dest are valid Node objects and that the edge is not already connected, unless reconnect is set to True. It also registers this edge in the out_edges of the source node and the in_edges of the destination node.

Parameters:
  • src (Node) –

    The source node to connect.

  • dest (Node) –

    The destination node to connect.

  • reconnect (bool, default: False ) –

    If True, allows reconnection even if the edge is already connected. Defaults to False.

Raises:
  • ValueError

    If the edge is already connected and reconnect is False.

  • ValueError

    If src or dest is not a valid Node instance.

Source code in src/factorysimpy/edges/edge.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def connect(self, src: Node, dest: Node, reconnect: bool = False):
    """
    Connects this edge to a source node and a destination node.

    This method checks that both `src` and `dest` are valid Node objects and that the edge is not already connected,
    unless `reconnect` is set to True. It also registers this edge in the `out_edges` of the source node and the
    `in_edges` of the destination node.

    Args:
        src (Node): The source node to connect.
        dest (Node): The destination node to connect.
        reconnect (bool, optional): If True, allows reconnection even if the edge is already connected. Defaults to False.

    Raises:
        ValueError: If the edge is already connected and `reconnect` is False.
        ValueError: If `src` or `dest` is not a valid Node instance.
    """

    if not reconnect:
        if self.src_node or self.dest_node:
            raise ValueError(f"Edge '{self.id}' is already connected source or destination node.")
        if not isinstance(src, Node):
            raise ValueError(f"Source '{src}' is not a valid Node.")
        if not isinstance(dest, Node):
            raise ValueError(f"Destination '{dest}' is not a valid Node.")

    self.src_node = src
    self.dest_node = dest

    # Register edge to nodes
    if src.out_edges is None:
        src.out_edges = []
    if self not in src.out_edges:
        src.out_edges.append(self)

    if dest.in_edges is None:
        dest.in_edges = []
    if self not in dest.in_edges:
        dest.in_edges.append(self)
    print(f"T={self.env.now:.2f}: Connected edge '{self.id}' from '{src.id}' to '{dest.id}'  ")
get_delay(delay)

Returns value based on the type of parameter delay provided.

Parameters:
  • delay (int, float, generator, or callable) –

    The delay time, which can be:

    • int or float: Used as a constant delay.
    • generator: A generator instance yielding delay values.
    • callable: A function that returns a delay values.
Returns:
  • Returns a constant delay if delay is an int or float, a value yielded if delay is a generator, or the value returned from a Callable function if delay is callable.

Source code in src/factorysimpy/edges/edge.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def get_delay(self,delay):
    """
    Returns value based on the type of parameter `delay` provided.

    Args:
         delay (int, float, generator, or callable): The delay time, which can be:

            - int or float: Used as a constant delay.
            - generator: A generator instance yielding delay values.
            - callable: A function that returns a delay values.

    Returns:
           Returns a constant delay if `delay` is an int or float, a value yielded  if `delay` is a generator, or the value returned from a Callable function if `delay` is callable.
    """
    if hasattr(delay, '__next__'):
        # Generator instance
        val = next(delay)
    elif callable(delay):
        # Function
        val = delay()
    else:
        # int or float
        val = delay

    assert val >= 0, "Delay must be non-negative"
    return val
update_state(new_state, current_time)

Update node state and track the time spent in the previous state.

Parameters:
  • new_state (str) –

    The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/edges/edge.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def update_state(self, new_state: str, current_time: float):
    """
    Update node state and track the time spent in the previous state.

    Args:
        new_state (str): The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".
        current_time (float): The current simulation time.

    """

    if self.state is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]

        self.stats["total_time_spent_in_states"][self.state] = (
            self.stats["total_time_spent_in_states"].get(self.state, 0.0) + elapsed
        )    
    self.state = new_state
    self.stats["last_state_change_time"] = current_time

fleet

Fleet(env, id, capacity=1, delay=1, transit_delay=0)

Bases: Edge

Fleet class representing an AGV (Automated Guided Vehicle) or a group of transporters. Inherits from the Edge class. This fleet can have a single input edge and a single output edge.

Attributes:
  • state (str) –

    The current state of the fleet.

  • capacity (int) –

    The capacity of the fleet's internal storage.

  • delay ((int, float)) –

    Delay after which fleet activates to move items incase the target capacity is not reached. It Can be

    • int or float: Used as a constant delay.
  • transit_delay ((int, float)) –

    It is the time taken by the fleet to transport the item from src node to destination node. (can be a constant, generator, or callable).

Behavior: The Fleet is a type of edge represents components that moves multiple items simulataneaously between nodes. User can specify a parameter capacity to specify how many items can be moved at once. Incoming edges can use reserve_get and reserve_put calls on the store in the fleet to reserve an item or space and after yielding the requests, an item can be put and obtained by using put and get methods.

Raises:
  • AssertionError

    If the fleet does not have at least one source node or one destination node.

Output performance metrics

The key performance metrics of the fleet edge are captured in the stats attribute (dict) during a simulation run.

last_state_change_time                      : Time when the state was last changed.
time_averaged_num_of_items_in_fleet        : Time-averaged number of items available in the fleet.
total_time_spent_in_states                  : Dictionary with total time spent in each state.
Source code in src/factorysimpy/edges/fleet.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def __init__(self, env, id,  capacity=1, delay=1, transit_delay=0):
      super().__init__( env, id, capacity)
      self.state = "IDLE_STATE"

      self.delay = delay
      self.capacity =  capacity
      self.transit_delay = transit_delay
      self.stats = {
        "last_state_change_time": None,
        "time_averaged_num_of_items_in_fleet": 0,
        "total_time_spent_in_states":{"IDLE_STATE": 0.0, "RELEASING_STATE": 0.0, "BLOCKED_STATE": 0.0}
    }






      # Initialize the fleet store

      self.inbuiltstore = FleetStore(env, capacity=self.capacity,  delay=self.delay, transit_delay=self.transit_delay)






      if callable(delay) or hasattr(delay, '__next__') or isinstance(delay, (int, float)) or delay is None:
        self.delay = delay

      else:
        raise ValueError("delay must be None, int, float, generator, or callable.")
can_get()

Check if the fleet can accept an item.

Returns

bool True if the fleet can give an item, False otherwise.

Source code in src/factorysimpy/edges/fleet.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def can_get(self):
    """
    Check if the fleet can accept an item.

    Returns
    -------
    bool
        True if the fleet can give an item, False otherwise.
    """
    if not self.inbuiltstore.ready_items:
        return False
    #only return items that are older than the delay. Count such items
    #count = sum(1 for item in self.inbuiltstore.items if item.time_stamp_creation + self.delay <= self.env.now)
    # count should be greater than the number of reservations that are already there
    return len(self.inbuiltstore.ready_items) > len(self.inbuiltstore.reservations_get)
can_put()

Check if the fleet can accept an item.

Returns

bool True if the fleet can accept an item, False otherwise.

Source code in src/factorysimpy/edges/fleet.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def can_put(self):
    """
    Check if the fleet can accept an item.

    Returns
    -------
    bool
        True if the fleet can accept an item, False otherwise.
    """
    # Check if the fleet has space for new items
    if  len(self.inbuiltstore.items)+len(self.inbuiltstore.ready_items)== self.capacity:
        return False
    # return True if the number of items in the fleet is less than the store capacity minus the number of reservations
    # reservations_put is the number of items that are already reserved to be put in the fleet
    return (self.capacity-len(self.inbuiltstore.items)-len(self.inbuiltstore.ready_items)) >len(self.inbuiltstore.reservations_put)
get(event)

Get an item from the fleet.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Returns

item : object The item retrieved from the fleet.

Source code in src/factorysimpy/edges/fleet.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def get(self, event):
    """
    Get an item from the fleet.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.

    Returns
    -------
    item : object
        The item retrieved from the fleet.
    """
    #print(f"T={self.env.now:.2f}: {self.id} is getting an item at time {self.env.now}, total item in fleet is {len(self.inbuiltstore.items)+len(self.inbuiltstore.ready_items)}")
    item = self.inbuiltstore.get(event)
    self._fleet_stats_collector()
    #print(f"T={self.env.now:.2f}, got an item!!!!")
    item.fleet_exit_time = self.env.now
    return item
reserve_get_cancel(event)

Cancel a reserved get event.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Source code in src/factorysimpy/edges/fleet.py
173
174
175
176
177
178
179
180
181
182
def reserve_get_cancel(self,event):
    """
    Cancel a reserved get event.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.
    """
    return self.inbuiltstore.reserve_get_cancel(event)
reserve_put_cancel(event)

Cancel a reserved put event.

Parameters

event : simpy.Event The event that was reserved for putting an item.

Source code in src/factorysimpy/edges/fleet.py
184
185
186
187
188
189
190
191
192
193
def reserve_put_cancel(self,event):
    """
    Cancel a reserved put event.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for putting an item.
    """
    return self.inbuiltstore.reserve_put_cancel(event)

slotted_conveyor

ConveyorBelt(env, id, capacity, delay, accumulating)

Bases: Edge

A conveyor belt system with optional accumulation.

Attributes:

capacity (int): Maximum capacity of the belt.
state (str): state of the conveyor belt.
delay (float): Time interval between two successive movements on the belt.
accumulation (bool): Whether the belt supports accumulation (1 for yes, 0 for no).
Source code in src/factorysimpy/edges/slotted_conveyor.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(self, env, id, capacity, delay,accumulating):
    super().__init__(env, id, capacity )

    self.state = "IDLE_STATE"
    #self.length= length #length of the item

    self.accumulating = accumulating
    self.delay=delay
    #self.delay = int(self.length/self.speed)*capacity

    self.belt = BeltStore(env, capacity, self.delay)



    #self.time_per_item = self.length/self.speed
    #self.inp_buf=ReservablePriorityReqStore(env, capacity=1)
    #self.out_buf=ReservablePriorityReqStore(env, capacity=1)
    self.stats = {
        "last_state_change_time": None,
        "time_averaged_num_of_items_in_conveyor": 0,
        "total_time_spent_in_states":{"IDLE_STATE": 0.0,"MOVING_STATE":0.0, "ACCUMULATING_STATE": 0.0, "STALLED_NONACCUMULATING_STATE": 0.0}
    }
    self.item_arrival_event = self.env.event()
    # self.item_get_event=self.env.event()
    self.get_events_available = self.env.event()
    self.put_events_available = self.env.event()


    self.noaccumulation_mode_on=False


    # self.get_request_queue = []
    # self.put_request_queue = []
    # self.active_events = []
    # self.get_dict = {}
    # self.put_dict = {}


    self.env.process(self.behaviour())
can_get()

Check if an item can be retrieved from the belt.

Source code in src/factorysimpy/edges/slotted_conveyor.py
138
139
140
141
142
143
144
def can_get(self):
    """Check if an item can be retrieved from the belt."""
    #first_item_to_go_out = self.items[0] if self.items else None
    if not self.out_buf.items:
        return False
    else:
       return True
can_put()

Check if an item can be added to the belt.

Source code in src/factorysimpy/edges/slotted_conveyor.py
153
154
155
156
157
158
def can_put(self):
    """Check if an item can be added to the belt."""
    if not self.inp_buf.items:
        return True
    else:
        return False
get(event)

Get an item from the belt.

Parameters

event : simpy.Event The event that was reserved for getting an item.

Returns

Item The item retrieved from the belt.

Source code in src/factorysimpy/edges/slotted_conveyor.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def get(self, event):
    """
    Get an item from the belt.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for getting an item.

    Returns
    -------
    Item
        The item retrieved from the belt.
    """
    print(f"T={self.env.now:.2f}: {self.id }:get: getting item from belt")
    item = self.belt.get(event)
    item.conveyor_exit_time = self.env.now
    self._conveyor_stats_collector()
    # event= self.env.event()
    # self.get_events_available.succeed()

    return item
is_empty()

Check if the belt is completely empty.

Source code in src/factorysimpy/edges/slotted_conveyor.py
127
128
129
def is_empty(self):
  """Check if the belt is completely empty."""
  return (len(self.belt.items)+len(self.belt.ready_items) == 0  )
is_full()

Check if the belt is full.

Source code in src/factorysimpy/edges/slotted_conveyor.py
134
135
136
def is_full(self):
      """Check if the belt is full."""
      return len(self.belt.items)+len(self.belt.ready_items) == self.belt.capacity
is_stalled()

Check if the belt is stalled due to time constraints.

Source code in src/factorysimpy/edges/slotted_conveyor.py
146
147
148
149
150
151
def is_stalled(self):
      """Check if the belt is stalled due to time constraints."""
      if self.belt.ready_items and len(self.belt.reservations_get)==0 :
        return True
      else:
        return False
put(event, item)

Put an item into the belt.

Parameters

event : simpy.Event The event that was reserved for putting an item. item : Item The item to be put on the belt.

Returns

simpy.Event An event that will be triggered when the item is successfully put on the belt.

Source code in src/factorysimpy/edges/slotted_conveyor.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def put(self, event, item):
    """
    Put an item into the belt.

    Parameters
    ----------
    event : simpy.Event
        The event that was reserved for putting an item.
    item : Item
        The item to be put on the belt.

    Returns
    -------
    simpy.Event
        An event that will be triggered when the item is successfully put on the belt.
    """
    #delay=self.get_delay(self.delay)
    print(f"T={self.env.now:.2f}: Conveyor:put: putting item {item.id} ")
    delay = self.capacity * self.delay
    item.conveyor_entry_time = self.env.now
    item_to_put = (item, delay)
    print(f"T={self.env.now:.2f}: {self.id }:put: putting item {item_to_put[0].id} on belt with delay {item_to_put[1]}")
    return_val = self.belt.put(event, item_to_put)
    self._conveyor_stats_collector()
    # if len(self.belt.items)==1:
    #     self.item_arrival_event.succeed()
    #     print(f"T={self.env.now:.2f}: {self.id }:put: item arrival event succeeded")
    # else: 

    #     self.put_events_available.succeed()
    #     print(f"T={self.env.now:.2f}: {self.id }:put: item arrival event else succeeded")

    return return_val
set_conveyor_state(new_state)

Set the conveyor state and manage belt store interrupts/resumes.

Parameters:
  • new_state (str) –

    The new conveyor state

Source code in src/factorysimpy/edges/slotted_conveyor.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def set_conveyor_state(self, new_state):
    """
    Set the conveyor state and manage belt store interrupts/resumes.

    Args:
        new_state (str): The new conveyor state
    """
    old_state = self.state
    self.state = new_state

    print(f"T={self.env.now:.2f}: {self.id} state changed from {old_state} to {new_state}")

    # Control belt store based on conveyor state changes
    if old_state in ["MOVING_STATE", "IDLE_STATE"] and new_state in ["STALLED_ACCUMULATING_STATE", "STALLED_NONACCUMULATING_STATE"]:
        # When conveyor becomes stalled (either accumulating or non-accumulating), apply selective interruption
        self.belt.selective_interrupt(f"Conveyor {self.id} selective interruption - {new_state}")
    elif old_state in ["STALLED_ACCUMULATING_STATE", "STALLED_NONACCUMULATING_STATE"] and new_state in ["MOVING_STATE", "IDLE_STATE"]:
        # When conveyor resumes to moving or becomes idle, resume all belt store processes
        self.belt.resume_all_move_processes()

helper

baseflowitem

BaseFlowItem(id)

A class representing an item .

Source code in src/factorysimpy/helper/baseflowitem.py
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def __init__(self, id):
    self.id = id
    self.timestamp_creation = None
    self.timestamp_destruction = None
    self.timestamp_node_entry = None
    self.timestamp_node_exit = None
    self.current_node_id = None
    self.source_id = None      # Track the source node
    self.payload = None
    self.destructed_in_node = None  # Node where item was destructed
    self.stats = {}  # Dictionary to store time spent at each node
set_creation(source_id, env)

Set creation time and source node ID.

Source code in src/factorysimpy/helper/baseflowitem.py
15
16
17
18
def set_creation(self, source_id, env):
    """Set creation time and source node ID."""
    self.timestamp_creation = env.now
    self.source_id = source_id
set_destruction(node_id, env)

set the destruction time and node of the item.

Source code in src/factorysimpy/helper/baseflowitem.py
20
21
22
23
def set_destruction(self, node_id,  env):
    """set the destruction time and node of the item."""
    self.timestamp_destruction = env.now
    self.destructed_in_node = node_id
update_node_event(node_id, env, event_type='entry')

Update item details and stats when entering or exiting a node.

Parameters:
  • node_id (str) –

    The ID of the node.

  • env (Environment) –

    The simulation environment (for current time).

  • event_type (str, default: 'entry' ) –

    "entry" or "exit" to specify the event.

Source code in src/factorysimpy/helper/baseflowitem.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def update_node_event(self, node_id, env, event_type="entry"):
    """
    Update item details and stats when entering or exiting a node.

    Args:
        node_id (str): The ID of the node.
        env (simpy.Environment): The simulation environment (for current time).
        event_type (str): "entry" or "exit" to specify the event.
    """
    if event_type == "entry":
        self.timestamp_node_entry = env.now
        #print(f"T={self.timestamp_node_entry:.2f}: {self.id} entered node {node_id}")
        self.current_node_id = node_id
    elif event_type == "exit":
        self.timestamp_node_exit = env.now
        # Calculate time spent at the node and update stats
        if self.current_node_id is not None and self.timestamp_node_entry is not None:
            time_spent = self.timestamp_node_exit - self.timestamp_node_entry
            if self.current_node_id in self.stats:
                self.stats[self.current_node_id] += time_spent
            else:
                self.stats[self.current_node_id] = time_spent

item

Item(id)

Bases: BaseFlowItem

A class representing a pallet, which can hold multiple items.

Source code in src/factorysimpy/helper/item.py
5
6
7
def __init__(self, id):
    super().__init__(id)
    self.flow_item_type = "item"

pallet

Pallet(id)

Bases: BaseFlowItem

A class representing a pallet, which can hold multiple items.

Source code in src/factorysimpy/helper/pallet.py
5
6
7
8
def __init__(self, id):
    super().__init__(id)
    self.flow_item_type = "Pallet"
    self.items = []  # List to hold contained items
add_item(item)

Add an item to the pallet.

Source code in src/factorysimpy/helper/pallet.py
10
11
12
def add_item(self, item):
    """Add an item to the pallet."""
    self.items.append(item)
remove_item()

Remove an item from the pallet if present.

Source code in src/factorysimpy/helper/pallet.py
14
15
16
17
18
19
def remove_item(self):
    """Remove an item from the pallet if present."""
    if self.items:
        item = self.items.pop(-1)  # Remove the last item
        return item
    return None

nodes

combiner

Combiner(env, id, in_edges=None, out_edges=None, node_setup_time=0, target_quantity_of_each_item=[1], processing_delay=0, blocking=True, out_edge_selection='FIRST_AVAILABLE')

Bases: Node

Combiner class representing a processing node in a factory simulation. Inherits from the Node class.The combiner can have multiple input edges and a multiple output edges. It gets items from the input edges and packs them into a pallet or box and pushes it to the output edge.

Parameters:
  • state (str) –

    Current state of the combiner node. One of :

    • SETUP_STATE: Initial setup phase before combiner starts to operate.
    • IDLE_STATE: Worker threads waiting to receive items.
    • PROCESSING_STATE: Actively processing items.
    • BLOCKED_STATE: When all the worker threads are waiting to push the processed item but the out going edge is full.
  • blocking (bool, default: True ) –

    If True, the source waits until it can put an item into the out edge. If False, it discards the item if the out edge is full and cannot accept the item that is being pushed by the combiner.

  • processing_delay ((None, int, float, Generator, Callable), default: 0 ) –

    Delay for processing items. Can be:

    • None: Used when the processing time depends on parameters of the node object (like current state of the object) or environment.
    • int or float: Used as a constant delay.
    • Generator: A generator function yielding delay values over time.
    • Callable: A function that returns a delay (int or float).
  • out_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the out edge. Options include "RANDOM", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: None: Used when out edge selction depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random out edge in the out_edges list.
      • "ROUND_ROBIN": Selects out edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can accept an item.
    • callable: A function that returns an edge index.
Behavior

The combiner node represents components that process or modify the items that flow in the simulation model. It can have multiple incoming edges and multiple outgoing edge. Edge to which processed item is pushed is decided using the method specified in the parameter out_edge_selection. Combiner will transition through the states- SETUP_STATE, PROCESSING_STATE, IDLE_STATE and BLOCKED_STATE. The combiner has a blocking behavior if blocking=True and gets blocked when all its worker threads have processed items and the out edge is full and cannot accept the item that is being pushed by the combiner and waits until the out edge can accept the item. If blocking=False, the combiner will discard the item if the out edge is full and cannot accept the item that is being pushed by the combiner.

Raises:
  • AssertionError

    If the combiner has no input or output edges.

Output performance metrics: The key performance metrics of the combiner node is captured in stats attribute (dict) during a simulation run.

last_state_change_time    : Time when the state was last changed.
num_item_processed        : Total number of items generated.
num_item_discarded        : Total number of items discarded.
total_time_spent_in_states: Dictionary with total time spent in each state.
Source code in src/factorysimpy/nodes/combiner.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def __init__(self, env, id, in_edges=None, out_edges=None,node_setup_time=0,target_quantity_of_each_item=[1],processing_delay=0,blocking=True,out_edge_selection="FIRST_AVAILABLE"):
    super().__init__(env, id,in_edges, out_edges, node_setup_time)

    self.state = "SETUP_STATE"  # Initial state of the combiner
    self.work_capacity = 1

    self.out_edge_selection = out_edge_selection
    self.blocking = blocking
    self.per_thread_total_time_in_blocked_state = 0.0
    self.per_thread_total_time_in_processing_state = 0.0
    self.target_quantity_of_each_item= target_quantity_of_each_item

    self.worker_thread_list = []  # List to keep track of worker threads

    self.item_in_process= None
    self.pallet_in_process=None
    self.num_workers = 0  # Number of worker threads currently processing
    self.time_last_occupancy_change = 0  # Time when the occupancy was last changed
    self.worker_thread = simpy.Resource(env, capacity=self.work_capacity)  # Resource for worker threads
    self.time_per_work_occupancy = [0.0 for _ in range(self.work_capacity+1)]  # Time spent by each worker thread
    self.stats={"total_time_spent_in_states": {"SETUP_STATE": 0.0, "IDLE_STATE":0.0, "PROCESSING_STATE": 0.0,"BLOCKED_STATE":0.0 },
                "last_state_change_time": None, "num_item_processed": 0, "num_item_discarded": 0,"processing_delay":[],"out_edge_selection":[]}






    # Initialize processing delay 
    # If processing_delay is a generator, callable, int, float or None, it is accepted.

    if callable(processing_delay) or hasattr(processing_delay, '__next__') or isinstance(processing_delay, (int, float)) or processing_delay is None:
        self.processing_delay = processing_delay

    else:
        raise ValueError(
            "processing_delay must be None, int, float, generator, or callable."
        )

    self.env.process(self.behaviour())  # Start the combiner behavior process
add_in_edges(edge)

Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an in_edge.

Source code in src/factorysimpy/nodes/combiner.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def add_in_edges(self, edge):
    """
    Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an in_edge.
        """
    if self.in_edges is None:
        self.in_edges = []

    # if len(self.in_edges) >= self.num_in_edges:
    #     raise ValueError(f"Combiner'{self.id}' already has {self.num_in_edges} in_edges. Cannot add more.")

    if edge not in self.in_edges:
        self.in_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Combiner '{self.id}' in_edges.")
add_out_edges(edge)

Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an out_edge.

Source code in src/factorysimpy/nodes/combiner.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def add_out_edges(self, edge):
    """
    Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an out_edge.
    """
    if self.out_edges is None:
        self.out_edges = []

    # if len(self.out_edges) >= 1:
    #     raise ValueError(f"Combiner '{self.id}' already has 1 out_edge. Cannot add more.")

    if edge not in self.out_edges:
        self.out_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Combiner '{self.id}' out_edges.")
update_state(new_state, current_time)

Update node state and track the time spent in the previous state.

Parameters:
  • i (int) –

    The index of the worker thread to update the state for.

  • new_state (str) –

    The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/nodes/combiner.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def update_state(self, new_state: str, current_time: float):
    """
    Update node state and track the time spent in the previous state.

    Args:
        i (int): The index of the worker thread to update the state for.
        new_state (str): The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".
        current_time (float): The current simulation time.

    """

    if self.state is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]

        self.stats["total_time_spent_in_states"][self.state] = (
            self.stats["total_time_spent_in_states"].get(self.state, 0.0) + elapsed
        )
    self.state = new_state
    self.stats["last_state_change_time"] = current_time

machine

Machine(env, id, in_edges=None, out_edges=None, node_setup_time=0, work_capacity=1, processing_delay=0, blocking=True, in_edge_selection='FIRST_AVAILABLE', out_edge_selection='FIRST_AVAILABLE')

Bases: Node

Machine represents a processing node in a factory simulation. This Machine can have multiple input edges and output edges.

Parameters:
  • state_rep (tuple) –

    Current state of the machine node representates as a tuple based on the state of the worker threads (num_worker_threads_processing, num_worker_threads_blocked). Worker threads can be either in the processing state or in the blocked state. Based on that machine can be in one or many of the following states:

    • SETUP_STATE: Initial setup phase before machine starts to operate. Denoted as (-1,-1)
    • IDLE_STATE: Worker threads waiting to receive items. Denoted as (0,0)
    • ATLEAST_ONE_PROCESSING_STATE: Active state where items are being processed. State when atleast one thread is in processing state.
    • ALL_ACTIVE_BLOCKED_STATE: The state when all the worker_threads that are currently active are in "BLOCKED_STATE" as they are waiting for the out edge to be available to accept the processed item. Number of active threads can be equal to greater than work_capacity.
    • ALL_ACTIVE_PROCESSING_STATE: The state when all the active threads are in processing state. Number of active threads can be equal to greater than work_capacity.
    • ATLEAST_ONE_BLOCKED_STATE: The state when atleast one of the worker_threads is in "BLOCKED_STATE" as it is waiting for the out edge to be available to accept the processed item.
  • blocking (bool, default: True ) –

    If True, the source waits until it can put an item into the out edge. If False, it discards the item if the out edge is full and cannot accept the item that is being pushed by the machine.

  • work_capacity (int, default: 1 ) –

    Maximum no. of processing that can be performed simultaneously.1 worker thread can process one item.

  • processing_delay ((None, int, float, Generator, Callable), default: 0 ) –

    Delay for processing items. Can be:

    • None: Used when the processing time depends on parameters of the node object (like current state of the object) or environment.
    • int or float: Used as a constant delay.
    • Generator: A generator function yielding delay values over time.
    • Callable: A function that returns a delay (int or float).
  • in_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the edge. Options include "RANDOM", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: None: Used when edge selction depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random edge.
      • "ROUND_ROBIN": Selects edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can give an item.
    • callable: A function that returns an edge index.
  • out_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the out edge. Options include "RANDOM", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: None: Used when out edge selction depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random out edge in the out_edges list.
      • "ROUND_ROBIN": Selects out edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can accept an item.
    • callable: A function that returns an edge index.
Behavior

The machine node represents components that process or modify the items that flow in the simulation model. It can have multiple incoming edges and multiple outgoing edge. Edge from which the item comes in and the edge to which processed item is pushed is decided using the method specified in the parameter in_edge_selection and out_edge_selection. Machine will transition through the states- SETUP_STATE, PROCESSING_STATE, IDLE_STATE and BLOCKED_STATE. The machine has a blocking behavior if blocking=True and gets blocked when all its worker threads have processed items and the out edge is full and cannot accept the item that is being pushed by the machine and waits until the out edge can accept the item. If blocking=False, the machine will discard the item if the out edge is full and cannot accept the item that is being pushed by the machine.

Raises:
  • AssertionError

    If the Machine has no input or output edges.

Output performance metrics: The key performance metrics of the Machine node is captured in stats attribute (dict) during a simulation run.

last_state_change_time    : Time when the state was last changed.
num_item_processed        : Total number of items generated.
num_item_discarded        : Total number of items discarded.
total_time_spent_in_states: Dictionary with total time spent in each state.
Source code in src/factorysimpy/nodes/machine.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def __init__(self, env, id, in_edges=None, out_edges=None,node_setup_time=0, work_capacity=1,processing_delay=0,blocking=True,in_edge_selection="FIRST_AVAILABLE",out_edge_selection="FIRST_AVAILABLE"):
    super().__init__(env, id,in_edges, out_edges, node_setup_time)


    self.work_capacity = work_capacity
    self.in_edge_selection = in_edge_selection
    self.out_edge_selection = out_edge_selection
    self.state_rep= None
    self.blocking = blocking
    self.per_thread_total_time_in_blocked_state = 0.0
    self.per_thread_total_time_in_processing_state = 0.0

    self.worker_thread_list = []  # List to keep track of worker threads

    self.item_in_process= None
    self.num_workers = 0  # Number of worker threads currently processing
    self.time_last_occupancy_change = 0  # Time when the occupancy was last changed
    self.worker_thread = simpy.Resource(env, capacity=self.work_capacity)  # Resource for worker threads
    self.time_per_work_occupancy = [0.0 for _ in range(work_capacity+1)]  # Time spent by each worker thread
    #self.stats={"total_time_spent_in_states": {"SETUP_STATE": 0.0, "IDLE_STATE":0.0, "PROCESSING_STATE": 0.0,"BLOCKED_STATE":0.0 },
     #           "last_state_change_time": None, "num_item_processed": 0, "num_item_discarded": 0,"processing_delay":[], "in_edge_selection":[],"out_edge_selection":[]}
    self.total_time_all_blocked = 0.0
    self.total_time_all_processing = 0.0
    self.total_time_atleast_one_blocked = 0.0
    self.total_time_atleast_one_processing = 0.0

    self.total_time_idle = 0.0
    self.total_time_setup = 0.0

    self.stats={"total_time_spent_in_states": {"SETUP_STATE": 0.0, "IDLE_STATE":0.0, "ATLEAST_ONE_PROCESSING_STATE": 0.0,  "ALL_ACTIVE_BLOCKED_STATE":0.0, "ALL_ACTIVE_PROCESSING_STATE":0.0 ,"ATLEAST_ONE_BLOCKED_STATE":0.0 },
                "last_state_change_time": None, "num_item_processed": 0, "num_item_discarded": 0,"processing_delay":[], "in_edge_selection":[],"out_edge_selection":[]}






    # Initialize processing delay 
    # If processing_delay is a generator, callable, int, float or None, it is accepted.

    if callable(processing_delay) or hasattr(processing_delay, '__next__') or isinstance(processing_delay, (int, float)) or processing_delay is None:
        self.processing_delay = processing_delay

    else:
        raise ValueError(
            "processing_delay must be None, int, float, generator, or callable."
        )

    self.env.process(self.behaviour())  # Start the machine behavior process
add_in_edges(edge)

Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an in_edge.

Source code in src/factorysimpy/nodes/machine.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def add_in_edges(self, edge):
    """
    Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an in_edge.
        """
    if self.in_edges is None:
        self.in_edges = []

    # if len(self.in_edges) >= self.num_in_edges:
    #     raise ValueError(f"Machine'{self.id}' already has {self.num_in_edges} in_edges. Cannot add more.")

    if edge not in self.in_edges:
        self.in_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Machine '{self.id}' in_edges.")
add_out_edges(edge)

Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an out_edge.

Source code in src/factorysimpy/nodes/machine.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def add_out_edges(self, edge):
    """
    Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an out_edge.
    """
    if self.out_edges is None:
        self.out_edges = []

    # if len(self.out_edges) >= 1:
    #     raise ValueError(f"Machine '{self.id}' already has 1 out_edge. Cannot add more.")

    if edge not in self.out_edges:
        self.out_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Machine '{self.id}' out_edges.")
update_state_rep(current_time)

Update the state representation tuple based on the current state of worker threads.

Parameters:
  • state (str) –

    The current state of the machine.

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/nodes/machine.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def update_state_rep(self, current_time):  
    """
    Update the state representation tuple based on the current state of worker threads.

    Args:
        state (str): The current state of the machine.
        current_time (float): The current simulation time.
    """
    previous_state_rep = self.state_rep


    if self.state_rep is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]
        # all idle
        if previous_state_rep == (0, 0):
            self.stats["total_time_spent_in_states"]["IDLE_STATE"] += elapsed
            self.total_time_idle += elapsed

        # all active are blocked and none processing
        if previous_state_rep[1] > 0 and previous_state_rep[0] == 0:
            self.stats["total_time_spent_in_states"]["ALL_ACTIVE_BLOCKED_STATE"] += elapsed
            self.total_time_all_blocked += elapsed
        # at least one processing 
        if previous_state_rep[0]>0  :
            self.stats["total_time_spent_in_states"]["ATLEAST_ONE_PROCESSING_STATE"] += elapsed
            self.total_time_atleast_one_processing += elapsed
        # all active are processing and none blocked
        if previous_state_rep[0]>0  and previous_state_rep[1]==0:
            self.stats["total_time_spent_in_states"]["ALL_ACTIVE_PROCESSING_STATE"] += elapsed
            self.total_time_all_processing += elapsed
        #at least one blocked
        if previous_state_rep[1] > 0:
            self.stats["total_time_spent_in_states"]["ATLEAST_ONE_BLOCKED_STATE"] += elapsed
            self.total_time_atleast_one_blocked += elapsed

        num_processing, num_blocked = self._count_worker_state()
        self.state_rep = (num_processing, num_blocked)




    self.stats["last_state_change_time"] = current_time

node

Node(env, id, in_edges=None, out_edges=None, node_setup_time=0)

Base class to represent an active entity in a manufacturing system, such as machines, splits, or joints.

Parameters:
  • id (str) –

    Identifier for the node.

  • node_setup_time (int, or float, default: 0 ) –

    Initial setup time for the node. Can be:

    • int or float: Used as a constant delay.
  • in_edges (list, default: None ) –

    List of input edges connected to the node.

  • out_edges (list, default: None ) –

    List of output edges connected to the node.

Raises:
  • TypeError

    If the type of env or id is incorrect.

  • ValueError

    If node_setup_time input is invalid.

Source code in src/factorysimpy/nodes/node.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __init__(self,env,id, in_edges = None, out_edges = None, node_setup_time= 0):

    # Type checks
    if not isinstance(env, simpy.Environment):
        raise TypeError("env must be a simpy.Environment instance")
    if not isinstance(id, str):
        raise TypeError("name must be a string")
    self.env = env
    self.id = id # Identifier for the node.
    self.node_setup_time = node_setup_time # Time taken to set up the node.
    self.in_edges = in_edges # List of input edges connected to the node.
    self.out_edges = out_edges #List of output edges connected to the node.


    if isinstance(node_setup_time, (int, float)):
        self.node_setup_time = node_setup_time
    else:
        raise ValueError(
            "Invalid node_setup_time value. Provide constant ( int or float)."
        )
get_delay(delay)

Returns value based on the type of parameter delay provided.

Parameters:
  • delay (int, float, generator, or callable) –

    The delay time, which can be:

    • int or float: Used as a constant delay.
    • generator: A generator instance yielding delay values.
    • callable: A function that returns a delay values.
Returns:
  • Returns a constant delay if delay is an int or float, a value yielded if delay is a generator, or the value returned from a Callable function if delay is callable.

Source code in src/factorysimpy/nodes/node.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def get_delay(self,delay):
    """
    Returns value based on the type of parameter `delay` provided.

    Args:
         delay (int, float, generator, or callable): The delay time, which can be:

            - int or float: Used as a constant delay.
            - generator: A generator instance yielding delay values.
            - callable: A function that returns a delay values.

    Returns:
           Returns a constant delay if `delay` is an int or float, a value yielded  if `delay` is a generator, or the value returned from a Callable function if `delay` is callable.
    """
    if hasattr(delay, '__next__'):
        # Generator instance
        val = next(delay)
    elif callable(delay):
        # Function
        val = delay()
    else:
        # int or float
        val = delay
    #print(self.id)
    assert val >= 0, f"{self.id}- Delay must be non-negative"
    return val
update_state(new_state, current_time)

Update node state and track the time spent in the previous state.

Parameters:
  • new_state (str) –

    The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/nodes/node.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def update_state(self, new_state: str, current_time: float):
    """
    Update node state and track the time spent in the previous state.

    Args:
        new_state (str): The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".
        current_time (float): The current simulation time.

    """

    if self.state is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]

        self.stats["total_time_spent_in_states"][self.state] = (
            self.stats["total_time_spent_in_states"].get(self.state, 0.0) + elapsed
        )
    self.state = new_state
    self.stats["last_state_change_time"] = current_time

sink

Sink(env, id, in_edges=None, node_setup_time=0)

Bases: Node

A Sink is a terminal node that collects flow items at the end. Once an item enters the Sink, it is considered to have exited the system and cannot be retrieved or processed further This sink can have multiple input edges and no output edges.

Raises

AssertionError: If the sink does not have at least 1 input edge or has an output edge.

Source code in src/factorysimpy/nodes/sink.py
23
24
25
26
27
28
29
30
31
32
33
def __init__(self, env, id,in_edges=None,  node_setup_time=0):

      super().__init__( env, id, in_edges, None,   node_setup_time)
      self.state = "COLLECTING_STATE"
      self.in_edge_events=[]
      self.stats={"num_item_received": 0, "last_state_change_time":0.0, "total_time_spent_in_states":{"COLLECTING_STATE":0.0}, "total_cycle_time":0.0}
      self.item_in_process = None
      self.buffertime=0
      # Start behavior process
      self.env.process(self.behaviour())
      self.item_list={}

source

Source(env, id, in_edges=None, out_edges=None, item_length=1, flow_item_type='item', inter_arrival_time=0, blocking=False, out_edge_selection='FIRST_AVAILABLE')

Bases: Node

Parameters:
  • state (str) –

    Current state of the source node. One of :

    • SETUP_STATE: Initial setup phase before item generation starts.
    • GENERATING_STATE: Actively generating and dispatching items.
    • BLOCKED_STATE: Waiting to transfer item when edge is full (in blocking mode).
  • inter_arrival_time (None, int, float, generator, or callable, default: 0 ) –

    Time between item generations. Can be:

    • None: Used when the setup time depends on parameters of the node object (like current state of the object) or environment.
    • int or float: Used as a constant delay.
    • Callable: A function that returns a delay (int or float).
    • Generator: A generator function yielding delay values over time.
  • flow_item_type (str, default: 'item' ) –

    Type of item to be generated. Default is "item". Can be

    • "item" : Smallest unit of discrete item and it cannot hold other items inside.
    • "pallet" : Entities that can store multiple smaller units of items
  • blocking (bool, default: False ) –

    If True, the source waits until it can put an item into the out edge.

  • out_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the out edge. Options include "RANDOM", "FIRST", "LAST", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: Used when out edge selection depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random out edge.
      • "ROUND_ROBIN": Selects out edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can accept an item.
    • callable: A function that returns an edge index.

Behavior:

The Source node is responsible for generating items that flow in the simulation model. It operates in two modes: blocking and non-blocking.

when blocking=True: - After each inter_arrival_time, the source generates an item. - If the selected out edge is full, the source waits until space is available. - Once space is available, the item is transferred to the selected edge. - inter_arrival_time must not be None.

when blocking=False: - After each inter_arrival_time, the source generates an item. - If the selected out edge is full, the item is discarded immediately. - If space is available, the item is transferred without waiting. - inter_arrival_time must not be 0.

Raises:
  • ValueError

    If inter_arrival_time is 0 in non-blocking mode or if out_edge_selection is not a valid type.

  • ValueError

    If out_edge_selection is not a string or callable.

  • ValueError

    If out_edges is not provided or has less than one edge.

  • ValueError

    If in_edges is provided, as Source nodes should not have input edges.

  • ValueError

    If out_edges already has an edge when trying to add a new one.

Output performance metrics: The key performance metrics of the Source node is captured in stats attribute (dict) during a simulation run.

last_state_change_time    : Time when the state was last changed.
num_item_generated        : Total number of items generated.
num_item_discarded        : Total number of items discarded due to lack of space in out edge.
total_time_spent_in_states: Dictionary with total time spent in each state.
Source code in src/factorysimpy/nodes/source.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def __init__(self, env, id, in_edges=None, out_edges=None, item_length=1, flow_item_type = "item", inter_arrival_time=0, blocking=False, out_edge_selection="FIRST_AVAILABLE" ):
    super().__init__( env, id,in_edges , out_edges )

    self.state = "SETUP_STATE" # Initial state of the source node
    self.blocking = blocking
    self.item_length = item_length
    self.out_edge_selection = out_edge_selection  # Selection strategy for out edges
    self.flow_item_type = flow_item_type # Type of item to be generated, default is "item"
    self.stats = {
        "last_state_change_time": None,
        "num_item_generated": 0,
        "num_item_discarded": 0,
        "total_time_spent_in_states":{"SETUP_STATE": 0.0, "GENERATING_STATE": 0.0, "BLOCKED_STATE": 0.0}
    }





    if inter_arrival_time == 0 and not self.blocking:
        raise ValueError("Non-blocking source must have a non-zero inter_arrival_time.")
    elif callable(inter_arrival_time):
        self.inter_arrival_time = inter_arrival_time  
    elif hasattr(inter_arrival_time, '__next__'):
        self.inter_arrival_time = inter_arrival_time    
    elif isinstance(inter_arrival_time, (int, float)):      
        self.inter_arrival_time = inter_arrival_time
    # interarrival_time is None and will be initialized later by the user
    elif inter_arrival_time is None:
        self.inter_arrival_time = inter_arrival_time
    else:
        #print("GGG",inter_arrival_time)
        raise ValueError("inter_arrival_time must be a None, int, float, generator, or callable.")
     # Start behavior process
    self.env.process(self.behaviour())
add_out_edges(edge)

Adds an out_edge to the source node. Raises an error if the source already has an out_edge or if the edge already exists in the out_edges list.

Parameters:
  • edge (Edge Object) –

    The edge to be added as an out_edge.

Source code in src/factorysimpy/nodes/source.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def add_out_edges(self, edge):
    """
    Adds an out_edge to the source node. Raises an error if the source already has an 
    out_edge or if the edge already exists in the out_edges list.

    Args:
        edge (Edge Object): The edge to be added as an out_edge.
    """
    if self.out_edges is None:
        self.out_edges = []

    if len(self.out_edges) >= 1:
        raise ValueError(f"Source '{self.id}' already has 1 out_edge. Cannot add more.")

    if edge not in self.out_edges:
        self.out_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Source '{self.id}' out_edges.")
update_state(new_state, current_time)

Update node state and track the time spent in the previous state.

Parameters:
  • i (int) –

    The index of the worker thread to update the state for.

  • new_state (str) –

    The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/nodes/source.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def update_state(self, new_state: str, current_time: float):
    """
    Update node state and track the time spent in the previous state.

    Args:
        i (int): The index of the worker thread to update the state for.
        new_state (str): The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".
        current_time (float): The current simulation time.

    """

    if self.state is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]

        self.stats["total_time_spent_in_states"][self.state] = (
            self.stats["total_time_spent_in_states"].get(self.state, 0.0) + elapsed
        )
    self.state = new_state
    self.stats["last_state_change_time"] = current_time

splitter

Splitter(env, id, in_edges=None, out_edges=None, node_setup_time=0, processing_delay=0, blocking=True, mode='UNPACK', split_quantity=None, in_edge_selection='FIRST_AVAILABLE', out_edge_selection='FIRST_AVAILABLE')

Bases: Node

Splitter class representing a processing node that can unpack an incoming item and send it to multiple outgoing edges in a factory simulation.

Parameters:
  • state (str) –

    Current state of the splitter node. One of :

    • SETUP_STATE: Initial setup phase before splitter starts to operate.
    • IDLE_STATE: Worker threads waiting to receive items.
    • PROCESSING_STATE: Actively processing items.
    • BLOCKED_STATE: When all the worker threads are waiting to push the processed item but the out going edge is full.
  • blocking (bool, default: True ) –

    If True, the node waits until it can put an item into the out edge. If False, it discards the item if the out edge is full and cannot accept the item that is being pushed by the splitter.

  • mode (str, default: 'UNPACK' ) –

    Mode of operation of the splitter. Either "UNPACK" or "SPLIT". - "UNPACK": The splitter unpacks all items from a packed item (like a pallet) and pushes each item to an output edge, one by one. After all items are pushed, the empty container itself is pushed to an output edge. - "SPLIT": The splitter splits the incoming item into a target quantity of items, specified by split_quantity and pushes each item to an output edge, one by one.

  • split_quantity (int, default: None ) –

    Target quantity of items to split the input flow item into. This parameter is required if mode is "SPLIT". If mode is "UNPACK", this parameter is ignored.

  • processing_delay ((None, int, float, Generator, Callable), default: 0 ) –

    Delay for processing items. Can be:

    • None: Used when the processing time depends on parameters of the node object (like current state of the object) or environment.
    • int or float: Used as a constant delay.
    • Generator: A generator function yielding delay values over time.
    • Callable: A function that returns a delay (int or float).
  • in_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the edge. Options include "RANDOM", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: Used when edge selction depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random edge.
      • "ROUND_ROBIN": Selects edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can give an item.
    • callable: A function that returns an edge index.
  • out_edge_selection (None or str or callable, default: 'FIRST_AVAILABLE' ) –

    Criterion or function for selecting the out edge. Options include "RANDOM", "ROUND_ROBIN", "FIRST_AVAILABLE".

    • None: None: Used when out edge selction depends on parameters of the node object (like current state of the object) or environment.
    • str: A string that specifies the selection method.
      • "RANDOM": Selects a random out edge in the out_edges list.
      • "ROUND_ROBIN": Selects out edges in a round-robin manner.
      • "FIRST_AVAILABLE": Selects the first out edge that can accept an item.
    • callable: A function that returns an edge index.
Behavior

The Splitter node represents components that process or modify the items that flow in the simulation model. It can have multiple incoming edges and multiple outgoing edge. Edge from which the item comes in and the edge to which processed item is pushed is decided using the method specified in the parameter in_edge_selection and out_edge_selection. Splitter will transition through the states- SETUP_STATE, PROCESSING_STATE, IDLE_STATE and BLOCKED_STATE. The Splitter has a blocking behavior if blocking=True and gets blocked when all its worker threads have processed items and the out edge is full and cannot accept the item that is being pushed by the Splitter and waits until the out edge can accept the item. If blocking=False, the Splitter will discard the item if the out edge is full and cannot accept the item that is being pushed by the Splitter.

Raises:
  • AssertionError

    If the Splitter has no input or output edges.

Output performance metrics: The key performance metrics of the Splitter node is captured in stats attribute (dict) during a simulation run.

last_state_change_time    : Time when the state was last changed.
num_item_processed        : Total number of items generated.
num_item_discarded        : Total number of items discarded.
total_time_spent_in_states: Dictionary with total time spent in each state.
Source code in src/factorysimpy/nodes/splitter.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(self, env, id, in_edges=None, out_edges=None,node_setup_time=0,processing_delay=0,blocking=True,mode= "UNPACK", split_quantity=None, in_edge_selection="FIRST_AVAILABLE",out_edge_selection="FIRST_AVAILABLE"):
    super().__init__(env, id,in_edges, out_edges, node_setup_time)

    self.state = "SETUP_STATE"  # Initial state of the Splitter
    self.work_capacity = 1
    self.in_edge_selection = in_edge_selection
    self.out_edge_selection = out_edge_selection
    self.blocking = blocking
    self.mode = mode
    self.split_quantity = split_quantity
    self.per_thread_total_time_in_blocked_state = 0.0
    self.per_thread_total_time_in_processing_state = 0.0

    self.worker_thread_list = []  # List to keep track of worker threads

    self.item_in_process= None
    self.pallet_in_process= None
    self.num_workers = 0  # Number of worker threads currently processing
    self.time_last_occupancy_change = 0  # Time when the occupancy was last changed
    self.worker_thread = simpy.Resource(env, capacity=self.work_capacity)  # Resource for worker threads
    self.time_per_work_occupancy = [0.0 for _ in range(self.work_capacity+1)]  # Time spent by each worker thread
    self.stats={"total_time_spent_in_states": {"SETUP_STATE": 0.0, "IDLE_STATE":0.0, "PROCESSING_STATE": 0.0,"BLOCKED_STATE":0.0 },
                "last_state_change_time": None, "num_item_processed": 0, "num_item_discarded": 0,"processing_delay":[], "in_edge_selection":[],"out_edge_selection":[]}






    # Initialize processing delay 
    # If processing_delay is a generator, callable, int, float or None, it is accepted.

    if callable(processing_delay) or hasattr(processing_delay, '__next__') or isinstance(processing_delay, (int, float)) or processing_delay is None:
        self.processing_delay = processing_delay

    else:
        raise ValueError(
            "processing_delay must be None, int, float, generator, or callable."
        )

    self.env.process(self.behaviour())  # Start the Splitter behavior process
add_in_edges(edge)

Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an in_edge.

Source code in src/factorysimpy/nodes/splitter.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def add_in_edges(self, edge):
    """
    Adds an in_edge to the node. Raises an error if the edge already exists in the in_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an in_edge.
        """
    if self.in_edges is None:
        self.in_edges = []

    # if len(self.in_edges) >= self.num_in_edges:
    #     raise ValueError(f"Splitter'{self.id}' already has {self.num_in_edges} in_edges. Cannot add more.")

    if edge not in self.in_edges:
        self.in_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Splitter '{self.id}' in_edges.")
add_out_edges(edge)

Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

Parameters:
  • edge (Edge Object)

    The edge to be added as an out_edge.

Source code in src/factorysimpy/nodes/splitter.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def add_out_edges(self, edge):
    """
    Adds an out_edge to the node. Raises an error if the edge already exists in the out_edges list.

    Args:
        edge (Edge Object) : The edge to be added as an out_edge.
    """
    if self.out_edges is None:
        self.out_edges = []

    # if len(self.out_edges) >= 1:
    #     raise ValueError(f"Splitter '{self.id}' already has 1 out_edge. Cannot add more.")

    if edge not in self.out_edges:
        self.out_edges.append(edge)
    else:
        raise ValueError(f"Edge already exists in Splitter '{self.id}' out_edges.")
update_state(new_state, current_time)

Update node state and track the time spent in the previous state.

Parameters:
  • i (int) –

    The index of the worker thread to update the state for.

  • new_state (str) –

    The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".

  • current_time (float) –

    The current simulation time.

Source code in src/factorysimpy/nodes/splitter.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def update_state(self, new_state: str, current_time: float):
    """
    Update node state and track the time spent in the previous state.

    Args:
        i (int): The index of the worker thread to update the state for.
        new_state (str): The new state to transition to. Must be one of "SETUP_STATE", "GENERATING_STATE", "BLOCKED_STATE".
        current_time (float): The current simulation time.

    """

    if self.state is not None and self.stats["last_state_change_time"] is not None:
        elapsed = current_time - self.stats["last_state_change_time"]

        self.stats["total_time_spent_in_states"][self.state] = (
            self.stats["total_time_spent_in_states"].get(self.state, 0.0) + elapsed
        )
    self.state = new_state
    self.stats["last_state_change_time"] = current_time

utils

stats_summary

compute_performance_metrics(stats, sim_time)

Compute cycle time, resource utilization, throughput, and % time spent in each state for each worker.

Parameters:
  • stats (dict) –

    The stats dictionary from the component (key: worker id, value: stats dict).

  • sim_time (float) –

    The total simulation time.

Returns:
  • dict

    A dictionary with metrics for each worker.

Source code in src/factorysimpy/utils/stats_summary.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def compute_performance_metrics(stats, sim_time):
    """
    Compute cycle time, resource utilization, throughput, and % time spent in each state for each worker.

    Args:
        stats (dict): The stats dictionary from the component (key: worker id, value: stats dict).
        sim_time (float): The total simulation time.

    Returns:
        dict: A dictionary with metrics for each worker.
    """
    results = {}
    for worker_id, s in stats.items():
        total_time = sum(s["total_time_spent_in_states"].values())
        # Defensive: If sim_time is 0, avoid division by zero
        sim_time = max(sim_time, 1e-8)
        # Utilization: % time in PROCESSING_STATE
        util = s["total_time_spent_in_states"].get("PROCESSING_STATE", 0.0) / sim_time
        # Throughput: items processed per unit time
        num_processed = s.get("num_item_processed", 0) + s.get("num_pallet_processed", 0)
        throughput = num_processed / sim_time
        # Cycle time: average time per processed item
        cycle_time = (s["total_time_spent_in_states"].get("PROCESSING_STATE", 0.0) / num_processed) if num_processed > 0 else None
        # % time in each state
        pct_time_states = {state: (t / sim_time) * 100 for state, t in s["total_time_spent_in_states"].items()}
        # Discarded
        num_discarded = s.get("num_item_discarded", 0) + s.get("num_pallet_discarded", 0)

        results[worker_id] = {
            "utilization": util,
            "throughput": throughput,
            "cycle_time": cycle_time,
            "percent_time_in_states": pct_time_states,
            "num_processed": num_processed,
            "num_discarded": num_discarded,
        }
    return results

utils

get_edge_selector(sel_type, node, env, edge_type=None)

Returns a generator that yields selected indices from the node's edge list.

Args:

sel_type (str): The selection strategy. One of: 'RANDOM', 'ROUND_ROBIN'.
node (object): The node object containing in_edges or out_edges.
env (simpy.Environment): The simulation environment.
edge_type (str, optional): Whether to select from 'out_edges' or 'in_edges'. Default is 'OUT'.

Returns:

generator: A generator yielding selected indices from the specified edge list.

Raises:

ValueError: If sel_type is not a valid selection strategy.
Source code in src/factorysimpy/utils/utils.py
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def get_edge_selector(sel_type, node, env, edge_type=None):
    """
    Returns a generator that yields selected indices from the node's edge list.

    Args:

        sel_type (str): The selection strategy. One of: 'RANDOM', 'ROUND_ROBIN'.
        node (object): The node object containing in_edges or out_edges.
        env (simpy.Environment): The simulation environment.
        edge_type (str, optional): Whether to select from 'out_edges' or 'in_edges'. Default is 'OUT'.

    Returns:

        generator: A generator yielding selected indices from the specified edge list.

    Raises:

        ValueError: If sel_type is not a valid selection strategy.

    """
    edge_type = edge_type.lower()
    assert edge_type in ["in", "out"], "edge_type must be either 'in' or 'out'."
    strategies = {
        "RANDOM": Random_edge_selector,

        "ROUND_ROBIN": RoundRobin_edge_selector,

    }

    if sel_type not in strategies:
        raise ValueError(
            f"Invalid selection type: {sel_type}. Must be one of: {', '.join(strategies.keys())}."
        )
    #print(edge_type)

    return strategies[sel_type](node, env, edge_type)