import asyncio
from sysmpy.relationship import *
import random
import queue
import traceback
import os
from sysmpy.entity_db import edb
from sysmpy.util import *
import sysmpy.util as util
import requests
import time
import warnings


# ========================================================================= #
#                                 Entity                                    #
# ========================================================================= #
class Entity():
    """ LIFECYCLE MODELING LANGUAGE (LML) SPECIFICATION 1.1:
    An entity is something can exist by itself and is uniquely identifiable.
    """
    _debug_mode = False

    def __init__(self, name, number=None, description=None, parent=None):
        self.name = name
        self.number = number
        self.description = description
        self.parent = parent
        self.relation = {}
        self.inv_relation = {}
        self.sim_with_decomposition = True
        self.is_root = False
        self.is_decomposed = False
        self.cloned_db = None
        self.graphic_info = None
        self.module = None

        if parent is not None:
            self.hierarchical_name = parent.hierarchical_name + '.' + name
        else:
            self.hierarchical_name = name

        # All entities created are stored in the static class Entity
        edb.store_entity(self)

    def __str__(self):
        relations = []
        for k, rels in self.relation.items():
            relations += rels

        relations = [r.__str__() for r in relations]
        return f"{self.name} -> {', '.join(relations)}"

    def __repr__(self):
        return self.name

    ########################################################################
    # Identification Methods
    #
    def numbering(self, number='E'):
        self.number = number

        if hasattr(self, 'end') and self.end is not None:
            self.end.number = number

        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                c.numbering(number+'.'+str(i+1))

    def get_numbered_name(self):
        return f'{self.number} {self.name}'

    def get_name(self, length=None):
        if length is not None:
            str = self.name[:length]+'.'
            return str

        return self.name

    def get_id(self):
        return f'ID{id(self)}'

    def set_decomposition(self, dec):
        """
        This sets the decomposition state when simulating
        :param dec: 'True' denotes to execute the simulation of sub-nodes,
                    while 'False' means to skip the sub-nodes simulation.
        """
        self.sim_with_decomposition = dec

    def set_module(self, path):
        # Check Ipython
        if 'ipython-input' in path:
            path = '[ipython]'

        self.module = path

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Property(self, name, range=None, value=None):
        obj = Property(name, range, value, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    # def decomposes(self, target):
    #     """
    #     This makes a 'decomposes' or 'decomposes' relationship between this and a target
    #     We define decomposition as composition as shown the following n-to-n relationship.
    #     # composition n to n
    #
    #     We define the relationship 'contains' as aggregation.
    #     # aggregation 1 to n
    #
    #     :param target: an entity is decomposed by this
    #     """
    #     Decomposes(self, target)

    def traced_from(self, target):
        """
        --
        """
        TracedFrom(self, target)

    def required_by(self, target):
        """
        --
        """
        RequiredBy(self, target)

    ########################################################################
    # Get or Search Methods
    # The following methods is for a local entity
    #
    def get_pairs(self):
        if 'pairs' in self.relation:
            pairs = [x.end for x in self.relation['pairs']]
        if 'paired with' in self.inv_relation:
            pairs = [x.start for x in self.inv_relation['paired with']]
        return pairs[0]

    def search_entity_by_relation(self, relation_class=[], entity_class=[], inverse=False, depth=None):
        """
           This returns an entity list by searching a relation list in advance
           e.g.,)
           properties = self.search_entity_by_relation(relation_class=[Sends], entity_class=[Property])
        """
        _, relation_results = self.search(inverse, words_search=relation_class, depth=depth)
        results = []
        for rel in relation_results:
            if inverse is False:
                entity_results, _ = rel.end.search(inverse, words_search=entity_class, depth=depth)
                results += entity_results
            elif inverse is True:
                entity_results, _ = rel.start.search(inverse, words_search=entity_class, depth=depth)
                results += entity_results

        return results

    def search(self, inverse=False, words_search=[], depth=None):
        """
        This returns an entity list and a relation list for this entity using search words
        e.g.,) entity_results, relation_results = self.search(words_search=[Requirement])
        :param inverse: 'True' means tracing inverse direction
        :param words_search: a list of searching words
            The ways getting entity objects
            (1) By class types
                search([Action, Item])
            (2) By relation types
                search([sends, receives])
            (3) By entity names
                search(['action1')
            (4) By entity hierarchical names
                search(['process1.and1.action1')
            (5) By id
                search(['ID902342'])
        :return: The entity and relation list
        """
        breaker = []
        entity_results = []
        relation_results = []

        # 1. Search for a hierarchy name ('Com1.size1')
        hierarchical_name = [w for w in words_search if isinstance(w, str) and '.' in w]
        if len(hierarchical_name) > 0:
            self.search_hierarchical_name(hierarchical_name, entity_results)
        else: # 2. Search for a simple name ('Action1), a entity type, and a relation type
            if isinstance(inverse, str) and inverse == 'both':
                self.search_op(breaker, '', False, words_search, entity_results, relation_results, depth)
                breaker = []
                self.search_op(breaker, '', True, words_search, entity_results, relation_results, depth)
            else:
                self.search_op(breaker, '', inverse, words_search, entity_results, relation_results, depth)

        return entity_results, relation_results

    def search_op(self, breaker, space, inverse, words_search, entity_results, relation_results, depth):
        # The list breaker contains all previous entities that were already used,
        # so prevent recursive search can be prevented.
        if self in breaker:
            return

        breaker.append(self)

        # When reaching the Process END, stop
        if isinstance(self, Process_END) and self.is_root:
            return

        # When reaching the predefined depth, stop
        if depth is not None:
            if len(space) > depth:
                return

        # print_out(space + f'{self.name}')
        space += ' '

        # Include this entity class if it is in the search list
        if self.__class__ in words_search and self not in entity_results:
            entity_results.append(self)
        elif self.name in words_search and self not in entity_results:
            entity_results.append(self)
        else:
            my_id = f'ID{id(self)}'
            if my_id in words_search and self not in entity_results:
                entity_results.append(self)

        if inverse is False:
            for rel_name, rel_list in self.relation.items():
                # print_out(space + f'rel: {rel_name}')

                for rel_ins in rel_list:
                    # Include this relation class if it is in the search list
                    if rel_ins.__class__ in words_search:
                        relation_results.append(rel_ins)

                    # Perform recursion
                    rel_ins.end.search_op(breaker, space, inverse, words_search, entity_results, relation_results, depth)
        elif inverse is True:
            for rel_name, rel_list in self.inv_relation.items():
                # print_out(space + f'rel: {rel_name}')

                for rel_ins in rel_list:
                    # Include this relation class if it is in the search list
                    if rel_ins.__class__ in words_search:
                        relation_results.append(rel_ins)

                    # Perform recursion
                    rel_ins.start.search_op(breaker, space, inverse, words_search, entity_results, relation_results, depth)

    def search_hierarchical_name(self, hierarchical_name, entity_results):
        for name in hierarchical_name:
            name_list = name.split('.')
            self.search_hierarchical_name_op(0, name_list, entity_results)

    def search_hierarchical_name_op(self, index, name_list, entity_results):
        name = name_list[index]
        index += 1

        if self.name != name:
            return

        if index == len(name_list):
            entity_results.append(self)
            return

        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                c.search_hierarchical_name_op(index, name_list, entity_results)


    def find_root(self):
        """
        This finds a root process.
        :return:
        A root process
        """
        if 'contained in' not in self.inv_relation:
            return self

        r = [x for x in self.inv_relation['contained in']]
        if r is None:
            return
        else:
            for x in r:
                root = x.start.find_root()
        return root

    def find_ancestor_nodes(self, class_type):
        """
        This finds all ancestor nodes by back tracking
        e.g.,)
        ret = find_all_nodes(Loop)
        :return:
        A list of all nodes created from the class rel_type
        """
        ret = []
        self.find_ancestor_nodes_op(class_type, ret)

        if len(ret) == 0:
            return None

        return ret

    def find_ancestor_nodes_op(self, class_type, ret_list):
        """
        This finds all nodes specified in the input 'rel_type'
        """

        if 'contained in' not in self.inv_relation:
            return

        found = [x for x in self.inv_relation['contained in'] if isinstance(x.start, class_type)]
        ret_list += found

        r = [x for x in self.inv_relation['contained in']]
        if r is None:
            return
        else:
            for x in r:
                x.start.find_ancestor_nodes_op(class_type, ret_list)

    def find_loop_end(self):
        """
        This returns a first Loop-End node
        """
        ret = self.find_ancestor_nodes(Loop)
        return ret[0].start.end

    #########################################################################
    # Static Methods
    # Get or Search Methods
    # The following methods is for a global entity
    #
    @staticmethod
    def debug_mode(b):
        Entity._debug_mode = b

    #########################################################################
    # Graphic Methods
    def set_graphic_info(self, **kargs):
        self.graphic_info = kargs

    def get_graphic_info(self):
        """
        :return: "strokeColor=gray;"
        """
        if self.graphic_info is not None:
            info_list = [f'{k}={v};' for k, v in self.graphic_info.items()]
            info_str = ''.join(info_list)
            return info_str

        return ''

# ========================================================================= #
#                             Static Entity                                 #
# ========================================================================= #
class StaticEntity(Entity):
    """ This is an abstract class regarding Static Entity"""

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def __str__(self):
        str = self.name
        # if 'decomposes' in self.relation:
        #     rel = [x.end for x in self.relation['decomposes']]
        #     s = ', '.join([r.name for r in rel])
        #     str += ": decomposes [{}]".format(s)

        if 'performs' in self.relation:
            rel = [x.end for x in self.relation['performs']]
            s = ', '.join([r.name for r in rel])
            str += ": performs [{}]".format(s)

        if 'traced from' in self.relation:
            rel = [x.end for x in self.relation['traced from']]
            s = ', '.join([r.name for r in rel])
            str += ": traced from [{}]".format(s)

        if 'required by' in self.relation:
            rel = [x.end for x in self.relation['required by']]
            s = ', '.join([r.name for r in rel])
            str += ": required by [{}]".format(s)

        return str


# ========================================================================= #
#                                Property                                   #
# ========================================================================= #
class Property(StaticEntity):
    def __init__(self, name, range=None, value=None, parent=None):
        """
        :param name: e.g., ) 'X'
        :param range: e.g., ) 'Less(10)', 'More(10)', 'Between(1, 10)',
        [1, 2, 3], ['A', 'B', 'C'], 'Normal(10, 1)', 'Uniform(1, 10)'
        ['Between(1, 10)', 'Between(30, 40)']
        :param value: e.g., ) 'T', 10
        """
        super().__init__(name, parent=parent)
        self.range = range
        self.value = value
        self.updated = False

    def set_value(self, value):
        self.updated = True
        self.value = value

    def get_random_value(self):
        # "updated" means this value was updated by a certain function
        # In this case, the random number generation is not used.
        if self.updated is True:
            self.updated = False
            return self.value
        elif self.updated is False:
            if isinstance(self.range, list):
                self.value = random.choice(self.range)
                return self.value
            else:
                self.value = self.range.get_random_value()
                return self.value

    def get_pop_one(self):
        if isinstance(self.range, list):
            self.value = self.range.pop(0)
            return self.value


# ========================================================================= #
#                                  Item                                     #
# ========================================================================= #
class Item(StaticEntity):
    def __init__(self, name, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)

        # Item size is used for conduit operation
        self._size = 0

    def size(self, s):
        self._size = s


# ========================================================================= #
#                                  Conduit                                  #
# ========================================================================= #
class Conduit(StaticEntity):
    """
    Conduit is a kind of link or gui.
    Conduit is associated with items.
    """

    def __init__(self, name, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self._capacity = 1
        self._delay = 0

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def transfers(self, item):
        """
        This makes a relationship between this and an item
        :param item: an item is transferred by this conduit
        """
        Transfers(self, item)

    def delay(self, delay):
        """
        This represents the time required to transmit an item over this Conduit.
        :param delay:
        """
        self._delay = delay

    def capacity(self, capacity):
        """
        This represents the maximum rate supported by the Conduit.
        :param capacity:
        """
        self._capacity = capacity


# ========================================================================= #
#                                  Resource                                 #
# ========================================================================= #
class Resource(StaticEntity):
    def __init__(self, name, amount=10, minimum=1, maximum=10, units=None, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self.amount = amount
        self.minimum = minimum
        self.maximum = maximum
        self.units = units


# ========================================================================= #
#                                Requirement                                #
# ========================================================================= #
class Requirement(Property):
    def __init__(self, name, range=None, value=None, parent=None, **kwargs):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, range, value, parent=parent)
        self.kwargs = kwargs
        self.is_root = True

    def check_property(self):
        if 'traced to' in self.inv_relation:
            rel = [x.start for x in self.inv_relation['traced to']]

            l = {r.name : r.value for r in rel}

            is_passed = 'passed' if rel[0].value in self.range else 'failed'

            str = f'{self.name} Range is {self.range}: {l} {is_passed}'

            print(str)

            return is_passed

        return None

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Requirement(self, name, range=None, value=None, **kwargs):
        obj = Requirement(name, range, value, parent=self, **kwargs)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def __str__(self):
        """Overriding StaticEntity.__str__ so that it prints kwargs as well (useful for debugging).
        :return: super().__str__ appended with self.kwargs
        """
        ret = super(Requirement, self).__str__()
        ret += " " + str(self.kwargs) + '\n'
        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                ret += c.__str__()

        return ret


# ========================================================================= #
#                                Component                                  #
# ========================================================================= #
class Component(StaticEntity):
    def __init__(self, name, parent=None, **kwargs):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self.kwargs = kwargs
        self.is_root = True

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def performs(self, target):
        Performs(self, target)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Component(self, name, **kwargs):
        obj = Component(name, parent=self, **kwargs)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


# ========================================================================= #
#                            Dynamic Entity                                 #
# ========================================================================= #
class DynamicEntity(Entity):
    """ This is an abstract class regarding Dynamic Entity"""

    def __init__(self, name, duration=0, parent=None):
        super().__init__(name, parent=parent)
        self.duration = duration
        self.time = 0
        self.total_time = 0
        self.waiting = False
        self.function = None
        self.sim_network = []

    def reset_waiting(self):
        self.waiting = False

    def reset_sim_status(self):
        self.waiting = False
        self.time = 0
        # self.total_time = 0
        self.is_root = False

        for rels in self.relation:
            rel = self.relation[rels]
            for r in rel:
                r.reset_sim_status()

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def seizes(self, resource, amount=10):
        """
        This make a relation 'seizes' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be taken
        """
        Seizes(self, resource, amount)

    def consumes(self, resource, amount=10):
        """
        This make a relation 'consumes' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be taken
        """
        Consumes(self, resource, amount)

    def produces(self, resource, amount=10):
        """
        This make a relation 'produces' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be produced
        """
        Produces(self, resource, amount)

    def flow(self, e):
        """
        This make a relation 'flow' between actions
        The relation 'flow' is used to represent a control flow between dynamic entities (e.g., Process A to Action B)

        :param
        e: The target dynamic entity of this dynamic entity
        """
        Flow(self, e)

    def receives(self, item):
        """
        This make a relation 'receives' between this action and an input trigger item
        The input trigger item differs from the input item that does not influence this action

        :param
        item: The input trigger item
        """
        Receives(self, item)

    def triggered(self, item):
        """
        This make a relation 'triggered' between this action and an input trigger item
        The input trigger item differs from the input item that does not influence this action

        :param
        item: The input trigger item
        """
        Triggered(self, item)

    def sends(self, item, receiver=None, amount=10):
        """
        This make a relation 'sends' between this DEntity and the target item
        The DEntity will fire, when the target resource are available

        :param
        item: The target item
        amount: The amount that will be sent
        """
        Sends(self, item, amount)
        if receiver is not None:
            receiver.receives(item)

    def func(self, function):
        self.function = function

    ########################################################################
    # RM_sim Methods
    #
    #
    def make_network(self):
        """
        This create a simulation network containing flows between nodes
        :return:
        """

        # Make a new entity db corresponding to the original entity db
        # After using the new entity db, this will be removed.
        # cloned_db = edb.get_cloned_db(path=self.module)

        # The new cloned db is stored in the original entity,
        # so after simulation, all result data can be accessed.
        # self.cloned_db = cloned_db

        # Get a new process from the cloned db
        # new_en = cloned_db.get(self.name, path=self.module)

        # Make a simulation network using the new process
        # new_en.make_network_op(new_en.sim_network)
        self.make_network_op(self.sim_network)

        # Set the cloned db to all entities
        # new_en.cloned_db = cloned_db
        # for e in new_en.sim_network:
        #     e.cloned_db = cloned_db

        return self

    def init_sim_network(self):
        """
        This create a simulation network containing flows between nodes
        :return:
        """
        # Make a network for the action model
        new_en = self.make_network()

        # Reset all states in the elements of the network
        for en in new_en.sim_network:
            en.reset_sim_status()

        new_en.reset_sim_status()

        # The function sim is called, which means this is a root process
        new_en.is_root = True
        new_en.end.is_root = True

        # The root process is linked to 'process_END' using the relation 'flow'
        # 'process_END' -> 'process'
        new_en.end.flow(new_en)

        # For the relation 'process_END' -> 'process', set 'sent = true' to activate the root process
        flowed_from = [r for r in new_en.inv_relation['flowed from'] if isinstance(r, Flow)]
        flowed_from[0].sent = True

        return new_en

    def make_network_op(self, entities):
        """
        This finds all node flows
        The returning list acts will have flow nodes which are activated by Python Asyncio

        :param
        entities: All entities that will be accumulated
        """

        if 'contains' in self.relation:
            contains = [x.end for x in self.relation['contains'] if not isinstance(x.end, Property)] # Property does not belong to the simulation network
            if contains is not None:
                entities += contains
                cur_e = None
                last_e = []
                for e in contains:
                    # if not isinstance(e, Property): # Property does not belong to the simulation network
                    if isinstance(self, And) or isinstance(self, Or) or isinstance(self, XOr) or isinstance(self, Condition):
                        # If e is not 'or' or 'and', then make a parallel flow
                        self.flow(e)
                        last_e.append(e)
                    else:
                        # If e is not 'or' or 'and', then make a serial flow

                        if cur_e is None:
                            # e.g.,) (self)Process1->(e)Action1
                            self.flow(e)
                        else:
                            if hasattr(cur_e, 'end') and cur_e.sim_with_decomposition:
                                # 1. 'cur_e.sim_with_decomposition' is checked for skipping the decomposition of sub-nodes
                                # Especially, 'Action' can be decomposed.
                                # 2. This 'cur_e' decomposes the node 'end'
                                # This means that the next node 'e' should be connected to the node 'end'
                                # e.g.,) (cur_e)Loop->Action1->Loop_END->Action2
                                # 'Action2' is linked to 'Loop_END', but not 'Loop'
                                cur_e.end.flow(e)
                            else:
                                cur_e.flow(e)

                        cur_e = e

                    # If this entity does not allow the decomposition, don't make any sub-flows for simulation
                    if e.sim_with_decomposition:
                        e.make_network_op(entities)

                    # If this entity is Action and the current child is Process,
                    # this means this is a decomposed entity
                    # The decomposed entity is set on here
                    if isinstance(e, Process) and isinstance(self, Action):
                        self.is_decomposed = True

                ######################################################################
                # Concatenate this node to the 'END' node
                #
                if cur_e is not None:
                    last_e.append(cur_e)

                if self.end is not None:
                    for e in last_e:
                        if isinstance(e, Action):
                            # If e does not have the node 'end' (e.g., 'Action')
                            # This means it doesn't have any pair
                            e.flow(self.end)
                            # entities.append(self.end)
                        elif isinstance(e, END) or isinstance(e, ExitLoop) :
                            # If e is the class 'END', this means e will terminate with the root process end
                            # When END is created, it is already associated with the root process end, so skip
                            pass
                        else:
                            # If e decomposes the node 'end' (e.g., 'Loop' and 'Action' containing 'process')
                            # This means there is the 'End' pair for this
                            # This 'End' pair is used for flowing to the current 'End' pair
                            e.end.flow(self.end)
                            # entities.append(self.end)

                    entities.append(self.end)

        elif isinstance(self, Process):   # This is likely to be an empty process (no 'contains' key)
            # Just let this root process to flow directly to the end
            self.flow(self.end)
            # Make sure the list contains the end node
            entities.append(self.end)

    def print_flows(self, entities):
        """
        This prints out the control flows of UC
        :param entities: the flow list generated from 'init_sim_network()'
        """
        flows = [r for r in self.relation['flows'] if isinstance(r, Flow)]
        for f in flows:
            print(f)

        for e in entities:
            flows = [r for r in e.relation['flows'] if isinstance(r, Flow)]
            for f in flows:
                print(f)

    async def run(self):
        """
        This is a sysmpy function that performs all operation about simulation
        Each DynamicEntity uses this function 'run' to do its own simulation

        There are two phases
            Phase 1: Check Activating Conditions, so that this can enter the activation stage of simulation
            Phase 2: Perform Action in which all defined activities are performed
        """
        while Process.activated:
            # Info is used to store any information in this process and be printed out by 'Process.store_event()'
            info = ''
            ##################################################################################
            #
            #       Phase 1: Check Activating Conditions
            #
            ##################################################################################

            if self.name == 'p1_END':
                print('I am this!')

            ########################################################################
            # Check 1: Check waiting state
            # Any process can be waiting, if there is no event to this process
            if self.waiting is False:
                Process.store_event(self, 'waiting')
                self.waiting = True

            # Reset all fire states
            # If all of these are true, then this process will fire all events to the child processes
            b_flow_fire = False
            b_consumes_fire = False
            b_seizes_fire = False
            b_triggered_fire = False

            ########################################################################
            # Check 2: Input Control Flows
            # This is receiving control flows
            # Non input flows mean that this is a root node
            # Such root node is just firing all events regardless any conditions
            flowed_from = None
            flows_true = None

            # Take a list for the relationship 'flowed from'
            if 'flowed from' in self.inv_relation:
                flowed_from = [r for r in self.inv_relation['flowed from'] if isinstance(r, Flow)]

            if flowed_from is None:
                # No flowed_from means there are no input events for this node
                # There are two cases: (1) There is End() or ExitLoop() before this
                return
            else:
                # Having flowed_from means there are input events for this node
                flows_true = [r for r in flowed_from if r.sent is True]

                # Check whether all input events are set to 'sent=True'
                # This means this node now can fire its output event
                if len(flowed_from) == len(flows_true) and len(flowed_from) > 0:
                    b_flow_fire = True

                # If this self is 'or_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Or_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                if isinstance(self, XOr_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Condition_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Condition_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Process_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Process_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Loop_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Loop_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'loop' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Loop) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this flow should be fired,
                # All process which sent were reset to 'sent = False'
                # if b_flow_fire:
                #     for r in flows_true:
                #         r.sent = False
                #
                #     # Set a new start time for this process by checking the times of the previous flow entities
                #     max_time = max([r.start.time for r in flowed_from])
                #     self.time = max_time
                #     info += 'Fired from: ' + ', '.join([r.start.name for r in flows_true])

            ########################################################################
            # Check 3: Resources Consumes
            consumes = None
            consumes_true = None

            # Take a list for the relationship 'consumes'
            if 'consumes' in self.relation:
                consumes = [r for r in self.relation['consumes'] if isinstance(r, Consumes)]

            if consumes is None:
                # No seizes means there are no resource events for this node
                b_consumes_fire = True
            else:
                # Having seizes means there are resource events for this node
                consumes_true = [r for r in consumes if r.amount <= r.end.amount]

                # Check whether all input events are set to 'sent=True'
                # This means this node now can fire its output event
                if len(consumes) == len(consumes_true) and len(consumes) > 0:
                    b_consumes_fire = True

            ########################################################################
            # Check 3: Resources Seizes
            seizes = None
            seizes_true = None

            # Take a list for the relationship 'seizes'
            if 'seizes' in self.relation:
                seizes = [r for r in self.relation['seizes'] if isinstance(r, Seizes)]

            if seizes is None:
                # No seizes means there are no resource events for this node
                b_seizes_fire = True
            else:
                # Having seizes means there are resource events for this node
                seizes_true = [r for r in seizes if r.amount <= r.end.amount]

                # Check whether all input events are set to 'True'
                # This means this node now can fire its output event
                if len(seizes) == len(seizes_true) and len(seizes) > 0:
                    b_seizes_fire = True

            ########################################################################
            # Check 4: Triggered Item

            triggered = None
            triggered_true = None

            # Take a list for the relationship 'triggered by'
            if 'triggered by' in self.relation:
                triggered = [r for r in self.relation['triggered by'] if isinstance(r, Triggered)]

            if triggered is None:
                # No receives means there are no triggering events for this node
                b_triggered_fire = True
            else:
                # Having receives means there are triggering events for this node
                triggered_true = [r for r in triggered if r.trigger is True]

                # Check whether all input events are set to 'trigger=True'
                # This means this node now can fire its output event
                if len(triggered) == len(triggered_true) and len(triggered) > 0:
                    b_triggered_fire = True

                    for r in triggered_true:
                        r.trigger = False

                    # Set a new start time for this process by checking the times from the process that triggered
                    #----------------------------------------------------
                    # Warning: This can make an error. We need to check only triggered items, but not common items.
                    pre_actions = [r.end.inv_relation['sent from'] for r in triggered_true]
                    max_time = max([r.start.time for r in pre_actions[0]])
                    self.time = max(max_time, self.time)
                    info += ' triggered from: ' + ', '.join([r.end.name for r in triggered_true])

                    # If this is associated with conduits, the times for conduits should be applied
                    # Calculate the conduit times
                    if 'transferred by' in r.end.inv_relation:
                        conduits = [r.end.inv_relation['transferred by'] for r in triggered_true]
                        max_delay = max([r.start._delay  for r in conduits[0]])
                        max_capacity = max([r.start._capacity for r in conduits[0]])
                        max_size = max([r.end._size for r in conduits[0]])
                        max_conduits_time = max_delay + max_size/max_capacity

                        Process.store_event(self, 'triggered', info=f'After conduit flows, '
                                                f'a time {max_conduits_time} was added')

                        # Apply the conduit times to this process time
                        self.time += max_conduits_time

            ##################################################################################
            #
            #       Phase 2: Perform Action
            #
            ##################################################################################
            # if self.name == 'A':
            #     print_out(self.name, b_flow_fire, b_seizes_fire, b_consumes_fire, b_triggered_fire)
            #     print_out('!')

            if b_flow_fire and b_seizes_fire and b_consumes_fire and b_triggered_fire:
                ########################################################################
                # Check 0: If this flow should be fired,
                # All process which sent were reset to 'sent = False'
                for r in flows_true:
                    r.sent = False

                # Set a new start time for this process by checking the times of the previous flow entities
                max_time = max([r.start.time for r in flowed_from])
                self.time = max_time
                info += 'Fired from: ' + ', '.join([r.start.name for r in flows_true])

                ########################################################################
                # Check 1: Activate this action
                Process.store_event(self, 'activated', info=info)
                self.waiting = False

                ########################################################################
                # Check 2: Check global time
                Process.global_time = max(Process.global_time, self.time)
                if Process.global_max is not None and Process.global_time >= Process.global_max:
                    Process.activated = False

                ########################################################################
                # Check 3: Resources Seizes
                # Seizes all related resources
                if seizes_true is not None:
                    for r in seizes_true:
                        r.end.amount -= r.amount
                        s = ''
                        s += ' Resources(' + ', '.join([r.end.name for r in seizes_true])+')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in seizes_true])+')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in seizes_true])+')'
                        Process.store_event(self, 'seizes resources', info=f'[{s}]')

                ########################################################################
                # Check 4: Sends Item
                # Note that 'Sends Item' is for trigger items, but not common items
                # Because the simulation uses only the trigger items
                # Later we may be able to use the common items in some purposes
                if 'sends' in self.relation:
                    sends = [x for x in self.relation['sends'] if isinstance(x, Sends)]
                    for r in sends:
                        r.trigger = True
                        # Check all send relations associated with the item of this relation r
                        sent_from_for_item = [x for x in r.end.inv_relation['sent from'] if isinstance(x, Sends)]
                        sent_from_for_item_true = [x for x in sent_from_for_item if x.trigger is True]

                        if len(sent_from_for_item) == len(sent_from_for_item_true):
                            # All relations 'sent from' were set to 'True'
                            # Then we can fire the related trigger items.
                            # So, set all relation 'triggered' in this item as 'True',
                            # if it decomposes the relation 'triggered'
                            if 'triggers' in r.end.inv_relation:
                                triggered_for_item = [x for x in r.end.inv_relation['triggers'] if isinstance(x, Triggered)]
                                for r2 in triggered_for_item:
                                    r2.trigger = True

                                s = ''
                                s += 'To : ' + ', '.join([r.end.name for r in triggered_for_item])
                                Process.store_event(self, 'sends', info=f'[{s}]')

                ########################################################################
                # Check 5: Produces Resources
                if 'produces' in self.relation:
                    produces = [x for x in self.relation['produces'] if isinstance(x, Produces)]
                    for r in produces:
                        r.end.amount += r.amount
                        Process.store_event(self, 'produces resources', info=f'[New resources({r.amount}) '
                                                                            f'Total resources({r.end.amount})]')

                ########################################################################
                # Check 6: Resources Consumes
                if consumes_true is not None:
                    for r in consumes_true:
                        r.end.amount -= r.amount
                        s = ''
                        s += ' Consumes resources(' + ', '.join([r.end.name for r in consumes_true]) + ')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in consumes_true]) + ')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in consumes_true]) + ')'
                        Process.store_event(self, 'consumes resources', info=f'[{s}]')

                ########################################################################
                # Check 7: Input Control Flows
                # Send all output events
                if 'flows' in self.relation:
                    flows = [x for x in self.relation['flows'] if isinstance(x, Flow)]

                    if isinstance(self, Action):
                        # Perform a function script defined in the script
                        # If there are no function and the properties of the items associated with the function,
                        # then the values of the properties are randomly sampled.
                        if self.function is not None:
                            # selected = self.function(self.cloned_db)
                            selected = self.function(edb)
                        else:
                            properties = self.search_entity_by_relation(relation_class=[Sends], entity_class=[Property])
                            for p in properties:
                                # This property performs the sampling by itself
                                p.get_random_value()

                    if isinstance(self, XOr): # Select only one process
                        selected = random.choice(flows)
                        Process.store_event(self, 'selects ' + selected.end.name)
                        selected.sent = True

                    if isinstance(self, Or): # Select at least one process
                        or_flows = or_selector(flows)
                        selected = random.choice(or_flows)
                        for sel in selected:
                            if sel is not None:
                                Process.store_event(self, 'selects ' + sel.end.name)
                                sel.sent = True
                    elif isinstance(self, Condition):
                        if self.function is not None:
                            # Perform a function script for selection
                            selected = self.function(edb)
                            # selected = self.function(self.cloned_db)

                            # Because 'selected' is the original process in db and r.end is a copied process,
                            # self.cloned_db.is_same() is used to check their same identity.
                            # selected_flows = [r for r in flows if self.cloned_db.is_same(r.end, selected)]
                            selected_flows = [r for r in flows if edb.is_same(r.end, selected)]
                            Process.store_event(self, 'selects ' + ', '.join([r.end.name for r in selected_flows]))
                            for r in selected_flows:
                                r.sent = True
                        else:
                            # Perform uniformly random selection for the branches
                            selected = random.choice(flows)
                            Process.store_event(self, 'selects ' + selected.end.name)
                            selected.sent = True
                    elif isinstance(self, Loop_END):
                        exit_loop = [x for x in flows_true if isinstance(x.start, ExitLoop)]

                        if self.count >= self.times-1 or len(exit_loop) > 0:
                            Process.store_event(self, 'finished repetition')
                            self.reset_waiting()
                            loops = [x for x in self.relation['flows'] if not isinstance(x.end, Loop)]
                            for x in loops:
                                x.sent = True
                        else:
                            self.count += 1
                            Process.store_event(self, 'repeats ' + str(self.count))
                            loops = [x for x in self.relation['flows'] if isinstance(x.end, Loop)]
                            for x in loops:
                                x.sent = True

                    elif isinstance(self, Process_END) and self.is_root:
                        self.reset_waiting()
                        for x in flows:
                            x.sent = True
                    else:
                        # 'And' or other entities will send all flows to their child entities.
                        for x in flows:
                            x.sent = True

                ########################################################################
                # Check 8: Properties for a process
                # A process can have properties which can be sampled, when the process was ended.
                if isinstance(self, Process_END):
                    proc = self.get_pairs()

                    if proc.function is not None:
                        # selected = proc.function(self.cloned_db)
                        selected = proc.function(edb)
                    else:
                        # Search properties of this process
                        list_properties, _ = proc.search(words_search=[Property], depth=1)

                        for p in list_properties:
                            # This property performs the sampling by itself
                            p.get_random_value()

                ########################################################################
                # Check 9: Complete this process
                self.time += self.duration
                self.total_time += self.duration
                Process.store_event(self, 'completed', info='')

                ########################################################################
                # Check 10: Resources Seizes
                # Release all seized resources
                if seizes_true is not None:
                    for r in seizes_true:
                        r.end.amount += r.amount
                        s = ''
                        s += ' Resources(' + ', '.join([r.end.name for r in seizes_true]) + ')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in seizes_true]) + ')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in seizes_true]) + ')'
                        Process.store_event(self, 'releases resources', info=f'[{s}]')

                ########################################################################
                # Check 11: One iteration of the root process was done
                if isinstance(self, Process_END) and self.is_root:
                    # Process.store_event(self, 'decomposes done as the root process', info='')
                    Process.store_event(self, f'simulation {Process.simulation_count}', info='')

                    # Global simulation count increases
                    Process.simulation_count += 1

                    # Save proper simulation results
                    proc = self.get_pairs()

                    if Process.global_max is None:
                        Process.store_event(self, 'deactivating root process now', info='')
                        Process.activated = False

            else:
                ########################################################################
                # Waiting all action internal operations
                await asyncio.sleep(0)


