# ===========================================================================
# 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 9 Nov 2012
@author: George
"""
"""
models the failures that servers can have
"""
# from SimPy.Simulation import now, Process, hold, request, release
from manpy.simulation.RandomNumberGenerator import RandomNumberGenerator
from manpy.simulation.core.ObjectInterruption import ObjectInterruption
from manpy.simulation.core.utils import check_config_dict
[docs]
class Failure(ObjectInterruption):
"""
Models failures of machines.
:param id: Internal ID of the failure.
:param name: Name of the failure.
:param victim: Victim (i.e. the machine that should fail) for the Failure
:param distribution: dictionary containing the individual distributions, e.g. TTF and TTR
:param index: Internal index, only used if name is empty.
:param repairman: Repairman with constrained resources that's assigned to this failure. Optional.
:param offshift: flag used to identify if the time between failures should be counted while the victim is off-shift
:param deteriorationType: shows how the time to failure is measured;
'constant' means it counts not matter the state of the victim;
'onShift' counts only if the victim is onShift;
'working' counts only working time
:param waitOnTie: flag to show if the failure will wait on tie with other events before interrupting the victim
:param conditional: function that evaluates a condition. If return value true, the failure will be triggered.
Is used instead of passing a TTF in distribution.
:param entity: With the parameter entity=True, the Time-to-Failure (TTF) is calculated based on the processing time of the entity within the machine
:param remove: Should the entities on which the failure occured be removed from the production line (=destroyed)?
:param kw:
"""
def __init__(
self,
id="",
name="",
victim=None,
distribution={},
index=0,
repairman=None,
offshift=False,
deteriorationType="constant",
waitOnTie=False,
conditional=None,
entity=False,
remove=False,
cost=0,
**kw
):
ObjectInterruption.__init__(self, id, name, victim=victim, remove=remove)
check_config_dict(distribution, ["TTF", "TTR"], name)
self.rngTTF = RandomNumberGenerator(
self, distribution.get("TTF", {"Fixed": {"mean": 0.1}})
)
self.rngTTR = RandomNumberGenerator(
self, distribution.get("TTR", {"Fixed": {"mean": 0.1}})
)
if self.name == "":
self.name = "F" + str(index)
self.repairman = repairman # the resource that may be needed to fix the failure
# if now resource is needed this will be "None"
self.type = "Failure"
# shows how the time to failure is measured
# 'constant' means it counts not matter the state of the victim
# 'onShift' counts only if the victim is onShift
# 'working' counts only working time
self.deteriorationType = deteriorationType
# flag used to identify if the time between failures should be counted while the victim is off-shift
self.offshift = offshift
# flag to show if the failure will wait on tie with other events before interrupting the victim
self.waitOnTie = waitOnTie
# set a default function for conditional if none is defined
if conditional == None:
def condition(self):
return None
self.conditional = condition
else:
self.conditional = conditional
self.entity = entity
self.cost = cost
[docs]
def initialize(self):
ObjectInterruption.initialize(self)
if self.conditional(self) != None:
self.entity = True
if self.entity == True:
self.deteriorationType="working"
self.victimStartsProcessing = self.env.event()
self.victimEndsProcessing = self.env.event()
self.contribution = self.env.event()
[docs]
def run(self):
"""The run method for the failure which has to served by a repairman"""
if self.conditional(self) != None:
from .Globals import G
while 1:
failureNotTriggered = True
self.expectedSignals["contribution"] = 1
yield self.contribution
self.contribution = self.env.event()
# wait for victim to start process
if self.conditional(self) == True:
# if the time that the victim is off-shift should not be counted
timeToFailure = self.rngTTF.generateNumber()
remainingTimeToFailure = timeToFailure
failureNotTriggered = True
# if time to failure counts not matter the state of the victim
if self.deteriorationType == "constant":
yield self.env.timeout(remainingTimeToFailure)
# if time to failure counts only in onShift time
elif self.deteriorationType == "onShift":
while failureNotTriggered:
timeRestartedCounting = self.env.now
self.isWaitingForVictimOffShift = True
self.expectedSignals["victimOffShift"] = 1
receivedEvent = (
yield self.env.timeout(remainingTimeToFailure)
| self.victimOffShift
)
# the failure should receive a signal if there is a shift-off triggered
if self.victimOffShift in receivedEvent:
assert (
self.victim.onShift == False
), "shiftFailure cannot recalculate TTF if the victim is onShift"
self.victimOffShift = self.env.event()
remainingTimeToFailure = remainingTimeToFailure - (
self.env.now - timeRestartedCounting
)
# wait for the shift to start again
self.isWaitingForVictimOnShift = True
self.expectedSignals["victimOnShift"] = 1
yield self.victimOnShift
self.isWaitingForVictimOnShift = False
self.victimOnShift = self.env.event()
assert (
self.victim.onShift == True
), "the victim of shiftFailure must be onShift to continue counting the TTF"
else:
self.isWaitingForVictimOffShift = False
failureNotTriggered = False
# if time to failure counts only in working time
elif self.deteriorationType == "working":
# wait for victim to start process
self.expectedSignals["victimStartsProcessing"] = 1
yield self.victimStartsProcessing
self.victimStartsProcessing = self.env.event()
# check if failure should occur on entity
if self.entity == True:
remainingTimeToFailure = timeToFailure * self.victim.tinM
while failureNotTriggered:
timeRestartedCounting = self.env.now
self.expectedSignals["victimEndsProcessing"] = 1
# wait either for the failure or end of process
receivedEvent = (
yield self.env.timeout(remainingTimeToFailure)
| self.victimEndsProcessing
)
# This happens because the victim stopped processing before remainingTimeToFailure passed;
# That means the countdown is paused until the victim starts processing
if self.victimEndsProcessing in receivedEvent:
self.victimEndsProcessing = self.env.event()
remainingTimeToFailure = remainingTimeToFailure - (
self.env.now - timeRestartedCounting
)
self.expectedSignals["victimStartsProcessing"] = 1
yield self.victimStartsProcessing
# wait for victim to start again processing
self.victimStartsProcessing = self.env.event()
# Countdown to failure is over
else:
self.expectedSignals["victimEndsProcessing"] = 0
failureNotTriggered = False
downtime = self.rngTTR.generateNumber()
if downtime != 0:
# if the mode is to wait on tie before interruption add a dummy hold for 0
# this is done so that if processing finishes exactly at the time of interruption
# the processing will finish first (if this mode is selected)
if self.waitOnTie:
if hasattr(self.victim, "timeToEndCurrentOperation"):
if float(self.victim.timeToEndCurrentOperation) == float(
self.env.now
):
yield self.env.timeout(0)
self.interruptVictim()
# check in the ObjectInterruptions of the victim. If there is a one that is waiting for victimFailed send it
for oi in self.victim.objectInterruptions:
if oi.expectedSignals["victimFailed"]:
self.sendSignal(receiver=oi, signal=oi.victimFailed)
for op in self.victim.objectProperties:
if op.expectedSignals["victimFailed"]:
self.sendSignal(receiver=op, signal=op.victimFailed)
self.victim.Up = False
self.victim.timeLastFailure = self.env.now
self.outputTrace(self.victim.name, self.victim.id, "is down")
# send Data to QuestDB
from .Globals import G
if G.db:
G.db.insert(
self.name,
{"time": float(self.env.now),
"message": self.victim.id + " is down"}
)
G.db.commit()
# update the failure time
failTime = self.env.now
if (
self.repairman and self.repairman != "None"
): # if the failure needs a resource to be fixed,
# the machine waits until the
# resource is available
with self.repairman.getResource().request() as request:
yield request
# update the time that the repair started
timeOperationStarted = self.env.now
self.repairman.timeLastOperationStarted = self.env.now
yield self.env.timeout(
self.rngTTR.generateNumber()
) # wait until the repairing process is over
self.victim.totalFailureTime += self.env.now - failTime
self.reactivateVictim() # since repairing is over, the Machine is reactivated
self.victim.Up = True
self.outputTrace(self.victim.name, self.victim.id, "is up")
# send Data to QuestDB
if G.db:
G.db.insert(
self.name,
{"time": float(self.env.now),
"message": self.victim.id + " is up"}
)
G.db.commit()
self.repairman.totalWorkingTime += (
self.env.now - timeOperationStarted
)
continue
yield self.env.timeout(downtime) # wait until the repairing process is over
# add the failure
# if victim is off shift add only the fail time before the shift ended
if not self.victim.onShift and failTime < self.victim.timeLastShiftEnded:
self.victim.totalFailureTime += (
self.victim.timeLastShiftEnded - failTime
)
# if the victim was off shift since the start of the failure add nothing
elif not self.victim.onShift and failTime >= self.victim.timeLastShiftEnded:
pass
# if victim was off shift in the start of the fail time, add on
elif self.victim.onShift and failTime < self.victim.timeLastShiftStarted:
self.victim.totalFailureTime += (
self.env.now - self.victim.timeLastShiftStarted
)
# this can happen only if deteriorationType is constant
assert (
self.deteriorationType == "constant"
), "object got failure while off-shift and deterioration type not constant"
else:
self.victim.totalFailureTime += self.env.now - failTime
self.reactivateVictim() # since repairing is over, the Machine is reactivated
self.victim.Up = True
self.outputTrace(self.victim.name, self.victim.id, "is up")
else:
while 1:
# if the time that the victim is off-shift should not be counted
timeToFailure = self.rngTTF.generateNumber()
remainingTimeToFailure = timeToFailure
failureNotTriggered = True
# if time to failure counts not matter the state of the victim
if self.deteriorationType == "constant":
yield self.env.timeout(remainingTimeToFailure)
# if time to failure counts only in onShift time
elif self.deteriorationType == "onShift":
while failureNotTriggered:
timeRestartedCounting = self.env.now
self.isWaitingForVictimOffShift = True
self.expectedSignals["victimOffShift"] = 1
receivedEvent = (
yield self.env.timeout(remainingTimeToFailure)
| self.victimOffShift
)
# the failure should receive a signal if there is a shift-off triggered
if self.victimOffShift in receivedEvent:
assert (
self.victim.onShift == False
), "shiftFailure cannot recalculate TTF if the victim is onShift"
self.victimOffShift = self.env.event()
remainingTimeToFailure = remainingTimeToFailure - (
self.env.now - timeRestartedCounting
)
# wait for the shift to start again
self.isWaitingForVictimOnShift = True
self.expectedSignals["victimOnShift"] = 1
yield self.victimOnShift
self.isWaitingForVictimOnShift = False
self.victimOnShift = self.env.event()
assert (
self.victim.onShift == True
), "the victim of shiftFailure must be onShift to continue counting the TTF"
else:
self.isWaitingForVictimOffShift = False
failureNotTriggered = False
# if time to failure counts only in working time
elif self.deteriorationType == "working":
# wait for victim to start process
self.expectedSignals["victimStartsProcessing"] = 1
yield self.victimStartsProcessing
self.victimStartsProcessing = self.env.event()
# check if failure should occur on entity
if self.entity == True:
remainingTimeToFailure = timeToFailure * self.victim.tinM
while failureNotTriggered:
timeRestartedCounting = self.env.now
self.expectedSignals["victimEndsProcessing"] = 1
# wait either for the failure or end of process
receivedEvent = (
yield self.env.timeout(remainingTimeToFailure)
| self.victimEndsProcessing
)
# This happens because the victim stopped processing before remainingTimeToFailure passed;
# That means the countdown is paused until the victim starts processing
if self.victimEndsProcessing in receivedEvent:
self.victimEndsProcessing = self.env.event()
remainingTimeToFailure = remainingTimeToFailure - (
self.env.now - timeRestartedCounting
)
self.expectedSignals["victimStartsProcessing"] = 1
yield self.victimStartsProcessing
# wait for victim to start again processing
self.victimStartsProcessing = self.env.event()
# Countdown to failure is over
else:
self.expectedSignals["victimEndsProcessing"] = 0
failureNotTriggered = False
downtime = self.rngTTR.generateNumber()
if downtime != 0:
# if the mode is to wait on tie before interruption add a dummy hold for 0
# this is done so that if processing finishes exactly at the time of interruption
# the processing will finish first (if this mode is selected)
if self.waitOnTie:
if hasattr(self.victim, "timeToEndCurrentOperation"):
if float(self.victim.timeToEndCurrentOperation) == float(
self.env.now
):
yield self.env.timeout(0)
self.interruptVictim()
# check in the ObjectInterruptions of the victim. If there is a one that is waiting for victimFailed send it
for oi in self.victim.objectInterruptions:
if oi.expectedSignals["victimFailed"]:
self.sendSignal(receiver=oi, signal=oi.victimFailed)
for op in self.victim.objectProperties:
if op.expectedSignals["victimFailed"]:
self.sendSignal(receiver=op, signal=op.victimFailed)
self.victim.Up = False
self.victim.timeLastFailure = self.env.now
self.outputTrace(self.victim.name, self.victim.id, "is down")
# send Data to QuestDB
from .Globals import G
if G.db:
G.db.insert(
self.name,
{"time": float(self.env.now),
"message": self.victim.id + " is down"}
)
G.db.commit()
# update the failure time
failTime = self.env.now
if (
self.repairman and self.repairman != "None"
): # if the failure needs a resource to be fixed,
# the machine waits until the
# resource is available
with self.repairman.getResource().request() as request:
yield request
# update the time that the repair started
timeOperationStarted = self.env.now
self.repairman.timeLastOperationStarted = self.env.now
yield self.env.timeout(
self.rngTTR.generateNumber()
) # wait until the repairing process is over
self.victim.totalFailureTime += self.env.now - failTime
self.reactivateVictim() # since repairing is over, the Machine is reactivated
self.victim.Up = True
self.outputTrace(self.victim.name, self.victim.id, "is up")
# send Data to QuestDB
if G.db:
G.db.insert(
self.name,
{"time": float(self.env.now),
"message": self.victim.id + " is up"}
)
G.db.commit()
self.repairman.totalWorkingTime += (
self.env.now - timeOperationStarted
)
continue
yield self.env.timeout(downtime) # wait until the repairing process is over
# add the failure
# if victim is off shift add only the fail time before the shift ended
if not self.victim.onShift and failTime < self.victim.timeLastShiftEnded:
self.victim.totalFailureTime += (
self.victim.timeLastShiftEnded - failTime
)
# if the victim was off shift since the start of the failure add nothing
elif not self.victim.onShift and failTime >= self.victim.timeLastShiftEnded:
pass
# if victim was off shift in the start of the fail time, add on
elif self.victim.onShift and failTime < self.victim.timeLastShiftStarted:
self.victim.totalFailureTime += (
self.env.now - self.victim.timeLastShiftStarted
)
# this can happen only if deteriorationType is constant
assert (
self.deteriorationType == "constant"
), "object got failure while off-shift and deterioration type not constant"
else:
self.victim.totalFailureTime += self.env.now - failTime
self.reactivateVictim() # since repairing is over, the Machine is reactivated
self.victim.Up = True
self.outputTrace(self.victim.name, self.victim.id, "is up")