def get_condition_change_enablers(self, influence_update, all_dts, cache):
        """ Calculates if an if/else condition within the function can change its value """
        logger.debug(
            f"Calculating condition change time in '{influence_update._name}' in entity '{influence_update._parent._name}' ({influence_update._parent.__class__.__name__})"
        )
        solver = z3.Optimize(cache.ctx)

        # build a mapping that shows the propagation of information to the influence/update source (what influences the guard)
        if isinstance(influence_update, model.Influence):
            modifier_map = self.get_modifier_map(
                [influence_update.source, influence_update.target],
                cache=False)
        else:
            read_ports = SH.get_accessed_ports(influence_update.function,
                                               influence_update,
                                               cache=False)
            read_ports.append(influence_update.target)
            modifier_map = self.get_modifier_map(read_ports, cache=False)

        z3_vars = cache.z3_vars

        # add the initial values for the sources of the dataflow
        for port, modifiers in modifier_map.items():
            # set default port value to the current value
            pre_value = port.value  #get_z3_value(port, port._name + "_0").translate(cache.ctx)
            solver.add(z3_vars[port][port._name + "_0"] == pre_value)
            if len(modifiers) == 0:
                solver.add(
                    z3_vars[port][port._name] == z3_vars[port][port._name +
                                                               "_0"])

        # create the constraints for updates and influences
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                if modifier != influence_update:  # skip the one we're actually analysing, this should be already done in the modifier-map creation...
                    constraints = cache.z3_modifier_constraints[modifier]
                    solver.add(
                        constraints
                    )  #[const.translate(ctx) for const in constraints])

        conditionchanged_constraintset, additionals = cache.z3_conditionchanged_constraintsets[
            influence_update]
        solver.add(additionals)  #[a.translate(ctx) for a in additionals])

        min_dt, label = get_behaviour_change_dt_from_constraintset(
            solver,
            conditionchanged_constraintset,
            z3_vars['dt'],
            ctx=cache.ctx)
        if min_dt is not None:
            logger.info(
                f"Minimum condition change times in '{influence_update._name}' in entity '{influence_update._parent._name}' ({influence_update._parent.__class__.__name__}) is {min_dt} (at label {label})"
            )
            ret = (to_python(min_dt), influence_update, label)
            all_dts.append(ret)
예제 #2
0
    def _get_update_function_value(self, update, time):
        solver = z3.Solver()

        # create the z3 variables
        z3_vars = {}

        # create the TIME UNIT for dt
        z3_vars['dt'] = get_z3_var(self.timeunit, 'dt')
        z3_vars['dt'].type = self.timeunit
        solver.add(z3_vars['dt'] == time)

        z3_vars[update.target] = {
            update.target._name:
            get_z3_variable(update.target, update.target._name)
        }

        accessed_ports = SH.get_accessed_ports(update.function, update)
        for access in accessed_ports:
            if access != update.target:
                z3_vars[access] = {
                    access._name: get_z3_value(access, access._name)
                }
            z3_vars[access][access._name + "_0"] = get_z3_value(
                access, access._name + "_0")

        # convert function
        conv = Z3Converter(z3_vars,
                           entity=update._parent,
                           container=update,
                           use_integer_and_real=self.default_to_integer_real)
        conv.target = update.target
        constraints = conv.to_z3(update.function)

        if logger.getEffectiveLevel(
        ) <= logging.DEBUG:  # only log if the level is appropriate, since z3's string-conversion takes ages
            logger.debug(
                f"{update._name} {update} constraints: { constraints }")

        if SH.is_lambda(update.function):
            # equation for lambda result
            tgt = conv.z3_vars[update.target][update.target._name]
            solver.add(tgt == constraints)
        else:
            solver.add(constraints)

        if solver.check() == z3.sat:
            new_target_value = solver.model()[z3_vars[update.target][
                update.target._name]]
            new_target_value.type = z3_vars[update.target][
                update.target._name].type  # remember to assign a type
            return new_target_value
        else:
            raise Exception(
                "Problem when calculating the target value of the influence")
