def categorize_bounds(formula: FNode, sender_symbol: FNode): """ categorize symbolic bounds for a given variable :param formula: FNode formula in CNF form :param sender_symbol: FNode of the bounded variable :return: upper_bounds, lower_bounds, parent_atoms """ clauses = set() formula = simplify(formula) if formula.is_or() or is_literal(formula): clauses.add(formula) else: # formula of CNF form for clause in formula.args(): assert is_literal(clause) or clause.is_or() clauses.add(clause) bounds = defaultdict(list) for clause in clauses: if clause.is_le() or clause.is_lt(): # single literal clause bound, bound_type = atom_to_bound(clause, sender_symbol) bounds[bound_type].append(bound) continue for atom in clause.get_atoms(): # atom: inequality a * x + b * y < c assert atom.is_le() or atom.is_lt() bound, bound_type = atom_to_bound(atom, sender_symbol) bounds[bound_type].append(bound) return list(set(bounds[UPPER])), list(set(bounds[LOWER])), \ list(set(bounds[NEITHER]))
def solve_equation(lhs: FNode, rhs: FNode, domains: FNode = None, solver_name: str = 'msat'): """ solve FNode equations :param lhs: FNode formula :param rhs: FNode formula :param domains: domain of variable, type FNode formula :param solver_name: :return: solution of variable, dict with key as variable and value as solution, both type FNode """ problem = lhs.Equals(rhs) formula = problem if not domains else domains.And(problem) real_variables = get_real_variables(formula) with Solver(name=solver_name) as solver: solver.add_assertion(formula) if solver.solve(): solution = {} for rvar in real_variables: solution[rvar] = solver.get_value(rvar) return solution else: return None
def cpy_tree(self, root, subs, to_sub): if root == None: return None, [] elif root.is_symbol(): to_sub = [] res = FNode( FNodeContent( root._content.node_type, (), (root._content.payload[0], root._content.payload[1])), self.node_num) if root in subs: to_sub = [res] self.node_num += 1 return res, to_sub else: childrens = [] to_sub = [] for node in root._content.args: cpy, sub = self.cpy_tree(node, subs, []) childrens.append(cpy) to_sub.extend(sub) node_id = self.node_num self.node_num += 1 content = FNodeContent(root._content.node_type, tuple(childrens), [None]) res = FNode(content, node_id) return res, to_sub
def get_all_number_symbols(self): real = [] int = [] for cmd in self.script: if cmd.name == 'declare-fun': if FNode.get_type(cmd.args[0]) == INT: int.append(cmd.args[0]) if FNode.get_type(cmd.args[0]) == REAL: real.append(cmd.args[0]) return int, real
def extract_labels_and_weight(weight: FNode) -> Tuple[label_dict_type, FNode]: labels = dict() terms = [] if weight.is_times(): for arg in weight.args(): # type: FNode label = get_bool_label(arg) if label is not None: labels[label[0]] = tuple(label[1:]) else: terms.append(arg) return labels, Times(*terms) else: return labels, weight
def check_clause(formula: FNode): """ check if formula is either an atom or disjunctive of atoms :param formula: :return: """ flag = True formula = simplify(formula) if len(formula.get_atoms()) == 1: return True if not formula.is_or(): return False for atom in formula.get_atoms(): flag = flag and (atom in atom.get_atoms()) return flag
def integrate(self, node_id: int, var: FNode) -> int: self.ub_cache = dict() self.lb_cache = dict() if self.reduce_strategy[0]: node_id = self.pool.diagram(node_id).reduce( method=self.method).root_id if logger.isEnabledFor(logging.DEBUG): self.pool.diagram(node_id).export_png( "log/integrate_{}_d_{}".format(node_id, var), pretty=True) if var.symbol_type() != BOOL and self.symbolic_integration_enabled: integrator = partial(self.symbolic_integrator, var) integrated = leaf_transform.transform_leaves( integrator, self.pool.diagram(node_id)) else: integrated = node_id # self.export(self.pool.diagram(integrated), "integrated") result_id = self.resolve_lb_ub(integrated, var) # result_id = order.order(self.pool.diagram(self.resolve_lb_ub(integrated, var))).root_id self.ub_cache = None self.lb_cache = None if all(self.reduce_strategy): result_id = self.pool.diagram(result_id).reduce( method=self.method).root_id return result_id
def ast_contains(self, node: FNode, what: Callable[[FNode], bool]) -> bool: if what(node): return True for child in node.args(): if self.ast_contains(child, what): return True return False
def mul_nums_and_fnodes(nums, nodes): temp = [] for index, n in enumerate(nums): temp.append(mul_num_and_fnode(n, nodes[index])) content = FNodeContent(13, tuple(temp), None) result = FNode(content, 300000 + nums[0]) return result
def formula_to_interval_set(formula: FNode, sender_symbol: FNode, test_point: float): clauses = set() if formula.is_or() or is_literal(formula): clauses.add(formula) else: # formula of CNF form for clause in formula.args(): assert is_literal(clause) or clause.is_or() clauses.add(clause) interval_list = [] for clause in clauses: intervals = clause_to_intervals(clause, sender_symbol, test_point) if intervals: interval_list.append(intervals) return interval_list
def get_coefficients(atom: FNode): """ obtain coefficient of variable x in atom of form a * x + b * y + const note that when there is a * x + b * x, simplify() doesn't do multiplication but still here we return (a + b) :param atom: FNode, formula :param x: FNode, symbol of variable :return: dict with keys as variable symbol and values as coefficient in atom """ variables = list(get_real_variables(atom)) coefficients = defaultdict(int) if len(variables) == 0: return coefficients const = get_constants(atom) atom = simplify(Plus(atom, -const)) sub_dict = dict().fromkeys(variables, Real(0)) for i in range(len(variables)): sub_dict[variables[i]] = Real(1) coefficient = simplify(atom.substitute(sub_dict)) coefficients[variables[i]] = coefficient sub_dict[variables[i]] = Real(0) return coefficients
def create_node(self, node_type, args, payload=None): content = FNodeContent(node_type, args, payload) if content in self.formulae: return self.formulae[content] else: n = FNode(content) self.formulae[content] = n self._do_type_check(n) return n
def literal_to_bounds(f: FNode): """ obtain bounds on variable x from inequality atom :param literal: ax + by + c < dx + ey + f :param x: variable :return: symbolic bound for x, is_lower, k_inclduded for example, given literal 3x + 2y + 4 < x + y + 1, first move all terms to left hand side: 2x + y + 3 < 0, then it returns [-1/2 y - 3/2, False] since the literal is equivalent to x < -1/2 y - 3/2 """ assert (is_literal(f)) assert f.is_le() or f.is_lt() variables = list(get_real_variables(f)) k_included = f.is_le() f = simplify(f) lhs, rhs = f.arg(0), f.arg(1) lhs_coef, lhs_const = get_coefficients(lhs), get_constants(lhs) rhs_coef, rhs_const = get_coefficients(rhs), get_constants(rhs) # move all terms to lhs const = simplify(lhs_const - rhs_const) coef = dict() for v in variables: coef[v] = simplify(lhs_coef[v] - rhs_coef[v]) bounds = dict() for v in variables: if float(coef[v].constant_value()) == 0: continue bound_terms = [simplify(const * Real(-1) / coef[v])] for w in variables: if w == v or float(coef[w].constant_value()) == 0: continue new_w_coef = simplify(coef[w] * Real(-1) / coef[v]) bound_terms.append(Times(new_w_coef, w)) bound = Plus(bound_terms) is_lower = coef[v].constant_value() < 0 bounds[v] = bound, is_lower, k_included return bounds
def create_node(self, node_type, args, payload=None): content = FNodeContent(node_type, args, payload, self.init_data()) if content in self.formulae: return self.formulae[content] else: n = FNode(content, self._next_free_id) self._next_free_id += 1 self.formulae[content] = n self._do_type_check(n) return n
def symbolic_integrator(self, var: FNode, terminal_node: TerminalNode, d: Diagram): algebra = self.pool.algebra sym = algebra.symbol(var.symbol_name()) lb = algebra.symbol("_lb") ub = algebra.symbol("_ub") expression_bounds = algebra.times(algebra.greater_than_equal(sym, lb), algebra.less_than_equal(sym, ub)) result = algebra.integrate( None, algebra.times(expression_bounds, terminal_node.expression), sym) return self.pool.terminal(result)
def atom_to_bound(atom: FNode, x_symbol: FNode): """ obtain bounds on variable x from inequality atom :param atom: must be of plus form or a single term on each side :param x_symbol: :return: """ assert atom.is_le() or atom.is_lt() variables = list(get_real_variables(atom)) if x_symbol not in variables: return [atom, NEITHER] lhs, rhs = atom.arg(0), atom.arg(1) lhs_coef, lhs_const = get_coefficients(lhs), get_constants(lhs) rhs_coef, rhs_const = get_coefficients(rhs), get_constants(rhs) lhs_x_coef = lhs_coef[x_symbol] if lhs_coef.get(x_symbol) else Real(0) rhs_x_coef = rhs_coef[x_symbol] if rhs_coef.get(x_symbol) else Real(0) x_coef_diff = simplify(lhs_x_coef - rhs_x_coef) assert float(x_coef_diff.constant_value()) != 0 flag = simplify(x_coef_diff > 0) bound = simplify((rhs_const - lhs_const) / x_coef_diff) bound_type = UPPER if flag.is_true() else LOWER if len(variables) == 1: return [bound, bound_type] y_symbol = variables[0] if x_symbol == variables[1] else variables[1] lhs_y_coef = lhs_coef[y_symbol] if lhs_coef.get(y_symbol) else Real(0) rhs_y_coef = rhs_coef[y_symbol] if rhs_coef.get(y_symbol) else Real(0) y_coef_diff = simplify(lhs_y_coef - rhs_y_coef) if float(y_coef_diff.constant_value()) == 0: return [bound, bound_type] new_y_coef = simplify(y_coef_diff * Real(-1) / x_coef_diff) bound = Plus(bound, Times(new_y_coef, y_symbol)) return [bound, bound_type]
def split_left_and_right(self, formula): L = list(formula.args()) formula_type = formula.node_type() if len(L) == 2: L.append(formula_type) else: length = len(L) c = FNodeContent(formula_type, tuple(L[1:]), None) newNode = FNode(c, -1) L = L[:1] L.append(newNode) L.append(formula_type) return L
def concrete_integrate(self, expression: Expression, var: FNode, lb: FNode, ub: FNode, prefix) -> Expression: logger.debug("%s integrate %s, %s <= %s <= %s", prefix, expression, lb, var, ub) algebra = self.pool.algebra lb = Polynomial.from_smt(lb).to_expression(algebra) ub = Polynomial.from_smt(ub).to_expression(algebra) var_name = var.symbol_name() result = algebra.integrate_poly(expression, [var_name], {var_name: (lb, ub)}) logger.debug("%s \t = %s", prefix, result) return result
def initiate_bound(bound: FNode, initiation: float) -> float: """ initiate an atom with only one variable with value initiation :param bound: FNode atom :param initiation: initiation of variable, type float :return: initiated bound, type float """ real_variables = list(get_real_variables(bound)) assert len(real_variables) < 2 if real_variables: # print(real_variables[0], Real(initiation)) bound = simplify( substitute(bound, {real_variables[0]: Real(initiation)})) return float(bound.constant_value())
def variable_inversion(script, ops): add_var(script, 'y', INT) for cmd in script: if cmd.name == 'declare-fun': continue for i in range(len(cmd.args)): to_search = [cmd.args[i]] if isinstance(cmd.args[i], pysmt.fnode.FNode): while len(to_search) != 0: curr = to_search.pop(0) if FNode.is_symbol(curr): if curr in ops: cmd.args[i] = cmd.args[i].substitute(ops) break for arg in curr.args(): to_search.append(arg)
def change_tree(self, root, sub, sub_dict): if root == None: return None elif root.is_symbol(): if root._node_id in sub_dict: if sub_dict[root._node_id] == 1: for key in sub: if key.symbol_name() == root.symbol_name(): return sub[key] return root else: childrens = [] for node in root._content.args: cpy = self.change_tree(node, sub, sub_dict) childrens.append(cpy) node_id = root._node_id content = FNodeContent(root._content.node_type, tuple(childrens), [None]) res = FNode(content, node_id) return res
def get_const(val: FNode, match: FNode = None) -> FNode: ''' Returns a bit-vector constant based on the input value. If match is an FNode instead of None, tries to match the bit-width ''' if type(val) == FNode: return val elif type(val) == int: if match is not None: if type(match) != FNode: Logger.error( "Expecting an FNode in get_const, but got {}".format( type(match))) match_width = get_type(match).width if val.bit_length() > match_width: Logger.error( "Trying to match bit-width of {} but can't express {} in {} bits" .format(match, val, match_width)) return BV(val, match_width) return BV(val, DEFAULTINT) else: raise RuntimeError("Unhandled case in get_const: {}".format(type(val)))
def super_bad_function(): sb = FNode(FNodeContent(node_type=AND, args=(self.p, self.p), payload=None), -1) return sb
def create_new_variable(variable_index): variable_name = "new_variable" + str(variable_index) content = FNodeContent(SYMBOL, (), (variable_name, INT)) node = FNode(content, 10000 + variable_index) return node
def get_bool_label(formula: FNode) -> Optional[Tuple[str, FNode, FNode]]: if formula.is_ite(): c, t, e = formula.args() # type: FNode if c.is_symbol() and c.symbol_type() == BOOL: return c.symbol_name(), t, e return None
def vlog_match_widths(left: FNode, right: FNode, extend=False) -> Tuple[FNode, FNode]: ''' Match the bit-widths for assignment using the Verilog standard semantics. if extend: zero extend to largest width else: left_width == right_width: no change left_width > right_width: right side is zero extended or sign extended depending on signedness left_width < right_width: right side is truncated (MSBs removed) ''' assert type(left) == FNode and get_type( left).is_bv_type(), "Expecting a bit-vector" assert type(right) == FNode and get_type( right).is_bv_type(), "Expecting a bit-vector" left_width, right_width = left.bv_width(), right.bv_width() if left_width == right_width: pass elif left_width > right_width: # TODO: Check signed-ness of right-side fun = None padding = 0 # handle ops with overflow: if right.is_bv_add(): fun = BVAdd padding = 1 elif right.is_bv_mul(): fun = BVMul padding = 1 elif right.is_bv_lshl(): fun = BVLShl padding = left_width - right_width assert padding >= 0, "Expecting a non-negative padding" # TODO: Handle signed values here as well # re-build the node if padding > 0: args = [BVZExt(a, padding) for a in right.args()] right = fun(*args) # re-evauluate left_width and right_width, in case they're updated left_width, right_width = left.bv_width(), right.bv_width() assert left_width >= right_width, "Unexpected bitwidth mismatch" if left_width > right_width: right = BVZExt(right, left_width - right_width) else: if extend: left = BVZExt(left, right_width - left_width) else: right = BVExtract(right, 0, left_width - 1) return simplify(left), simplify(right)
def create_new_fnode_for_num(num): content = FNodeContent(11, (), long(num)) node = FNode(content, 100000 + num) return node
def mul_num_and_fnode(num, node): node0 = create_new_fnode_for_num(num) content = FNodeContent(15, (node0, node), None) result = FNode(content, 200000 + num) return result
def equation_to_inequation(Formula): NewContent = FNodeContent(16, Formula.args(), None) NewFormula = FNode(NewContent, -1) return NewFormula
def test(): f = open("./benchmark/test4.smt2") Str = f.read() parser = SmtLibParser() script = parser.get_script(cStringIO(Str)) target_formula = script.get_last_formula() formulas_arith = list(target_formula.get_atoms()) equations = [] inequations = [] # split formulas into equations and inequations. for x in formulas_arith: if is_equation(x): equations.append(x.simplify()) else: inequations.append(x.simplify()) print0(equations, inequations) # find all let sentences (it seems that all the lets have been substituted by original terms) # lets = script.filter_by_command_name("let") # if len(lets) == 0: # print("no lets") # else: # print("lets") # for l in lets: # print(l) #From now on, start step 2. #Construct a new question according to left inequations. new_question = reduce(And, inequations) if not is_sat(new_question): print("Unsat") analyse_unsat(new_question) return False else: #turn all <= into < new_formulas = [] for x in inequations: new_formulas.append(less_than_to_less(x)) print("*" * 25, "new_formulas", "*" * 25) print(new_formulas) #Construct a new question according to left inequations(new). new_questionLQ = reduce(And, new_formulas) if is_sat(new_questionLQ): #call spass and return pass else: unsat_core = analyse_unsat(new_questionLQ) for x in unsat_core: equations.append(inequation_to_equation(x).simplify()) inequations.remove(find_same_formula(inequations, x)) print0(equations, inequations) #From now on, start step 3 if equations != []: for e in equations: e.simplify() variable_list = list(find_all_variables(equations)) equations_coefficient = extract_coefficient_in_formulas( equations, variable_list) print("variable list is:\n %s" % (variable_list)) print("coefficient matrix is:\n%s" % (equations_coefficient)) # start smithify. coefficient_matrix = smith_nf.Matrix(equations_coefficient) right_matrix = extract_right_side_of_formulas(equations) print(type(coefficient_matrix.matrix[:][0])) coefficient_matrix.smithify() print("smith normal form is:\n%s" % (coefficient_matrix.matrix)) print("matrix U is\n%s" % (coefficient_matrix.U)) print("matrix V is\n%s" % (coefficient_matrix.V)) print("right side is:\n%s" % (right_matrix)) U_mul_right = np.dot(coefficient_matrix.U, right_matrix) print("U * right =\n%s" % (U_mul_right)) # if the rank of the smithify matrix is n, then we just want first n elements of the u_mul_tight. trancate_index = np.linalg.matrix_rank(coefficient_matrix.matrix) U_mul_right_m = U_mul_right[:][:trancate_index] print("After truncation, U * right = \n%s" % (U_mul_right_m)) variable_num = len(variable_list) new_variables_list = [] for i in range(len(coefficient_matrix.V[0]) - trancate_index): new_variables_list.append(create_new_variable(i)) transform_equations_dict = {} for i in range(len(coefficient_matrix.V)): temp0 = np.dot(coefficient_matrix.V[i][:trancate_index], U_mul_right_m) temp0_node = create_new_fnode_for_num(temp0) nums_list = coefficient_matrix.V[i][trancate_index:] temp1 = mul_nums_and_fnodes(nums_list, new_variables_list) #print("temp1 is: %s" % (temp1)) content = FNodeContent(13, (temp0_node, temp1), None) temp2 = FNode(content, 400000 + i) #print("temp2 is: %s" % (temp2.simplify())) transform_equations_dict[variable_list[i]] = temp2.simplify() print("the transform dictionary is:\n%s" % (transform_equations_dict)) print(transform_equations_dict[variable_list[0]]) print("new variables list is:") print(new_variables_list) #return transform_equations_dict #print(transform_equations_dict[variable_list[0].serialize()]) else: # if there is no equation, solve the question by spass pass #From now on ,start step 4. new_inequations = [] for f in inequations: new_inequations.append( f.substitute(transform_equations_dict).simplify()) print("new inequations is:\n%s" % (new_inequations)) #print(new_inequations[0].args()[0]) #From now on, start step 5. #Transform equations are stored in a dictionary called transform_equations_dict, and applying this transform, we turn inequations into new_inequations. #In step 6, we need to construct a new script variable for this question, and return the transform dictionary and the new question. #Also, we need to declare the new variable in script new_parser = SmtLibParser() new_script = new_parser.get_script(cStringIO(Str)) new_assert_command = SmtLibCommand(smtcmd.ASSERT, [And(new_inequations)]) #print("new command is:") #print(type(new_command.args)) for index, cmd in enumerate(script.commands): if cmd.name == smtcmd.ASSERT: new_script.commands[index] = new_assert_command if cmd.name == smtcmd.DECLARE_FUN: last_declare_fun = index for i in range(0, len(new_variables_list)): temp_new_declare_command = SmtLibCommand(smtcmd.DECLARE_FUN, [new_variables_list[i]]) new_script.commands.insert(last_declare_fun + i + 1, temp_new_declare_command) print("new script's content is:") print(new_script.commands) new_script.to_file("./benchmark/new_test4.smt2")