Я столкнулся с проблемой синхронизации в Simpy. Под этим я подразумеваю, что события обрабатываются компьютером не в том порядке, в котором мне хотелось бы. Я искал много дополнительной информации, помимо той, что уже есть в документации Simpy, о том, как события ставятся в очередь, сортируются и обрабатываются компьютером. Я обнаружил везде, что они были отсортированы по времени, в которое они должны срабатывать. Прежде чем читать остальные, может быть у кого-нибудь есть какие-либо ссылки или документ, чтобы посоветовать мне?
В частности, я пытаюсь смоделировать и симулировать системы реального мира (экземпляр класса PoolSystem), который представляет собой пул подсистем, которые могут быть либо далее разбиты на подсистемы, либо могут выходить из строя (эта последняя категория систем). называется AtomicSystem). Подводя итог, можно сказать, что PoolSystem состоит из подсистем, которые могут быть либо PoolSystem, либо AtomicSystem.
Например, автомобиль может быть экземпляром этого класса PoolSystem с двигателем в качестве подсистемы. Но двигатель можно разбить на несколько других подсистем, таких как поршень или свеча зажигания, которые могут выйти из строя. В этом случае двигатель будет определен как экземпляр PoolSystem, а поршень и свеча зажигания — как экземпляры AtomicSystem.
Классы AtomicSystem и PoolSystem основаны на одной и той же стандартной модели. У них обоих есть:
- «критический» логический атрибут, который имеет значение «Истина», если сбой данной подсистемы вызывает сбой всей системы (что означает, что все другие подсистемы должны быть прерваны)
- событие «update_order», которое действует как сигнал для системы для связи со своими подсистемами (если таковые имеются)
- событие «dysfunction_signal», которое является сигналом для подсистем сообщить своей системе, что они вышли из строя
- «прерванное» событие, которое запускается всякий раз, когда данная система не может работать должным образом или всякий раз, когда она была прервана системой более высокого уровня.
- событие «update_end», которое действует как сигнал для подсистемы, чтобы сообщить своей системе более высокого уровня, что она завершила свое обновление
- атрибут «жизненного цикла», который представляет собой процесс, моделирующий операционное обслуживание данной системы.
Я надеюсь, что следующая схема поможет понять то, что вы только что прочитали: Разбивка автомобиля, определенного как Пул Система
В этой схеме автомобиль определяется как экземпляр PoolSystem, подсистемами которого являются двигатель и шина. Шина может быть причиной отказа автомобиля, поэтому она определяется как экземпляр AtomicSystem. Двигатель определяется как другая система PoolSystem, подсистемами которой являются поршень и свеча зажигания, которые могут выйти из строя, поэтому они определяются как экземпляры AtomicSystem.
Класс AtomicSystem можно найти ниже:
class AtomicSystem(object):
def __init__(self, env, mtbd, backlog, user_defined_critical=True, ids=None):
self.env = env # environment()
self.mtbd = mtbd # mean time between dysfunction
self.critical = user_defined_critical # boolean
self.ids = ids # list of strings
self.ttd = self.time_to_dysfunction() # time before dysfunction
self.update_order = self.env.event()
self.dysfunction_signal = self.env.event()
self.interrupted = self.env.event()
self.update_end = self.env.event()
self.lifecycle = self.env.process(self.run(backlog))
def time_to_dysfunction(self):
return self.mtbd
def run(self, backlog):
# the atomic system starts service when its update_order event is triggered
yield self.update_order
print("t = " + str(self.env.now) + " : " + self.ids[-1] + " starts service.")
self.update_order = self.env.event()
# atomic system specifies to higher level system that it has started service
self.update_end.succeed()
self.update_end = self.env.event()
try:
# as long as the atomic system remains in this while loop, it is said to be in service.
while True:
start = self.env.now
time_out = self.env.timeout(self.ttd)
# wait for a dysfunction (time_out) or interruption (interrupted) or an update from a higher level system (update_order)
result = yield time_out | self.interrupted | self.update_order
if time_out in result:
print("t = " + str(self.env.now) + " : " + self.ids[-1] + " fails.")
# if the atomic system fails, trigger dysfunction_signal event destined to be detected by higher level system
self.dysfunction_signal.succeed()
# when the atomic system fails, its interrupted event is automatically triggered
self.interrupted.succeed()
if self.ttd > 0:
backlog.append({"Dysfunction time": self.env.now, "IDs": self.ids})
self.ttd = 0
if self.interrupted.triggered:
print("t = " + str(self.env.now) + " : " + self.ids[-1] + " interrupts service.")
if self.ttd > 0:
operation_duration = self.env.now - start
self.ttd -= operation_duration
# the atomic system waits for update_order trigger when it has been interrupted
yield self.update_order
if self.update_order.triggered:
# here, the atomic system returns to service
print("t = " + str(self.env.now) + " : " + self.ids[-1] + " is updated.")
if self.ttd > 0:
operation_duration = self.env.now - start
self.ttd -= operation_duration
self.update_end.succeed()
self.update_order = self.env.event()
self.dysfunction_signal = self.env.event()
self.interrupted = self.env.event()
self.update_end = self.env.event()
except:
# here the atomic system is terminated (end of service)
print("t = " + str(self.env.now) + " : " + self.ids[-1] + " is terminated.")
self.env.exit()
Класс PoolSystem можно найти ниже:
class PoolSystem(object):
def __init__(self, env, id, init_subsystems, user_defined_critical=True):
self.env = env
self.id = id
self.subsystems = init_subsystems
self.working_subsystems = [self.subsystems[key] for key in self.subsystems.keys()]
self.critical = user_defined_critical
self.update_order = self.env.event()
self.dysfunction_signal = simpy.AnyOf(self.env, [syst.dysfunction_signal for syst in self.working_subsystems])
self.interrupted = self.env.event()
self.update_end = self.env.event()
self.lifecycle = self.env.process(self.run())
def start_subsystems(self):
for key in self.subsystems.keys():
self.subsystems[key].update_order.succeed()
def run(self):
user_defined_critical = self.critical
# the pool system is started here when its update_order event is triggered
yield self.update_order
print("t = " + str(self.env.now) + " : " + self.id + " starts service.")
self.update_order = self.env.event()
# Here, the pool system starts all of its subsystems (which can be atomic and/or pool systems)
self.start_subsystems()
# here, update_end is triggered if all the update_end events of the subsystems have been triggered
self.update_end = simpy.AllOf(self.env, [self.subsystems[key].update_end for key in self.subsystems.keys()])
yield self.update_end
try:
while True:
# wait for a dysfunction (dysfunction_signal), interruption (interrupted) or an update from a higher level system (update_order)
yield self.dysfunction_signal | self.interrupted | self.update_order
if self.dysfunction_signal.triggered:
crit = []
for syst in self.working_subsystems:
if syst.dysfunction_signal.triggered:
crit.append(syst.critical)
if True in crit: # if one of the failed subsystems is critical (critical = True), then trigger interrupted event()
print("t = " + str(self.env.now) + " : " + self.id + " fails completely.")
# pool system is interrupted
self.critical = user_defined_critical
self.interrupted.succeed()
else:
# no critical subsystem has failed yet so the pool system can continue working (no interruption here)
self.critical = False
self.working_subsystems = [self.subsystems[key] for key in self.subsystems.keys() if
not self.subsystems[key].interrupted.triggered]
if len(self.working_subsystems) is not 0:
print("t = " + str(self.env.now) + " : " + self.id + " fails partially.")
self.dysfunction_signal = simpy.AnyOf(self.env, [syst.dysfunction_signal for syst in
self.working_subsystems])
else:
# pool system is interrupted if all of its subsystems have failed
print("t = " + str(self.env.now) + " : " + self.id + " fails completely (no working EUs).")
self.interrupted.succeed()
if self.interrupted.triggered:
print("t = " + str(self.env.now) + " : " + self.id + " interrupts service.")
# interrupt all subsystems
for key in self.subsystems.keys():
if not self.subsystems[key].interrupted.triggered:
self.subsystems[key].interrupted.succeed()
# waits for update_order from higher level system
yield self.update_order
if self.update_order.triggered:
print("t = " + str(self.env.now) + " : " + self.id + " is updated.")
# update_order has been troggered by higher level system
self.update_order = self.env.event()
self.start_subsystems()
self.update_end = simpy.AllOf(self.env,
[self.subsystems[key].update_end for key in self.subsystems.keys()])
# wait for the end of the update of the subsystems
yield self.update_end
print("t = " + str(self.env.now) + " : " + self.id + " receives update-end signal.")
self.working_subsystems = [self.subsystems[key] for key in self.subsystems.keys()]
self.dysfunction_signal = simpy.AnyOf(self.env,
[syst.dysfunction_signal for syst in self.working_subsystems])
self.interrupted = self.env.event()
except simpy.Interrupt:
# here the pool system is terminated, it leaves service.
for key in self.subsystems.keys():
self.subsystems[key].lifecycle.interrupt()
self.env.exit()
Я определил еще два класса: Eu (наследуется от AtomicSystem) и ModSat (наследуется от PoolSystem). По сути, я создаю объект modsat из нескольких объектов Eu (всего два системных уровня). Я разместил код ниже:
class Eu(AtomicSystem):
def __init__(self, env, identity, mtbd, backlog, critical=True, ids=None):
self.id = identity
ids.append(self.id)
AtomicSystem.__init__(self, env, mtbd, backlog, critical, ids)
class ModSat(PoolSystem):
def __init__(self, env, digit_id, eu_mtbds_criticals, backlog, critical=True):
identity = "ModSat" + str(digit_id)
self.eus = self.initialize(env, identity, eu_mtbds_criticals, backlog)
PoolSystem.__init__(self, env, identity, self.eus, critical)
def initialize(self, env, identity, eu_mtbds_criticals, backlog):
eus = {}
for i in range(1, len(eu_mtbds_criticals) + 1):
eu_id = "EU" + str(i) + ":" + identity
eu = Eu(env, eu_id, eu_mtbds_criticals[i - 1][0], backlog, eu_mtbds_criticals[i - 1][1], [identity])
eus[eu_id] = eu
return eus
Наконец, я хотел протестировать объекты ModSat и посмотреть, смогу ли я легко заменить одну из отказавших подсистем (типа Eu) объекта modsat, не влияя на хорошее поведение modsat. Я создал функцию имитации, которая позволяет мне взаимодействовать с объектами modsat. Я провел тесты с двумя объектами modsat, определенными:
backlog = []
eu_mtbds_criticals1 = [[5, False], [11, False], [19, False]]
eu_mtbds_criticals2 = [[4, False], [27, False], [38, False]]
env = simpy.Environment()
sat1 = ModSat(env, 1, eu_mtbds_criticals1, backlog, True)
sat2 = ModSat(env, 2, eu_mtbds_criticals2, backlog, True)
constellation = {'ModSat1': sat1, 'ModSat2': sat2}
env.process(simulate(constellation, env, backlog))
env.run(until=100)
Первый тест был очень простым со следующей функцией имитации:
def simulate(constellation, env, backlog):
for key in constellation.keys():
# start service of each ModSat object included in the constellation dictionary,
# by triggering their update_order event.
constellation[key].update_order.succeed()
# wait for a while to be sure that the modsat objects have been completely simulated.
yield env.timeout(50)
Вывод — это то, что я хотел, потому что все события, кажется, были запущены и обработаны компьютером в правильном порядке:
# the 1st update_order event of PoolSystem is triggered
t = 0 : ModSat1 starts service.
t = 0 : ModSat2 starts service.
# the 1st update_order event of AtomicSystem is triggered
t = 0 : EU1:ModSat1 starts service.
t = 0 : EU3:ModSat1 starts service.
t = 0 : EU2:ModSat1 starts service.
t = 0 : EU2:ModSat2 starts service.
t = 0 : EU1:ModSat2 starts service.
t = 0 : EU3:ModSat2 starts service.
# 1st failure here. Since critical attribute of EU1:ModSat2 is set to False ModSat2 is not interrupted (partial failure)
t = 4 : EU1:ModSat2 fails.
t = 4 : EU1:ModSat2 interrupts service.
t = 4 : ModSat2 fails partially.
# 2nd failure here
t = 5 : EU1:ModSat1 fails.
t = 5 : EU1:ModSat1 interrupts service.
t = 5 : ModSat1 fails partially.
t = 11 : EU2:ModSat1 fails.
t = 11 : EU2:ModSat1 interrupts service.
t = 11 : ModSat1 fails partially.
# here the last failure of ModSat1: ModSat1 is interrupted because it has no more working Eus
t = 19 : EU3:ModSat1 fails.
t = 19 : EU3:ModSat1 interrupts service.
t = 19 : ModSat1 fails completely (no working EUs).
t = 19 : ModSat1 interrupts service.
t = 27 : EU2:ModSat2 fails.
t = 27 : EU2:ModSat2 interrupts service.
t = 27 : ModSat2 fails partially.
# here the last failure of ModSat2: ModSat2 is interrupted because it has no more working Eus
t = 38 : EU3:ModSat2 fails.
t = 38 : EU3:ModSat2 interrupts service.
t = 38 : ModSat2 fails completely (no working EUs).
t = 38 : ModSat2 interrupts service.
Теперь я хочу протестировать свой код с помощью следующей функции имитации:
def simulate(constellation, env, backlog):
for key in constellation.keys():
# start service of each ModSat object included in the constellation dictionary,
# by triggering their update_order event.
constellation[key].update_order.succeed()
# detect failure
request_signal = simpy.AnyOf(env, [constellation[key].dysfunction_signal for key in constellation.keys()])
yield request_signal
# The servicer's backlog is updated with the first item of the backlog list
print("t = " + str(env.now) + " : a service request is detected.")
servicer_backlog = []
servicer_backlog.append(backlog[0])
del backlog[0]
# the next line models the servicer time of service
yield env.timeout(5)
# The servicer gets the ID of the failed Eu to replace from its backlog
sat_id = servicer_backlog[0]['IDs'][0]
eu_id = servicer_backlog[0]['IDs'][1]
failed_eu = constellation[sat_id].eus[eu_id]
# the servicer gives the values of the attributes of the failed EU to the new EU
new_eu = Eu(failed_eu.env, failed_eu.id, failed_eu.mtbd, backlog, failed_eu.critical, failed_eu.ids)
# the failed eu is terminated (its service ends)
failed_eu.lifecycle.interrupt()
# the new EU replaces the failed_eu
constellation[sat_id].eus[eu_id] = new_eu
# the modsat concerned by the replacement has its update_order event triggered
constellation[sat_id].update_order.succeed()
print("t = " + str(env.now) + " : a service is provided")
Приведенная выше функция имитации просто моделирует замену первого вышедшего из строя Eu новым. Результат:
# the 1st update_order event of PoolSystem is triggered
t = 0 : ModSat1 starts service.
t = 0 : ModSat2 starts service.
# the 1st update_order event of AtomicSystem is triggered
t = 0 : EU3:ModSat1 starts service.
t = 0 : EU2:ModSat1 starts service.
t = 0 : EU1:ModSat1 starts service.
t = 0 : EU1:ModSat2 starts service.
t = 0 : EU2:ModSat2 starts service.
t = 0 : EU3:ModSat2 starts service.
t = 0 : ModSat1 receives update-end signal.
t = 0 : ModSat2 receives update-end signal.
# the first Eu of modsat2 fails, and its failure is detected by the simulate function
t = 4 : EU1:ModSat2 fails.
t = 4 : EU1:ModSat2 interrupts service.
t = 4 : a service request is detected.
t = 4 : ModSat2 fails partially.
# HERE IS MY CONCERN: at time t = 5, EU1 of modsat1 fails and interrupts service. However, there should be a line "t = 5 : ModSat1 fails partially" which does not appear...
t = 5 : EU1:ModSat1 fails.
t = 5 : EU1:ModSat1 interrupts service.
t = 9 : a service is provided
t = 9 : EU1:ModSat2 is terminated.
t = 9 : ModSat2 is updated.
t = 9 : EU1:ModSat2 starts service.
t = 9 : EU2:ModSat2 is updated.
t = 9 : EU3:ModSat2 is updated.
t = 9 : ModSat2 receives update-end signal.
t = 11 : EU2:ModSat1 fails.
t = 11 : EU2:ModSat1 interrupts service.
t = 13 : EU1:ModSat2 fails.
t = 13 : EU1:ModSat2 interrupts service.
t = 13 : ModSat2 fails partially.
t = 19 : EU3:ModSat1 fails.
t = 19 : EU3:ModSat1 interrupts service.
t = 27 : EU2:ModSat2 fails.
t = 27 : EU2:ModSat2 interrupts service.
t = 27 : ModSat2 fails partially.
t = 38 : EU3:ModSat2 fails.
t = 38 : EU3:ModSat2 interrupts service.
t = 38 : ModSat2 fails completely (no working EUs).
t = 38 : ModSat2 interrupts service.
Как указано выше, должна быть строка «t = 5: ModSat1 частично не работает» после строки «t = 5: EU1:ModSat1 прерывает обслуживание». Но вместо этого компьютер сразу переходит к первой строке после «yield env.timeout(5)» функции имитации.
Я не понимаю, что здесь происходит, я думаю, это из-за моего незнания того, как Simpy определяет и сортирует очередь событий. Я не мог найти в Интернете ни намека на то, что здесь происходит. Я не видел подобных вопросов на stackoverflow и других форумах. Буду рад любой помощи.
Мой код довольно длинный для объяснения, поэтому я надеюсь, что комментариев в коде, который я разместил, достаточно:\
Большое спасибо!