예제 #3
0
def gen_Transition(obj, name="", parent=None, **kwargs):
    returnlist = []
    color = get_color(id(obj)) if kwargs["color_updates"] else "black"

    label = ""
    if kwargs["transition_labels"]:
        guard_ast = SH.get_ast_body(obj.guard)
        label = astor.to_source(guard_ast)
    returnlist.append(
        f"{id(obj.source)} -> {id(obj.target)} [label=\"{label}\"]")

    accessed = SH.get_accessed_ports(obj.guard, obj)
    for acc in accessed:
        style = "dashed" if kwargs["show_transition_ports"] else "invis"
        returnlist.append(
            f"{id(acc)} -> {id(obj.target)} [style=\"{style}\" color=\"{color}\" ]"
        )

    return returnlist
예제 #4
0
    def create_input_space(self):
        """ create observation space and store it in local field"""
        logger.debug(f"Creating observation space")
        all_parameters = []

        all_parameters.extend(
            SH.get_accessed_ports(self.original_function,
                                  self.modifier,
                                  exclude_pre=True,
                                  cache=False))

        if isinstance(self.modifier, crest.Influence):
            all_parameters.append(self.modifier.source)

        # use openai's spaces for sampling, it's easier!
        space_dict = dict()
        for port in all_parameters:
            portname = port._name
            if port._parent != self.modifier._parent:
                portname = f"{port._parent._name}.{portname}"

            if "ranges" in self.kwargs:
                valuerange = self.kwargs["ranges"].get(
                    port, self.kwargs["ranges"].get(portname, None))
            else:
                valuerange = None

            space_dict[portname] = get_space_from_domain(
                port.resource.domain, valuerange)

        if isinstance(self.modifier, crest.Update):
            low = 0
            high = DEFAULT_SPREAD
            if "ranges" in self.kwargs and "dt" in self.kwargs["ranges"]:
                low = self.kwargs["ranges"].get("dt")[0]
                high = self.kwargs["ranges"].get("dt")[1]
            space_dict["dt"] = spaces.Box(low=low,
                                          high=high,
                                          shape=(1, ),
                                          dtype=np.float)
        assert GYM_INSTALLED, "Couldn't detect the gym package. Please ensure it is installed or run 'pip install gym'."
        return spaces.Dict(spaces=space_dict)
예제 #5
0
    def _get_transition_guard_value(self, transition):
        solver = z3.Solver()

        # create the z3 variables
        z3_vars = {}

        transition_ports = SH.get_accessed_ports(transition.guard, transition)
        for port in transition_ports:
            z3_vars[port] = {port._name: get_z3_value(port, port._name)}
            z3_vars[port][port._name + "_0"] = get_z3_value(
                port, port._name + "_0")

        conv = Z3Converter(z3_vars,
                           entity=transition._parent,
                           container=transition,
                           use_integer_and_real=self.default_to_integer_real)
        constraints = conv.to_z3(transition.guard)
        solver.add(constraints)

        return solver.check() == z3.sat
