Beispiel #1
0
    def test_get_transitions_from_subclass_instance_multi_transition_defs(
            self):
        class Test(crest.Entity):
            s1 = current = crest.State()
            s2 = crest.State()
            s3 = crest.State()

            @crest.transition(source=[s1, s3], target=s2)
            def t1(self):
                return True

            t2 = crest.Transition(source=s2,
                                  target=s3,
                                  guard=(lambda self: True))
            t3 = crest.Transition(source=[s3, s2],
                                  target=s1,
                                  guard=(lambda self: False))

        class SubTest(Test):
            pass

        instance = SubTest()
        transitions = crest.get_transitions(instance)
        self.assertCountEqual(transitions, [
            getattr(instance, att)
            for att in ["t1___0", "t1___1", "t2", "t3___0", "t3___1"]
        ])
Beispiel #2
0
    def test_get_transitions_from_subclass_definition_multi_transition_defs(
            self):
        class Test(crest.Entity):
            s1 = current = crest.State()
            s2 = crest.State()
            s3 = crest.State()

            @crest.transition(source=[s1, s3], target=s2)
            def t1(self):
                return True

            t2 = crest.Transition(source=s2,
                                  target=s3,
                                  guard=(lambda self: True))
            t3 = crest.Transition(source=[s3, s2],
                                  target=s1,
                                  guard=(lambda self: False))

        class SubTest(Test):
            pass

        klass = SubTest
        transitions = crest.get_transitions(klass)
        self.assertCountEqual(
            transitions,
            [klass.t1___0, klass.t1___1, klass.t2, klass.t3___0, klass.t3___1])
Beispiel #3
0
    def test_get_transitions_from_subclass_instance_single_transition_defs(
            self):
        class Test(crest.Entity):
            s1 = current = crest.State()
            s2 = crest.State()
            s3 = crest.State()

            @crest.transition(source=s1, target=s2)
            def t1(self):
                return True

            t2 = crest.Transition(source=s2,
                                  target=s3,
                                  guard=(lambda self: True))
            t3 = crest.Transition(source=s3,
                                  target=s1,
                                  guard=(lambda self: False))

        class SubTest(Test):
            pass

        instance = SubTest()
        transitions = crest.get_transitions(instance)
        self.assertCountEqual(transitions,
                              [instance.t1, instance.t2, instance.t3])
    def test_transition_correctly_created(self):
        self.testclass.trans = crest.Transition(source="A", target="B", guard=(lambda self: True))

        instance = self.instance()
        transitions = crest.get_transitions(instance)
        self.assertEqual(len(transitions), 1)
        self.assertEqual(self._count_transitions_from_to(transitions, instance.A, instance.B), 1)
Beispiel #5
0
    def collect_transition_times_from_entity(self, entity=None):
        """ collect all transitions and their times """
        if not entity:
            entity = self.entity
        logger.debug("Calculating transition times for entity: %s (%s)",
                     entity._name, entity.__class__.__name__)

        dts = []
        for name, trans in get_transitions(entity, as_dict=True).items():
            if entity.current == trans.source:
                dt = self.get_transition_time(trans)
                dts.append((entity, trans, name, dt))

        if dts:
            if logger.getEffectiveLevel() <= logging.DEBUG:
                logger.debug("times: ")
                logger.debug(
                    str([(e._name,
                          f"{t.source._name} -> {t.target._name} ({dt})", dt)
                         for (e, t, name, dt) in dts]))
        else:
            if logger.getEffectiveLevel() <= logging.DEBUG:
                logger.debug("times: []")
        dts = list(filter(lambda t: t[3] is not None,
                          dts))  # filter values with None as dt
        return dts
