Source code for manpy.simulation.core.Machine

# ===========================================================================
# Copyright 2013 University of Limerick
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM.  If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
"""
Created on 8 Nov 2012

@author: George
"""

# from SimPy.Simulation import Process, Resource, SimEvent
# from SimPy.Simulation import activate, passivate, waituntil, now, hold, request, release, waitevent
import simpy
import numpy as np

from manpy.simulation.core.CoreObject import CoreObject

from manpy.simulation.OperatorRouter import Router
from manpy.simulation.SkilledOperatorRouter import SkilledRouter

from manpy.simulation.OperatedPoolBroker import Broker
from manpy.simulation.OperatorPool import OperatorPool

from manpy.simulation.RandomNumberGenerator import RandomNumberGenerator

from manpy.simulation.core.Globals import G


[docs] class Machine(CoreObject): """ Models a machine that can also have failures """ family = "Server" # ======================================================================= # initialise the id the capacity, of the resource and the distribution # ======================================================================= def __init__( self, id, name, capacity=1, processingTime=None, repairman="None", operatorPool="None", operationType="None", setupTime=None, loadTime=None, preemption={}, canDeliverOnInterruption=False, technology=None, priority=0, control=None, cost=0, **kw, ): self.type = "Machine" # String that shows the type of object CoreObject.__init__(self, id, name, cost) from manpy.simulation.core.Globals import G processingTime = self.getOperationTime(time=processingTime) setupTime = self.getOperationTime(time=setupTime) loadTime = self.getOperationTime(time=loadTime) # holds the capacity of the machine self.capacity = capacity # sets the repairman resource of the Machine self.repairman = repairman # Sets the attributes of the processing (and failure) time(s) self.rng = RandomNumberGenerator(self, processingTime) # check whether the operators are provided with a skills set # check whether the operators are provided with a skills set self.dedicatedOperator = self.checkForDedicatedOperators() if operatorPool and not (operatorPool == "None"): self.operatorPool = operatorPool else: if len(G.OperatorPoolsList) > 0: for ( operatorPool ) in ( G.OperatorPoolsList ): # find the operatorPool assigned to the machine if ( self.id in operatorPool.coreObjectIds ): # and add it to the machine's operatorPool machineOperatorPoolList = operatorPool # there must only one operator pool assigned to the machine, # otherwise only one of them will be taken into account else: machineOperatorPoolList = ( [] ) # if there is no operatorPool assigned to the machine else: # then machineOperatorPoolList/operatorPool is a list machineOperatorPoolList = ( [] ) # if there are no operatorsPool created then the # then machineOperatorPoolList/operatorPool is a list if ( type(machineOperatorPoolList) is list ): # if the machineOperatorPoolList is a list # find the operators assigned to it and add them to the list for ( operator ) in G.OperatorsList: # check which operator in the G.OperatorsList if ( self.id in operator.coreObjectIds ): # (if any) is assigned to operate machineOperatorPoolList.append( operator ) # the machine with ID equal to id self.operatorPool = machineOperatorPoolList self.dedicatedOperator = self.checkForDedicatedOperators() # create an operatorPool if needed self.createOperatorPool(self.operatorPool) # holds the Operator currently processing the Machine self.currentOperator = None # define if load/setup/removal/processing are performed by the operator self.operationType = operationType # boolean to check whether the machine is being operated self.toBeOperated = False # define the load times self.loadRng = RandomNumberGenerator(self, loadTime) # XX variable that informs on the need for setup self.setUp = True # define the setup times self.stpRng = RandomNumberGenerator(self, setupTime) # examine if there are multiple operation types performed by the operator # there can be Setup/Processing operationType # or the combination of both (MT-Load-Setup-Processing) self.multOperationTypeList = [] if isinstance(self.operationType, str) and self.operationType.startswith("MT"): OTlist = operationType.split("-") self.operationType = OTlist.pop(0) self.multOperationTypeList = OTlist else: self.multOperationTypeList.append(self.operationType) # technology is used to group machines that perform the same operation when needed self.technology = technology # flags used for preemption purposes self.isPreemptive = False self.resetOnPreemption = False if len(preemption) > 0: self.isPreemptive = bool(int(preemption.get("isPreemptive") or 0)) self.resetOnPreemption = bool(int(preemption.get("resetOnPreemption", 0))) # flag notifying that there is operator assigned to the actievObject self.assignedOperator = True # flag notifying the the station can deliver entities that ended their processing while interrupted self.canDeliverOnInterruption = canDeliverOnInterruption self.repairman = "None" for ( repairman ) in G.RepairmanList: # check which repairman in the G.RepairmanList if self.id in repairman.coreObjectIds: # (if any) is assigned to repair self.repairman = repairman # the machine with ID equal to id G.MachineList.append(self) # add machine to global MachineList if self.operatorPool != "None": G.OperatedMachineList.append( self ) # add the machine to the operatedMachines List # attribute to prioritize against competing parallel machines self.priority = priority self.processed_entities = [] # set a default function for control if none is defined if control == None: def condition(self): return None self.control = condition else: self.control = control # list for Entities who fail control self.discards = [] # list for finished Entities self.entities = [] # if currently operating self.operationNotFinished = False
[docs] def initialize(self): """initializes the machine""" # using the Process __init__ and not the CoreObject __init__ CoreObject.initialize(self) # initialize the internal Queue (type Resource) of the Machine self.Res = simpy.Resource(self.env, capacity=self.capacity) # initiate the Broker and the router self.createBroker() self.createRouter() # initialize the operator pool if any self.initializeOperatorPool() # initiate the Broker responsible to control the request/release self.initializeBroker() # initialise the router if not initialized already self.initializeRouter() # variables used for interruptions self.isProcessing = False # variable that shows what kind of operation is the station performing at the moment """ it can be Processing or Setup XXX: others not yet implemented """ self.currentlyPerforming = None self.tinM = 0 self.timeLastProcessingStarted = 0 self.timeLastOperationStarted = -1 self.interruption = False self.breakTime = 0 # flag notifying that there is operator assigned to the actievObject self.assignedOperator = True self.brokerIsSet = self.env.event() # this event is generated every time an operator is requested by machine for Load operation type. # if the machine has not succeeded in getting an entity due to the resource absence # and waits for the next event to get the entity, # then it must be signalled that the operator is now available self.loadOperatorAvailable = self.env.event() # signal used for preemption self.preemptQueue = self.env.event() # signal used for informing objectInterruption objects that the current entity processed has finished processing self.endedLastProcessing = self.env.event() # signal used for waiting for ObjectProperties to end self.objectPropertyEnd = self.env.event() self.expectedSignals["isRequested"] = 1 self.expectedSignals["interruptionEnd"] = 1 self.expectedSignals["loadOperatorAvailable"] = 1 self.expectedSignals["initialWIP"] = 1 # events about the availability of process operator # TODO group those operator relate events self.processOperatorUnavailable = self.env.event() # holds the Operator currently processing the Machine self.currentOperator = None
[docs] @staticmethod def getOperationTime(time): # XXX update time to comply with old definition """returns the dictionary updated""" if not time: time = {"Fixed": {"mean": 0}} if "Normal" in list(time.keys()) and time["Normal"].get("max", None) is None: time["Normal"]["max"] = float(time["Normal"]["mean"]) + 5 * float( time["Normal"]["stdev"] ) return time
[docs] def createOperatorPool(self, operatorPool): """create an operatorPool if needed""" # sets the operator resource of the Machine # check if the operatorPool is a List or a OperatorPool type Object # if it is a list then initiate a OperatorPool type object containing # the list of operators provided # if the list is empty create operator pool with empty list from manpy.simulation.core.Globals import G # XXX operatorPool is not None ? # if a list of operators is provided as argument if (type(operatorPool) is list) and len(operatorPool) > 0: id = self.id + "_OP" name = self.objName + "_operatorPool" self.operatorPool = OperatorPool(id, name, operatorsList=operatorPool) G.OperatorPoolsList.append(self.operatorPool) # if an operatorPool object is connected to the machine elif type(operatorPool) is OperatorPool: self.operatorPool = operatorPool # otherwise else: # if there are operators with skillList if self.dedicatedOperator: id = self.id + "_OP" name = self.objName + "_operatorPool" self.operatorPool = OperatorPool(id, name, operatorsList=[]) G.OperatorPoolsList.append(self.operatorPool) # otherwise create no operatorPool else: self.operatorPool = "None" # update the operatorPool coreObjects list if self.operatorPool != "None": self.operatorPool.coreObjectIds.append(self.id) self.operatorPool.coreObjects.append(self)
[docs] def createBroker(self): """create broker if needed""" # initiate the Broker and the router if self.operatorPool != "None": self.broker = Broker(operatedMachine=self)
[docs] def createRouter(self): """create router if needed""" # initiate the Broker and the router if self.operatorPool != "None": from manpy.simulation.core.Globals import G # if there is no router if not G.RouterList: # TODO if the dedicatedOperator flag is raised then create a SkilledRouter (temp) if self.dedicatedOperator: self.router = SkilledRouter() else: self.router = Router() G.RouterList[0] = self.router # otherwise set the already existing router as the machines Router else: self.router = G.RouterList[0]
[docs] def initializeOperatorPool(self): """initialise broker if needed""" # initialise the operator pool if any if self.operatorPool != "None": self.operatorPool.initialize() # if the flag dedicatedOperator is true then reset/empty the operators list of the pool if self.dedicatedOperator: self.operatorPool.operators = [] # otherwise update the coreObjectIds/coreObjects list of the operators else: for operator in self.operatorPool.operators: operator.coreObjectIds.append(self.id) operator.coreObjects.append(self)
[docs] def initializeBroker(self): """initialise broker if needed""" # initiate the Broker responsible to control the request/release if self.operatorPool != "None": self.broker.initialize() self.env.process(self.broker.run())
[docs] def initializeRouter(self): """initialise router if needed""" if self.operatorPool != "None": # initialise the router only once if not self.router.isInitialized: self.router.initialize() if not self.router.isActivated: self.env.process(self.router.run()) self.router.isActivated = True
# =========================================================================== # get the initial operationTypes (setup/processing) : manual or automatic # =========================================================================== def checkInitialOperationTypes(self): pass # =========================================================================== # get the initial operation times (setup/processing); # XXX initialy only setup time is calculated here # =========================================================================== def calculateInitialOperationTimes(self): pass
[docs] def shouldYield(self, operationTypes={}, methods={}): """method controlling if there is a need to yield""" # Method that controls if the machine should yield for releasing the operator # "methods":{'isInterrupted':'0'}, % the method isInterrupted should return False (0) or True (1) # "operationTypes"={"Processing":1, % processing must be in multiOperationTypeList # "Setup":0 % setup must not be in multiOperationTypeList # } operationsRequired = [] operationsNotRequired = [] opRequest = True if operationTypes: for operationType, func in list(operationTypes.items()): tup = (operationType, func) if bool(func): operationsRequired.append(tup) else: operationsNotRequired.append(tup) required = False # operations that should be in the multOperationTypeList if operationsRequired: for opTup in operationsRequired: operation, func = opTup required = required or ( any( type == str(operation) for type in self.multOperationTypeList ) ) else: required = True notRequired = False # operations that should NOT be in the multOperationTypeList if operationsNotRequired: for opTup in operationsNotRequired: operation, func = opTup notRequired = notRequired or ( any( type == str(operation) for type in self.multOperationTypeList ) ) opRequest = required and not notRequired methodsRequired = [] methodsNotRequired = [] methodsRequest = True if methods: for methodType, func in list(methods.items()): tup = (methodType, func) if bool(func): methodsRequired.append(tup) else: methodsNotRequired.append(tup) # methods that should return TRUE required = True from manpy.simulation.core.Globals import getMethodFromName if methodsRequired: for methodTup in methodsRequired: method, func = methodTup objMethod = getMethodFromName("manpy.core.Machine." + method) required = required and (objMethod(self)) # methods that should return FALSE notRequired = True if methodsNotRequired: for methodTup in methodsNotRequired: method, func = methodTup objMethod = getMethodFromName("manpy.core.Machine." + method) notRequired = notRequired and (objMethod(self)) else: notRequired = False methodsRequest = required and not notRequired if (self.operatorPool != "None") and opRequest and methodsRequest: return True return False
[docs] def release(self): """yielding the broker process for releasing the resource""" # after getting the entity release the operator # machine has to release the operator self.releaseOperator() # wait until the Broker has finished processing self.expectedSignals["brokerIsSet"] = 1 yield self.brokerIsSet transmitter, eventTime = self.brokerIsSet.value assert ( transmitter == self.broker ), "brokerIsSet is not sent by the stations broker" assert eventTime == self.env.now, "brokerIsSet is not received on time" self.brokerIsSet = self.env.event()
[docs] def request(self): """yielding the broker process for requesting an operator""" # when it's ready to accept (canAcceptAndIsRequested) then inform the broker # machines waits to be operated (waits for the operator) self.requestOperator() # wait until the Broker has waited times equal to loadTime (if any) self.expectedSignals["brokerIsSet"] = 1 yield self.brokerIsSet transmitter, eventTime = self.brokerIsSet.value assert ( transmitter == self.broker ), "brokerIsSet is not sent by the stations broker" assert eventTime == self.env.now, "brokerIsSet is not received on time" self.brokerIsSet = self.env.event()
[docs] def operation(self, type="Processing"): """general process, it can be processing or setup""" # assert that the type is not None and is one of the already implemented ones assert type != None, "there must be an operation type defined" assert type in set( ["Processing", "Setup"] ), "the operation type provided is not yet defined" # identify the method to get the operation time and initialise the totalOperationTime activeObjectQueue = self.Res.users if len(activeObjectQueue) > 0: self.activeEntity = activeObjectQueue[0] if type == "Setup": self.totalOperationTime = self.totalSetupTime elif type == "Processing": self.totalOperationTime = self.totalWorkingTime # if there are task_ids defined for each step if self.currentEntity.schedule[-1].get("task_id", None): # if exit Time is defined for the previous step of the schedule then add a new step for processing (the previous step is setup and is concluded) if self.currentEntity.schedule[-1].get("exitTime", None): # define a new schedule step for processing self.currentEntity.schedule.append( { "station": self, "entranceTime": self.env.now, "task_id": self.currentEntity.currentStep["task_id"], } ) # variables dedicated to hold the processing times, the time when the Entity entered, # and the processing time left # get the operation time, tinMStarts holds the processing time of the machine self.totalOperationTimeInCurrentEntity = self.calculateTime(type="Processing") # timer to hold the operation time left self.tinM = self.totalOperationTimeInCurrentEntity self.timeToEndCurrentOperation = self.env.now + self.tinM # variables used to flag any interruptions and the end of the processing self.interruption = False # local variable that is used to check whether the operation is concluded self.operationNotFinished = True # if there is a failure that depends on the working time of the Machine # send it the victimStartsProcess signal for oi in self.objectInterruptions: if oi.type == "Failure": if oi.deteriorationType == "working": if oi.expectedSignals["victimStartsProcessing"]: self.sendSignal(receiver=oi, signal=oi.victimStartsProcessing) for op in self.objectProperties: if op.expectedSignals["victimStartsProcessing"]: self.sendSignal(receiver=op, signal=op.victimStartsProcessing) # this loop is repeated until the processing time is expired with no failure # check when the processingEndedFlag switched to false while self.operationNotFinished: self.expectedSignals["interruptionStart"] = 1 self.expectedSignals["preemptQueue"] = 1 self.expectedSignals["processOperatorUnavailable"] = 1 # dummy variable to keep track of the time that the operation starts after every interruption # update timeLastOperationStarted both for Machine and Operator (if any) self.timeLastOperationStarted = self.env.now if self.currentOperator: self.currentOperator.timeLastOperationStarted = self.env.now # # if the type is setup then the time to record is timeLastProcessingStarted # if type=='Setup': # self.timeLastSetupStarted=self.timeLastOperationStarted # # else if the type is processing then the time to record is timeLastProcessingStarted # elif type=='Processing': # self.timeLastProcessingStarted=self.timeLastOperationStarted # processing starts, update the flags self.isProcessing = True self.currentlyPerforming = type # wait for the processing time left tinM, if no interruption occurs then change the processingEndedFlag and exit loop, # else (if interrupted()) set interruption flag to true (only if tinM==0), # and recalculate the processing time left tinM, passivate while waiting for repair. # if a preemption has occurred then react accordingly (proceed with getting the critical entity) receivedEvent = yield self.env.any_of( [ self.interruptionStart, self.env.timeout(self.tinM), self.preemptQueue, self.processOperatorUnavailable, ] ) # if a failure occurs while processing the machine is interrupted. if self.interruptionStart in receivedEvent: transmitter, eventTime = self.interruptionStart.value assert ( eventTime == self.env.now ), "the interruption has not been processed on the time of activation" self.interruptionStart = self.env.event() self.interruptionActions(type) # execute interruption actions # =========================================================== # # release the operator if there is interruption # =========================================================== if self.shouldYield( operationTypes={str(type): 1}, methods={"isOperated": 1} ): yield self.env.process(self.release()) # loop until we reach at a state that there is no interruption while 1: self.expectedSignals["interruptionEnd"] = 1 yield self.interruptionEnd # interruptionEnd to be triggered by ObjectInterruption transmitter, eventTime = self.interruptionEnd.value assert ( eventTime == self.env.now ), "the interruptionEnd was received later than anticipated" self.interruptionEnd = self.env.event() self.postInterruptionActions() # execute interruption actions # check if the machine is active and break if self.checkIfActive(): if self.shouldYield( operationTypes={str(type): 1}, methods={"isInterrupted": 0} ): self.timeWaitForOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForOperatorEnded = self.env.now self.operatorWaitTimeCurrentEntity += ( self.timeWaitForOperatorEnded - self.timeWaitForOperatorStarted ) break # =========================================================== # # request a resource after the repair # =========================================================== if self.shouldYield( operationTypes={str(type): 1}, methods={"isInterrupted": 0} ): self.timeWaitForOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForOperatorEnded = self.env.now self.operatorWaitTimeCurrentEntity += ( self.timeWaitForOperatorEnded - self.timeWaitForOperatorStarted ) # if the processing operator left elif self.processOperatorUnavailable in receivedEvent: transmitter, eventTime = self.processOperatorUnavailable.value assert ( self.env.now == eventTime ), "the operator leaving has not been processed at the time it should" self.processOperatorUnavailable = self.env.event() # carry interruption actions self.interruptionActions(type) # =========================================================== # # release the operator # =========================================================== self.currentOperator.totalWorkingTime += ( self.env.now - self.currentOperator.timeLastOperationStarted ) yield self.env.process(self.release()) from manpy.simulation.core.Globals import G # append the entity that was stopped to the pending ones if G.RouterList: G.pendingEntities.append(self.currentEntity) # =========================================================== # # request a resource after the interruption # =========================================================== self.timeWaitForOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForOperatorEnded = self.env.now self.operatorWaitTimeCurrentEntity += ( self.timeWaitForOperatorEnded - self.timeWaitForOperatorStarted ) # carry post interruption actions self.postInterruptionActions() # if the station is reactivated by the preempt method elif self.shouldPreempt: if self.preemptQueue in receivedEvent: transmitter, eventTime = self.preemptQueue.value assert ( eventTime == self.env.now ), "the preemption must be performed on the time of request" self.preemptQueue = self.env.event() self.interruptionActions(type) # execute interruption actions # =========================================================== # # release the operator if there is interruption # =========================================================== if self.shouldYield( operationTypes={str(self.currentlyPerforming): 1}, methods={"isOperated": 1}, ): yield self.env.process(self.release()) self.postInterruptionActions() # execute interruption actions break # if no interruption occurred the processing in M1 is ended else: if self.processOperatorUnavailable.triggered: self.processOperatorUnavailable = self.env.event() self.operationNotFinished = False
[docs] def run(self): """the main process of the machine""" # request for allocation if needed from manpy.simulation.core.Globals import G self.initialAllocationRequest() # execute all through simulation time while 1: # waitEvent isRequested /interruptionEnd/loadOperatorAvailable while 1: self.printTrace(self.id, waitEvent="") self.expectedSignals["isRequested"] = 1 self.expectedSignals["interruptionEnd"] = 1 self.expectedSignals["loadOperatorAvailable"] = 1 self.expectedSignals["initialWIP"] = 1 receivedEvent = yield self.env.any_of( [ self.isRequested, self.interruptionEnd, self.loadOperatorAvailable, self.initialWIP, ] ) self.printTrace(self.id, received="") # if the machine can accept an entity and one predecessor requests it continue with receiving the entity if self.isRequested in receivedEvent: transmitter, eventTime = self.isRequested.value self.printTrace(self.id, isRequested=transmitter.id) assert eventTime <= self.env.now, ( f"isRequested was triggered earlier, not now. Event time: {eventTime}, Now: {self.env.now}, " f"Id: {self.id}, transmitter: {transmitter.id}" ) assert ( transmitter == self.giver ), "the giver is not the requestingObject" assert ( self.giver.receiver == self ), "the receiver of the signalling object in not the station" # reset the signalparam of the isRequested event self.isRequested = self.env.event() break # if an interruption caused the control to be taken by the machine or # if an operator was rendered available while it was needed by the machine to proceed with getEntity if ( self.interruptionEnd in receivedEvent or self.loadOperatorAvailable in receivedEvent ): if self.interruptionEnd in receivedEvent: transmitter, eventTime = self.interruptionEnd.value assert ( eventTime == self.env.now ), "interruptionEnd received later than created" self.printTrace(self.id, interruptionEnd=str(eventTime)) self.interruptionEnd = self.env.event() # try to signal the Giver, otherwise wait until it is requested if self.signalGiver(): # XXX cleaner implementation needed # if there is skilled router the giver should also check if G.RouterList: if "Skilled" in str(G.RouterList[0].__class__): continue break if self.loadOperatorAvailable in receivedEvent: transmitter, eventTime = self.loadOperatorAvailable.value assert ( eventTime == self.env.now ), "loadOperatorAvailable received later than created" self.printTrace(self.id, loadOperatorAvailable=str(eventTime)) self.loadOperatorAvailable = self.env.event() # try to signal the Giver, otherwise wait until it is requested if self.signalGiver(): # XXX cleaner implementation needed # if there is router that is not skilled break if G.RouterList: if not "Skilled" in str(G.RouterList[0].__class__): break # else continue, the giver should also check continue if self.initialWIP in receivedEvent: transmitter, eventTime = self.initialWIP.value assert ( transmitter == self.env ), "initialWIP was not sent by the Environment" assert ( eventTime == self.env.now ), "initialWIP was not received on time" self.initialWIP = self.env.event() self.isProcessingInitialWIP = True if not self.checkIfActive(): continue break if self.isProcessingInitialWIP: break # TODO: maybe here have to assigneExit of the giver and add self to operator activeCallers list # here or in the getEntity (apart from the loadTimeCurrentEntity) # in case they are placed inside the getEntity then the initialize of # the corresponding variables should be moved to the initialize() of the CoreObject self.operatorWaitTimeCurrentEntity = 0 self.loadOperatorWaitTimeCurrentEntity = 0 self.loadTimeCurrentEntity = 0 self.setupTimeCurrentEntity = 0 # =================================================================== # # request a resource if there is a need for load operation # =================================================================== if ( self.shouldYield(operationTypes={"Load": 1}) and not self.isProcessingInitialWIP ): self.timeWaitForLoadOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForLoadOperatorEnded = self.env.now self.loadOperatorWaitTimeCurrentEntity += ( self.timeWaitForLoadOperatorEnded - self.timeWaitForLoadOperatorStarted ) self.totalTimeWaitingForLoadOperator += ( self.loadOperatorWaitTimeCurrentEntity ) # =================================================================== # =================================================================== # =================================================================== # # # loading # =================================================================== # =================================================================== # =================================================================== # ======= Load the machine if the Load is defined as one of the Operators' operation types if ( any(type == "Load" for type in self.multOperationTypeList) and self.isOperated() ): self.timeLoadStarted = self.env.now yield self.env.timeout(self.calculateTime(type="Load")) # TODO: if self.interrupted(): There is the issue of failure during the Loading self.timeLoadEnded = self.env.now self.loadTimeCurrentEntity = self.timeLoadEnded - self.timeLoadStarted self.totalLoadTime += self.loadTimeCurrentEntity # =================================================================== # =================================================================== # =================================================================== # # # end loading # =================================================================== # =================================================================== # =================================================================== # =================================================================== # =================================================================== # =================================================================== # # # getting entity # =================================================================== # =================================================================== # =================================================================== # get the entity # TODO: if there was loading time then we must solve the problem of getting an entity # from an unidentified giver or not getting an entity at all as the giver # may fall in failure mode (assignExit()?) if ( not self.isProcessingInitialWIP ): # if we are in the state of having initial wip no need to take an Entity self.currentEntity = self.getEntity() else: # find out if the initialWIP requires manual operations (manual/setup) self.checkInitialOperationTypes() # calculate initial processing and setup times self.calculateInitialOperationTimes() # TODO: the Machine receive the entity after the operator is available # the canAcceptAndIsRequested method checks only in case of Load type of operation # =================================================================== # # release a resource if the only operation type is Load # =================================================================== if self.shouldYield( operationTypes={"Load": 1, "Processing": 0, "Setup": 0}, methods={"isOperated": 1}, ): yield self.env.process(self.release()) # =================================================================== # # request a resource if it is not already assigned an Operator # =================================================================== if self.shouldYield( operationTypes={"Setup": 1, "Processing": 1}, methods={"isOperated": 0} ): self.timeWaitForOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForOperatorEnded = self.env.now self.operatorWaitTimeCurrentEntity += ( self.timeWaitForOperatorEnded - self.timeWaitForOperatorStarted ) # =================================================================== # =================================================================== # =================================================================== # # # setup # =================================================================== # =================================================================== # =================================================================== # if the distribution is Fixed and the mean is zero then yield not if not (eval(self.stpRng.mean) == 0 and self.stpRng.distributionType == "Fixed"): yield self.env.process(self.operation(type="Setup")) self.endOperationActions(type="Setup") # =================================================================== # =================================================================== # =================================================================== # # # end setup # =================================================================== # =================================================================== # =================================================================== # =================================================================== # # release a resource at the end of setup # =================================================================== if self.shouldYield( operationTypes={"Setup": 1, "Load": 1, "Processing": 0}, methods={"isOperated": 1}, ): yield self.env.process(self.release()) elif self.currentOperator: if self.currentOperator.skillDict: if not self.id in self.currentOperator.skillDict["process"].get( "stationIdList", [] ): yield self.env.process(self.release()) # =================================================================== # # request a resource if it is not already assigned an Operator # =================================================================== if self.shouldYield( operationTypes={"Processing": 1}, methods={"isOperated": 0} ): self.timeWaitForOperatorStarted = self.env.now yield self.env.process(self.request()) self.timeWaitForOperatorEnded = self.env.now self.operatorWaitTimeCurrentEntity += ( self.timeWaitForOperatorEnded - self.timeWaitForOperatorStarted ) # =================================================================== # =================================================================== # =================================================================== # # # processing # =================================================================== # =================================================================== # =================================================================== yield self.env.process(self.operation(type="Processing")) self.endOperationActions(type="Processing") # wait for the Feature to commence if self.objectProperties: self.expectedSignals["objectPropertyEnd"] = 1 yield self.objectPropertyEnd self.objectPropertyEnd = self.env.event() # Control and end of processing activeObjectQueue = self.Res.users if self.control(self) == True: if len(activeObjectQueue) > 0: self.activeEntity.result = "Fail" self.removeEntity(self.activeEntity) self.discards.append(self.activeEntity) # blocking starts self.isBlocked = True self.timeLastBlockageStarted = self.env.now from manpy.simulation.core.Globals import G # update the variables keeping track of Entity related attributes of the machine self.timeLastEntityEnded = self.env.now self.nameLastEntityEnded = ( self.currentEntity.name ) # this holds the name of the last entity that ended processing in Machine self.completedJobs += 1 # Machine completed one more Job# it will be used self.isProcessingInitialWIP = False # if there is a failure that depends on the working time of the Machine if self.isWorkingOnTheLast: # for the scheduled Object interruptions # XXX add the SkilledOperatorRouter to this list and perform the signalling only once for interruption in G.ObjectInterruptionList: # if the objectInterruption is waiting for a a signal if ( interruption.victim == self and interruption.expectedSignals["endedLastProcessing"] ): self.sendSignal(receiver=self, signal=self.endedLastProcessing) interruption.waitingSignal = False self.isWorkingOnTheLast = False # set timeLastShiftEnded attribute so that if it is overtime working it is not counted as off-shift time if self.interruptedBy == "ShiftScheduler": self.timeLastShiftEnded = self.env.now else: if len(activeObjectQueue) > 0: self.activeEntity.result = "Success" # blocking starts self.isBlocked = True self.timeLastBlockageStarted = self.env.now if len(activeObjectQueue) > 0: self.printTrace(self.getActiveObjectQueue()[0].name, processEnd=self.objName) else: self.printTrace(None, processEnd=self.objName) # output to trace that the processing in the Machine self.objName ended try: self.outputTrace( activeObjectQueue[0].name, activeObjectQueue[0].id, "Finished processing on " + str(self.id), ) # send Data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": activeObjectQueue[0].id + " finished processing"} ) G.db.commit() except IndexError: pass from manpy.simulation.core.Globals import G if G.RouterList: # the just processed entity is added to the list of entities # pending for the next processing G.pendingEntities.append(activeObjectQueue[0]) # set the variable that flags an Entity is ready to be disposed self.waitToDispose = True # update the variables keeping track of Entity related attributes of the machine self.timeLastEntityEnded = ( self.env.now ) # this holds the time that the last entity ended processing in Machine self.nameLastEntityEnded = ( self.currentEntity.name ) # this holds the name of the last entity that ended processing in Machine self.completedJobs += 1 # Machine completed one more Job# it will be used self.isProcessingInitialWIP = False # if there is a failure that depends on the working time of the Machine # in case Machine just performed the last work before the scheduled maintenance signal the corresponding object if self.isWorkingOnTheLast: # for the scheduled Object interruptions # XXX add the SkilledOperatorRouter to this list and perform the signalling only once for interruption in G.ObjectInterruptionList: # if the objectInterruption is waiting for a a signal if ( interruption.victim == self and interruption.expectedSignals["endedLastProcessing"] ): self.sendSignal(receiver=self, signal=self.endedLastProcessing) interruption.waitingSignal = False self.isWorkingOnTheLast = False # set timeLastShiftEnded attribute so that if it is overtime working it is not counted as off-shift time if self.interruptedBy == "ShiftScheduler": self.timeLastShiftEnded = self.env.now # =================================================================== # =================================================================== # =================================================================== # # # end of processing # =================================================================== # =================================================================== # =================================================================== # =================================================================== # # release resource after the end of processing # =================================================================== if self.shouldYield( operationTypes={"Processing": 1}, methods={"isInterrupted": 0, "isOperated": 1}, ): yield self.env.process(self.release()) # =================================================================== # =================================================================== # =================================================================== # # # disposing if not already emptied # =================================================================== # =================================================================== # =================================================================== # signal the receiver that the activeObject has something to dispose of if not self.signalReceiver(): # if there was no available receiver, get into blocking control while 1: if not len(self.getActiveObjectQueue()): break self.expectedSignals["interruptionStart"] = 1 self.expectedSignals["canDispose"] = 1 self.expectedSignals["entityRemoved"] = 1 self.timeLastBlockageStarted = self.env.now # blockage is starting # wait the event canDispose, this means that the station can deliver the item to successor self.printTrace( self.id, waitEvent="(canDispose or interruption start)" ) receivedEvent = yield self.env.any_of( [self.canDispose, self.interruptionStart, self.entityRemoved] ) # if there was interruption # TODO not good implementation if self.interruptionStart in receivedEvent: transmitter, eventTime = self.interruptionStart.value assert ( eventTime == self.env.now ), "the interruption has not been processed on the time of activation" self.interruptionStart = self.env.event() # wait for the end of the interruption self.interruptionActions() # execute interruption actions # loop until we reach at a state that there is no interruption while 1: self.expectedSignals["interruptionEnd"] = 1 if not self.canDeliverOnInterruption: receivedEvent = (yield self.interruptionEnd) # interruptionEnd to be triggered by ObjectInterruption # if the object canDeliverOnInterruption then it has to wait also for canDispose else: self.expectedSignals["canDispose"] = 1 receivedEvent = yield self.env.any_of( [self.canDispose, self.interruptionEnd] ) # if we have interruption end if (self.interruptionEnd in receivedEvent) or ( not self.canDeliverOnInterruption ): transmitter, eventTime = self.interruptionEnd.value assert ( eventTime == self.env.now ), "the victim of the failure is not the object that received it" self.interruptionEnd = self.env.event() # if there is no other interruption if self.checkIfActive(): # Machine is back to blocked state self.isBlocked = True break # else signalReceiver and continue elif ( self.canDispose in receivedEvent ) and self.canDeliverOnInterruption: transmitter, eventTime = self.canDispose.value if eventTime != self.env.now: self.canDispose = self.env.event() continue assert ( eventTime == self.env.now ), "canDispose signal is late" self.canDispose = self.env.event() self.signalReceiver() continue self.postInterruptionActions() if self.signalReceiver(): self.timeLastBlockageStarted = self.env.now break else: continue if self.canDispose in receivedEvent: transmitter, eventTime = self.canDispose.value if eventTime != self.env.now: self.canDispose = self.env.event() continue assert eventTime == self.env.now, "canDispose signal is late" self.canDispose = self.env.event() # try to signal a receiver, if successful then proceed to get an other entity if self.signalReceiver(transmitter): break if self.entityRemoved in receivedEvent: transmitter, eventTime = self.entityRemoved.value self.printTrace(self.id, entityRemoved=eventTime) assert ( eventTime == self.env.now ), "entityRemoved event activated earlier than received" self.waitEntityRemoval = False self.entityRemoved = self.env.event() # if while waiting (for a canDispose event) became free as the machines that follows emptied it, then proceed if not self.haveToDispose(): break
[docs] def endOperationActions(self, type): """actions to be performed after an operation (setup or processing)""" from manpy.simulation.core.Globals import G activeObjectQueue = self.Res.users if len(activeObjectQueue) > 0: self.activeEntity = activeObjectQueue[0] # set isProcessing to False self.isProcessing = False # the machine is currently performing no operation self.currentlyPerforming = None # add working time self.totalOperationTime += self.env.now - self.timeLastOperationStarted if type == "Processing": self.totalWorkingTime = self.totalOperationTime elif type == "Setup": self.totalSetupTime = self.totalOperationTime # if there are task_ids defined for each step if len(activeObjectQueue) > 0: if self.activeEntity.schedule[-1].get("task_id", None): # if the setup is finished then record an exit time for the setup self.activeEntity.schedule[-1]["exitTime"] = self.env.now # reseting variables used by operation() process self.totalOperationTime = None self.timeLastOperationStarted = 0 # reseting flags self.shouldPreempt = False # reset the variables used to handle the interruptions timing self.breakTime = 0 # update totalWorking time for operator and also print trace if self.currentOperator: operator = self.currentOperator self.outputTrace(operator.name, operator.id, "ended a process in " + self.objName) # send data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": operator.id + " ended a process in " + self.objName} ) G.db.commit() operator.totalWorkingTime += ( self.env.now - operator.timeLastOperationStarted ) # if the station has just concluded a processing turn then if type == "Processing": for oi in self.objectInterruptions: if oi.type == "Failure": if oi.deteriorationType == "working": if oi.expectedSignals["victimEndsProcessing"]: self.sendSignal(receiver=oi, signal=oi.victimEndsProcessing) for op in self.objectProperties: if op.expectedSignals["victimEndsProcessing"]: self.sendSignal(receiver=op, signal=op.victimEndsProcessing) if self.activeEntity not in self.discards: self.entities.append(self.activeEntity)
[docs] def interruptionActions(self, type="Processing"): """actions to be carried out when the processing of an Entity ends""" # if object was processing add the working time # only if object is not preempting though # in case of preemption endProcessingActions will be called for oi in self.objectInterruptions: if oi.type == "Feature": if oi.deteriorationType == "working": if oi.expectedSignals["victimIsInterrupted"]: # print(f"{self.name} Sending victimIsInterrupted to {oi.name}") self.sendSignal(receiver=oi, signal=oi.victimIsInterrupted) for op in self.objectProperties: if op.expectedSignals["victimIsInterrupted"]: self.sendSignal(receiver=op, signal=op.victimIsInterrupted) if self.isProcessing and not self.shouldPreempt: self.totalOperationTime += self.env.now - self.timeLastOperationStarted if type == "Processing": self.totalWorkingTime = self.totalOperationTime elif type == "Setup": self.totalSetupTime = self.totalOperationTime # if object was blocked add the working time if self.isBlocked: self.addBlockage() # the machine is currently performing nothing self.currentlyPerforming = None activeObjectQueue = self.Res.users if len(activeObjectQueue): activeEntity = activeObjectQueue[0] self.printTrace(activeEntity.name, interrupted=self.objName) self.outputTrace( activeObjectQueue[0].name, activeObjectQueue[0].id, "Interrupted at " + self.objName, ) # send Data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": "Interrupted"} ) G.db.commit() # recalculate the processing time left tinM if self.timeLastOperationStarted >= 0: self.tinM = round( self.tinM - (self.env.now - self.timeLastOperationStarted), 4 ) self.timeToEndCurrentOperation = self.env.now + self.tinM if np.isclose( self.tinM, 0 ): # sometimes the failure may happen exactly at the time that the processing would finish # this may produce disagreement with the simul8 because in both SimPy and Simul8 # it seems to be random which happens 1st # this should not appear often to stochastic models though where times are random self.tinM = 0 self.interruption = True # start counting the down time at breatTime dummy variable self.breakTime = self.env.now # dummy variable that the interruption happened # set isProcessing to False self.isProcessing = False # set isBlocked to False self.isBlocked = False
[docs] def isInterrupted(self): """returns true if the station is interrupted""" return self.interruption
[docs] def postInterruptionActions(self): """actions to be carried out when the processing of an Entity ends""" for oi in self.objectInterruptions: if oi.type == "Failure": if oi.deteriorationType == "working": # print(f"OI {oi.name} expects {oi.expectedSignals}") if oi.expectedSignals["victimResumesProcessing"]: # print(f"{self.name} sending victimResumesProcessing to {oi.name}") self.sendSignal(receiver=oi, signal=oi.victimResumesProcessing) for op in self.objectProperties: if op.expectedSignals["victimResumesProcessing"]: self.sendSignal(receiver=op, signal=op.victimResumesProcessing) activeObjectQueue = self.Res.users if len(activeObjectQueue): activeEntity = activeObjectQueue[0] # if the machine is empty signal giver else: self.signalGiver() # if the machine returns from an failure while processing an entity if not self.waitToDispose: # use the timers to count the time that Machine is down and related self.timeLastFailureEnded = self.env.now # set the timeLastFailureEnded # output to trace that the Machine self.objName was passivated for the current failure time if len(activeObjectQueue): self.outputTrace( activeObjectQueue[0].name, activeObjectQueue[0].id, "passivated in " + self.objName + " for " + str(self.env.now - self.breakTime), ) # send Data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": activeObjectQueue[0].id + " passivated in " + self.objName + " for " + str(self.env.now - self.breakTime)} ) G.db.commit() # when a machine returns from failure while trying to deliver an entity else: # calculate the time the Machine was down while trying to dispose the current Entity, # and the total down time while on current Entity self.downTimeInTryingToReleaseCurrentEntity += self.env.now - self.breakTime # update the timeLastFailureEnded self.timeLastFailureEnded = self.env.now # reset the variable holding the time the failure happened self.breakTime = 0
[docs] def checkIfMachineIsUp(self): """checks if the machine is Up""" return self.Up
[docs] def canAccept(self, callerObject=None): """ checks if the Machine can accept an entity. it checks also who called it and returns TRUE only to the predecessor that will give the entity. """ if self.isLocked: return False activeObjectQueue = self.Res.users thecaller = callerObject # return True ONLY if the length of the activeOjbectQue is smaller than # the object capacity, and the callerObject is not None but the giverObject if self.operatorPool != "None" and ( any(type == "Load" for type in self.multOperationTypeList) or any(type == "Setup" for type in self.multOperationTypeList) ): return ( self.operatorPool.checkIfResourceIsAvailable() and self.checkIfMachineIsUp() and len(activeObjectQueue) < self.capacity and self.isInRouteOf(thecaller) and not self.entryIsAssignedTo() ) else: # the operator doesn't have to be present for the loading of the machine as the load operation # is not assigned to operators return ( self.checkIfMachineIsUp() and len(activeObjectQueue) < self.capacity and self.isInRouteOf(thecaller) and not self.entryIsAssignedTo() )
[docs] def canAcceptAndIsRequested(self, callerObject=None): """checks if the Machine can accept an entity and there is an entity in some possible giver waiting for it. also updates the giver to the one that is to be taken""" activeObjectQueue = self.Res.users giverObject = callerObject assert giverObject, "there must be a caller for canAcceptAndIsRequested" # check if there is a place, the machine is up and the predecessor has an entity to dispose. if the machine has to compete # for an Operator that loads the entities onto it check if the predecessor if blocked by an other Machine. if not then the machine # has to block the predecessor giverObject to avoid conflicts with other competing machines if self.operatorPool != "None" and ( any(type == "Load" for type in self.multOperationTypeList) ): if giverObject.haveToDispose(self): if ( self.checkOperator() and self.checkIfActive() and len(activeObjectQueue) < self.capacity ): # if the exit of the object is already assigned somewhere else, return false if ( giverObject.exitIsAssignedTo() and giverObject.exitIsAssignedTo() != self ): return False return True else: return False else: # the operator performs no load and the entity is received by the machine while there is # no need for operators presence. The operator needs to be present only where the load Type # operation is assigned if ( self.checkIfActive() and len(activeObjectQueue) < self.capacity and giverObject.haveToDispose(self) ): # if the exit of the object is already assigned somewhere else, return false if ( giverObject.exitIsAssignedTo() and giverObject.exitIsAssignedTo() != self ): return False return True
[docs] def isLoadRequested(self): """return whether Load or setup Requested""" return any( type == "Load" or type == "Setup" for type in self.multOperationTypeList )
[docs] def checkOperator(self, callerObject=None): """to be called by canAcceptAndIsRequested and check for the operator""" mayProceed = False if self.operatorPool.operators: # flag notifying that there is operator assigned to the actievObject self.assignedOperator = False # the operators operating the station operators = self.operatorPool.operators if self.operatorPool.checkIfResourceIsAvailable(): for operator in [ x for x in operators if x.checkIfResourceIsAvailable() ]: # if there are operators available not assigned to the station then the station may proceed signalling the Router if not operator.isAssignedTo(): mayProceed = True # if there are operators assigned to the station then proceed without invoking the Router elif operator.isAssignedTo() == self: self.assignedOperator = True break return mayProceed or self.assignedOperator else: return True
[docs] def getEntity(self): """get an entity from the giver""" activeEntity = CoreObject.getEntity(self) # run the default method # update the entity attribute of the operator's schedule if self.currentOperator: self.currentOperator.schedule[-1]["entity"] = activeEntity # after the machine receives an entity, it must be removed from the pendingEntities list from manpy.simulation.core.Globals import G if G.RouterList: if activeEntity in G.pendingEntities: G.pendingEntities.remove(activeEntity) return activeEntity
[docs] def removeEntity(self, entity=None): """removes an entity from the Machine""" activeEntity = CoreObject.removeEntity(self, entity) # run the default method self.waitToDispose = False # update the waitToDispose flag # if the Machine canAccept then signal a giver if self.canAccept(): self.printTrace(self.id, attemptSignalGiver="(removeEntity)") self.signalGiver() return activeEntity
[docs] def haveToDispose(self, callerObject=None): """checks if the Machine can dispose an entity to the following object""" activeObjectQueue = self.Res.users # if we have only one successor just check if machine waits to dispose and also is up # this is done to achieve better (cpu) processing time if callerObject == None: return ( len(activeObjectQueue) > 0 and self.waitToDispose and ( self.canDeliverOnInterruption or self.timeLastEntityEnded == self.env.now or self.checkIfActive() ) ) thecaller = callerObject return ( len(activeObjectQueue) > 0 and self.waitToDispose and thecaller.isInRouteOf(self) and ( self.canDeliverOnInterruption or self.timeLastEntityEnded == self.env.now or self.checkIfActive() ) )
[docs] def canDeliver(self, entity=None): """checks whether the entity can proceed to a successor object""" assert self.isInActiveQueue(entity), ( entity.id + " not in the internalQueue of" + self.id ) activeEntity = entity from manpy.simulation.core.Globals import G router = G.RouterList[0] # if the entity is in a machines who's broker waits for operator then if self in router.pendingMachines: activeEntity.proceed = True activeEntity.candidateReceivers.append(self) return True return False
[docs] def requestOperator(self): """prepare the machine to be operated""" self.broker.invokeType = "request" self.broker.invoke() self.toBeOperated = True
[docs] def releaseOperator(self): """prepare the machine to be released""" from manpy.simulation.core.Globals import G # this checks if the operator is working on the last element. # If yes the time that he was set off-shift should be updated operator = self.currentOperator station = operator.schedule[-1].get("station", None) if station: if issubclass(station.__class__, CoreObject): operator.schedule[-1]["exitTime"] = self.env.now elif not operator.schedule[-1]["station"].get("id", None) in [ "off-shift", "on-break", ]: operator.schedule[-1]["exitTime"] = self.env.now # if the operator becomes unavailable if (not self.currentOperator.onShift) or self.currentOperator.onBreak: if not self.currentOperator.onShift: operator.timeLastShiftEnded = self.env.now if self.currentOperator.onBreak: operator.timeLastBreakStarted = self.env.now operator.unAssign() # set the flag operatorAssignedTo to None operator.workingStation = None operator.operatorDedicatedTo = None self.toBeOperated = False self.outputTrace(operator.name, operator.id, "Left " + str(self.id)) # send Data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": operator.id + " left " + str(self.id)} ) G.db.commit() # XXX in case of skilled operators which stay at the same station should that change elif not operator.operatorDedicatedTo == self: operator.unAssign() # set the flag operatorAssignedTo to None operator.workingStation = None self.outputTrace(operator.name, operator.id, "Left " + str(self.id)) # send Data to QuestDB if G.db: G.db.insert( self.name, {"time": float(self.env.now), "message": operator.id + " left " + str(self.id)} ) G.db.commit() # if the Router is expecting for signal send it from manpy.simulation.core.Globals import G from manpy.simulation.SkilledOperatorRouter import SkilledRouter self.toBeOperated = False if G.RouterList[0].__class__ is SkilledRouter: if G.RouterList[0].expectedFinishSignals: if self.id in G.RouterList[0].expectedFinishSignalsDict: signal = G.RouterList[0].expectedFinishSignalsDict[self.id] self.sendSignal(receiver=G.RouterList[0], signal=signal) self.broker.invokeType = "release" self.broker.invoke()
[docs] def isOperated(self): """check if the machine is currently operated by an operator""" return self.toBeOperated
[docs] def outputResultsJSON(self): """outputs results to JSON File""" from manpy.simulation.core.Globals import G json = { "_class": "manpy.%s" % self.__class__.__name__, "id": self.id, "family": self.family, "results": {}, } json["results"]["failure_ratio"] = self.Failure json["results"]["working_ratio"] = self.Working json["results"]["blockage_ratio"] = self.Blockage json["results"]["waiting_ratio"] = self.Waiting json["results"]["off_shift_ratio"] = self.OffShift json["results"]["setup_ratio"] = self.SettingUp json["results"]["loading_ratio"] = self.Loading json["results"]["break_ratio"] = self.OnBreak G.outputJSON["elementList"].append(json)
def getActiveEntity(self): return self.Res.users[0]