예제 #6
0
def gen_Update(obj, name="", parent=None, **kwargs):
    returnlist = []
    color = get_color(id(obj)) if kwargs["color_updates"] else "black"
    label = ""
    if kwargs["update_labels"]:
        # print("There's an issue with the display of update-labels. Waiting for astor 0.6 to be available...")
        # """ deactivate until astor 0.6 is available"""
        func_ast = SH.get_ast_from_function_definition(obj.function)
        label = astor.to_source(func_ast)
    returnlist.append(
        f"{id(obj.state)} -> {id(obj.target)} [style=\"dashed\" color=\"{color}\" label=\"{label}\"]"
    )

    accessed = SH.get_accessed_ports(obj.function, obj)
    for acc in accessed:
        style = "dashed" if kwargs["show_update_ports"] else "invis"
        returnlist.append(
            f"{id(acc)} -> {id(obj.target)} [style=\"{style}\" color=\"{color}\" ]"
        )

    return returnlist
    def get_transition_time(self, transition, all_dts, cache):
        """
        - we need to find a solution for the guard condition (e.g. using a theorem prover)
        - guards are boolean expressions over port values
        - ports are influenced by Influences starting at other ports (find recursively)
        """
        logger.debug(
            f"Calculating the transition time of '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__})"
        )
        solver = z3.Optimize(cache.ctx)

        # find the ports that influence the transition
        transition_ports = SH.get_accessed_ports(transition.guard,
                                                 transition,
                                                 cache=False)
        # logger.debug(f"The transitions influencing ports are called: {[p._name for p in transition_ports]}")
        # build a mapping that shows the propagation of information to the guard (what influences the guard)
        modifier_map = self.get_modifier_map(transition_ports, cache=False)

        z3_vars = cache.z3_vars
        solver.add(z3_vars['dt'] >= 0)

        # add the initial values for the sources of the dataflow
        for port, modifiers in modifier_map.items():
            # set default port value to the current value
            pre_value = port.value  #get_z3_value(port, port._name + "_0").translate(cache.ctx)
            solver.add(z3_vars[port][port._name + "_0"] == pre_value)
            if len(modifiers) == 0:
                solver.add(
                    z3_vars[port][port._name] == z3_vars[port][port._name +
                                                               "_0"])

        # create the constraints for updates and influences
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                constraints = cache.z3_modifier_constraints[modifier]
                solver.add(constraints)

        # guard_constraint = cache.z3_modifier_constraints[transition] if not isinstance(guardconst, bool)]
        # # this is because we cannot add booleans directly to a z3.Optimize (it works for Solver)
        # # the issue is here:  https://github.com/Z3Prover/z3/issues/1736
        # if isinstance(guard_constraint, bool):
        #     guard_constraint = z3.And(guard_constraint, ctx)

        solver.add(cache.z3_modifier_constraints[transition])

        objective = solver.minimize(z3_vars['dt'])  # find minimal value of dt
        check = solver.check()
        # logger.debug("satisfiability: %s", check)
        if solver.check() == z3.sat:
            log_if_level(
                logging.INFO,
                f"Minimum time to enable transition '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) will be enabled in {to_python(objective.value())}"
            )
            # return (objective.value(), transition, as_epsilon_expr)
            inf_coeff, numeric_coeff, eps_coeff = objective.lower_values()
            ret = (Epsilon(numeric_coeff, eps_coeff), transition)
            all_dts.append(ret)
        elif check == z3.unknown:
            log_if_level(
                logging.WARNING,
                f"The calculation of the minimum transition time for '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) was UNKNOWN. This usually happening in the presence of non-linear constraints. Do we have any?"
            )
            std_solver = z3.Solver()
            std_solver.add(solver.assertions())
            std_solver_check = std_solver.check()
            if std_solver_check == z3.sat:
                min_dt = std_solver.model()[z3_vars['dt']]
                log_if_level(
                    logging.INFO,
                    f"We did get a solution using the standard solver though: {to_python(min_dt)} Assuming that this is the smallest solution. CAREFUL THIS MIGHT BE WRONG (especially when the transition is an inequality constraint)!!!"
                )
                ret = (to_python(min_dt), transition)
                all_dts.append(ret)
            elif std_solver_check == z3.unknown:
                logger.error(
                    f"The standard solver was also not able to decide if there is a solution or not. The constraints are too hard!!!"
                )
            else:
                logger.info(
                    "The standard solver says there is no solution to the constraints. This means we also couldn't minimize. Problem solved."
                )
        else:
            logger.debug(
                f"Constraint set to enable transition '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) is unsatisfiable."
            )
예제 #8
0
def get_modifier_map(root_entity, port_list, cache=True):
    """Creates a dict that has ports as keys and a list of influences/updates that influence those ports as values."""

    logger.debug(
        f"creating modifier map for ports {[p._name +' (in: '+ p._parent._name+')' for p in port_list]}"
    )
    modifier_map = {port: list() for port in port_list}
    map_change = True

    all_updates = get_all_updates(root_entity)
    all_influences = get_all_influences(root_entity)

    while map_change:
        map_change = False  # initially we think there are no changes
        for port, modifiers in modifier_map.copy().items(
        ):  # iterate over a copy, so we can modify the original list
            # we only look at ports that have no influences (it might be because there exist none, but that small overhead is okay for now)
            if len(modifiers) == 0:
                logger.debug(
                    f"trying to find modifiers for port '{port._name}' of entity '{port._parent._name} ({port._parent.__class__.__name__})'"
                )
                influences = [
                    inf for inf in all_influences if port == inf.target
                ]
                modifier_map[port].extend(influences)
                for inf in influences:
                    logger.debug(
                        f"'{port._name}' is modified by influence '{inf._name}'"
                    )
                    # this means influences is not empty, hence we change the map (probably)
                    map_change = True
                    if inf.source not in modifier_map:
                        modifier_map[inf.source] = list(
                        )  # add an empty list, the next iteration will try to fill it

                updates = [
                    up for up in all_updates
                    if port == up.target and up.state == up._parent.current
                ]

                modifier_map[port].extend(updates)
                for up in updates:
                    # logger.debug(f"'{port._name}' is modified by update '{up._name}'")
                    # read_ports = SH.get_read_ports_from_update(up.function, up)  # +[up.target]
                    accessed_ports = SH.get_accessed_ports(up.function,
                                                           up,
                                                           exclude_pre=False,
                                                           cache=cache)

                    # logger.debug(f"'{up._name}' in '{up._parent._name}' reads the following ports: {[(p._name, p._parent._name) for p in accessed_ports]}")
                    for read_port in accessed_ports:
                        # this means there are updates and we change the map
                        map_change = True
                        if read_port not in modifier_map:
                            # logger.debug(f"adding {read_port._name} to modifier_map")
                            modifier_map[read_port] = list(
                            )  # add an empty list, the next iteration will try to fill it
    logger.debug(
        f"the modifier map looks like this: \n{pformat(prettify_modifier_map(modifier_map))}"
    )
    return modifier_map
