Example #1
0
    def advance_to_behaviour_change(
            self,
            consider_behaviour_changes=config.consider_behaviour_changes):
        """
        Calculates when the next discrete change in behaviour will happen and advance as many time units.
        Note, this also does the according stabilisation, so you cannot stop "before" the behaviour change.
                
        Parameters
        ----------
        consider_behaviour_changes: bool
            You usually won't have to modify this (so don't!)
            Allows you to deactivate searching for if/else condition changes.
            It will be removed soon anyway!
        """
        if consider_behaviour_changes:
            nbct = self.next_behaviour_change_time()
        else:
            nbct = self.next_transition_time()

        if nbct is None:  # no behaviour change and no next transition through time advance
            return

        dt = to_python(nbct[0])
        if dt > 0:
            return self.advance(dt)
        else:
            return self.stabilise()
    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)
Example #3
0
 def get_next_transition_time(self):
     """ this function is a convenience for debugging, so we don't have to create a TransitionTimeCalculator manually """
     ntt = self.next_transition_time()
     logger.warning(
         "Warning. This will really only consider transitions. Not if/else conditions in updates and influences. Are you sure you want to ignore these behaviour changes?"
     )
     if ntt:
         logger.info(
             f"The next transition to fire is '{ntt[1]._name}' in ntt={to_python(ntt[0])} time steps"
         )
         return (ntt[1]._name, to_python(ntt[0]))
     else:
         logger.info("There is no transition reachable by time advance.")
         return None
Example #4
0
    def update(self, entity, time):
        time = to_python(time)  # assert it's a python number
        logger.info(
            f"entity <<{entity._name}>> ({entity.__class__.__name__}) dt = {time}"
        )

        for _in in get_inputs(entity):
            _in.pre = _in.value
        for _out in get_outputs(entity):
            _out.pre = _out.value

        before = {port: port.value for port in get_all_ports(entity)}
        retval = self.update_system(entity, time)
        logger.info(
            f"finished entity <<{entity._name}>> ({entity.__class__.__name__}) dt = {time}"
        )
        for port in get_all_ports(entity):
            if port.value != before[port]:
                logger.info(
                    f"The following port value changed: {port._name} ({port._parent._name}) {before[port]} -> {port.value}"
                )
        return retval
    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."
            )
