# ===========================================================================
# 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 18 Feb 2013
@author: George
"""
# from SimPy.Simulation import Process, Resource
# from SimPy.Simulation import waitevent, now, hold
import simpy
from manpy.simulation.RandomNumberGenerator import RandomNumberGenerator
from manpy.simulation.core.CoreObject import CoreObject
from manpy.simulation.core import Globals
from manpy.simulation.core.Entity import Entity
# ===============================================================================
# the Assembly object
# ===============================================================================
[docs]
class Assembly(CoreObject):
"""
Models an assembly object
it gathers frames and parts which are loaded to the frames
"""
class_name = "manpy.Assembly"
# ===========================================================================
# initialize the object
# ===========================================================================
def __init__(self, id="", name="", cost=0, processingTime=None, entity="manpy.Part", inputsDict=None, **kw):
self.type = "Assembly" # String that shows the type of object
self.next = [] # list with the next objects in the flow
self.previous = [] # list with the previous objects in the flow
self.previousPart = [] # list with the previous objects that send parts
self.previousFrame = [] # list with the previous objects that send frames
self.nextIds = [] # list with the ids of the next objects in the flow
self.previousIds = [] # list with the ids of the previous objects in the flow
# lists to hold statistics of multiple runs
self.Waiting = []
self.Working = []
self.Blockage = []
if not processingTime:
processingTime = {"Fixed": {"mean": 0}}
if (
"Normal" in list(processingTime.keys())
and processingTime["Normal"].get("max", None) is None
):
processingTime["Normal"]["max"] = float(
processingTime["Normal"]["mean"]
) + 5 * float(processingTime["Normal"]["stdev"])
CoreObject.__init__(self, id, name, cost)
self.rng = RandomNumberGenerator(self, processingTime)
# ============================== variable that is used for the loading of machines =============
self.exitAssignedToReceiver = False # by default the objects are not blocked
# when the entities have to be loaded to operatedMachines
# then the giverObjects have to be blocked for the time
# that the machine is being loaded
from manpy.simulation.core.Globals import G
if isinstance(entity, str):
self.item = Globals.getClassFromName(entity)
elif isinstance(entity, Entity) or issubclass(entity, Entity):
self.item = entity
self.numberOfArrivals = 0
G.AssemblyList.append(self)
[docs]
def initialize(self):
"""initialize method"""
# Process.__init__(self)
CoreObject.initialize(self)
self.waitToDispose = (
False # flag that shows if the object waits to dispose an entity
)
self.Up = True # Boolean that shows if the object is in failure ("Down") or not ("up")
self.currentEntity = None
self.totalFailureTime = 0 # holds the total failure time
self.timeLastFailure = (
0 # holds the time that the last failure of the object started
)
self.timeLastFailureEnded = (
0 # holds the time that the last failure of the object Ended
)
self.downTimeProcessingCurrentEntity = 0 # holds the time that the object was down while processing the current entity
self.downTimeInTryingToReleaseCurrentEntity = (
0 # holds the time that the object was down while trying
)
# to release the current entity
self.downTimeInCurrentEntity = 0 # holds the total time that the object was down while holding current entity
self.timeLastEntityLeft = (
0 # holds the last time that an entity left the object
)
self.processingTimeOfCurrentEntity = (
0 # holds the total processing time that the current entity required
)
self.totalBlockageTime = 0 # holds the total blockage time
self.totalWaitingTime = 0 # holds the total waiting time
self.totalWorkingTime = 0 # holds the total working time
self.completedJobs = 0 # holds the number of completed jobs
self.timeLastEntityEnded = (
0 # holds the last time that an entity ended processing in the object
)
self.timeLastEntityEntered = (
0 # holds the last time that an entity ended processing in the object
)
self.timeLastFrameWasFull = 0 # holds the time that the last frame was full, ie that assembly process started
self.nameLastFrameWasFull = "" # holds the name of the last frame that was full, ie that assembly process started
self.nameLastEntityEntered = (
"" # holds the name of the last frame that entered processing in the object
)
self.nameLastEntityEnded = (
"" # holds the name of the last frame that ended processing in the object
)
self.Res = simpy.Resource(self.env, 1)
self.Res.users = []
self.numberOfArrivals = 0
# self.Res.waitQ=[]
[docs]
def run(self):
"""class generator"""
activeObjectQueue = self.getActiveObjectQueue()
while 1:
from manpy.simulation.core.Globals import G
# Create new Entity
entity = self.createEntity()
entity.creationTime = self.env.now
entity.startTime = self.env.now
entity.currentStation = self
G.EntityList.append(entity)
G.numberOfEntities += 1
self.numberOfArrivals += 1
self.printTrace(self.id, waitEvent="")
# wait until the Queue can accept an entity and one predecessor requests it
self.expectedSignals["isRequested"] = 1
yield self.isRequested # [self.isRequested,self.canDispose, self.loadOperatorAvailable]
if self.isRequested.value:
transmitter, eventTime = self.isRequested.value
self.printTrace(self.id, isRequested=transmitter.id)
# reset the isRequested signal parameter
self.isRequested = self.env.event()
frame = self.getEntity("Frame") # get the Frame
entity.features = frame.features.copy()
entity.feature_times = frame.feature_times.copy()
for i in range(
self.getActiveObjectQueue()[0].capacity
): # this loop will be carried until the Frame is full with the parts
self.printTrace(self.id, waitEvent="(to load parts)")
self.expectedSignals["isRequested"] = 1
yield self.isRequested
if self.isRequested.value:
transmitter, eventTime = self.isRequested.value
self.printTrace(self.id, isRequested=transmitter.id)
# reset the isRequested signal parameter
self.isRequested = self.env.event()
# TODO: fix the getEntity 'Part' case
part = self.getEntity("Part")
# Fill features and feature_times of new Entity
for i in range(len(entity.feature_times)):
if entity.features[i] == None:
entity.features[i] = part.features[i]
if entity.feature_times[i] == None:
entity.feature_times[i] = part.feature_times[i]
# set new Entity as active Entity
self.getActiveObjectQueue()[0] = entity
self.expectedSignals["isRequested"] = 0
self.outputTrace(
self.getActiveObjectQueue()[0].name,
self.getActiveObjectQueue()[0].id,
"is now full in " + self.objName,
)
self.isProcessing = True
self.timeLastFrameWasFull = self.env.now
self.nameLastFrameWasFull = self.getActiveObjectQueue()[0].name
self.timeLastProcessingStarted = self.env.now
self.totalProcessingTimeInCurrentEntity = self.calculateProcessingTime()
yield self.env.timeout(
self.totalProcessingTimeInCurrentEntity
) # hold for the time the assembly operation is carried
self.totalWorkingTime += self.env.now - self.timeLastProcessingStarted
self.isProcessing = False
self.outputTrace(
self.getActiveObjectQueue()[0].name,
self.getActiveObjectQueue()[0].id,
"ended processing in " + self.objName,
)
self.timeLastEntityEnded = self.env.now
self.nameLastEntityEnded = self.getActiveObjectQueue()[0].name
self.timeLastBlockageStarted = self.env.now
self.isBlocked = True
self.completedJobs += 1 # Assembly completed a job
self.waitToDispose = True # since all the frame is full
self.printTrace(self.id, attemptSignalReceiver="(generator)")
# 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
self.expectedSignals["canDispose"] = 1
self.expectedSignals["interruptionStart"] = 1
while 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]
)
# 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
yield self.interruptionEnd # interruptionEnd to be triggered by ObjectInterruption
self.expectedSignals["interruptionEnd"] = 0
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 self.Up and self.onShift:
break
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():
break
# TODO: router most probably should signal givers and not receivers in order to avoid this hold,self,0
# As the receiver (e.g.) a machine that follows the machine receives an loadOperatorAvailable event,
# signals the preceding station (e.g. self.machine) and immediately after that gets the entity.
# the preceding machine gets the canDispose signal which is actually useless, is emptied by the following station
# and then cannot exit an infinite loop.
if not self.haveToDispose():
break
# notify that the station waits the entity to be removed
self.waitEntityRemoval = True
self.printTrace(self.id, waitEvent="(entityRemoved)")
self.expectedSignals["entityRemoved"] = 1
yield self.entityRemoved
self.expectedSignals["entityRemoved"] = 0
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
self.expectedSignals["canDispose"] = 0
self.expectedSignals["interruptionStart"] = 0
[docs]
def canAccept(self, callerObject=None):
"""checks if the Assembly can accept an entity"""
# get active and giver objects
activeObject = self.getActiveObject()
activeObjectQueue = self.getActiveObjectQueue()
# if there is no caller then perform the default
if callerObject == None:
return len(activeObjectQueue) == 0
thecaller = callerObject
# if the object holds a finished item then return False
if len(activeObjectQueue) > 0:
if activeObjectQueue[0].type == "Part":
return False
# if the object holds nothing then return true
if len(self.getActiveObjectQueue()) == 0:
return not activeObject.entryIsAssignedTo()
# if it holds already a frame then return true only for parts and if the frame has still space
if len(activeObjectQueue[0].getFrameQueue()) < activeObjectQueue[0].capacity:
if callerObject.getActiveObjectQueue()[0].type == "Part":
return not activeObject.entryIsAssignedTo()
return False
[docs]
def canAcceptAndIsRequested(self, callerObject=None):
"""checks if the Assembly can accept a part or a Frame"""
# get the active and the giver objects
activeObject = self.getActiveObject()
activeObjectQueue = self.getActiveObjectQueue()
giverObject = callerObject
assert giverObject, "there must be a caller for canAcceptAndIsRequested"
if len(giverObject.getActiveObjectQueue()) > 0:
# activate only if the caller carries Frame
if giverObject.getActiveObjectQueue()[0].type == "Frame":
return len(activeObjectQueue) == 0 and giverObject.haveToDispose(
activeObject
)
# activate only if the caller carries Part
if giverObject.getActiveObjectQueue()[0].type == "Part":
return len(activeObjectQueue) == 1 and giverObject.haveToDispose(
activeObject
)
return False
[docs]
def haveToDispose(self, callerObject=None):
"""checks if the Assembly can dispose an entity to the following object"""
# get active object and its queue
activeObject = self.getActiveObject()
activeObjectQueue = self.getActiveObjectQueue()
# if we have only one possible receiver just check if the Queue holds one or more entities
if callerObject == None:
return len(activeObjectQueue) > 0
thecaller = callerObject
return (
len(activeObjectQueue) > 0
and (thecaller in activeObject.next)
and activeObject.waitToDispose
)
[docs]
def removeEntity(self, entity=None):
"""removes an entity from the Assembly"""
activeEntity = CoreObject.removeEntity(self, entity) # run the default method
self.waitToDispose = False
if self.canAccept():
self.printTrace(self.id, attemptSignalGiver="(removeEntity)")
self.signalGiver()
return activeEntity
[docs]
def getEntity(self, type):
"""gets an entity from the giver. it may handle both Parts and Frames"""
activeObject = self.getActiveObject()
activeObjectQueue = self.getActiveObjectQueue()
giverObject = self.getGiverObject()
giverObject.sortEntities() # sort the Entities of the giver according to the scheduling rule if applied
giverObjectQueue = self.getGiverObjectQueue()
# if the giverObject is blocked then unBlock it
if giverObject.exitIsAssignedTo():
giverObject.unAssignExit()
# if the activeObject entry is blocked then unBlock it
if activeObject.entryIsAssignedTo():
activeObject.unAssignEntry()
activeEntity = self.identifyEntityToGet()
assert activeEntity.type == type, (
"the type of the entity to get must be of type "
+ type
+ " while it is "
+ activeEntity.type
)
# remove the entity from the previews object
giverObject.removeEntity(activeEntity)
self.printTrace(activeEntity.name, enter=self.id)
self.outputTrace(activeEntity.name, activeEntity.id, "got into " + self.objName)
# if the type is Frame
if activeEntity.type == "Frame":
self.nameLastEntityEntered = activeEntity.name
self.timeLastEntityEntered = self.env.now
activeEntity.currentStation = self
# if the frame is not fully loaded then signal a giver
if len(activeObjectQueue[0].getFrameQueue()) < activeObjectQueue[0].capacity:
self.printTrace(self.id, attemptSignalGiver="(getEntity)")
self.signalGiver()
return activeEntity
[docs]
def appendEntity(self, entity=None):
"""appends entity to the receiver object. to be called by the removeEntity of the giver.
this method is created to be overridden by the Assembly class in its getEntity where Frames are loaded
"""
activeObject = self.getActiveObject()
activeObjectQueue = self.getActiveObjectQueue()
assert entity, "the entity to be appended cannot be None"
if entity.type == "Part":
activeObjectQueue[0].getFrameQueue().append(
entity
) # get the part from the giver and append it to the frame!
elif entity.type == "Frame":
activeObjectQueue.append(
entity
) # get the frame and append it to the internal queue
[docs]
def outputResultsJSON(self):
"""outputs results to JSON File"""
from manpy.simulation.core.Globals import G
json = {"_class": self.class_name, "id": self.id, "results": {}}
json["results"]["working_ratio"] = self.Working
json["results"]["blockage_ratio"] = self.Blockage
json["results"]["waiting_ratio"] = self.Waiting
G.outputJSON["elementList"].append(json)
def createEntity(self):
from manpy.simulation.core.Globals import G
self.printTrace(self.id, create="")
return self.item(
id=self.item.type + str(G.numberOfEntities),
name=self.item.type + str(self.numberOfArrivals),
) # return the newly created Entity