# ========================================================================= #
#                                Action                                     #
# ========================================================================= #
class Action(DynamicEntity):
    """
    Action Dynamic Entity
    """
    def __init__(self, name, duration=1, parent=None):
        super().__init__(name, duration, parent=parent)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        # This action decomposes a sub-process
        # Consequently, this action will have the 'Action_end'
        self.end = Action_END(self.name + '_END', parent=self)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

        return obj


class Action_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                And                                        #
# ========================================================================= #
class And(DynamicEntity):
    """
    And Dynamic Entity
    """
    def __init__(self, name='and', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = And_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class And_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                   Or                                      #
# ========================================================================= #
class Or(DynamicEntity):
    """
    Or Dynamic Entity
    """
    def __init__(self, name='or', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Or_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Or_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                  XOr                                      #
# ========================================================================= #
class XOr(DynamicEntity):
    """
    XOr Dynamic Entity
    """

    def __init__(self, name='xor', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = XOr_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class XOr_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                Condition                                  #
# ========================================================================= #
class Condition(DynamicEntity):
    """
    Condition Dynamic Entity
    """

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Condition_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Condition_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                    END                                    #
# ========================================================================= #
class END(DynamicEntity):
    """
    END Dynamic Entity
    """

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                 ExitLoop                                  #
# ========================================================================= #
class ExitLoop(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                 Process                                   #
# ========================================================================= #
class Process(DynamicEntity):
    """
    Process Dynamic Entity
    """

    # 'activated' is used to denote SMRE_Example's activation. False means 'stop simulation'
    activated = False
    # 'global_time' is used to denote SMRE_Example's global time
    global_time = 0
    # 'global_max' is used to denote when SMRE_Example will stop
    global_max = None
    # RM_sim count is used to count each simulation run
    simulation_count = 0
    # simulation results are stored in queue
    event_queue = queue.Queue()
    # is there a web distributor
    web_distributor = False

    def __init__(self, name, parent=None):
        # Find a module which is using this class
        # and set the caller's path to this class, so we can know where this object came form:
        # - last element ([-1]) is me, the one before ([-2]) is my caller.
        # - The first element in caller's data is the filename
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)

        # Loop can inherit process. And a pure process uses the following codes.
        if isinstance(self, Loop) is False:
            self.is_root = True

            # This decomposes a pair dynamic entity called 'end' or name+'_END'
            self.end = Process_END(name + '_END', parent=parent)

            # Link this and the end using the relation 'Pairs'
            Pairs(self, self.end)

            # AsyncIO workers are used to be performed as computing threads
            self.workers = []

            # sim_network is a network containing all processes, actions, and items associated with simulation
            self.sim_network = []

    ########################################################################
    # Analysis Methods
    #
    #
    def evaluate_interfaces(self):
        """
        This checks interfaces between functions (or actions)
        """
        from sysmpy.interface_analyzer import InterfaceAnalyzer as IA
        ia = IA(self)
        return ia.get_critical_elements(self), \
               ia.get_sphere_of_influence(self), \
               ia.get_feedback_elements(self), \
               ia.get_recursive_elements(self), \
               ia.get_absence_items(self), \
               ia.get_unused_items(self)

    def evaluate_requirements(self):
        """
        This checks all requirements and evaluates them using simulation results
        """

        entity_results, relation_results = self.search(words_search=[Requirement])

        evaluation_results = {}
        for e in entity_results:
            # print_out(e)
            is_passed = e.check_property()

            evaluation_results[e.name] = is_passed

        return evaluation_results

    def get_action_times(self):
        # if self.current_clone is not None:
        #     actions, _ = self.current_clone.search(words_search=[Action])
        # else:
        #     actions, _ = self.search(words_search=[Action])
        path = os.path.splitext(traceback.extract_stack()[-2][0])[0]

        actions = edb.get(Action, path=path)
        # actions = self.cloned_db.get(Action, path=path)

        dict_action = {x.name:x.total_time for x in actions}
        return dict_action

    def view(self):
        """
        This opens a web browser to show simulation results
        """
        if self.properties is not None:
            property_list = [f"'{x.name}':0" for x in self.properties]
            prop_str = '{'+", ".join(property_list) + '}'
            u = f"http://127.0.0.1:9191/pc/?g={prop_str}"
            import webbrowser
            webbrowser.open(u)
            time.sleep(0.5)

    ########################################################################
    # staticmethod
    #
    #
    @staticmethod
    def store_event(act, event, info=''):
        if Entity._debug_mode:
            if len(info) == 0:
                print_out(f'T: {act.time}, N: {act.name}, E: {event}')
            else:
                print_out(f'T: {act.time}, N: {act.name}, E: {event}, I: {info}')
        else:
            # Ignore unnecessary entities
            if isinstance(act, And) or \
               isinstance(act, And_END) or \
               isinstance(act, Loop) or \
               isinstance(act, Loop_END) or \
               isinstance(act, Condition) or \
               isinstance(act, Condition_END) or \
               isinstance(act, END) or \
               isinstance(act, ExitLoop) or \
               isinstance(act, Process) or \
               isinstance(act, Or) or \
               isinstance(act, Or_END):
                return

            # If process-end
            if isinstance(act, Process_END):
                if event != 'waiting' and \
                   event != 'activated' and \
                   event != 'completed' :
                    print_out(f'At Time {act.time}, "{act.name}" {event}.')
                    evt = {'class': act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                    Process.event_queue.put(evt)

                    if Process.web_distributor is True:
                        root_process = act.get_pairs()
                        # If this root_process contains any properties in the child entities
                        # Send the values of the properties
                        if root_process.properties is not None and len(root_process.properties) > 0:
                            data = {}

                            # Make data containing property values
                            for p in root_process.properties:
                                data[p.hierarchical_name] = p.value

                            # Check the property values with the requirement ranges
                            evaluation_results = root_process.evaluate_requirements()

                            data.update(evaluation_results)

                            # Send the data to all clients
                            Process.send_event_to_web_distributor(data)
            else:
                if Entity._debug_mode:
                    if event != 'waiting':
                        print_out(f'At Time {act.time}, "{act.name}" {event}.')
                        evt = {'class':act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                        Process.event_queue.put(evt)
                else:
                    print_out(f'At Time {act.time}, "{act.name}" {event}.')
                    evt = {'class': act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                    Process.event_queue.put(evt)

    @staticmethod
    def get_event():
        if Process.event_queue.empty():
            return None
        return Process.event_queue.get_nowait()

    @staticmethod
    def check_web_distributor():
        """
        The web distributor is a web server sending simulation events to the connected clients
        """
        try:
            r = requests.get(f"http://127.0.0.1:9191")
            Process.web_distributor = True
            print_out('The simulation is connected to the web distributor.')
        except requests.exceptions.RequestException as e:
            # raise SystemExit(e)
            Process.web_distributor = False

    @staticmethod
    def send_event_to_web_distributor(body):
        """
        This sends simulation events to the web distributor
        """
        try:
            # sess = requests.Session()
            # adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
            # sess.mount('http://', adapter)
            sim_url = f"http://127.0.0.1:9191/sim_updated?g={body}"
            r = requests.get(sim_url)
            # print_out("send events to web_distributor", str(r))
            time.sleep(0.5)
        except requests.exceptions.RequestException as e:
            Process.web_distributor = False
            raise SystemExit(e)

    async def sim(self, until=5, property_view=False, print_out=False, use_web_distributor=False):
        """
        This triggers the simulation.

        :param
        until: The maximum time, modeled in the process flows, for simulation run
        If until is None, the simulation performs just one time.
        """

        # Check whether this contains actions. If not, simulation can't work
        actions, _ = self.search(words_search=[Action])
        if len(actions) == 0:
            warnings.warn("At least one action is required.", stacklevel=2)
            return

        util.is_print_out = print_out

        # Find other flows and set them into asyncio
        if 'flows' or 'contains' in self.relation:
            # Reset all global variables regarding simulation
            Process.simulation_count = 0
            Process.global_time = 0
            Process.activated = True
            Process.global_max = until

            # get flows from the relation 'flow' or 'contains'
            new_proc = self.init_sim_network()

            # print out control flows of UC
            new_proc.print_flows(new_proc.sim_network)

            # check whether a web distributor exist or not
            # The web distributor is a web server sending simulation events to the connected clients
            if use_web_distributor is True:
                new_proc.check_web_distributor()

            # get list of properties which are updated by the simulation
            new_proc.properties, _ = new_proc.search(words_search=[Property])

            # open a web browser for the property view
            if property_view is True:
                new_proc.view()

            workers = [x.run() for x in new_proc.sim_network]
            workers.append(new_proc.run())

            try:
                res = await asyncio.gather(*workers)
                print('--------- Simulation Completed ---------')

                del new_proc

            except asyncio.CancelledError:
                print('CancelledError')


    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Action(self, name, duration=1):
        """
        :param name:
        :param duration: The time modeled in the process flows
        :return:
        """
        obj = Action(name, duration, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def And(self, *args):
        obj = And(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def Or(self, *args):
        obj = Or(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def XOr(self, *args):
        obj = XOr(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def Loop(self, name, times=2):
        obj = Loop(name=name, times=times, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def Condition(self, name, *args):
        obj = Condition(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:

            ret.append(obj)

            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def End(self, name='END'):
        obj = END(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        root_process = obj.find_root()
        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        obj.flow(root_process.end)
        return obj

    def ExitLoop(self, name='ExitLoop'):
        obj = ExitLoop(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        loop_end = obj.find_loop_end()
        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        obj.flow(loop_end)
        return obj


class Process_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                   Loop                                    #
# ========================================================================= #
class Loop(Process):
    """
    Loop Dynamic Entity
    """

    def __init__(self, name='loop', times=2, parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Loop_END(name + '_END', times=times, parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        self.end.flow(self)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Loop_END(DynamicEntity):
    def __init__(self, name, times=2, parent=None):
        super().__init__(name, parent=parent)

        # 'loop_END' is set with the looping times
        self.times = times
        self.count = 0

    def reset_waiting(self):
        super().reset_waiting()
        self.count = 0

import asyncio
from sysmpy.relationship import *
import random
import queue
import traceback
import os
from sysmpy.entity_db import edb
from sysmpy.util import *
import sysmpy.util as util
import requests
import time
import warnings


# ========================================================================= #
#                                 Entity                                    #
# ========================================================================= #
class Entity():
    """ LIFECYCLE MODELING LANGUAGE (LML) SPECIFICATION 1.1:
    An entity is something can exist by itself and is uniquely identifiable.
    """
    _debug_mode = False

    def __init__(self, name, number=None, description=None, parent=None):
        self.name = name
        self.number = number
        self.description = description
        self.parent = parent
        self.relation = {}
        self.inv_relation = {}
        self.sim_with_decomposition = True
        self.is_root = False
        self.is_decomposed = False
        self.cloned_db = None
        self.graphic_info = None
        self.module = None

        if parent is not None:
            self.hierarchical_name = parent.hierarchical_name + '.' + name
        else:
            self.hierarchical_name = name

        # All entities created are stored in the static class Entity
        edb.store_entity(self)

    def __str__(self):
        relations = []
        for k, rels in self.relation.items():
            relations += rels

        relations = [r.__str__() for r in relations]
        return f"{self.name} -> {', '.join(relations)}"

    def __repr__(self):
        return self.name

    ########################################################################
    # Identification Methods
    #
    def numbering(self, number='E'):
        self.number = number

        if hasattr(self, 'end') and self.end is not None:
            self.end.number = number

        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                c.numbering(number+'.'+str(i+1))

    def get_numbered_name(self):
        return f'{self.number} {self.name}'

    def get_name(self, length=None):
        if length is not None:
            str = self.name[:length]+'.'
            return str

        return self.name

    def get_id(self):
        return f'ID{id(self)}'

    def set_decomposition(self, dec):
        """
        This sets the decomposition state when simulating
        :param dec: 'True' denotes to execute the simulation of sub-nodes,
                    while 'False' means to skip the sub-nodes simulation.
        """
        self.sim_with_decomposition = dec

    def set_module(self, path):
        # Check Ipython
        if 'ipython-input' in path:
            path = '[ipython]'

        self.module = path

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Property(self, name, range=None, value=None):
        obj = Property(name, range, value, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    # def decomposes(self, target):
    #     """
    #     This makes a 'decomposes' or 'decomposes' relationship between this and a target
    #     We define decomposition as composition as shown the following n-to-n relationship.
    #     # composition n to n
    #
    #     We define the relationship 'contains' as aggregation.
    #     # aggregation 1 to n
    #
    #     :param target: an entity is decomposed by this
    #     """
    #     Decomposes(self, target)

    def traced_from(self, target):
        """
        --
        """
        TracedFrom(self, target)

    def required_by(self, target):
        """
        --
        """
        RequiredBy(self, target)

    ########################################################################
    # Get or Search Methods
    # The following methods is for a local entity
    #
    def get_pairs(self):
        if 'pairs' in self.relation:
            pairs = [x.end for x in self.relation['pairs']]
        if 'paired with' in self.inv_relation:
            pairs = [x.start for x in self.inv_relation['paired with']]
        return pairs[0]

    def search_entity_by_relation(self, relation_class=[], entity_class=[], inverse=False, depth=None):
        """
           This returns an entity list by searching a relation list in advance
           e.g.,)
           properties = self.search_entity_by_relation(relation_class=[Sends], entity_class=[Property])
        """
        _, relation_results = self.search(inverse, words_search=relation_class, depth=depth)
        results = []
        for rel in relation_results:
            if inverse is False:
                entity_results, _ = rel.end.search(inverse, words_search=entity_class, depth=depth)
                results += entity_results
            elif inverse is True:
                entity_results, _ = rel.start.search(inverse, words_search=entity_class, depth=depth)
                results += entity_results

        return results

    def search(self, inverse=False, words_search=[], depth=None):
        """
        This returns an entity list and a relation list for this entity using search words
        e.g.,) entity_results, relation_results = self.search(words_search=[Requirement])
        :param inverse: 'True' means tracing inverse direction
        :param words_search: a list of searching words
            The ways getting entity objects
            (1) By class types
                search([Action, Item])
            (2) By relation types
                search([sends, receives])
            (3) By entity names
                search(['action1')
            (4) By entity hierarchical names
                search(['process1.and1.action1')
            (5) By id
                search(['ID902342'])
        :return: The entity and relation list
        """
        breaker = []
        entity_results = []
        relation_results = []

        # 1. Search for a hierarchy name ('Com1.size1')
        hierarchical_name = [w for w in words_search if isinstance(w, str) and '.' in w]
        if len(hierarchical_name) > 0:
            self.search_hierarchical_name(hierarchical_name, entity_results)
        else: # 2. Search for a simple name ('Action1), a entity type, and a relation type
            if isinstance(inverse, str) and inverse == 'both':
                self.search_op(breaker, '', False, words_search, entity_results, relation_results, depth)
                breaker = []
                self.search_op(breaker, '', True, words_search, entity_results, relation_results, depth)
            else:
                self.search_op(breaker, '', inverse, words_search, entity_results, relation_results, depth)

        return entity_results, relation_results

    def search_op(self, breaker, space, inverse, words_search, entity_results, relation_results, depth):
        # The list breaker contains all previous entities that were already used,
        # so prevent recursive search can be prevented.
        if self in breaker:
            return

        breaker.append(self)

        # When reaching the Process END, stop
        if isinstance(self, Process_END) and self.is_root:
            return

        # When reaching the predefined depth, stop
        if depth is not None:
            if len(space) > depth:
                return

        # print_out(space + f'{self.name}')
        space += ' '

        # Include this entity class if it is in the search list
        if self.__class__ in words_search and self not in entity_results:
            entity_results.append(self)
        elif self.name in words_search and self not in entity_results:
            entity_results.append(self)
        else:
            my_id = f'ID{id(self)}'
            if my_id in words_search and self not in entity_results:
                entity_results.append(self)

        if inverse is False:
            for rel_name, rel_list in self.relation.items():
                # print_out(space + f'rel: {rel_name}')

                for rel_ins in rel_list:
                    # Include this relation class if it is in the search list
                    if rel_ins.__class__ in words_search:
                        relation_results.append(rel_ins)

                    # Perform recursion
                    rel_ins.end.search_op(breaker, space, inverse, words_search, entity_results, relation_results, depth)
        elif inverse is True:
            for rel_name, rel_list in self.inv_relation.items():
                # print_out(space + f'rel: {rel_name}')

                for rel_ins in rel_list:
                    # Include this relation class if it is in the search list
                    if rel_ins.__class__ in words_search:
                        relation_results.append(rel_ins)

                    # Perform recursion
                    rel_ins.start.search_op(breaker, space, inverse, words_search, entity_results, relation_results, depth)

    def search_hierarchical_name(self, hierarchical_name, entity_results):
        for name in hierarchical_name:
            name_list = name.split('.')
            self.search_hierarchical_name_op(0, name_list, entity_results)

    def search_hierarchical_name_op(self, index, name_list, entity_results):
        name = name_list[index]
        index += 1

        if self.name != name:
            return

        if index == len(name_list):
            entity_results.append(self)
            return

        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                c.search_hierarchical_name_op(index, name_list, entity_results)


    def find_root(self):
        """
        This finds a root process.
        :return:
        A root process
        """
        if 'contained in' not in self.inv_relation:
            return self

        r = [x for x in self.inv_relation['contained in']]
        if r is None:
            return
        else:
            for x in r:
                root = x.start.find_root()
        return root

    def find_ancestor_nodes(self, class_type):
        """
        This finds all ancestor nodes by back tracking
        e.g.,)
        ret = find_all_nodes(Loop)
        :return:
        A list of all nodes created from the class rel_type
        """
        ret = []
        self.find_ancestor_nodes_op(class_type, ret)

        if len(ret) == 0:
            return None

        return ret

    def find_ancestor_nodes_op(self, class_type, ret_list):
        """
        This finds all nodes specified in the input 'rel_type'
        """

        if 'contained in' not in self.inv_relation:
            return

        found = [x for x in self.inv_relation['contained in'] if isinstance(x.start, class_type)]
        ret_list += found

        r = [x for x in self.inv_relation['contained in']]
        if r is None:
            return
        else:
            for x in r:
                x.start.find_ancestor_nodes_op(class_type, ret_list)

    def find_loop_end(self):
        """
        This returns a first Loop-End node
        """
        ret = self.find_ancestor_nodes(Loop)
        return ret[0].start.end

    #########################################################################
    # Static Methods
    # Get or Search Methods
    # The following methods is for a global entity
    #
    @staticmethod
    def debug_mode(b):
        Entity._debug_mode = b

    #########################################################################
    # Graphic Methods
    def set_graphic_info(self, **kargs):
        self.graphic_info = kargs

    def get_graphic_info(self):
        """
        :return: "strokeColor=gray;"
        """
        if self.graphic_info is not None:
            info_list = [f'{k}={v};' for k, v in self.graphic_info.items()]
            info_str = ''.join(info_list)
            return info_str

        return ''

# ========================================================================= #
#                             Static Entity                                 #
# ========================================================================= #
class StaticEntity(Entity):
    """ This is an abstract class regarding Static Entity"""

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def __str__(self):
        str = self.name
        # if 'decomposes' in self.relation:
        #     rel = [x.end for x in self.relation['decomposes']]
        #     s = ', '.join([r.name for r in rel])
        #     str += ": decomposes [{}]".format(s)

        if 'performs' in self.relation:
            rel = [x.end for x in self.relation['performs']]
            s = ', '.join([r.name for r in rel])
            str += ": performs [{}]".format(s)

        if 'traced from' in self.relation:
            rel = [x.end for x in self.relation['traced from']]
            s = ', '.join([r.name for r in rel])
            str += ": traced from [{}]".format(s)

        if 'required by' in self.relation:
            rel = [x.end for x in self.relation['required by']]
            s = ', '.join([r.name for r in rel])
            str += ": required by [{}]".format(s)

        return str


# ========================================================================= #
#                                Property                                   #
# ========================================================================= #
class Property(StaticEntity):
    def __init__(self, name, range=None, value=None, parent=None):
        """
        :param name: e.g., ) 'X'
        :param range: e.g., ) 'Less(10)', 'More(10)', 'Between(1, 10)',
        [1, 2, 3], ['A', 'B', 'C'], 'Normal(10, 1)', 'Uniform(1, 10)'
        ['Between(1, 10)', 'Between(30, 40)']
        :param value: e.g., ) 'T', 10
        """
        super().__init__(name, parent=parent)
        self.range = range
        self.value = value
        self.updated = False

    def set_value(self, value):
        self.updated = True
        self.value = value

    def get_random_value(self):
        # "updated" means this value was updated by a certain function
        # In this case, the random number generation is not used.
        if self.updated is True:
            self.updated = False
            return self.value
        elif self.updated is False:
            if isinstance(self.range, list):
                self.value = random.choice(self.range)
                return self.value
            else:
                self.value = self.range.get_random_value()
                return self.value

    def get_pop_one(self):
        if isinstance(self.range, list):
            self.value = self.range.pop(0)
            return self.value


# ========================================================================= #
#                                  Item                                     #
# ========================================================================= #
class Item(StaticEntity):
    def __init__(self, name, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)

        # Item size is used for conduit operation
        self._size = 0

    def size(self, s):
        self._size = s


# ========================================================================= #
#                                  Conduit                                  #
# ========================================================================= #
class Conduit(StaticEntity):
    """
    Conduit is a kind of link or gui.
    Conduit is associated with items.
    """

    def __init__(self, name, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self._capacity = 1
        self._delay = 0

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def transfers(self, item):
        """
        This makes a relationship between this and an item
        :param item: an item is transferred by this conduit
        """
        Transfers(self, item)

    def delay(self, delay):
        """
        This represents the time required to transmit an item over this Conduit.
        :param delay:
        """
        self._delay = delay

    def capacity(self, capacity):
        """
        This represents the maximum rate supported by the Conduit.
        :param capacity:
        """
        self._capacity = capacity


# ========================================================================= #
#                                  Resource                                 #
# ========================================================================= #
class Resource(StaticEntity):
    def __init__(self, name, amount=10, minimum=1, maximum=10, units=None, parent=None):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self.amount = amount
        self.minimum = minimum
        self.maximum = maximum
        self.units = units


# ========================================================================= #
#                                Requirement                                #
# ========================================================================= #
class Requirement(Property):
    def __init__(self, name, range=None, value=None, parent=None, **kwargs):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, range, value, parent=parent)
        self.kwargs = kwargs
        self.is_root = True

    def check_property(self):
        if 'traced to' in self.inv_relation:
            rel = [x.start for x in self.inv_relation['traced to']]

            l = {r.name : r.value for r in rel}

            is_passed = 'passed' if rel[0].value in self.range else 'failed'

            str = f'{self.name} Range is {self.range}: {l} {is_passed}'

            print(str)

            return is_passed

        return None

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Requirement(self, name, range=None, value=None, **kwargs):
        obj = Requirement(name, range, value, parent=self, **kwargs)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def __str__(self):
        """Overriding StaticEntity.__str__ so that it prints kwargs as well (useful for debugging).
        :return: super().__str__ appended with self.kwargs
        """
        ret = super(Requirement, self).__str__()
        ret += " " + str(self.kwargs) + '\n'
        if 'contains' in self.relation:
            children = [r.end for r in self.relation['contains'] if isinstance(r, Contains)]
            for i, c in enumerate(children):
                ret += c.__str__()

        return ret


# ========================================================================= #
#                                Component                                  #
# ========================================================================= #
class Component(StaticEntity):
    def __init__(self, name, parent=None, **kwargs):
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)
        self.kwargs = kwargs
        self.is_root = True

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def performs(self, target):
        Performs(self, target)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Component(self, name, **kwargs):
        obj = Component(name, parent=self, **kwargs)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


# ========================================================================= #
#                            Dynamic Entity                                 #
# ========================================================================= #
class DynamicEntity(Entity):
    """ This is an abstract class regarding Dynamic Entity"""

    def __init__(self, name, duration=0, parent=None):
        super().__init__(name, parent=parent)
        self.duration = duration
        self.time = 0
        self.total_time = 0
        self.waiting = False
        self.function = None
        self.sim_network = []

    def reset_waiting(self):
        self.waiting = False

    def reset_sim_status(self):
        self.waiting = False
        self.time = 0
        # self.total_time = 0
        self.is_root = False

        for rels in self.relation:
            rel = self.relation[rels]
            for r in rel:
                r.reset_sim_status()

    ########################################################################
    # Relationship Methods
    # !start with an lowercase letter
    #
    def seizes(self, resource, amount=10):
        """
        This make a relation 'seizes' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be taken
        """
        Seizes(self, resource, amount)

    def consumes(self, resource, amount=10):
        """
        This make a relation 'consumes' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be taken
        """
        Consumes(self, resource, amount)

    def produces(self, resource, amount=10):
        """
        This make a relation 'produces' between this DEntity and the target resource
        The DEntity will fire, when the target resource are available

        :param
        resource: The target resource
        amount: The amount that will be produced
        """
        Produces(self, resource, amount)

    def flow(self, e):
        """
        This make a relation 'flow' between actions
        The relation 'flow' is used to represent a control flow between dynamic entities (e.g., Process A to Action B)

        :param
        e: The target dynamic entity of this dynamic entity
        """
        Flow(self, e)

    def receives(self, item):
        """
        This make a relation 'receives' between this action and an input trigger item
        The input trigger item differs from the input item that does not influence this action

        :param
        item: The input trigger item
        """
        Receives(self, item)

    def triggered(self, item):
        """
        This make a relation 'triggered' between this action and an input trigger item
        The input trigger item differs from the input item that does not influence this action

        :param
        item: The input trigger item
        """
        Triggered(self, item)

    def sends(self, item, receiver=None, amount=10):
        """
        This make a relation 'sends' between this DEntity and the target item
        The DEntity will fire, when the target resource are available

        :param
        item: The target item
        amount: The amount that will be sent
        """
        Sends(self, item, amount)
        if receiver is not None:
            receiver.receives(item)

    def func(self, function):
        self.function = function

    ########################################################################
    # RM_sim Methods
    #
    #
    def make_network(self):
        """
        This create a simulation network containing flows between nodes
        :return:
        """

        # Make a new entity db corresponding to the original entity db
        # After using the new entity db, this will be removed.
        # cloned_db = edb.get_cloned_db(path=self.module)

        # The new cloned db is stored in the original entity,
        # so after simulation, all result data can be accessed.
        # self.cloned_db = cloned_db

        # Get a new process from the cloned db
        # new_en = cloned_db.get(self.name, path=self.module)

        # Make a simulation network using the new process
        # new_en.make_network_op(new_en.sim_network)
        self.make_network_op(self.sim_network)

        # Set the cloned db to all entities
        # new_en.cloned_db = cloned_db
        # for e in new_en.sim_network:
        #     e.cloned_db = cloned_db

        return self

    def init_sim_network(self):
        """
        This create a simulation network containing flows between nodes
        :return:
        """
        # Make a network for the action model
        new_en = self.make_network()

        # Reset all states in the elements of the network
        for en in new_en.sim_network:
            en.reset_sim_status()

        new_en.reset_sim_status()

        # The function sim is called, which means this is a root process
        new_en.is_root = True
        new_en.end.is_root = True

        # The root process is linked to 'process_END' using the relation 'flow'
        # 'process_END' -> 'process'
        new_en.end.flow(new_en)

        # For the relation 'process_END' -> 'process', set 'sent = true' to activate the root process
        flowed_from = [r for r in new_en.inv_relation['flowed from'] if isinstance(r, Flow)]
        flowed_from[0].sent = True

        return new_en

    def make_network_op(self, entities):
        """
        This finds all node flows
        The returning list acts will have flow nodes which are activated by Python Asyncio

        :param
        entities: All entities that will be accumulated
        """

        if 'contains' in self.relation:
            contains = [x.end for x in self.relation['contains'] if not isinstance(x.end, Property)] # Property does not belong to the simulation network
            if contains is not None:
                entities += contains
                cur_e = None
                last_e = []
                for e in contains:
                    # if not isinstance(e, Property): # Property does not belong to the simulation network
                    if isinstance(self, And) or isinstance(self, Or) or isinstance(self, XOr) or isinstance(self, Condition):
                        # If e is not 'or' or 'and', then make a parallel flow
                        self.flow(e)
                        last_e.append(e)
                    else:
                        # If e is not 'or' or 'and', then make a serial flow

                        if cur_e is None:
                            # e.g.,) (self)Process1->(e)Action1
                            self.flow(e)
                        else:
                            if hasattr(cur_e, 'end') and cur_e.sim_with_decomposition:
                                # 1. 'cur_e.sim_with_decomposition' is checked for skipping the decomposition of sub-nodes
                                # Especially, 'Action' can be decomposed.
                                # 2. This 'cur_e' decomposes the node 'end'
                                # This means that the next node 'e' should be connected to the node 'end'
                                # e.g.,) (cur_e)Loop->Action1->Loop_END->Action2
                                # 'Action2' is linked to 'Loop_END', but not 'Loop'
                                cur_e.end.flow(e)
                            else:
                                cur_e.flow(e)

                        cur_e = e

                    # If this entity does not allow the decomposition, don't make any sub-flows for simulation
                    if e.sim_with_decomposition:
                        e.make_network_op(entities)

                    # If this entity is Action and the current child is Process,
                    # this means this is a decomposed entity
                    # The decomposed entity is set on here
                    if isinstance(e, Process) and isinstance(self, Action):
                        self.is_decomposed = True

                ######################################################################
                # Concatenate this node to the 'END' node
                #
                if cur_e is not None:
                    last_e.append(cur_e)

                if self.end is not None:
                    for e in last_e:
                        if isinstance(e, Action):
                            # If e does not have the node 'end' (e.g., 'Action')
                            # This means it doesn't have any pair
                            e.flow(self.end)
                            # entities.append(self.end)
                        elif isinstance(e, END) or isinstance(e, ExitLoop) :
                            # If e is the class 'END', this means e will terminate with the root process end
                            # When END is created, it is already associated with the root process end, so skip
                            pass
                        else:
                            # If e decomposes the node 'end' (e.g., 'Loop' and 'Action' containing 'process')
                            # This means there is the 'End' pair for this
                            # This 'End' pair is used for flowing to the current 'End' pair
                            e.end.flow(self.end)
                            # entities.append(self.end)

                    entities.append(self.end)

        elif isinstance(self, Process):   # This is likely to be an empty process (no 'contains' key)
            # Just let this root process to flow directly to the end
            self.flow(self.end)
            # Make sure the list contains the end node
            entities.append(self.end)

    def print_flows(self, entities):
        """
        This prints out the control flows of UC
        :param entities: the flow list generated from 'init_sim_network()'
        """
        flows = [r for r in self.relation['flows'] if isinstance(r, Flow)]
        for f in flows:
            print(f)

        for e in entities:
            flows = [r for r in e.relation['flows'] if isinstance(r, Flow)]
            for f in flows:
                print(f)

    async def run(self):
        """
        This is a sysmpy function that performs all operation about simulation
        Each DynamicEntity uses this function 'run' to do its own simulation

        There are two phases
            Phase 1: Check Activating Conditions, so that this can enter the activation stage of simulation
            Phase 2: Perform Action in which all defined activities are performed
        """
        while Process.activated:
            # Info is used to store any information in this process and be printed out by 'Process.store_event()'
            info = ''
            ##################################################################################
            #
            #       Phase 1: Check Activating Conditions
            #
            ##################################################################################

            if self.name == 'p1_END':
                print('I am this!')

            ########################################################################
            # Check 1: Check waiting state
            # Any process can be waiting, if there is no event to this process
            if self.waiting is False:
                Process.store_event(self, 'waiting')
                self.waiting = True

            # Reset all fire states
            # If all of these are true, then this process will fire all events to the child processes
            b_flow_fire = False
            b_consumes_fire = False
            b_seizes_fire = False
            b_triggered_fire = False

            ########################################################################
            # Check 2: Input Control Flows
            # This is receiving control flows
            # Non input flows mean that this is a root node
            # Such root node is just firing all events regardless any conditions
            flowed_from = None
            flows_true = None

            # Take a list for the relationship 'flowed from'
            if 'flowed from' in self.inv_relation:
                flowed_from = [r for r in self.inv_relation['flowed from'] if isinstance(r, Flow)]

            if flowed_from is None:
                # No flowed_from means there are no input events for this node
                # There are two cases: (1) There is End() or ExitLoop() before this
                return
            else:
                # Having flowed_from means there are input events for this node
                flows_true = [r for r in flowed_from if r.sent is True]

                # Check whether all input events are set to 'sent=True'
                # This means this node now can fire its output event
                if len(flowed_from) == len(flows_true) and len(flowed_from) > 0:
                    b_flow_fire = True

                # If this self is 'or_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Or_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                if isinstance(self, XOr_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Condition_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Condition_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Process_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Process_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'Loop_END' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Loop_END) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this self is 'loop' and decomposes one true inputting event
                # Then this node now can fire its output event
                if isinstance(self, Loop) and len(flows_true) >= 1:
                    b_flow_fire = True

                # If this flow should be fired,
                # All process which sent were reset to 'sent = False'
                # if b_flow_fire:
                #     for r in flows_true:
                #         r.sent = False
                #
                #     # Set a new start time for this process by checking the times of the previous flow entities
                #     max_time = max([r.start.time for r in flowed_from])
                #     self.time = max_time
                #     info += 'Fired from: ' + ', '.join([r.start.name for r in flows_true])

            ########################################################################
            # Check 3: Resources Consumes
            consumes = None
            consumes_true = None

            # Take a list for the relationship 'consumes'
            if 'consumes' in self.relation:
                consumes = [r for r in self.relation['consumes'] if isinstance(r, Consumes)]

            if consumes is None:
                # No seizes means there are no resource events for this node
                b_consumes_fire = True
            else:
                # Having seizes means there are resource events for this node
                consumes_true = [r for r in consumes if r.amount <= r.end.amount]

                # Check whether all input events are set to 'sent=True'
                # This means this node now can fire its output event
                if len(consumes) == len(consumes_true) and len(consumes) > 0:
                    b_consumes_fire = True

            ########################################################################
            # Check 3: Resources Seizes
            seizes = None
            seizes_true = None

            # Take a list for the relationship 'seizes'
            if 'seizes' in self.relation:
                seizes = [r for r in self.relation['seizes'] if isinstance(r, Seizes)]

            if seizes is None:
                # No seizes means there are no resource events for this node
                b_seizes_fire = True
            else:
                # Having seizes means there are resource events for this node
                seizes_true = [r for r in seizes if r.amount <= r.end.amount]

                # Check whether all input events are set to 'True'
                # This means this node now can fire its output event
                if len(seizes) == len(seizes_true) and len(seizes) > 0:
                    b_seizes_fire = True

            ########################################################################
            # Check 4: Triggered Item

            triggered = None
            triggered_true = None

            # Take a list for the relationship 'triggered by'
            if 'triggered by' in self.relation:
                triggered = [r for r in self.relation['triggered by'] if isinstance(r, Triggered)]

            if triggered is None:
                # No receives means there are no triggering events for this node
                b_triggered_fire = True
            else:
                # Having receives means there are triggering events for this node
                triggered_true = [r for r in triggered if r.trigger is True]

                # Check whether all input events are set to 'trigger=True'
                # This means this node now can fire its output event
                if len(triggered) == len(triggered_true) and len(triggered) > 0:
                    b_triggered_fire = True

                    for r in triggered_true:
                        r.trigger = False

                    # Set a new start time for this process by checking the times from the process that triggered
                    #----------------------------------------------------
                    # Warning: This can make an error. We need to check only triggered items, but not common items.
                    pre_actions = [r.end.inv_relation['sent from'] for r in triggered_true]
                    max_time = max([r.start.time for r in pre_actions[0]])
                    self.time = max(max_time, self.time)
                    info += ' triggered from: ' + ', '.join([r.end.name for r in triggered_true])

                    # If this is associated with conduits, the times for conduits should be applied
                    # Calculate the conduit times
                    if 'transferred by' in r.end.inv_relation:
                        conduits = [r.end.inv_relation['transferred by'] for r in triggered_true]
                        max_delay = max([r.start._delay  for r in conduits[0]])
                        max_capacity = max([r.start._capacity for r in conduits[0]])
                        max_size = max([r.end._size for r in conduits[0]])
                        max_conduits_time = max_delay + max_size/max_capacity

                        Process.store_event(self, 'triggered', info=f'After conduit flows, '
                                                f'a time {max_conduits_time} was added')

                        # Apply the conduit times to this process time
                        self.time += max_conduits_time

            ##################################################################################
            #
            #       Phase 2: Perform Action
            #
            ##################################################################################
            # if self.name == 'A':
            #     print_out(self.name, b_flow_fire, b_seizes_fire, b_consumes_fire, b_triggered_fire)
            #     print_out('!')

            if b_flow_fire and b_seizes_fire and b_consumes_fire and b_triggered_fire:
                ########################################################################
                # Check 0: If this flow should be fired,
                # All process which sent were reset to 'sent = False'
                for r in flows_true:
                    r.sent = False

                # Set a new start time for this process by checking the times of the previous flow entities
                max_time = max([r.start.time for r in flowed_from])
                self.time = max_time
                info += 'Fired from: ' + ', '.join([r.start.name for r in flows_true])

                ########################################################################
                # Check 1: Activate this action
                Process.store_event(self, 'activated', info=info)
                self.waiting = False

                ########################################################################
                # Check 2: Check global time
                Process.global_time = max(Process.global_time, self.time)
                if Process.global_max is not None and Process.global_time >= Process.global_max:
                    Process.activated = False

                ########################################################################
                # Check 3: Resources Seizes
                # Seizes all related resources
                if seizes_true is not None:
                    for r in seizes_true:
                        r.end.amount -= r.amount
                        s = ''
                        s += ' Resources(' + ', '.join([r.end.name for r in seizes_true])+')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in seizes_true])+')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in seizes_true])+')'
                        Process.store_event(self, 'seizes resources', info=f'[{s}]')

                ########################################################################
                # Check 4: Sends Item
                # Note that 'Sends Item' is for trigger items, but not common items
                # Because the simulation uses only the trigger items
                # Later we may be able to use the common items in some purposes
                if 'sends' in self.relation:
                    sends = [x for x in self.relation['sends'] if isinstance(x, Sends)]
                    for r in sends:
                        r.trigger = True
                        # Check all send relations associated with the item of this relation r
                        sent_from_for_item = [x for x in r.end.inv_relation['sent from'] if isinstance(x, Sends)]
                        sent_from_for_item_true = [x for x in sent_from_for_item if x.trigger is True]

                        if len(sent_from_for_item) == len(sent_from_for_item_true):
                            # All relations 'sent from' were set to 'True'
                            # Then we can fire the related trigger items.
                            # So, set all relation 'triggered' in this item as 'True',
                            # if it decomposes the relation 'triggered'
                            if 'triggers' in r.end.inv_relation:
                                triggered_for_item = [x for x in r.end.inv_relation['triggers'] if isinstance(x, Triggered)]
                                for r2 in triggered_for_item:
                                    r2.trigger = True

                                s = ''
                                s += 'To : ' + ', '.join([r.end.name for r in triggered_for_item])
                                Process.store_event(self, 'sends', info=f'[{s}]')

                ########################################################################
                # Check 5: Produces Resources
                if 'produces' in self.relation:
                    produces = [x for x in self.relation['produces'] if isinstance(x, Produces)]
                    for r in produces:
                        r.end.amount += r.amount
                        Process.store_event(self, 'produces resources', info=f'[New resources({r.amount}) '
                                                                            f'Total resources({r.end.amount})]')

                ########################################################################
                # Check 6: Resources Consumes
                if consumes_true is not None:
                    for r in consumes_true:
                        r.end.amount -= r.amount
                        s = ''
                        s += ' Consumes resources(' + ', '.join([r.end.name for r in consumes_true]) + ')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in consumes_true]) + ')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in consumes_true]) + ')'
                        Process.store_event(self, 'consumes resources', info=f'[{s}]')

                ########################################################################
                # Check 7: Input Control Flows
                # Send all output events
                if 'flows' in self.relation:
                    flows = [x for x in self.relation['flows'] if isinstance(x, Flow)]

                    if isinstance(self, Action):
                        # Perform a function script defined in the script
                        # If there are no function and the properties of the items associated with the function,
                        # then the values of the properties are randomly sampled.
                        if self.function is not None:
                            # selected = self.function(self.cloned_db)
                            selected = self.function(edb)
                        else:
                            properties = self.search_entity_by_relation(relation_class=[Sends], entity_class=[Property])
                            for p in properties:
                                # This property performs the sampling by itself
                                p.get_random_value()

                    if isinstance(self, XOr): # Select only one process
                        selected = random.choice(flows)
                        Process.store_event(self, 'selects ' + selected.end.name)
                        selected.sent = True

                    if isinstance(self, Or): # Select at least one process
                        or_flows = or_selector(flows)
                        selected = random.choice(or_flows)
                        for sel in selected:
                            if sel is not None:
                                Process.store_event(self, 'selects ' + sel.end.name)
                                sel.sent = True
                    elif isinstance(self, Condition):
                        if self.function is not None:
                            # Perform a function script for selection
                            selected = self.function(edb)
                            # selected = self.function(self.cloned_db)

                            # Because 'selected' is the original process in db and r.end is a copied process,
                            # self.cloned_db.is_same() is used to check their same identity.
                            # selected_flows = [r for r in flows if self.cloned_db.is_same(r.end, selected)]
                            selected_flows = [r for r in flows if edb.is_same(r.end, selected)]
                            Process.store_event(self, 'selects ' + ', '.join([r.end.name for r in selected_flows]))
                            for r in selected_flows:
                                r.sent = True
                        else:
                            # Perform uniformly random selection for the branches
                            selected = random.choice(flows)
                            Process.store_event(self, 'selects ' + selected.end.name)
                            selected.sent = True
                    elif isinstance(self, Loop_END):
                        exit_loop = [x for x in flows_true if isinstance(x.start, ExitLoop)]

                        if self.count >= self.times-1 or len(exit_loop) > 0:
                            Process.store_event(self, 'finished repetition')
                            self.reset_waiting()
                            loops = [x for x in self.relation['flows'] if not isinstance(x.end, Loop)]
                            for x in loops:
                                x.sent = True
                        else:
                            self.count += 1
                            Process.store_event(self, 'repeats ' + str(self.count))
                            loops = [x for x in self.relation['flows'] if isinstance(x.end, Loop)]
                            for x in loops:
                                x.sent = True

                    elif isinstance(self, Process_END) and self.is_root:
                        self.reset_waiting()
                        for x in flows:
                            x.sent = True
                    else:
                        # 'And' or other entities will send all flows to their child entities.
                        for x in flows:
                            x.sent = True

                ########################################################################
                # Check 8: Properties for a process
                # A process can have properties which can be sampled, when the process was ended.
                if isinstance(self, Process_END):
                    proc = self.get_pairs()

                    if proc.function is not None:
                        # selected = proc.function(self.cloned_db)
                        selected = proc.function(edb)
                    else:
                        # Search properties of this process
                        list_properties, _ = proc.search(words_search=[Property], depth=1)

                        for p in list_properties:
                            # This property performs the sampling by itself
                            p.get_random_value()

                ########################################################################
                # Check 9: Complete this process
                self.time += self.duration
                self.total_time += self.duration
                Process.store_event(self, 'completed', info='')

                ########################################################################
                # Check 10: Resources Seizes
                # Release all seized resources
                if seizes_true is not None:
                    for r in seizes_true:
                        r.end.amount += r.amount
                        s = ''
                        s += ' Resources(' + ', '.join([r.end.name for r in seizes_true]) + ')'
                        s += ' Taking Amounts(' + ', '.join([str(r.amount) for r in seizes_true]) + ')'
                        s += ' Result Amounts(' + ', '.join([str(r.end.amount) for r in seizes_true]) + ')'
                        Process.store_event(self, 'releases resources', info=f'[{s}]')

                ########################################################################
                # Check 11: One iteration of the root process was done
                if isinstance(self, Process_END) and self.is_root:
                    # Process.store_event(self, 'decomposes done as the root process', info='')
                    Process.store_event(self, f'simulation {Process.simulation_count}', info='')

                    # Global simulation count increases
                    Process.simulation_count += 1

                    # Save proper simulation results
                    proc = self.get_pairs()

                    if Process.global_max is None:
                        Process.store_event(self, 'deactivating root process now', info='')
                        Process.activated = False

            else:
                ########################################################################
                # Waiting all action internal operations
                await asyncio.sleep(0)


# ========================================================================= #
#                                Action                                     #
# ========================================================================= #
class Action(DynamicEntity):
    """
    Action Dynamic Entity
    """
    def __init__(self, name, duration=1, parent=None):
        super().__init__(name, duration, parent=parent)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        # This action decomposes a sub-process
        # Consequently, this action will have the 'Action_end'
        self.end = Action_END(self.name + '_END', parent=self)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

        return obj


class Action_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                And                                        #
# ========================================================================= #
class And(DynamicEntity):
    """
    And Dynamic Entity
    """
    def __init__(self, name='and', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = And_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class And_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                   Or                                      #
# ========================================================================= #
class Or(DynamicEntity):
    """
    Or Dynamic Entity
    """
    def __init__(self, name='or', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Or_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Or_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                  XOr                                      #
# ========================================================================= #
class XOr(DynamicEntity):
    """
    XOr Dynamic Entity
    """

    def __init__(self, name='xor', parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = XOr_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class XOr_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                Condition                                  #
# ========================================================================= #
class Condition(DynamicEntity):
    """
    Condition Dynamic Entity
    """

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Condition_END(name + '_END', parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Condition_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                    END                                    #
# ========================================================================= #
class END(DynamicEntity):
    """
    END Dynamic Entity
    """

    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                 ExitLoop                                  #
# ========================================================================= #
class ExitLoop(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)

    def reset_waiting(self):
        super().reset_waiting()


# ========================================================================= #
#                                 Process                                   #
# ========================================================================= #
class Process(DynamicEntity):
    """
    Process Dynamic Entity
    """

    # 'activated' is used to denote SMRE_Example's activation. False means 'stop simulation'
    activated = False
    # 'global_time' is used to denote SMRE_Example's global time
    global_time = 0
    # 'global_max' is used to denote when SMRE_Example will stop
    global_max = None
    # RM_sim count is used to count each simulation run
    simulation_count = 0
    # simulation results are stored in queue
    event_queue = queue.Queue()
    # is there a web distributor
    web_distributor = False

    def __init__(self, name, parent=None):
        # Find a module which is using this class
        # and set the caller's path to this class, so we can know where this object came form:
        # - last element ([-1]) is me, the one before ([-2]) is my caller.
        # - The first element in caller's data is the filename
        if parent is None:
            self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])
        super().__init__(name, parent=parent)

        # Loop can inherit process. And a pure process uses the following codes.
        if isinstance(self, Loop) is False:
            self.is_root = True

            # This decomposes a pair dynamic entity called 'end' or name+'_END'
            self.end = Process_END(name + '_END', parent=parent)

            # Link this and the end using the relation 'Pairs'
            Pairs(self, self.end)

            # AsyncIO workers are used to be performed as computing threads
            self.workers = []

            # sim_network is a network containing all processes, actions, and items associated with simulation
            self.sim_network = []

    ########################################################################
    # Analysis Methods
    #
    #
    def evaluate_interfaces(self):
        """
        This checks interfaces between functions (or actions)
        """
        from sysmpy.interface_analyzer import InterfaceAnalyzer as IA
        ia = IA(self)
        return ia.get_critical_elements(self), \
               ia.get_sphere_of_influence(self), \
               ia.get_feedback_elements(self), \
               ia.get_recursive_elements(self), \
               ia.get_absence_items(self), \
               ia.get_unused_items(self)

    def evaluate_requirements(self):
        """
        This checks all requirements and evaluates them using simulation results
        """

        entity_results, relation_results = self.search(words_search=[Requirement])

        evaluation_results = {}
        for e in entity_results:
            # print_out(e)
            is_passed = e.check_property()

            evaluation_results[e.name] = is_passed

        return evaluation_results

    def get_action_times(self):
        # if self.current_clone is not None:
        #     actions, _ = self.current_clone.search(words_search=[Action])
        # else:
        #     actions, _ = self.search(words_search=[Action])
        path = os.path.splitext(traceback.extract_stack()[-2][0])[0]

        actions = edb.get(Action, path=path)
        # actions = self.cloned_db.get(Action, path=path)

        dict_action = {x.name:x.total_time for x in actions}
        return dict_action

    def view(self):
        """
        This opens a web browser to show simulation results
        """
        if self.properties is not None:
            property_list = [f"'{x.name}':0" for x in self.properties]
            prop_str = '{'+", ".join(property_list) + '}'
            u = f"http://127.0.0.1:9191/pc/?g={prop_str}"
            import webbrowser
            webbrowser.open(u)
            time.sleep(0.5)

    ########################################################################
    # staticmethod
    #
    #
    @staticmethod
    def store_event(act, event, info=''):
        if Entity._debug_mode:
            if len(info) == 0:
                print_out(f'T: {act.time}, N: {act.name}, E: {event}')
            else:
                print_out(f'T: {act.time}, N: {act.name}, E: {event}, I: {info}')
        else:
            # Ignore unnecessary entities
            if isinstance(act, And) or \
               isinstance(act, And_END) or \
               isinstance(act, Loop) or \
               isinstance(act, Loop_END) or \
               isinstance(act, Condition) or \
               isinstance(act, Condition_END) or \
               isinstance(act, END) or \
               isinstance(act, ExitLoop) or \
               isinstance(act, Process) or \
               isinstance(act, Or) or \
               isinstance(act, Or_END):
                return

            # If process-end
            if isinstance(act, Process_END):
                if event != 'waiting' and \
                   event != 'activated' and \
                   event != 'completed' :
                    print_out(f'At Time {act.time}, "{act.name}" {event}.')
                    evt = {'class': act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                    Process.event_queue.put(evt)

                    if Process.web_distributor is True:
                        root_process = act.get_pairs()
                        # If this root_process contains any properties in the child entities
                        # Send the values of the properties
                        if root_process.properties is not None and len(root_process.properties) > 0:
                            data = {}

                            # Make data containing property values
                            for p in root_process.properties:
                                data[p.hierarchical_name] = p.value

                            # Check the property values with the requirement ranges
                            evaluation_results = root_process.evaluate_requirements()

                            data.update(evaluation_results)

                            # Send the data to all clients
                            Process.send_event_to_web_distributor(data)
            else:
                if Entity._debug_mode:
                    if event != 'waiting':
                        print_out(f'At Time {act.time}, "{act.name}" {event}.')
                        evt = {'class':act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                        Process.event_queue.put(evt)
                else:
                    print_out(f'At Time {act.time}, "{act.name}" {event}.')
                    evt = {'class': act.__class__.__name__, 'name': act.name, 'time': act.time, 'event': event}
                    Process.event_queue.put(evt)

    @staticmethod
    def get_event():
        if Process.event_queue.empty():
            return None
        return Process.event_queue.get_nowait()

    @staticmethod
    def check_web_distributor():
        """
        The web distributor is a web server sending simulation events to the connected clients
        """
        try:
            r = requests.get(f"http://127.0.0.1:9191")
            Process.web_distributor = True
            print_out('The simulation is connected to the web distributor.')
        except requests.exceptions.RequestException as e:
            # raise SystemExit(e)
            Process.web_distributor = False

    @staticmethod
    def send_event_to_web_distributor(body):
        """
        This sends simulation events to the web distributor
        """
        try:
            # sess = requests.Session()
            # adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
            # sess.mount('http://', adapter)
            sim_url = f"http://127.0.0.1:9191/sim_updated?g={body}"
            r = requests.get(sim_url)
            # print_out("send events to web_distributor", str(r))
            time.sleep(0.5)
        except requests.exceptions.RequestException as e:
            Process.web_distributor = False
            raise SystemExit(e)

    async def sim(self, until=5, property_view=False, print_out=False, use_web_distributor=False):
        """
        This triggers the simulation.

        :param
        until: The maximum time, modeled in the process flows, for simulation run
        If until is None, the simulation performs just one time.
        """

        # Check whether this contains actions. If not, simulation can't work
        actions, _ = self.search(words_search=[Action])
        if len(actions) == 0:
            warnings.warn("At least one action is required.", stacklevel=2)
            return

        util.is_print_out = print_out

        # Find other flows and set them into asyncio
        if 'flows' or 'contains' in self.relation:
            # Reset all global variables regarding simulation
            Process.simulation_count = 0
            Process.global_time = 0
            Process.activated = True
            Process.global_max = until

            # get flows from the relation 'flow' or 'contains'
            new_proc = self.init_sim_network()

            # print out control flows of UC
            new_proc.print_flows(new_proc.sim_network)

            # check whether a web distributor exist or not
            # The web distributor is a web server sending simulation events to the connected clients
            if use_web_distributor is True:
                new_proc.check_web_distributor()

            # get list of properties which are updated by the simulation
            new_proc.properties, _ = new_proc.search(words_search=[Property])

            # open a web browser for the property view
            if property_view is True:
                new_proc.view()

            workers = [x.run() for x in new_proc.sim_network]
            workers.append(new_proc.run())

            try:
                res = await asyncio.gather(*workers)
                print('--------- Simulation Completed ---------')

                del new_proc

            except asyncio.CancelledError:
                print('CancelledError')


    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Action(self, name, duration=1):
        """
        :param name:
        :param duration: The time modeled in the process flows
        :return:
        """
        obj = Action(name, duration, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def And(self, *args):
        obj = And(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def Or(self, *args):
        obj = Or(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def XOr(self, *args):
        obj = XOr(parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:
            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def Loop(self, name, times=2):
        obj = Loop(name=name, times=times, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj

    def Condition(self, name, *args):
        obj = Condition(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)

        ret = []
        if args is not None and len(args) > 0:

            ret.append(obj)

            for proc in args:
                ret.append(obj.Process(proc))
            return tuple(ret)

        return obj

    def End(self, name='END'):
        obj = END(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        root_process = obj.find_root()
        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        obj.flow(root_process.end)
        return obj

    def ExitLoop(self, name='ExitLoop'):
        obj = ExitLoop(name=name, parent=self)
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        loop_end = obj.find_loop_end()
        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        obj.flow(loop_end)
        return obj


class Process_END(DynamicEntity):
    def __init__(self, name, parent=None):
        super().__init__(name, parent=parent)


# ========================================================================= #
#                                   Loop                                    #
# ========================================================================= #
class Loop(Process):
    """
    Loop Dynamic Entity
    """

    def __init__(self, name='loop', times=2, parent=None):
        super().__init__(name, parent=parent)

        # This decomposes a pair dynamic entity called 'end' or name+'_END'
        self.end = Loop_END(name + '_END', times=times, parent=parent)

        # Link this and the end using the relation 'Pairs'
        Pairs(self, self.end)

        # 'loop' is linked to 'loop_END' using the relation 'flow'
        # 'loop_END' -> 'loop'
        self.end.flow(self)

    ########################################################################
    # Class Creation Methods
    # !start with an uppercase letter
    #
    def Process(self, name):
        obj = Process(name, parent=self)
        obj.is_root = False
        # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0])

        Contains(self, obj)
        return obj


class Loop_END(DynamicEntity):
    def __init__(self, name, times=2, parent=None):
        super().__init__(name, parent=parent)

        # 'loop_END' is set with the looping times
        self.times = times
        self.count = 0

    def reset_waiting(self):
        super().reset_waiting()
        self.count = 0

# ========================================================================= # #                                 Entity                                    # # ========================================================================= # LIFECYCLE MODELING LANGUAGE (LML) SPECIFICATION 1.1:     An entity is something can exist by itself and is uniquely identifiable. # All entities created are stored in the static class Entity ######################################################################## # Identification Methods # This sets the decomposition state when simulating         :param dec: 'True' denotes to execute the simulation of sub-nodes,                     while 'False' means to skip the sub-nodes simulation. # Check Ipython ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) ######################################################################## # Relationship Methods # !start with an lowercase letter # # def decomposes(self, target): #     """ #     This makes a 'decomposes' or 'decomposes' relationship between this and a target #     We define decomposition as composition as shown the following n-to-n relationship. #     # composition n to n # #     We define the relationship 'contains' as aggregation. #     # aggregation 1 to n # #     :param target: an entity is decomposed by this #     """ #     Decomposes(self, target) -- -- ######################################################################## # Get or Search Methods # The following methods is for a local entity # This returns an entity list by searching a relation list in advance            e.g.,)            properties = self.search_entity_by_relation(relation_class=[Sends], entity_class=[Property]) This returns an entity list and a relation list for this entity using search words         e.g.,) entity_results, relation_results = self.search(words_search=[Requirement])         :param inverse: 'True' means tracing inverse direction         :param words_search: a list of searching words             The ways getting entity objects             (1) By class types                 search([Action, Item])             (2) By relation types                 search([sends, receives])             (3) By entity names                 search(['action1')             (4) By entity hierarchical names                 search(['process1.and1.action1')             (5) By id                 search(['ID902342'])         :return: The entity and relation list # 1. Search for a hierarchy name ('Com1.size1') # 2. Search for a simple name ('Action1), a entity type, and a relation type # The list breaker contains all previous entities that were already used, # so prevent recursive search can be prevented. # When reaching the Process END, stop # When reaching the predefined depth, stop # print_out(space + f'{self.name}') # Include this entity class if it is in the search list # print_out(space + f'rel: {rel_name}') # Include this relation class if it is in the search list # Perform recursion # print_out(space + f'rel: {rel_name}') # Include this relation class if it is in the search list # Perform recursion This finds a root process.         :return:         A root process This finds all ancestor nodes by back tracking         e.g.,)         ret = find_all_nodes(Loop)         :return:         A list of all nodes created from the class rel_type This finds all nodes specified in the input 'rel_type' This returns a first Loop-End node ######################################################################### # Static Methods # Get or Search Methods # The following methods is for a global entity # ######################################################################### # Graphic Methods :return: "strokeColor=gray;" # ========================================================================= # #                             Static Entity                                 # # ========================================================================= # This is an abstract class regarding Static Entity # if 'decomposes' in self.relation: #     rel = [x.end for x in self.relation['decomposes']] #     s = ', '.join([r.name for r in rel]) #     str += ": decomposes [{}]".format(s) # ========================================================================= # #                                Property                                   # # ========================================================================= # :param name: e.g., ) 'X'         :param range: e.g., ) 'Less(10)', 'More(10)', 'Between(1, 10)',         [1, 2, 3], ['A', 'B', 'C'], 'Normal(10, 1)', 'Uniform(1, 10)'         ['Between(1, 10)', 'Between(30, 40)']         :param value: e.g., ) 'T', 10 # "updated" means this value was updated by a certain function # In this case, the random number generation is not used. # ========================================================================= # #                                  Item                                     # # ========================================================================= # # Item size is used for conduit operation # ========================================================================= # #                                  Conduit                                  # # ========================================================================= # Conduit is a kind of link or gui.     Conduit is associated with items. ######################################################################## # Relationship Methods # !start with an lowercase letter # This makes a relationship between this and an item         :param item: an item is transferred by this conduit This represents the time required to transmit an item over this Conduit.         :param delay: This represents the maximum rate supported by the Conduit.         :param capacity: # ========================================================================= # #                                  Resource                                 # # ========================================================================= # # ========================================================================= # #                                Requirement                                # # ========================================================================= # ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) Overriding StaticEntity.__str__ so that it prints kwargs as well (useful for debugging).         :return: super().__str__ appended with self.kwargs # ========================================================================= # #                                Component                                  # # ========================================================================= # ######################################################################## # Relationship Methods # !start with an lowercase letter # ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # ========================================================================= # #                            Dynamic Entity                                 # # ========================================================================= # This is an abstract class regarding Dynamic Entity # self.total_time = 0 ######################################################################## # Relationship Methods # !start with an lowercase letter # This make a relation 'seizes' between this DEntity and the target resource         The DEntity will fire, when the target resource are available          :param         resource: The target resource         amount: The amount that will be taken This make a relation 'consumes' between this DEntity and the target resource         The DEntity will fire, when the target resource are available          :param         resource: The target resource         amount: The amount that will be taken This make a relation 'produces' between this DEntity and the target resource         The DEntity will fire, when the target resource are available          :param         resource: The target resource         amount: The amount that will be produced This make a relation 'flow' between actions         The relation 'flow' is used to represent a control flow between dynamic entities (e.g., Process A to Action B)          :param         e: The target dynamic entity of this dynamic entity This make a relation 'receives' between this action and an input trigger item         The input trigger item differs from the input item that does not influence this action          :param         item: The input trigger item This make a relation 'triggered' between this action and an input trigger item         The input trigger item differs from the input item that does not influence this action          :param         item: The input trigger item This make a relation 'sends' between this DEntity and the target item         The DEntity will fire, when the target resource are available          :param         item: The target item         amount: The amount that will be sent ######################################################################## # RM_sim Methods # # This create a simulation network containing flows between nodes         :return: # Make a new entity db corresponding to the original entity db # After using the new entity db, this will be removed. # cloned_db = edb.get_cloned_db(path=self.module) # The new cloned db is stored in the original entity, # so after simulation, all result data can be accessed. # self.cloned_db = cloned_db # Get a new process from the cloned db # new_en = cloned_db.get(self.name, path=self.module) # Make a simulation network using the new process # new_en.make_network_op(new_en.sim_network) # Set the cloned db to all entities # new_en.cloned_db = cloned_db # for e in new_en.sim_network: #     e.cloned_db = cloned_db This create a simulation network containing flows between nodes         :return: # Make a network for the action model # Reset all states in the elements of the network # The function sim is called, which means this is a root process # The root process is linked to 'process_END' using the relation 'flow' # 'process_END' -> 'process' # For the relation 'process_END' -> 'process', set 'sent = true' to activate the root process This finds all node flows         The returning list acts will have flow nodes which are activated by Python Asyncio          :param         entities: All entities that will be accumulated # Property does not belong to the simulation network # if not isinstance(e, Property): # Property does not belong to the simulation network # If e is not 'or' or 'and', then make a parallel flow # If e is not 'or' or 'and', then make a serial flow # e.g.,) (self)Process1->(e)Action1 # 1. 'cur_e.sim_with_decomposition' is checked for skipping the decomposition of sub-nodes # Especially, 'Action' can be decomposed. # 2. This 'cur_e' decomposes the node 'end' # This means that the next node 'e' should be connected to the node 'end' # e.g.,) (cur_e)Loop->Action1->Loop_END->Action2 # 'Action2' is linked to 'Loop_END', but not 'Loop' # If this entity does not allow the decomposition, don't make any sub-flows for simulation # If this entity is Action and the current child is Process, # this means this is a decomposed entity # The decomposed entity is set on here ###################################################################### # Concatenate this node to the 'END' node # # If e does not have the node 'end' (e.g., 'Action') # This means it doesn't have any pair # entities.append(self.end) # If e is the class 'END', this means e will terminate with the root process end # When END is created, it is already associated with the root process end, so skip # If e decomposes the node 'end' (e.g., 'Loop' and 'Action' containing 'process') # This means there is the 'End' pair for this # This 'End' pair is used for flowing to the current 'End' pair # entities.append(self.end) # This is likely to be an empty process (no 'contains' key) # Just let this root process to flow directly to the end # Make sure the list contains the end node This prints out the control flows of UC         :param entities: the flow list generated from 'init_sim_network()' This is a sysmpy function that performs all operation about simulation         Each DynamicEntity uses this function 'run' to do its own simulation          There are two phases             Phase 1: Check Activating Conditions, so that this can enter the activation stage of simulation             Phase 2: Perform Action in which all defined activities are performed # Info is used to store any information in this process and be printed out by 'Process.store_event()' ################################################################################## # #       Phase 1: Check Activating Conditions # ################################################################################## ######################################################################## # Check 1: Check waiting state # Any process can be waiting, if there is no event to this process # Reset all fire states # If all of these are true, then this process will fire all events to the child processes ######################################################################## # Check 2: Input Control Flows # This is receiving control flows # Non input flows mean that this is a root node # Such root node is just firing all events regardless any conditions # Take a list for the relationship 'flowed from' # No flowed_from means there are no input events for this node # There are two cases: (1) There is End() or ExitLoop() before this # Having flowed_from means there are input events for this node # Check whether all input events are set to 'sent=True' # This means this node now can fire its output event # If this self is 'or_END' and decomposes one true inputting event # Then this node now can fire its output event # If this self is 'Condition_END' and decomposes one true inputting event # Then this node now can fire its output event # If this self is 'Process_END' and decomposes one true inputting event # Then this node now can fire its output event # If this self is 'Loop_END' and decomposes one true inputting event # Then this node now can fire its output event # If this self is 'loop' and decomposes one true inputting event # Then this node now can fire its output event # If this flow should be fired, # All process which sent were reset to 'sent = False' # if b_flow_fire: #     for r in flows_true: #         r.sent = False # #     # Set a new start time for this process by checking the times of the previous flow entities #     max_time = max([r.start.time for r in flowed_from]) #     self.time = max_time #     info += 'Fired from: ' + ', '.join([r.start.name for r in flows_true]) ######################################################################## # Check 3: Resources Consumes # Take a list for the relationship 'consumes' # No seizes means there are no resource events for this node # Having seizes means there are resource events for this node # Check whether all input events are set to 'sent=True' # This means this node now can fire its output event ######################################################################## # Check 3: Resources Seizes # Take a list for the relationship 'seizes' # No seizes means there are no resource events for this node # Having seizes means there are resource events for this node # Check whether all input events are set to 'True' # This means this node now can fire its output event ######################################################################## # Check 4: Triggered Item # Take a list for the relationship 'triggered by' # No receives means there are no triggering events for this node # Having receives means there are triggering events for this node # Check whether all input events are set to 'trigger=True' # This means this node now can fire its output event # Set a new start time for this process by checking the times from the process that triggered #---------------------------------------------------- # Warning: This can make an error. We need to check only triggered items, but not common items. # If this is associated with conduits, the times for conduits should be applied # Calculate the conduit times # Apply the conduit times to this process time ################################################################################## # #       Phase 2: Perform Action # ################################################################################## # if self.name == 'A': #     print_out(self.name, b_flow_fire, b_seizes_fire, b_consumes_fire, b_triggered_fire) #     print_out('!') ######################################################################## # Check 0: If this flow should be fired, # All process which sent were reset to 'sent = False' # Set a new start time for this process by checking the times of the previous flow entities ######################################################################## # Check 1: Activate this action ######################################################################## # Check 2: Check global time ######################################################################## # Check 3: Resources Seizes # Seizes all related resources ######################################################################## # Check 4: Sends Item # Note that 'Sends Item' is for trigger items, but not common items # Because the simulation uses only the trigger items # Later we may be able to use the common items in some purposes # Check all send relations associated with the item of this relation r # All relations 'sent from' were set to 'True' # Then we can fire the related trigger items. # So, set all relation 'triggered' in this item as 'True', # if it decomposes the relation 'triggered' ######################################################################## # Check 5: Produces Resources ######################################################################## # Check 6: Resources Consumes ######################################################################## # Check 7: Input Control Flows # Send all output events # Perform a function script defined in the script # If there are no function and the properties of the items associated with the function, # then the values of the properties are randomly sampled. # selected = self.function(self.cloned_db) # This property performs the sampling by itself # Select only one process # Select at least one process # Perform a function script for selection # selected = self.function(self.cloned_db) # Because 'selected' is the original process in db and r.end is a copied process, # self.cloned_db.is_same() is used to check their same identity. # selected_flows = [r for r in flows if self.cloned_db.is_same(r.end, selected)] # Perform uniformly random selection for the branches # 'And' or other entities will send all flows to their child entities. ######################################################################## # Check 8: Properties for a process # A process can have properties which can be sampled, when the process was ended. # selected = proc.function(self.cloned_db) # Search properties of this process # This property performs the sampling by itself ######################################################################## # Check 9: Complete this process ######################################################################## # Check 10: Resources Seizes # Release all seized resources ######################################################################## # Check 11: One iteration of the root process was done # Process.store_event(self, 'decomposes done as the root process', info='') # Global simulation count increases # Save proper simulation results ######################################################################## # Waiting all action internal operations # ========================================================================= # #                                Action                                     # # ========================================================================= # Action Dynamic Entity ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # This action decomposes a sub-process # Consequently, this action will have the 'Action_end' # Link this and the end using the relation 'Pairs' # ========================================================================= # #                                And                                        # # ========================================================================= # And Dynamic Entity # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # ========================================================================= # #                                   Or                                      # # ========================================================================= # Or Dynamic Entity # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # ========================================================================= # #                                  XOr                                      # # ========================================================================= # XOr Dynamic Entity # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # ========================================================================= # #                                Condition                                  # # ========================================================================= # Condition Dynamic Entity # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # ========================================================================= # #                                    END                                    # # ========================================================================= # END Dynamic Entity # ========================================================================= # #                                 ExitLoop                                  # # ========================================================================= # # ========================================================================= # #                                 Process                                   # # ========================================================================= # Process Dynamic Entity # 'activated' is used to denote SMRE_Example's activation. False means 'stop simulation' # 'global_time' is used to denote SMRE_Example's global time # 'global_max' is used to denote when SMRE_Example will stop # RM_sim count is used to count each simulation run # simulation results are stored in queue # is there a web distributor # Find a module which is using this class # and set the caller's path to this class, so we can know where this object came form: # - last element ([-1]) is me, the one before ([-2]) is my caller. # - The first element in caller's data is the filename # Loop can inherit process. And a pure process uses the following codes. # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' # AsyncIO workers are used to be performed as computing threads # sim_network is a network containing all processes, actions, and items associated with simulation ######################################################################## # Analysis Methods # # This checks interfaces between functions (or actions) This checks all requirements and evaluates them using simulation results # print_out(e) # if self.current_clone is not None: #     actions, _ = self.current_clone.search(words_search=[Action]) # else: #     actions, _ = self.search(words_search=[Action]) # actions = self.cloned_db.get(Action, path=path) This opens a web browser to show simulation results ######################################################################## # staticmethod # # # Ignore unnecessary entities # If process-end # If this root_process contains any properties in the child entities # Send the values of the properties # Make data containing property values # Check the property values with the requirement ranges # Send the data to all clients The web distributor is a web server sending simulation events to the connected clients # raise SystemExit(e) This sends simulation events to the web distributor # sess = requests.Session() # adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100) # sess.mount('http://', adapter) # print_out("send events to web_distributor", str(r)) This triggers the simulation.          :param         until: The maximum time, modeled in the process flows, for simulation run         If until is None, the simulation performs just one time. # Check whether this contains actions. If not, simulation can't work # Find other flows and set them into asyncio # Reset all global variables regarding simulation # get flows from the relation 'flow' or 'contains' # print out control flows of UC # check whether a web distributor exist or not # The web distributor is a web server sending simulation events to the connected clients # get list of properties which are updated by the simulation # open a web browser for the property view ######################################################################## # Class Creation Methods # !start with an uppercase letter # :param name:         :param duration: The time modeled in the process flows         :return: # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # 'loop' is linked to 'loop_END' using the relation 'flow' # 'loop_END' -> 'loop' # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # 'loop' is linked to 'loop_END' using the relation 'flow' # 'loop_END' -> 'loop' # ========================================================================= # #                                   Loop                                    # # ========================================================================= # Loop Dynamic Entity # This decomposes a pair dynamic entity called 'end' or name+'_END' # Link this and the end using the relation 'Pairs' # 'loop' is linked to 'loop_END' using the relation 'flow' # 'loop_END' -> 'loop' ######################################################################## # Class Creation Methods # !start with an uppercase letter # # self.set_module(os.path.splitext(traceback.extract_stack()[-2][0])[0]) # 'loop_END' is set with the looping times