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)
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")
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
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)
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
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." )
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
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
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