Example #6
0
    def advance_rec(
            self,
            time_to_advance,
            consider_behaviour_changes=config.consider_behaviour_changes):
        self.save_trace()
        if time_to_advance > self.max_step_size:
            logger.debug(
                f"Time to advance {time_to_advance} is larger than max step size {self.max_step_size}. Splitting"
            )
            self.advance_rec(
                self.max_step_size,
                consider_behaviour_changes=consider_behaviour_changes)
            self.advance_rec(
                time_to_advance - self.max_step_size,
                consider_behaviour_changes=consider_behaviour_changes)

        logger.info(
            f"Received instructions to advance {time_to_advance} time steps. (Current global time: {self.global_time})"
        )
        logger.debug(
            f"starting advance of {time_to_advance} time units. (global time now: {self.global_time})"
        )
        if evaluate_to_bool(time_to_advance <= 0):
            logger.warning(
                "Advancing or less is not allowed. Use stabilise_fp instead.")
            return

        if consider_behaviour_changes:
            next_trans = self.next_behaviour_change_time()
        else:
            next_trans = self.next_transition_time()

        if next_trans is None:
            logger.info(f"No next transition, just advance {t}")
            self.global_time += time_to_advance
            # execute all updates in all entities
            self.update(self.system, time_to_advance)
            logger.debug("Finished updates after advance")

            # stabilise the system
            self._stabilise_fp(self.system)

            self.save_trace()
            return

        # ntt = next_trans[0]
        ntt = to_python(next_trans[0])
        if evaluate_to_bool(ntt >= time_to_advance):
            logger.info(f"Advancing {t}")
            self.global_time += time_to_advance
            # execute all updates in all entities
            self.update(self.system, time_to_advance)
            logger.debug("Finished updates after advance")

            # stabilise the system
            self._stabilise_fp(self.system)
            logger.info(f"Finished Advancing {time_to_advance}")
        else:
            logger.info(
                f"The next transition is in {ntt} time units. Advancing that first, then the rest of the {t}."
            )
            self.advance_rec(ntt, consider_behaviour_changes)
            logger.info(
                f"Now need to advance the rest of the {time_to_advance}: {time_to_advance - ntt}"
            )
            self.advance_rec(t - ntt, consider_behaviour_changes)
            logger.debug(
                f"finished total advance of {time_to_advance} (time is now {self.global_time})"
            )

        self.save_trace()
    def isreachable_check(self, check, interval=None):
        logger.debug(f"Calculating whether all of the checks are reachable")
        solver = z3.Optimize()

        check_ports = check.get_ports()

        # build a mapping that shows the propagation of information to the guard (what influences the guard)
        modifier_map = self.get_modifier_map(check_ports)

        z3var_constraints, z3_vars = self.get_z3_vars(modifier_map)
        solver.add(z3var_constraints)

        if interval is None:
            solver.add(z3_vars['dt'] >= 0)
            logger.debug(f"Adding time start constraint: dt >= 0")
        else:
            interval = interval.resolve_infinitesimal()
            solver.add(interval.start_operator(z3_vars['dt'], interval.start))
            # logger.debug(f"Adding time start constraint: {interval.start_operator(z3_vars['dt'], interval.start)}")
            if interval.end is not math.inf:  # if it's infinity, just drop it...
                end = interval.end
                solver.add(interval.end_operator(z3_vars['dt'], end))
                # logger.debug(f"Adding time end constraint: {interval.end_operator(z3_vars['dt'], end)}")

        # 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)

        conv = checklib.CheckZ3Converter(z3_vars)
        check_constraints = conv.convert(check)
        if logger.getEffectiveLevel(
        ) <= logging.DEBUG:  # only log if the level is appropriate, since z3's string-conversion takes ages
            logger.debug(f"Check constraints: {check_constraints}")
        solver.add(check_constraints)

        objective = solver.minimize(z3_vars['dt'])  # find minimal value of dt
        check = solver.check()
        # logger.debug("satisfiability: %s", check)
        if solver.check() == z3.sat:
            inf_coeff, numeric_coeff, eps_coeff = objective.lower_values()
            returnvalue = Epsilon(numeric_coeff, eps_coeff)
            logger.info(
                f"Minimum time to reach passing checks is {returnvalue}")
            return returnvalue
        elif check == z3.unknown:
            logger.warning(
                f"The calculation of the check reachability 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']]
                as_python = to_python(min_dt)
                logger.info(
                    f"We did get a solution using the standard solver though: {as_python} Assuming that this is the smallest solution. CAREFUL THIS MIGHT BE WRONG!!!"
                )
                return as_python
            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 False
            else:
                logger.info(
                    "The standard solver says there is no solution to the constraints. This means we also couldn't minimize. Problem solved."
                )
                return False
        else:
            logger.debug(f"Constraint set to reach checks is unsatisfiable.")
            return False
Example #8
0
    def advance_rec(
            self,
            t,
            consider_behaviour_changes=config.consider_behaviour_changes):
        self.save_trace()

        logger.info(
            f"Time: {self.global_time} | Received instructions to advance {t} time steps. (Current global time: {self.global_time})"
        )
        logger.debug(
            f"Time: {self.global_time} | starting advance of {t} time units.")
        if evaluate_to_bool(t <= 0):
            logger.warning(
                f"Time: {self.global_time} | Advancing 0 is not allowed. Use stabilise() instead."
            )
            return

        if consider_behaviour_changes:
            excludes = self._get_excludes(self._transition_log)
            next_trans = self.next_behaviour_change_time(excludes=excludes)
        else:
            next_trans = self.next_transition_time()

        if next_trans is None:
            log_if_level(logging.INFO,
                         f"Time: {self.global_time} | No next transition")
            return self._actually_advance(t, logging.INFO)

        # ntt = next_trans[0]

        ntt = to_python(next_trans[0])

        # to discover if we have epsilon loops
        if ntt != eps:
            self._transition_log = [
            ]  # reset transition log if we don't have an epsilon transition
        else:
            self._transition_log.append(next_trans)

        if evaluate_to_bool(ntt >= t):
            log_if_level(
                logging.INFO,
                f"Time: {self.global_time} | Next behaviour change in {ntt} ({next_trans[1]._name}). That's ntt >= t, hence just advancing.)"
            )
            return self._actually_advance(t, logging.INFO)
        else:
            log_if_level(
                logging.INFO,
                f"Time: {self.global_time} | The next behaviour change is in {ntt} ({next_trans[1]._name}) time units. Advancing that first, then the rest of the {t}."
            )

            if not self._actually_advance(
                    ntt, logging.INFO
            ):  # no recursion, but inlined for higher performance (avoids re-calculating ntt one level down)
                return False  # this means that we had an eror, just drop out here

            log_if_level(
                logging.INFO,
                f"Time: {self.global_time} | Now need to advance the rest of the {t}: {t - ntt}"
            )

            self.advance_rec(t - ntt, consider_behaviour_changes)

            # DONE !!
            log_if_level(
                logging.DEBUG,
                f"Time: {self.global_time} | finished total advance of {t} (time is now {self.global_time})"
            )
Example #9
0
 def __init__(self, numeric=0, epsilon=0):
     self.numeric = to_python(numeric)
     self.epsilon = to_python(epsilon)
     assert isinstance(self.numeric, numbers.Number) and not isinstance(self.numeric, bool)
     assert isinstance(self.epsilon, numbers.Number) and not isinstance(self.epsilon, bool)
Example #10
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
Example #11
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