Beispiel #6
0
def gen_Entity(obj, name="", parent=None, **kwargs):
    islogical = issubclass(obj.__class__, Model.LogicalEntity)
    hide_behaviour = kwargs["interface_only"] or kwargs["no_behaviour"] or \
        (islogical and kwargs["logical_interface_only"])

    style = "dashed" if islogical else "solid"

    inputs = ""
    outputs = ""
    centre = ""
    body = ""
    """ Inputs """
    for name_, input_ in Model.get_inputs(obj, as_dict=True).items():
        inputs += "\n" + generate(input_, name_, obj, **kwargs)
    """ Centre """
    if not hide_behaviour:
        for name_, state in Model.get_states(obj, as_dict=True).items():
            if not name_ == CURRENT_IDENTIFIER:
                centre += "\n" + generate(state, name_, obj, **kwargs)

    if not hide_behaviour:
        for name_, local in Model.get_locals(obj, as_dict=True).items():
            centre += "\n" + generate(local, name_, obj, **kwargs)
    """ Outputs """
    for name_, output in Model.get_outputs(obj, as_dict=True).items():
        outputs += "\n" + generate(output, name_, obj, **kwargs)
    """ Body """
    if not hide_behaviour:
        if kwargs["transitions"]:
            for name_, trans in Model.get_transitions(obj,
                                                      as_dict=True).items():
                body += "\n" + "\n".join(generate(trans, name_, obj, **kwargs))

    if not kwargs["interface_only"] and not (islogical and
                                             kwargs["logical_interface_only"]):
        for name_, entity in Model.get_entities(obj, as_dict=True).items():
            if name_ != PARENT_IDENTIFIER:
                centre += "\n" + generate(entity, name_, obj, **kwargs)

    if not kwargs["interface_only"] and not (islogical and
                                             kwargs["logical_interface_only"]):
        for name_, influence in Model.get_influences(obj,
                                                     as_dict=True).items():
            body += "\n" + generate(influence, name_, obj, **kwargs)

    if not hide_behaviour:
        if kwargs["updates"]:
            for name_, update in Model.get_updates(obj, as_dict=True).items():
                body += "\n" + "\n".join(generate(update, name_, obj, **
                                                  kwargs))

    if not hide_behaviour:
        if kwargs["actions"]:
            for name_, action in Model.get_actions(obj, as_dict=True).items():
                body += "\n" + "\n".join(generate(action, name_, obj, **
                                                  kwargs))

    typename = obj.__class__.__name__ if isinstance(
        obj, CrestObject) else obj.__name__
    return f"""
Beispiel #7
0
    def select_transition_to_trigger(self, entity):
        transitions_from_current_state = [
            t for t in crest.get_transitions(entity)
            if t.source is entity.current
        ]
        enabled_transitions = [
            t for t in transitions_from_current_state
            if self._get_transition_guard_value(t)
        ]

        if len(enabled_transitions) == 1:  # no choice
            return enabled_transitions[0]
        elif len(enabled_transitions) > 1:  # this is the tricky part
            logger.debug(
                f"Multiple transitions enabled in entity {api.get_name(entity)}"
            )
            transition = self._select_transition_according_to_execution_plan(
                entity)
            if transition is False:
                return random.choice(enabled_transitions)
            else:
                assert transition is not None and transition in enabled_transitions, \
                    f"The plan says to execute transition {api.get_name(transition)}, but it's not amongst the enabled ones."
                return transition
        else:  # no transitions at all
            return None
    def test_transitions_source_slash(self):
        self.testclass.trans = crest.Transition(source="/", target="A", guard=(lambda self: True))

        instance = self.instance()
        transitions = crest.get_transitions(instance)
        self.assertEqual(len(transitions), 2)
        self.assertEqual(self._count_transitions_from_to(transitions, instance.B, instance.A), 1)
        self.assertEqual(self._count_transitions_from_to(transitions, instance.C, instance.A), 1)
Beispiel #9
0
def gen_Entity(obj, name="", parent=None, **kwargs):
    parentinfo = "" if parent is None else parent._name
    logger.debug(
        f"Adding entity '{name}' of type {type(obj)} with parent (id={id(parent)})"
    )
    typename = obj.__class__.__name__ if isinstance(
        obj, CrestObject) else obj.__name__
    node = {
        'id': str(id(obj)),
        'label': {
            'label': f'{name} | {typename}',
            'text': name
        },
        'children': [],
        'ports': [],
        'edges': [],
        'cresttype': 'entity',
        'width': 300,
        'height': 200
    }
    """ Inputs """
    for name_, input_ in Model.get_inputs(obj, as_dict=True).items():
        node["ports"].append(generate(input_, name_, obj, **kwargs))
    """ Centre """
    for name_, state in Model.get_states(obj, as_dict=True).items():
        if not name_ == CURRENT_IDENTIFIER:
            node["children"].append(generate(state, name_, obj, **kwargs))

    for name_, local in Model.get_locals(obj, as_dict=True).items():
        node["children"].append(generate(local, name_, obj, **kwargs))
    """ Outputs """
    for name_, output in Model.get_outputs(obj, as_dict=True).items():
        node["ports"].append(generate(output, name_, obj, **kwargs))

    for name_, entity in Model.get_entities(obj, as_dict=True).items():
        if name_ != PARENT_IDENTIFIER:
            node["children"].append(generate(entity, name_, obj, **kwargs))

    for name_, trans in Model.get_transitions(obj, as_dict=True).items():
        node["edges"].extend(generate(trans, name_, obj, **kwargs))
        node["children"].append(generate_midpoint(trans, name_, obj, **kwargs))

    for name_, influence in Model.get_influences(obj, as_dict=True).items():
        node["edges"].extend(generate(influence, name_, obj, **kwargs))

    for name_, update in Model.get_updates(obj, as_dict=True).items():
        node["edges"].extend(generate(update, name_, obj, **kwargs))
        pass

    for name_, action in Model.get_actions(obj, as_dict=True).items():
        node["edges"].extend(generate(action, name_, obj, **kwargs))

    return node
Beispiel #10
0
    def select_transition_to_trigger(self, entity):
        """ This one operates randomly """
        transitions_from_current_state = [
            t for t in get_transitions(entity) if t.source is entity.current
        ]
        enabled_transitions = [
            t for t in transitions_from_current_state
            if self._get_transition_guard_value(t)
        ]

        if len(enabled_transitions) >= 1:  # by default, select one randomly
            return random.choice(enabled_transitions)
        else:
            return None
Beispiel #11
0
    def calculate_entity_hook(self, entity):
        all_dts = []
        logger.debug(
            f"Calculating behaviour change for entity {entity._name} ({entity.__class__.__name__})"
        )
        for influence in get_influences(entity):
            if self.contains_if_condition(influence):
                inf_dts = self.get_condition_change_enablers(influence)
                if inf_dts is not None:
                    all_dts.append(inf_dts)

        # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current]
        for update in get_updates(entity):
            if update.state is update._parent.current:  # only the currently active updates
                if self.contains_if_condition(update):
                    up_dts = self.get_condition_change_enablers(update)
                    if up_dts is not None:
                        all_dts.append(up_dts)

        # TODO: check for transitions whether they can be done by time only
        for name, trans in get_transitions(entity, as_dict=True).items():
            if entity.current is trans.source:
                trans_dts = self.get_transition_time(trans)
                if trans_dts is not None:
                    all_dts.append(trans_dts)

        if logger.getEffectiveLevel() <= logging.DEBUG:
            if len(all_dts) == 0:
                logger.debug(
                    "There were no change times in entity {entity._name} ({entity.__class__.__name__})."
                )
                return []

            min_dt_eps = min(all_dts, key=(lambda x: x[0]))
            # min_dt = get_minimum_dt_of_several(all_dts, self.timeunit)
            if min_dt_eps is not None:
                logger.debug(
                    f"Minimum behaviour change time for entity {entity._name} ({entity.__class__.__name__}) is {min_dt_eps}"
                )
            return [min_dt_eps]  # return a list

            # min_dt = get_minimum_dt_of_several(all_dts, self.timeunit)
            # if min_dt is not None:
            #     logger.debug(f"Minimum behaviour change time for entity {entity._name} ({entity.__class__.__name__}) is {min_dt}")
            # return [min_dt]  # return a list
        else:
            # XXX This is the faster one that we run when we're not debugging !!
            return all_dts
Beispiel #12
0
    def test_get_transitions_from_class_definition_single_transition_defs(
            self):
        class Test(crest.Entity):
            s1 = current = crest.State()
            s2 = crest.State()
            s3 = crest.State()

            @crest.transition(source=s1, target=s2)
            def t1(self):
                return True

            t2 = crest.Transition(source=s2,
                                  target=s3,
                                  guard=(lambda self: True))
            t3 = crest.Transition(source=s3,
                                  target=s1,
                                  guard=(lambda self: False))

        klass = Test
        transitions = crest.get_transitions(klass)
        self.assertCountEqual(transitions, [klass.t1, klass.t2, klass.t3])
Beispiel #13
0
    def check_action_sanity(self):
        """Check that each action is properly named, 
        has a transition and from the same entity and a target port that is in the "targets" of the entity. 
        Also verifies the signature of the action function.
        """
        for action in crest.get_all_actions(self.model):
            assert action._name is not None, f"There is an Action in {action._parent._name} ({action._parent.__class__.__name__}) whose name is 'None'"
            assert action._name != "", f"There is an Action in {action._parent._name} ({action._parent.__class__.__name__}) whose name is empty string"

            assert isinstance(action.transition, crest.Transition), f"Action {action._name}'s state is not a crest.Transition. It is: {action.transition} ({action.transition.__class__})"
            assert action.state in crest.get_transitions(action._parent), f"Action's transition {action.transition._name} ({action.transition}) is not in the transitions of entity {action._parent._name} ({action._parent})"

            assert isinstance(action.target, crest.Port), f"Action {action._name}'s target is not a crest.Port"
            assert action.target in api.get_targets(action._parent), f"Action's target {action.target._name} ({action.target}) is not in the targets of entity {action._parent._name} ({action._parent})"

            assert isinstance(action.function, (crestml.LearnedFunction, types.FunctionType)), f"Action {action._name}'s function needs to be of type types.FunctionType or crestdsl.ml.LearnedFunction"
            assert 'self' in inspect.signature(action.function).parameters, f"Action {action._name}'s function has no self parameter. entity: {action._parent._name} ({action._parent.__class__.__name__})"
            assert len(inspect.signature(action.function).parameters) == 1, f"An action should have only one one argument 'self'"

            for port in SH.get_read_ports_from_update(action.function, action):
                assert port in api.get_sources(action._parent), f"Action {action._name} seems to be reading a port {port._name} ({port}) which is not in the sources of its entity {action._parent._name} ({action._parent})"
Beispiel #14
0
    def transition(self, entity):
        # logger.debug(f"transitions in entity {entity._name} ({entity.__class__.__name__})")
        transitions_from_current_state = [
            t for t in model.get_transitions(entity)
            if t.source is entity.current
        ]
        enabled_transitions = [
            t for t in transitions_from_current_state
            if self._get_transition_guard_value(t)
        ]

        state_before = SystemState(self.system).save()  # backup the state

        states_after = []
        for transition in enabled_transitions:
            state_before.apply()  # reset to original state
            entity.current = transition.target
            # logger.info(f"Time: {self.global_time} | Firing transition <<{transition._name}>> in {entity._name} ({entity.__class__.__name__}) : {transition.source._name} -> {transition.target._name}  | current global time: {self.global_time}")

            transition_updates = [
                up for up in model.get_updates(transition._parent)
                if up.state is transition
            ]  # FIXME: until we completely switched to only allowing actions...
            actions = [
                a for a in model.get_actions(transition._parent)
                if a.transition is transition
            ]
            for act in actions + transition_updates:
                newval = self._get_action_function_value(act)
                if newval != act.target.value:
                    act.target.value = newval

            state_after = SystemState(self.system).save()
            states_after.append((state_after, [transition]))

        # return the new states if there are any (this means that empty list means no transitions were fired)
        # logger.debug(f"finished transitions in entity {entity._name} ({entity.__class__.__name__}): Created {len(states_after)} new states!")
        return states_after
Beispiel #15
0
def _replacability_checks(entity, name, obj, existing):
    # TODO: clean me up ? These are lots of checks, maybe we need to make them look nice

    if isinstance(obj, crest.Entity):
        for update in crest.get_updates(
                entity
        ):  # check if an update already writes to one of the entity's ports
            if update.target in crest.get_inputs(existing):
                raise AttributeError(
                    f"Cannot reassign SubEntity '{name}' since one of its Input ports was used as target of Update '{get_name(update)}'."
                )
        for influence in crest.get_influences(
                entity
        ):  # check if an influence already reads from or writes to the entity's ports
            if influence.source in crest.get_outputs(existing):
                raise AttributeError(
                    f"Cannot reassign SubEntity '{name}' since one of its Output ports was used as source of Influence '{get_name(influence)}'."
                )
            if influence.target in crest.get_inputs(existing):
                raise AttributeError(
                    f"Cannot reassign SubEntity '{name}' since one of its Input ports was used as target of Influence '{get_name(influence)}'."
                )
        for action in crest.get_actions(
                entity
        ):  # check if an action already writes to the entity's ports
            if action.target in crest.get_inputs(existing):
                raise AttributeError(
                    f"Cannot reassign SubEntity '{name}' since one of its Input ports was used as target of Action '{get_name(action)}'."
                )

    elif isinstance(obj, crest.Port):
        for update in crest.get_updates(
                entity
        ):  # check if an update already writes to the port that we want to override
            if update.target == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} port '{name}' after it was used as target of Update '{get_name(update)}'."
                )
        for influence in crest.get_influences(
                entity
        ):  # check if an influence already reads from or writes to the port that we want to override
            if influence.source == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} port '{name}' after it was used as source of Influence '{get_name(influence)}'."
                )
            if influence.target == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} port '{name}' after it was used as target of Influence '{get_name(influence)}'."
                )
        for action in crest.get_actions(
                entity
        ):  # check if an action already writes to the port that we want to override
            if action.target == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} port '{name}' after it was used as target of Action '{get_name(action)}'."
                )

    elif isinstance(obj, crest.State):
        for update in crest.get_updates(
                entity
        ):  # check if an update is already linked to the state that we want to override
            if update.state == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} '{name}' after it was used in Update '{get_name(update)}'."
                )
        for transition in crest.get_transitions(
                entity
        ):  # check if a transition already starts from or goes to the state that we want to override
            if transition.source == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} '{name}' after it was used as source of Transition '{get_name(transition)}'."
                )
            if transition.target == existing:
                raise AttributeError(
                    f"Cannot reassign {obj.__class__.__name__} '{name}' after it was used as target of Transition '{get_name(transition)}'."
                )

    elif isinstance(obj, crest.Transition):
        # TODO: check here for any action uses the transition already
        # this is non-trivial however
        pass
Beispiel #16
0
    def run_plan(self, execution_plan):
        """
        Executes a plan.
        The execution plan is a heterogeneous list of the following items:
          - numeric values: (specify time advances)
          - pairs of (numeric, {entity: transition}-dict): 
            advance a certain time and choose transition, 
            everytime non-determinism is encountered in entity
          - {port: value} dicts (for setting of input ports)
          - {entity: transitions} dicts (for resolving conflicts)
        
        The list items will be iteratively consumed.
        If errors are observed they raise ValueError exceptions.
        
        If there is non-determinism, but no dict specifies how to resolve it, then we fall back to randomness.
        
        Here's a documented example of a plan:
        [
            # advance 10 time units:
            10,
            
            # set values:
            {entity.port : 33, entity.port2: -200},
            # advance 20 time units and choose these transitions everytime there is a conflict in this period
            (20, {entity: entity.transition1, entity.subentity: entity.subentity.transition2} ),
            
            # advance 33 time units. 
            # When you hit a conflict, check if the first element is an entity-state dict
            # if the entity is a key in the first element, then pop it and 
            # use it to reolve the conflict (otherwise choose randomly)
            # then continue until anothoer conflict or end of advance 
            33,
            {entity: entity.transitionA},
            {entity: entity.transitionB},
            {entity: entity.transitionA},
            
            # if you have two entities and you don't know which one will be conflicting first 
            # (because they'll have conflicts at the same time)
            # you can put them both in a dict and duplicate the dict. 
            # the first one will pop the first dict, the second one the second dict:
            
            444,
            {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB},
            {entity.subentity1: entity.subentity2.transA, entity.subentity2: entity.subentity2.transB},
        ]
        
        Parameters
        ----------
        execution_plan: list
            The list of instructions that should be executed.
            
        Raises
        -------
        ValueError
            In case there is something wrongly specified (e.g. take a transition that is not enabled, or so)
        """
        # some setup
        self.execution_plan = execution_plan
        self._transition_selection = None

        while len(self.execution_plan) > 0:
            next_action = self.execution_plan.pop(0)
            logger.debug(f"Next action is {repr(next_action)}")
            if isinstance(next_action, numbers.Number) and not isinstance(
                    next_action, bool):
                logger.info(
                    f"Consuming command: advance {next_action} time units")
                self.advance(next_action)
            elif isinstance(next_action, tuple):
                assert isinstance(
                    next_action[0], numbers.Number) and not isinstance(
                        next_action,
                        bool), "List entry have a numeric value first"
                assert isinstance(
                    next_action[1],
                    dict), "List entry must have a dict value second"

                # do some checks
                for entity, trans in next_action[1].items():
                    assert isinstance(entity, crest.Entity)
                    assert isinstance(trans, crest.Transition)
                    assert trans in crest.get_transitions(entity)

                self._transition_selection = next_action[1]
                self.advance(next_action[0])
                self._transition_selection = None  # reset it to None
            elif isinstance(next_action, dict):
                if not all([
                        isinstance(port, crest.Port)
                        for port in next_action.keys()
                ]):
                    raise ValueError(
                        f"When consuming command I found a dict whose keys are not Port objects. Dict: \n{repr(next_action)}"
                    )
                as_strings = [
                    f"{api.get_name(port)}: {value}"
                    for port, value in next_action.items()
                ]
                logger.info(
                    f"Consuming command: setting port values { ', '.join(as_strings) }"
                )
                self.set_values(next_action)
            else:
                raise ValueError(
                    f"Don't know how to act for plan item:\n{repr(next_action)}."
                )