def to_formula(self): """Given list of variables, compute constraints :param variables: Variables for each dimension :return: A formula specifying the points inside the hyperrectangle :rtype: pc.Constraint or pc.Formula """ constraint = pc.Constraint(True) for par in self.get_parameters(): constraint = constraint & pc.Constraint( pc.Polynomial(par.variable) - self[par], pc.Relation.EQ) return constraint
def to_formula(self, variables): """Given list of variables, compute constraints :param variables: Variables for each dimension :return: A formula specifying the points inside the hyperrectangle :rtype: pc.Constraint or pc.Formula """ constraint = pc.Constraint(True) for variable, interval in zip(variables, self.intervals): if interval.left_bound != -inf: lbound_relation = pc.Relation.GEQ if interval.left_bound_type( ) == BoundType.closed else pc.Relation.GREATER lbound = pc.Constraint( pc.Polynomial(variable) - interval.left_bound(), lbound_relation) constraint = constraint & lbound if interval.right_bound != inf: rbound_relation = pc.Relation.LEQ if interval.right_bound_type( ) == BoundType.closed else pc.Relation.LESS rbound = pc.Constraint( pc.Polynomial(variable) - interval.right_bound(), rbound_relation) constraint = constraint & rbound return constraint
def compute_solution_function(state, export): logging.info("Compute the rational function using " + state.mc.name() + " " + state.mc.version()) state.mc.load_model(state.problem_description.model, state.problem_description.constants) state.mc.set_pctl_formula(state.problem_description.property) result = state.mc.get_rational_function() #state.problem_description.parameters.update_variables(result.ratfunc.gather_variables()) # Mapping for parameters from solution function def get_matching_model_parameter(model_parameters, variable_name): """Return matching parameter or None.""" return next((v for v in model_parameters if v.name == variable_name), None) parameter_mapping = {} model_parameters = state.problem_description.parameters for sf_param in result.ratfunc.gather_variables(): model_param = get_matching_model_parameter(model_parameters, sf_param.name) parameter_mapping[sf_param] = pc.Polynomial(model_param) for parameter in result.ratfunc.gather_variables(): assert parameter in parameter_mapping, repr( parameter) + " not in " + str(parameter_mapping) # Convert variables to prophesy variables according to generated mapping # Note that the substitution looses the factorization state.problem_description.solution_function = pc.substitute_variables_ratfunc( result.ratfunc, parameter_mapping) state.problem_description.welldefined_constraints = result.welldefined_constraints state.problem_description.graph_preserving_constraints = result.graph_preservation_constraints if export: write_pstorm_result(export, result) return state
def initialize(self, problem_description, constants=None, fixed_threshold = True, fixed_direction = None): """ Initializes the smt solver to consider the problem at hand. :param problem_description: :type problem_description: ProblemDescription :param threshold: """ if fixed_direction is not None: if fixed_direction not in ["safe", "bad"]: raise ValueError("Direction can only be fixed to safe or bad") self._fixed_direction = fixed_direction self.fixed_threshold = fixed_threshold lower_bounded_variables = True upper_bounded_variables = False assert problem_description.solution_function is not None assert problem_description.parameters is not None if self.fixed_threshold: assert problem_description.threshold is not None encoding_start = time.time() #TODO expanding might be a stupid idea. self._ratfunc = pc.expand(problem_description.solution_function) self.parameters = problem_description.parameters for p in self.parameters: self._smt2interface.add_variable(p.name, VariableDomain.Real) safeVar = pc.Variable("?_safe", pc.VariableType.BOOL) badVar = pc.Variable("?_bad", pc.VariableType.BOOL) equalsVar = pc.Variable("?_equals", pc.VariableType.BOOL) self._thresholdVar = pc.Variable("T") rf1Var = pc.Variable("rf1") rf2Var = pc.Variable("rf2") self._smt2interface.add_variable(safeVar.name, VariableDomain.Bool) self._smt2interface.add_variable(badVar.name, VariableDomain.Bool) self._smt2interface.add_variable(equalsVar.name, VariableDomain.Bool) self._smt2interface.add_variable(self._thresholdVar.name, VariableDomain.Real) #Fix direction after declaring variables if self._fixed_direction is not None: excluded_dir = "safe" if self._fixed_direction == "bad" else "bad" # Notice that we have to flip the values, as we are checking all-quantification self._smt2interface.fix_guard("?_" + self._fixed_direction, False) self._smt2interface.fix_guard("?_" + excluded_dir, True) self._smt2interface.fix_guard("?_equals", False) #TODO denominator unequal constant. if pc.denominator(self._ratfunc) != 1: # We do know now that the well-defined points are connected. sample = self._get_welldefined_point(problem_description.welldefined_constraints) value = pc.denominator(self._ratfunc).evaluate(sample) self._smt2interface.add_variable(rf1Var.name, VariableDomain.Real) self._smt2interface.add_variable(rf2Var.name, VariableDomain.Real) if upper_bounded_variables and problem_description.property.operator == OperatorType.probability: self._smt2interface.assert_constraint(pc.Constraint(pc.Polynomial(rf1Var) - rf2Var, pc.Relation.LESS)) if value < 0: if lower_bounded_variables: self._smt2interface.assert_constraint(pc.Constraint(rf1Var, pc.Relation.LESS, pc.Rational(0))) self._smt2interface.assert_constraint(pc.Constraint(rf2Var, pc.Relation.LESS, pc.Rational(0))) safe_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, self._bad_relation) bad_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, self._safe_relation) eq_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, pc.Relation.EQ) else: if lower_bounded_variables: self._smt2interface.assert_constraint(pc.Constraint(rf1Var, pc.Relation.GREATER, pc.Rational(0))) self._smt2interface.assert_constraint(pc.Constraint(rf2Var, pc.Relation.GREATER, pc.Rational(0))) safe_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, self._safe_relation) bad_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, self._bad_relation) eq_constraint = Constraint(pc.Polynomial(rf1Var) - self._thresholdVar * rf2Var, pc.Relation.EQ) rf1_constraint = Constraint(pc.Polynomial(rf1Var) - pc.numerator(self._ratfunc), Relation.EQ) rf2_constraint = Constraint(pc.Polynomial(rf2Var) - pc.denominator(self._ratfunc), Relation.EQ) self._smt2interface.assert_constraint(rf1_constraint) self._smt2interface.assert_constraint(rf2_constraint) else: safe_constraint = Constraint(pc.numerator(self._ratfunc) - pc.Polynomial(self._thresholdVar), self._safe_relation) bad_constraint = Constraint(pc.numerator(self._ratfunc) - pc.Polynomial(self._thresholdVar), self._bad_relation) eq_constraint = Constraint(pc.numerator(self._ratfunc) - pc.Polynomial(self._thresholdVar), pc.Relation.EQ) self._smt2interface.assert_guarded_constraint("?_safe", safe_constraint) self._smt2interface.assert_guarded_constraint("?_bad", bad_constraint) self._smt2interface.assert_guarded_constraint("?_equals", eq_constraint) if self.fixed_threshold: self._add_threshold_constraint(problem_description.threshold) self._encoding_timer += time.time() - encoding_start
def _add_threshold_constraint(self, threshold): threshold_constraint = pc.Constraint(pc.Polynomial(self._thresholdVar) - threshold, pc.Relation.EQ) self._smt2interface.assert_constraint(threshold_constraint)
def initialize(self, problem_description, fixed_threshold=True, fixed_direction=None): """ :param problem_description: :type problem_description: :param constants: :return: """ if self.fixed_threshold: if not problem_description.threshold: raise ValueError("ETR with fixed threshold needs a threshold") else: self._threshold = problem_description.threshold if problem_description.model is None: raise ValueError( "ETR checker requires the model as part of the problem description" ) if fixed_direction is not None: if fixed_direction not in ["safe", "bad", "border"]: raise ValueError( "Direction can only be fixed to safe, bad, border") self._fixed_direction = fixed_direction model = self.model_explorer.get_model() _property = Property.from_string( str(self.model_explorer.pctlformula[0].raw_formula)) #TODO ugly :( if model.model_type == sp.ModelType.MDP: # TODO for logging smt calls, and obtaining proper stats in the cli, this is not a good idea. self._smt2interface_bad = type(self._smt2interface)( self._smt2interface.location) self._smt2interface_bad.run() if _property.operator_direction == OperatorDirection.max: self._safe_demonic = True else: assert _property.operator_direction == OperatorDirection.min self._safe_demonic = False if len(model.initial_states) > 1: raise NotImplementedError( "We only support models with a single initial state") if len(model.initial_states) == 0: raise RuntimeError("We only support models with an initial state.") logger.info("Writing equation system to solver") self.fixed_threshold = fixed_threshold _bounded_variables = True # Add bounds to all state variables. _additional_constraints = self._additional_constraints encoding_start = time.time() safeVar = pc.Variable("?_safe", pc.VariableType.BOOL) badVar = pc.Variable("?_bad", pc.VariableType.BOOL) equalsVar = pc.Variable("?_equals", pc.VariableType.BOOL) exactVar = pc.Variable("?_exact", pc.VariableType.BOOL) self._thresholdVar = pc.Variable("T") self.parameters = problem_description.parameters for par in self.parameters: self._add_variable_to_smtinterfaces(par.name, VariableDomain.Real) self._add_variable_to_smtinterfaces(safeVar.name, VariableDomain.Bool) self._add_variable_to_smtinterfaces(badVar.name, VariableDomain.Bool) self._add_variable_to_smtinterfaces(equalsVar.name, VariableDomain.Bool) self._add_variable_to_smtinterfaces(exactVar.name, VariableDomain.Bool) self._add_variable_to_smtinterfaces(self._thresholdVar.name, VariableDomain.Real) if self._fixed_direction is not None: #TODO support this for MDPs if model.model_type != sp.ModelType.DTMC: raise NotImplementedError( "Support for fixed directions and MDPs is not yet present") if self._fixed_direction == "border": self._smt2interface.fix_guard("?_safe", False) self._smt2interface.fix_guard("?_bad", False) self._smt2interface.fix_guard("?_equals", True) else: excluded_dir = "safe" if self._fixed_direction == "bad" else "bad" # Notice that we have to flip the values, as we are checking all-quantification self._smt2interface.fix_guard("?_" + self._fixed_direction, False) self._smt2interface.fix_guard("?_" + excluded_dir, True) self._smt2interface.fix_guard("?_equals", False) if self._smt2interface_bad: self._smt2interface_bad.fix_guard("?_bad", True) self._smt2interface_bad.fix_guard("?_safe", False) self._smt2interface_bad.fix_guard("?_equals", False) self._smt2interface.fix_guard("?_safe", True) self._smt2interface.fix_guard("?_bad", False) self._smt2interface.fix_guard("?_equals", False) if self._exact: self._fix_guard("?_exact", True) else: raise NotImplementedError( "Support for inexact solvign for MDPs is not yet present") initial_state_var = None self._state_var_mapping = dict() if _property.operator == OperatorType.probability: prob0, prob1 = self.model_explorer.prob01_states() for state in model.states: if prob0.get(state.id): continue if prob1.get(state.id): continue stateVar = pc.Variable("s_" + str(state)) self._state_var_mapping[state.id] = stateVar self._add_variable_to_smtinterfaces(stateVar.name, VariableDomain.Real) if state.id in model.initial_states: initial_state_var = stateVar if initial_state_var is None: # TODO raise RuntimeError( "Initial state is a prob0/prob1 state. Currently not supported" ) else: assert _property.operator == OperatorType.reward reward_model = self._get_reward_model(model, _property) rew0 = self.model_explorer.rew0_states() for state in model.states: if rew0.get(state.id): continue stateVar = pc.Variable("s_" + str(state)) self._state_var_mapping[state.id] = stateVar self._add_variable_to_smtinterfaces(stateVar.name, VariableDomain.Real) if state.id in model.initial_states: initial_state_var = stateVar if initial_state_var is None: raise RuntimeError( "Initial state is a reward 0 state. Currently not supported" ) safe_constraint = pc.Constraint( pc.Polynomial(initial_state_var) - self._thresholdVar, self._safe_relation) bad_constraint = pc.Constraint( pc.Polynomial(initial_state_var) - self._thresholdVar, self._bad_relation) equals_constraint = pc.Constraint( pc.Polynomial(initial_state_var) - self._thresholdVar, self._equals_relation) self._assert_guarded_constraint("?_safe", safe_constraint) self._assert_guarded_constraint("?_bad", bad_constraint) self._assert_guarded_constraint("?_equals", equals_constraint) if self.fixed_threshold: self._add_threshold_constraint(self._threshold) if problem_description.property.operator == OperatorType.probability: for state in model.states: state_equations = [] state_var = self._state_var_mapping.get(state.id) if state_var is None: continue if _bounded_variables: # if bounded variable constraints are to be added, do so. self._assert_constraint( pc.Constraint(state_var, pc.Relation.GREATER, pc.Rational(0))) self._assert_constraint( pc.Constraint(state_var, pc.Relation.LESS, pc.Rational(1))) state_equation = -pc.Polynomial(state_var) for action in state.actions: action_equation = state_equation for transition in action.transitions: if prob0.get(transition.column): continue # obtain the transition value as a polynomial. if transition.value().is_constant(): value = pc.Polynomial( pc.convert_from_storm_type( transition.value().constant_part())) else: denom = pc.denominator( pc.convert_from_storm_type(transition.value())) if not denom.is_constant(): denom = denom.constant_part() value = pc.numerator( pc.convert_from_storm_type( transition.value())) value = value.polynomial() * (1 / denom) else: value = pc.expand_from_storm_type( transition.value()) if prob1.get(transition.column): action_equation += value continue action_equation += value * self._state_var_mapping.get( transition.column) #logger.debug(state_equation) state_equations.append(action_equation) self._add_state_constraint(state_equations, model.model_type, state.id) #Nothing left to be done for the model. else: for state in model.states: state_equations = [] state_var = self._state_var_mapping.get(state.id) if state_var is None: continue if _bounded_variables: # if bounded variable constraints are to be added, do so. self._assert_constraint( pc.Constraint(state_var, pc.Relation.GREATER, pc.Rational(0))) state_reward = pc.expand_from_storm_type( reward_model.state_rewards[state.id]) if state_reward.is_constant(): reward_value = state_reward.constant_part() else: denom = pc.denominator(state_reward) if denom.is_constant(): denom = denom.constant_part() reward_value = pc.numerator(state_reward) * (1 / denom) else: reward_value = state_reward state_equation = -pc.Polynomial(state_var) + reward_value for action in state.actions: action_equation = state_equation for transition in action.transitions: if rew0.get(transition.column): continue # obtain the transition value as a polynomial. if transition.value().is_constant(): value = pc.Polynomial( pc.convert_from_storm_type( transition.value().constant_part())) else: denom = pc.denominator( pc.convert_from_storm_type(transition.value())) if denom.is_constant(): denom = denom.constant_part() value = pc.numerator( pc.convert_from_storm_type( transition.value())) value = value.polynomial() * (1 / denom) else: value = pc.expand_from_storm_type( transition.value()) action_equation += value * self._state_var_mapping.get( transition.column) #logger.debug(state_equation) state_equations.append(action_equation) self._add_state_constraint(state_equations, model.model_type, state.id) #Nothing left to be one if _additional_constraints and problem_description.property.operator == OperatorType.probability: assert model.model_type == sp.ModelType.DTMC for state in model.states: state_var = self._state_var_mapping.get(state.id) if state_var is None: continue state_equation = -pc.Polynomial(state_var) for action in state.actions: trans = [] for transition in action.transitions: if prob0.get(transition.column): continue # obtain the transition value as a polynomial. if transition.value().is_constant(): value = pc.Polynomial( pc.convert_from_storm_type( transition.value().constant_part())) else: denom = pc.denominator( pc.convert_from_storm_type(transition.value())) if not denom.is_constant(): raise RuntimeError( "only polynomial constraints are supported right now." ) denom = denom.constant_part() value = pc.numerator( pc.convert_from_storm_type(transition.value())) value = value.polynomial() * (1 / denom) if prob1.get(transition.column): trans.append((None, value)) else: trans.append((self._state_var_mapping.get( transition.column), value)) prob0, prob1 = self.model_explorer.prob01_states() if len(trans) == 1: if trans[0][0] and trans[0][1] != 1: self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][0], pc.Relation.LESS)) self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][1], pc.Relation.LESS)) self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][0] - trans[0][1] + pc.Rational(1), pc.Relation.GREATER)) if len(trans) == 2: if trans[0][0] and trans[0][1] != 1 and trans[1][ 0] and trans[1][1] != 1: self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][0] - trans[1][0], pc.Relation.LESS), name="app2ubstst{}".format(state.id)) self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][0] - trans[1][1], pc.Relation.LESS), name="app2ubsttr{}".format(state.id)) self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][1] - trans[1][0], pc.Relation.LESS), name="app2ubtrst{}".format(state.id)) self._smt2interface.assert_constraint( pc.Constraint( pc.Polynomial(state_var) - trans[0][0] - trans[1][0] + pc.Rational(1), pc.Relation.GREATER), name="app2lbsttr{}".format(state.id)) #Nothing left to be done for the model. self._encoding_timer += time.time() - encoding_start
def read_pstorm_result(location, require_result=True): """ Read the output of pstorm into a ParametricResult :param location: The location of the file to be read :type location: str :param require_result: If true, parsing fails if no result is found :type require_result: Bool :return: The data obtained from the model. :rtype: ParametricResult """ logging.debug("Open result file from storm...") with open(location) as f: inputstring = f.read() # Build parameters logger.debug("Reading parameters...") parameters = ParameterOrder() parameter_strings = re.findall(r'\$Parameters:\s(.*)', inputstring)[0].split(";") for parameter_string in parameter_strings: if parameter_string.strip(): name_and_info = parameter_string.split() var = pc.variable_with_name(name_and_info[0].strip()) if var.is_no_variable: var = pc.Variable(name_and_info[0].strip()) if len(name_and_info) == 1: bound = interval.Interval(pc.Rational(0), interval.BoundType.open, pc.Rational(1), interval.BoundType.open) else: bound = interval.string_to_interval(name_and_info[1], pc.Rational) parameters.append(Parameter(var, bound)) logger.debug("Parameters: %s", str(parameters)) # Build well-defined constraints logging.debug("Reading constraints...") constraints_string = re.findall( r'(\$Well-formed Constraints:\s*\n.+?)(?=\$|(?:\s*\Z))', inputstring, re.DOTALL)[0] constraints_string = constraints_string.split("\n")[:-1] constraints = [pc.parse(cond) for cond in constraints_string[1:]] logger.debug("Constraints: %s", ",".join([str(c) for c in constraints])) # Build graph-preserving constraints constraints_string = re.findall( r'(\$Graph-preserving Constraints:\s*\n.+?)(?=\$|(?:\s*\Z))', inputstring, re.DOTALL)[0] constraints_string = constraints_string.split("\n") gpconstraints = [ pc.parse(cond) for cond in constraints_string[1:] if cond.strip() != "" ] logger.debug("GP Constraints: %s", ",".join([str(c) for c in gpconstraints])) # Build rational function logger.debug("Looking for solution function...") match = re.findall(r'\$Result:(.*)$', inputstring, re.MULTILINE) if require_result: if len(match) == 0: raise ValueError( "Expected a result in the result file at {}".format(location)) if len(match) > 0: logger.debug("Building solution function...") solution = pc.parse(match[0]) if isinstance(solution, pc.Monomial): solution = pc.Polynomial(solution) logger.debug("Solution function is %s", solution) else: solution = None logger.debug("Parsing complete.") return ParametricResult(parameters, constraints, gpconstraints, solution)