Как компьютер сохраняет, сортирует и обрабатывает события во время моделирования, реализованного с помощью Simpy?

Я столкнулся с проблемой синхронизации в 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 и других форумах. Буду рад любой помощи.

Мой код довольно длинный для объяснения, поэтому я надеюсь, что комментариев в коде, который я разместил, достаточно:\

Большое спасибо!


person Tristan SDJ    schedule 05.11.2016    source источник


Ответы (1)


Я (наконец-то) запустил руководство по времени в SimPy. Это все еще WIP, но вы можете следить за обсуждением здесь .

person Stefan Scherfke    schedule 12.12.2016