예제 #9
0
    def get_transition_time(self, transition):
        """
        - we need to find a solution for the guard condition (e.g. using a theorem prover)
        - guards are boolean expressions over port values
        - ports are influenced by Influences starting at other ports (find recursively)
        """
        logger.debug(
            f"Calculating the transition time of '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__})"
        )
        solver = z3.Optimize()

        # find the ports that influence the transition
        transition_ports = SH.get_accessed_ports(transition.guard, transition)
        # logger.debug(f"The transitions influencing ports are called: {[p._name for p in transition_ports]}")
        # build a mapping that shows the propagation of information to the guard (what influences the guard)
        modifier_map = self.get_modifier_map(transition_ports)

        z3var_constraints, z3_vars = self.get_z3_vars(modifier_map)
        solver.add(z3var_constraints)
        solver.add(z3_vars['dt'] >= 0)

        # check if there are actually any timed behaviour changes
        any_modifier_uses_dt = False
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                any_modifier_uses_dt = any_modifier_uses_dt or uses_dt_variable(
                    modifier)

        if not any_modifier_uses_dt:
            currently_enabled = transition.guard(api.get_parent(transition))
            return (0, transition) if currently_enabled else None

        # create the constraints for updates and influences
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                constraints = self._get_constraints_from_modifier(
                    modifier, z3_vars)
                solver.add(constraints)

        # for port, modifiers in modifier_map.items():
        # write pre value

        # logger.debug(f"adding constraints for transition guard: {transition._name}")
        conv = Z3Converter(z3_vars,
                           entity=transition._parent,
                           container=transition,
                           use_integer_and_real=self.use_integer_and_real)
        guard_constraint = conv.to_z3(transition.guard)

        # this is because we cannot add booleans directly to a z3.Optimize (it works for Solver)
        # the issue is here:  https://github.com/Z3Prover/z3/issues/1736
        if isinstance(guard_constraint, bool):
            guard_constraint = z3.And(guard_constraint)
        solver.add(guard_constraint)

        # import pprint;pprint.pprint(z3_vars)

        # log_if_level(logging.DEBUG, f"Constraints handed to solver:\n{solver}")

        objective = solver.minimize(z3_vars['dt'])  # find minimal value of dt
        check = solver.check()
        # logger.debug("satisfiability: %s", check)
        if solver.check() == z3.sat:
            log_if_level(
                logging.INFO,
                f"Minimum time to enable transition '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) will be enabled in {to_python(objective.value())}"
            )
            # return (objective.value(), transition, as_epsilon_expr)
            inf_coeff, numeric_coeff, eps_coeff = objective.lower_values()
            return (Epsilon(numeric_coeff, eps_coeff), transition)
        elif check == z3.unknown:
            log_if_level(
                logging.WARNING,
                f"The calculation of the minimum transition time for '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) was UNKNOWN. This usually happening in the presence of non-linear constraints. Do we have any?"
            )
            std_solver = z3.Solver()
            std_solver.add(solver.assertions())
            std_solver_check = std_solver.check()
            if std_solver_check == z3.sat:
                min_dt = std_solver.model()[z3_vars['dt']]
                log_if_level(
                    logging.INFO,
                    f"We did get a solution using the standard solver though: {to_python(min_dt)} Assuming that this is the smallest solution. CAREFUL THIS MIGHT BE WRONG (especially when the transition is an inequality constraint)!!!"
                )
                return (to_python(min_dt), transition)
            elif std_solver_check == z3.unknown:
                logger.error(
                    f"The standard solver was also not able to decide if there is a solution or not. The constraints are too hard!!!"
                )
                return None
            else:
                logger.info(
                    "The standard solver says there is no solution to the constraints. This means we also couldn't minimize. Problem solved."
                )
                return None
        else:
            logger.debug(
                f"Constraint set to enable transition '{transition._name}' in entity '{transition._parent._name}' ({transition._parent.__class__.__name__}) is unsatisfiable."
            )
            return None
예제 #10
0
    def get_condition_change_enablers(self, influence_update):
        """ Calculates if an if/else condition within the function can change its value """
        logger.debug(
            f"Calculating condition change time in '{influence_update._name}' in entity '{influence_update._parent._name}' ({influence_update._parent.__class__.__name__})"
        )
        solver = z3.Optimize()

        # build a mapping that shows the propagation of information to the influence/update source (what influences the guard)
        if isinstance(influence_update, Influence):
            modifier_map = self.get_modifier_map(
                [influence_update.source, influence_update.target])
        else:
            read_ports = SH.get_accessed_ports(influence_update.function,
                                               influence_update)
            read_ports.append(influence_update.target)
            modifier_map = self.get_modifier_map(read_ports)

        z3var_constraints, z3_vars = self.get_z3_vars(modifier_map)
        solver.add(z3var_constraints)
        # NOTE: we do not add a "dt >= 0" or "dt == 0" constraint here, because it would break the solving

        # check if there are actually any timed behaviour changes
        any_modifier_uses_dt = False
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                if modifier != influence_update:  # skip the one we're actually analysing, this should be already done in the modifier-map creation...
                    any_modifier_uses_dt = any_modifier_uses_dt or uses_dt_variable(
                        modifier)

        influence_update_uses_dt = uses_dt_variable(influence_update)
        if not influence_update_uses_dt and not any_modifier_uses_dt:
            return None  # nobody uses dt, no point in trying to find out when things will change...

        # create the constraints for updates and influences
        for port, modifiers in modifier_map.items():
            for modifier in modifiers:
                if modifier != influence_update:  # skip the one we're actually analysing, this should be already done in the modifier-map creation...
                    constraints = self._get_constraints_from_modifier(
                        modifier, z3_vars)
                    solver.add(constraints)

        # solver.push()  # backup

        # if it's an influence, we need to add the source param equation
        if isinstance(influence_update, Influence):
            z3_src = z3_vars[influence_update.source][
                influence_update.source._name]
            params = SH.get_param_names(influence_update.function)
            param_key = params[0] + "_" + str(id(influence_update))
            z3_param = get_z3_variable(influence_update.source, params[0],
                                       str(id(influence_update)))

            log_if_level(
                logging.DEBUG,
                f"adding param: z3_vars[{param_key}] = {params[0]}_0 : {z3_param} "
            )

            z3_vars[param_key] = {params[0] + "_0": z3_param}

            log_if_level(
                logging.DEBUG,
                f"influence entry constraint: {z3_src} == {z3_param}")
            solver.add(z3_src == z3_param)

        # solver.push()

        if not hasattr(influence_update,
                       "_cached_z3_behaviour_change_constraints"):
            conv = Z3ConditionChangeCalculator(
                z3_vars,
                entity=influence_update._parent,
                container=influence_update,
                use_integer_and_real=self.use_integer_and_real)
            influence_update._cached_z3_behaviour_change_constraints = conv.calculate_constraints(
                influence_update.function)

        constraints = influence_update._cached_z3_behaviour_change_constraints
        min_dt, label = get_behaviour_change_dt_from_constraintset(
            solver, constraints, z3_vars['dt'])
        if min_dt is not None:
            logger.info(
                f"Minimum condition change times in '{influence_update._name}' in entity '{influence_update._parent._name}' ({influence_update._parent.__class__.__name__}) is {min_dt} (at label {label})"
            )
            return (to_python(min_dt), influence_update, label)
        else:
            return None