diff --git a/CMakeLists.txt b/CMakeLists.txt index a086afd71..2ace973a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,7 @@ if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") list(APPEND Z3_COMPONENT_CXX_DEFINES "-D_USE_THREAD_LOCAL") endif() elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") - # Does OSX really not need any special flags? + # Does macOS really not need any special flags? message(STATUS "Platform: Darwin") elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") message(STATUS "Platform: FreeBSD") @@ -380,9 +380,17 @@ endif() ################################################################################ # FIXME: Support ARM "-mfpu=vfp -mfloat-abi=hard" if (("${TARGET_ARCHITECTURE}" STREQUAL "x86_64") OR ("${TARGET_ARCHITECTURE}" STREQUAL "i686")) - if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel")) + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel") + # Intel's compiler requires linking with libiomp5 + list(APPEND Z3_DEPENDENT_LIBS "iomp5") + endif() set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") # FIXME: Remove "x.." when CMP0054 is set to NEW + elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Intel") + set(SSE_FLAGS "-mfpmath=sse" "-msse" "-msse2") + # Intel's compiler requires linking with libiomp5 + list(APPEND Z3_DEPENDENT_LIBS "iomp5") elseif ("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC") set(SSE_FLAGS "/arch:SSE2") else() diff --git a/README.md b/README.md index 447034a84..e02719161 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ See the [release notes](RELEASE_NOTES) for notes on various stable releases of Z ## Build status -| Windows x64 | Windows x86 | Windows x64 | Ubuntu x64 | Debian x64 | OSX | TravisCI | -| ----------- | ----------- | ----------- | ---------- | ---------- | --- | -------- | +| Windows x64 | Windows x86 | Windows x64 | Ubuntu x64 | Debian x64 | macOS | TravisCI | +| ----------- | ----------- | ----------- | ---------- | ---------- | ----- | -------- | [![win64-badge](https://z3build.visualstudio.com/_apis/public/build/definitions/2e0aa542-a22c-4b1a-8dcd-3ebae8e12db4/4/badge)](https://z3build.visualstudio.com/Z3Build/_build/index?definitionId=4) | [![win32-badge](https://cz3.visualstudio.com/_apis/public/build/definitions/bf14bcc7-ebd4-4240-812c-5972fa59e0ad/4/badge)](https://cz3.visualstudio.com/Z3/_build/index?definitionId=4) | [![win64-badge](https://cz3.visualstudio.com/_apis/public/build/definitions/bf14bcc7-ebd4-4240-812c-5972fa59e0ad/7/badge)](https://cz3.visualstudio.com/Z3/_build/index?definitionId=7) | [![ubuntu-x64-badge](https://cz3.visualstudio.com/_apis/public/build/definitions/bf14bcc7-ebd4-4240-812c-5972fa59e0ad/3/badge)](https://cz3.visualstudio.com/Z3/_build/index?definitionId=3) | [![debian-badge](https://cz3.visualstudio.com/_apis/public/build/definitions/bf14bcc7-ebd4-4240-812c-5972fa59e0ad/5/badge)](https://cz3.visualstudio.com/Z3/_build/index?definitionId=5) | [![osx-badge](https://cz3.visualstudio.com/_apis/public/build/definitions/bf14bcc7-ebd4-4240-812c-5972fa59e0ad/2/badge)](https://cz3.visualstudio.com/Z3/_build/index?definitionId=2) | [![Build Status](https://travis-ci.org/Z3Prover/z3.svg?branch=master)](https://travis-ci.org/Z3Prover/z3) [1]: #building-z3-on-windows-using-visual-studio-command-prompt @@ -75,7 +75,7 @@ A 32 bit build should work similarly (but is untested); the same is true for 32/ By default, it will install z3 executable at ``PREFIX/bin``, libraries at ``PREFIX/lib``, and include files at ``PREFIX/include``, where ``PREFIX`` installation prefix if inferred by the ``mk_make.py`` script. It is usually -``/usr`` for most Linux distros, and ``/usr/local`` for FreeBSD and OSX. Use +``/usr`` for most Linux distros, and ``/usr/local`` for FreeBSD and macOS. Use the ``--prefix=`` command line option to change the install prefix. For example: ```bash diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 3337f098b..acdb627d8 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -21,7 +21,7 @@ Version 4.8.0 extracting models from apply_result have been replaced. - An optional mode handles xor constraints using a custom xor propagator. It is off by default and its value not demonstrated. - - The SAT solver includes new inprocessing technques that are available during simplification. + - The SAT solver includes new inprocessing techniques that are available during simplification. It performs asymmetric tautology elimination by default, and one can turn on more powerful inprocessing techniques (known as ACCE, ABCE, CCE). Asymmetric branching also uses features introduced in Lingeling by exploiting binary implication graphs. Use sat.acce=true to enable the full repertoire of inprocessing methods. By default, clauses that are "eliminated" by acce are tagged @@ -318,7 +318,7 @@ First source code release (October 2, 2012) - Added support for numbers in scientific notation at Z3_ast Z3_mk_numeral(__in Z3_context c, __in Z3_string numeral, __in Z3_sort ty). -- New builtin symbols in the arithmetic theory: pi, euler, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh. The first two are constants, and the others are unary functions. These symbols are not available if the a SMT 2.0 logic is specified (e.g., QF_LRA, QF_NRA, QF_LIA, etc) because these symbols are not defined in these logics. That is, the new symbols are only available if the logic is not specified. +- New builtin symbols in the arithmetic theory: pi, euler, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh. The first two are constants, and the others are unary functions. These symbols are not available if a SMT 2.0 logic is specified (e.g., QF_LRA, QF_NRA, QF_LIA, etc) because these symbols are not defined in these logics. That is, the new symbols are only available if the logic is not specified. Version 4.1 =========== diff --git a/examples/c++/README b/examples/c++/README index 56775e537..2b3c5affc 100644 --- a/examples/c++/README +++ b/examples/c++/README @@ -5,6 +5,6 @@ in the build directory. This command will create the executable cpp_example. On Windows, you can just execute it. -On OSX and Linux, you must install z3 first using +On macOS and Linux, you must install z3 first using sudo make install -OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (OSX) with the build directory. You need that to be able to find the Z3 shared library. \ No newline at end of file +OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS) with the build directory. You need that to be able to find the Z3 shared library. diff --git a/examples/c/README b/examples/c/README index 4ca71e0f8..af9dd39f6 100644 --- a/examples/c/README +++ b/examples/c/README @@ -5,7 +5,7 @@ in the build directory. This command will create the executable c_example. On Windows, you can just execute it. -On OSX and Linux, you must install z3 first using +On macOS and Linux, you must install z3 first using sudo make install -OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (OSX) with the build directory. You need that to be able to find the Z3 shared library. +OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS) with the build directory. You need that to be able to find the Z3 shared library. diff --git a/examples/java/README b/examples/java/README index 1939afc49..d3ff93fe0 100644 --- a/examples/java/README +++ b/examples/java/README @@ -10,5 +10,5 @@ which can be run on Windows via On Linux and FreeBSD, we must use LD_LIBRARY_PATH=. java -cp com.microsoft.z3.jar:. JavaExample -On OSX, the corresponding option is DYLD_LIBRARY_PATH: +On macOS, the corresponding option is DYLD_LIBRARY_PATH: DYLD_LIBRARY_PATH=. java -cp com.microsoft.z3.jar:. JavaExample diff --git a/examples/maxsat/README b/examples/maxsat/README index 6c24da66b..8c7d3b0f7 100644 --- a/examples/maxsat/README +++ b/examples/maxsat/README @@ -5,8 +5,8 @@ in the build directory. This command will create the executable maxsat. On Windows, you can just execute it. -On OSX and Linux, you must install z3 first using +On macOS and Linux, you must install z3 first using sudo make install -OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (OSX) with the build directory. You need that to be able to find the Z3 shared library. +OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS) with the build directory. You need that to be able to find the Z3 shared library. This directory contains a test file (ex.smt) that can be used as input for the maxsat test application. diff --git a/examples/ml/README b/examples/ml/README index 1c474fe33..9797b85e3 100644 --- a/examples/ml/README +++ b/examples/ml/README @@ -20,4 +20,4 @@ ocamlfind ocamlopt -o ml_example -package Z3 -linkpkg ml_example.ml Note that the resulting binaries depend on the shared z3 library (libz3.dll/.so/.dylb), which needs to be in the PATH (Windows), LD_LIBRARY_PATH -(Linux), or DYLD_LIBRARY_PATH (OSX). +(Linux), or DYLD_LIBRARY_PATH (macOS). diff --git a/examples/ml/ml_example.ml b/examples/ml/ml_example.ml index eb64f8ee8..5b4e6e9ed 100644 --- a/examples/ml/ml_example.ml +++ b/examples/ml/ml_example.ml @@ -65,8 +65,7 @@ let model_converter_test ( ctx : context ) = | None -> raise (TestFailedException "") | Some (m) -> Printf.printf "Solver says: %s\n" (string_of_status q) ; - Printf.printf "Model: \n%s\n" (Model.to_string m) ; - Printf.printf "Converted Model: \n%s\n" (Model.to_string (convert_model ar 0 m)) + Printf.printf "Model: \n%s\n" (Model.to_string m) ) (** @@ -330,12 +329,14 @@ let _ = let ss = (Symbol.mk_string ctx "mySymbol") in let bs = (Boolean.mk_sort ctx) in let ints = (Integer.mk_sort ctx) in - let rs = (Real.mk_sort ctx) in + let rs = (Real.mk_sort ctx) in + let v = (Arithmetic.Integer.mk_numeral_i ctx 8000000000) in Printf.printf "int symbol: %s\n" (Symbol.to_string is); Printf.printf "string symbol: %s\n" (Symbol.to_string ss); Printf.printf "bool sort: %s\n" (Sort.to_string bs); Printf.printf "int sort: %s\n" (Sort.to_string ints); Printf.printf "real sort: %s\n" (Sort.to_string rs); + Printf.printf "integer: %s\n" (Expr.to_string v); basic_tests ctx ; quantifier_example1 ctx ; fpa_example ctx ; diff --git a/examples/python/data/horn1.smt2 b/examples/python/data/horn1.smt2 new file mode 100644 index 000000000..20d043534 --- /dev/null +++ b/examples/python/data/horn1.smt2 @@ -0,0 +1,50 @@ +(declare-rel Goal (Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool)) +(declare-rel Invariant (Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool)) +(declare-var A Bool) +(declare-var B Bool) +(declare-var C Bool) +(declare-var D Bool) +(declare-var E Bool) +(declare-var F Bool) +(declare-var G Bool) +(declare-var H Bool) +(declare-var I Bool) +(declare-var J Bool) +(declare-var K Bool) +(declare-var L Bool) +(declare-var M Bool) +(declare-var N Bool) +(declare-var O Bool) +(declare-var P Bool) +(declare-var Q Bool) +(declare-var R Bool) +(declare-var S Bool) +(declare-var T Bool) +(declare-var U Bool) +(declare-var V Bool) +(declare-var W Bool) +(declare-var X Bool) +(rule (=> (not (or L K J I H G F E D C B A)) (Invariant L K J I H G F E D C B A))) +(rule (let ((a!1 (and (Invariant X W V U T S R Q P O N M) + (=> (not (and true)) (not F)) + (=> (not (and true)) (not E)) + (=> (not (and W)) (not D)) + (=> (not (and W)) (not C)) + (=> (not (and U)) (not B)) + (=> (not (and U)) (not A)) + (= L (xor F X)) + (= K (xor E W)) + (= J (xor D V)) + (= I (xor C U)) + (= H (xor B T)) + (= G (xor A S)) + (=> D (not E)) + (=> C (not E)) + (=> B (not C)) + (=> A (not C)) + ((_ at-most 5) L K J I H G)))) + (=> a!1 (Invariant L K J I H G F E D C B A)))) +(rule (=> (and (Invariant L K J I H G F E D C B A) L (not K) J (not I) H G) + (Goal L K J I H G F E D C B A))) + +(query Goal) \ No newline at end of file diff --git a/examples/python/data/horn2.smt2 b/examples/python/data/horn2.smt2 new file mode 100644 index 000000000..478c39d5f --- /dev/null +++ b/examples/python/data/horn2.smt2 @@ -0,0 +1,44 @@ +(declare-rel Invariant (Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool)) +(declare-rel Goal (Bool Bool Bool Bool Bool Bool Bool Bool Bool Bool)) +(declare-var A Bool) +(declare-var B Bool) +(declare-var C Bool) +(declare-var D Bool) +(declare-var E Bool) +(declare-var F Bool) +(declare-var G Bool) +(declare-var H Bool) +(declare-var I Bool) +(declare-var J Bool) +(declare-var K Bool) +(declare-var L Bool) +(declare-var M Bool) +(declare-var N Bool) +(declare-var O Bool) +(declare-var P Bool) +(declare-var Q Bool) +(declare-var R Bool) +(declare-var S Bool) +(declare-var T Bool) +(rule (=> (not (or J I H G F E D C B A)) (Invariant J I H G F E D C B A))) +(rule (let ((a!1 (and (Invariant T S R Q P O N M L K) + (=> (not (and true)) (not E)) + (=> (not (and T)) (not D)) + (=> (not (and S)) (not C)) + (=> (not (and R)) (not B)) + (=> (not (and Q)) (not A)) + (= J (xor E T)) + (= I (xor D S)) + (= H (xor C R)) + (= G (xor B Q)) + (= F (xor A P)) + (=> D (not E)) + (=> C (not D)) + (=> B (not C)) + (=> A (not B)) + ((_ at-most 3) J I H G F)))) + (=> a!1 (Invariant J I H G F E D C B A)))) +(rule (=> (and (Invariant J I H G F E D C B A) (not J) (not I) (not H) (not G) F) + (Goal J I H G F E D C B A))) + +(query Goal) diff --git a/examples/python/example.py b/examples/python/example.py index a17668506..761ae10be 100644 --- a/examples/python/example.py +++ b/examples/python/example.py @@ -20,7 +20,7 @@ # export PYTHONPATH=MYZ3/bin/python # python example.py -# Running this example on OSX: +# Running this example on macOS: # export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:MYZ3/bin # export PYTHONPATH=MYZ3/bin/python # python example.py diff --git a/examples/python/mini_ic3.py b/examples/python/mini_ic3.py new file mode 100644 index 000000000..5a8ea566b --- /dev/null +++ b/examples/python/mini_ic3.py @@ -0,0 +1,438 @@ +from z3 import * +import heapq + + +# Simplistic (and fragile) converter from +# a class of Horn clauses corresponding to +# a transition system into a transition system +# representation as +# It assumes it is given three Horn clauses +# of the form: +# init(x) => Invariant(x) +# Invariant(x) and trans(x,x') => Invariant(x') +# Invariant(x) and goal(x) => Goal(x) +# where Invariant and Goal are uninterpreted predicates + +class Horn2Transitions: + def __init__(self): + self.trans = True + self.init = True + self.goal = True + self.index = 0 + + def parse(self, file): + fp = Fixedpoint() + goals = fp.parse_file(file) + for r in fp.get_rules(): + if not is_quantifier(r): + continue + b = r.body() + if not is_implies(b): + continue + f = b.arg(0) + g = b.arg(1) + if self.is_goal(f, g): + continue + if self.is_transition(f, g): + continue + if self.is_init(f, g): + continue + + def is_pred(self, p, name): + return is_app(p) and p.decl().name() == name + + def is_goal(self, body, head): + if not self.is_pred(head, "Goal"): + return False + pred, inv = self.is_body(body) + if pred is None: + return False + self.goal = self.subst_vars("x", inv, pred) + return True + + def is_body(self, body): + if not is_and(body): + return None, None + fmls = [f for f in body.children() if self.is_inv(f) is None] + inv = None + for f in body.children(): + if self.is_inv(f) is not None: + inv = f; + break + return And(fmls), inv + + def is_inv(self, f): + if self.is_pred(f, "Invariant"): + return f + return None + + def is_transition(self, body, head): + pred, inv0 = self.is_body(body) + if pred is None: + return False + inv1 = self.is_inv(head) + if inv1 is None: + return False + pred = self.subst_vars("x", inv0, pred) + self.xs = self.vars + pred = self.subst_vars("xn", inv1, pred) + self.xns = self.vars + self.trans = pred + return True + + def is_init(self, body, head): + for f in body.children(): + if self.is_inv(f) is not None: + return False + inv = self.is_inv(head) + if inv is None: + return False + self.init = self.subst_vars("x", inv, body) + return True + + def subst_vars(self, prefix, inv, fml): + subst = self.mk_subst(prefix, inv) + self.vars = [ v for (k,v) in subst ] + return substitute(fml, subst) + + def mk_subst(self, prefix, inv): + self.index = 0 + return [(f, self.mk_bool(prefix)) for f in inv.children()] + + def mk_bool(self, prefix): + self.index += 1 + return Bool("%s%d" % (prefix, self.index)) + +# Produce a finite domain solver. +# The theory QF_FD covers bit-vector formulas +# and pseudo-Boolean constraints. +# By default cardinality and pseudo-Boolean +# constraints are converted to clauses. To override +# this default for cardinality constraints +# we set sat.cardinality.solver to True + +def fd_solver(): + s = SolverFor("QF_FD") + s.set("sat.cardinality.solver", True) + return s + + +# negate, avoid double negation +def negate(f): + if is_not(f): + return f.arg(0) + else: + return Not(f) + +def cube2clause(cube): + return Or([negate(f) for f in cube]) + +class State: + def __init__(self, s): + self.R = set([]) + self.solver = s + + def add(self, clause): + if clause not in self.R: + self.R |= { clause } + self.solver.add(clause) + +class Goal: + def __init__(self, cube, parent, level): + self.level = level + self.cube = cube + self.parent = parent + +def is_seq(f): + return isinstance(f, list) or isinstance(f, tuple) or isinstance(f, AstVector) + +# Check if the initial state is bad +def check_disjoint(a, b): + s = fd_solver() + s.add(a) + s.add(b) + return unsat == s.check() + + +# Remove clauses that are subsumed +def prune(R): + removed = set([]) + s = fd_solver() + for f1 in R: + s.push() + for f2 in R: + if f2 not in removed: + s.add(Not(f2) if f1.eq(f2) else f2) + if s.check() == unsat: + removed |= { f1 } + s.pop() + return R - removed + +class MiniIC3: + def __init__(self, init, trans, goal, x0, xn): + self.x0 = x0 + self.xn = xn + self.init = init + self.bad = goal + self.trans = trans + self.min_cube_solver = fd_solver() + self.min_cube_solver.add(Not(trans)) + self.goals = [] + s = State(fd_solver()) + s.add(init) + s.solver.add(trans) + self.states = [s] + self.s_bad = fd_solver() + self.s_good = fd_solver() + self.s_bad.add(self.bad) + self.s_good.add(Not(self.bad)) + + def next(self, f): + if is_seq(f): + return [self.next(f1) for f1 in f] + return substitute(f, zip(self.x0, self.xn)) + + def prev(self, f): + if is_seq(f): + return [self.prev(f1) for f1 in f] + return substitute(f, zip(self.xn, self.x0)) + + def add_solver(self): + s = fd_solver() + s.add(self.trans) + self.states += [State(s)] + + def R(self, i): + return And(self.states[i].R) + + # Check if there are two states next to each other that have the same clauses. + def is_valid(self): + i = 1 + while i + 1 < len(self.states): + if not (self.states[i].R - self.states[i+1].R): + return And(prune(self.states[i].R)) + i += 1 + return None + + def value2literal(self, m, x): + value = m.eval(x) + if is_true(value): + return x + if is_false(value): + return Not(x) + return None + + def values2literals(self, m, xs): + p = [self.value2literal(m, x) for x in xs] + return [x for x in p if x is not None] + + def project0(self, m): + return self.values2literals(m, self.x0) + + def projectN(self, m): + return self.values2literals(m, self.xn) + + # Determine if there is a cube for the current state + # that is potentially reachable. + def unfold(self): + core = [] + self.s_bad.push() + R = self.R(len(self.states)-1) + self.s_bad.add(R) + is_sat = self.s_bad.check() + if is_sat == sat: + m = self.s_bad.model() + props = self.project0(m) + self.s_good.push() + self.s_good.add(R) + is_sat2 = self.s_good.check(props) + assert is_sat2 == unsat + core = self.s_good.unsat_core() + self.s_good.pop() + self.s_bad.pop() + return is_sat, core + + # Block a cube by asserting the clause corresponding to its negation + def block_cube(self, i, cube): + self.assert_clause(i, cube2clause(cube)) + + # Add a clause to levels 0 until i + def assert_clause(self, i, clause): + for j in range(i + 1): + self.states[j].add(clause) + + # minimize cube that is core of Dual solver. + # this assumes that props & cube => Trans + def minimize_cube(self, cube, lits): + is_sat = self.min_cube_solver.check(lits + [c for c in cube]) + assert is_sat == unsat + core = self.min_cube_solver.unsat_core() + assert core + return [c for c in core if c in set(cube)] + + # push a goal on a heap + def push_heap(self, goal): + heapq.heappush(self.goals, (goal.level, goal)) + + # A state s0 and level f0 such that + # not(s0) is f0-1 inductive + def ic3_blocked(self, s0, f0): + self.push_heap(Goal(self.next(s0), None, f0)) + while self.goals: + f, g = heapq.heappop(self.goals) + sys.stdout.write("%d." % f) + sys.stdout.flush() + # Not(g.cube) is f-1 invariant + if f == 0: + print("") + return g + cube, f, is_sat = self.is_inductive(f, g.cube) + if is_sat == unsat: + self.block_cube(f, self.prev(cube)) + if f < f0: + self.push_heap(Goal(g.cube, g.parent, f + 1)) + elif is_sat == sat: + self.push_heap(Goal(cube, g, f - 1)) + self.push_heap(g) + else: + return is_sat + print("") + return None + + # Rudimentary generalization: + # If the cube is already unsat with respect to transition relation + # extract a core (not necessarily minimal) + # otherwise, just return the cube. + def generalize(self, cube, f): + s = self.states[f - 1].solver + if unsat == s.check(cube): + return s.unsat_core(), f + return cube, f + + # Check if the negation of cube is inductive at level f + def is_inductive(self, f, cube): + s = self.states[f - 1].solver + s.push() + s.add(self.prev(Not(And(cube)))) + is_sat = s.check(cube) + if is_sat == sat: + m = s.model() + s.pop() + if is_sat == sat: + cube = self.next(self.minimize_cube(self.project0(m), self.projectN(m))) + elif is_sat == unsat: + cube, f = self.generalize(cube, f) + return cube, f, is_sat + + def run(self): + if not check_disjoint(self.init, self.bad): + return "goal is reached in initial state" + level = 0 + while True: + inv = self.is_valid() + if inv is not None: + return inv + is_sat, cube = self.unfold() + if is_sat == unsat: + level += 1 + print("Unfold %d" % level) + sys.stdout.flush() + self.add_solver() + elif is_sat == sat: + cex = self.ic3_blocked(cube, level) + if cex is not None: + return cex + else: + return is_sat + +def test(file): + h2t = Horn2Transitions() + h2t.parse(file) + mp = MiniIC3(h2t.init, h2t.trans, h2t.goal, h2t.xs, h2t.xns) + result = mp.run() + if isinstance(result, Goal): + g = result + print("Trace") + while g: + print(g.level, g.cube) + g = g.parent + return + if isinstance(result, ExprRef): + print("Invariant:\n%s " % result) + return + print(result) + +test("data/horn1.smt2") +test("data/horn2.smt2") + + + +""" +# TBD: Quip variant of IC3 + +must = True +may = False + +class QGoal: + def __init__(self, cube, parent, level, must): + self.level = level + self.cube = cube + self.parent = parent + self.must = must + +class Quip(MiniIC3): + + # prev & tras -> r', such that r' intersects with cube + def add_reachable(self, prev, cube): + s = fd_solver() + s.add(self.trans) + s.add(prev) + s.add(Or(cube)) + is_sat = s.check() + assert is_sat == sat + m = s.model(); + result = self.values2literals(m, cube) + assert result + self.reachable.add(result) + + # A state s0 and level f0 such that + # not(s0) is f0-1 inductive + def quip_blocked(self, s0, f0): + self.push_heap(QGoal(self.next(s0), None, f0, must)) + while self.goals: + f, g = heapq.heappop(self.goals) + sys.stdout.write("%d." % f) + sys.stdout.flush() + if f == 0: + if g.must: + print("") + return g + self.add_reachable(self.init, p.parent.cube) + continue + + # TBD + return None + + + def run(self): + if not check_disjoint(self.init, self.bad): + return "goal is reached in initial state" + level = 0 + while True: + inv = self.is_valid() + if inv is not None: + return inv + is_sat, cube = self.unfold() + if is_sat == unsat: + level += 1 + print("Unfold %d" % level) + sys.stdout.flush() + self.add_solver() + elif is_sat == sat: + cex = self.quipie_blocked(cube, level) + if cex is not None: + return cex + else: + return is_sat + +""" \ No newline at end of file diff --git a/examples/tptp/README b/examples/tptp/README index c28a53da4..b3edfe6a8 100644 --- a/examples/tptp/README +++ b/examples/tptp/README @@ -5,9 +5,9 @@ in the build directory. This command will create the executable tptp. On Windows, you can just execute it. -On OSX and Linux, you must install z3 first using +On macOS and Linux, you must install z3 first using sudo make install -OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (OSX) +OR update LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS) with the build directory. You need that to be able to find the Z3 shared library. diff --git a/package/Microsoft.Z3.x64.nuspec b/package/Microsoft.Z3.x64.nuspec new file mode 100644 index 000000000..95d594dfb --- /dev/null +++ b/package/Microsoft.Z3.x64.nuspec @@ -0,0 +1,22 @@ + + + + Microsoft.Z3.x64 + $(releaseVersion) + © Microsoft Corporation. All rights reserved. + Microsoft + Microsoft,Z3Prover + $(iconUrlFromReleaseCommit) + https://github.com/Z3Prover/z3 + $(licenseUrlFromReleaseCommit) + + true + Z3 is a constraint/SMT solver and theorem prover from Microsoft Research. + smt constraint solver theorem prover + + diff --git a/package/Microsoft.Z3.x64.targets b/package/Microsoft.Z3.x64.targets new file mode 100644 index 000000000..a5b636f69 --- /dev/null +++ b/package/Microsoft.Z3.x64.targets @@ -0,0 +1,10 @@ + + + + + false + libz3.dll + PreserveNewest + + + diff --git a/package/PackageCreationDirections.md b/package/PackageCreationDirections.md new file mode 100644 index 000000000..930ba14f7 --- /dev/null +++ b/package/PackageCreationDirections.md @@ -0,0 +1,36 @@ +# Z3 NuGet packaging + +## Creation + + 1. After tagging a commit for release, sign Microsoft.Z3.dll and libz3.dll (both x86 and x64 versions) with Microsoft's Authenticode certificate + 2. Test the signed DLLs with the `Get-AuthenticodeSignature` PowerShell commandlet + 3. Create the following directory structure for the x64 package (for x86, substitute the "x64" strings for "x86" and use x86 DLLs): + ``` + +-- Microsoft.Z3.x64 + | +-- Microsoft.Z3.x64.nuspec + | +-- lib + | +-- net40 + | +-- Microsoft.Z3.dll + | +-- build + | +-- Microsoft.Z3.x64.targets + | +-- libz3.dll + ``` + 4. Open the nuspec file and fill in the appropriate macro values (note that for all URLs, preserve link integrity by linking to a specific commit): + * $(releaseVersion) - the Z3 version being released in this package + * $(iconUrlFromReleaseCommit) - URL for the Z3 icon file + * $(licenseUrlFromReleaseCommit) - URL for the Z3 repo license + * $(releaseCommitHash) - hash of the release commit + 5. Run `nuget pack Microsoft.Z3.x64\Microsoft.Z3.x64.nuspec` + 6. Test the resulting nupkg file (described below) then submit the package for signing before uploading to NuGet.org + +## Testing + + 1. Create a directory on your machine at C:\nuget-test-source + 2. Put the Microsoft.Z3.x64.nupkg file in the directory + 3. Open Visual Studio 2017, create a new C# project, then right click the project and click "Manage NuGet packages" + 4. Add a new package source - your C:\nuget-test-source directory + 5. Find the Microsoft.Z3.x64 package, ensuring in preview window that icon is present and all fields correct + 6. Install the Microsoft.Z3.x64 package, ensuring you are asked to accept the license + 7. Build your project. Check the output directory to ensure both Microsoft.Z3.dll and libz3.dll are present + 8. Import Microsoft.Z3 to your project then add a simple line of code like `using (var ctx = new Context()) { }`; build then run your project to ensure the assemblies load properly + \ No newline at end of file diff --git a/package/icon.jpg b/package/icon.jpg new file mode 100644 index 000000000..a862aa824 Binary files /dev/null and b/package/icon.jpg differ diff --git a/scripts/mk_project.py b/scripts/mk_project.py index ca62f5c5f..1ec5f05b5 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -10,7 +10,7 @@ from mk_util import * # Z3 Project definition def init_project_def(): set_version(4, 8, 0, 0) - add_lib('util', []) + add_lib('util', [], includes2install = ['z3_version.h']) add_lib('polynomial', ['util'], 'math/polynomial') add_lib('sat', ['util']) add_lib('nlsat', ['polynomial', 'sat']) diff --git a/scripts/mk_util.py b/scripts/mk_util.py index 770e118ee..97e2b65f2 100644 --- a/scripts/mk_util.py +++ b/scripts/mk_util.py @@ -396,7 +396,7 @@ def check_java(): libdirs = m.group(1).split(',') for libdir in libdirs: q = os.path.dirname(libdir) - if cdirs.count(q) == 0: + if cdirs.count(q) == 0 and len(q) > 0: cdirs.append(q) t.close() @@ -1668,9 +1668,6 @@ class DotNetDLLComponent(Component): '/noconfig', '/nostdlib+', '/reference:mscorlib.dll', - # Under mono this isn't neccessary as mono will search the system - # library paths for libz3.so - '/linkresource:{}.dll'.format(get_component(Z3_DLL_COMPONENT).dll_name), ] ) @@ -2805,8 +2802,8 @@ def get_full_version_string(major, minor, build, revision): # Update files with the version number def mk_version_dot_h(major, minor, build, revision): c = get_component(UTIL_COMPONENT) - version_template = os.path.join(c.src_dir, 'version.h.in') - version_header_output = os.path.join(c.src_dir, 'version.h') + version_template = os.path.join(c.src_dir, 'z3_version.h.in') + version_header_output = os.path.join(c.src_dir, 'z3_version.h') # Note the substitution names are what is used by the CMake # builds system. If you change these you should change them # in the CMake build too diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 826f87e8c..c497c19ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,8 @@ foreach (header ${libz3_public_headers}) set_property(TARGET libz3 APPEND PROPERTY PUBLIC_HEADER "${CMAKE_SOURCE_DIR}/src/api/${header}") endforeach() +set_property(TARGET libz3 APPEND PROPERTY + PUBLIC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/util/z3_version.h") install(TARGETS libz3 EXPORT Z3_EXPORTED_TARGETS diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index 52be66e77..a28315cda 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -468,7 +468,7 @@ extern "C" { Z3_TRY; LOG_Z3_get_decl_symbol_parameter(c, d, idx); RESET_ERROR_CODE(); - CHECK_VALID_AST(d, 0); + CHECK_VALID_AST(d, nullptr); if (idx >= to_func_decl(d)->get_num_parameters()) { SET_ERROR_CODE(Z3_IOB, nullptr); return nullptr; @@ -486,7 +486,7 @@ extern "C" { Z3_TRY; LOG_Z3_get_decl_sort_parameter(c, d, idx); RESET_ERROR_CODE(); - CHECK_VALID_AST(d, 0); + CHECK_VALID_AST(d, nullptr); if (idx >= to_func_decl(d)->get_num_parameters()) { SET_ERROR_CODE(Z3_IOB, nullptr); RETURN_Z3(nullptr); @@ -504,7 +504,7 @@ extern "C" { Z3_TRY; LOG_Z3_get_decl_ast_parameter(c, d, idx); RESET_ERROR_CODE(); - CHECK_VALID_AST(d, 0); + CHECK_VALID_AST(d, nullptr); if (idx >= to_func_decl(d)->get_num_parameters()) { SET_ERROR_CODE(Z3_IOB, nullptr); RETURN_Z3(nullptr); @@ -522,7 +522,7 @@ extern "C" { Z3_TRY; LOG_Z3_get_decl_func_decl_parameter(c, d, idx); RESET_ERROR_CODE(); - CHECK_VALID_AST(d, 0); + CHECK_VALID_AST(d, nullptr); if (idx >= to_func_decl(d)->get_num_parameters()) { SET_ERROR_CODE(Z3_IOB, nullptr); RETURN_Z3(nullptr); @@ -596,7 +596,7 @@ extern "C" { Z3_TRY; LOG_Z3_get_domain(c, d, i); RESET_ERROR_CODE(); - CHECK_VALID_AST(d, 0); + CHECK_VALID_AST(d, nullptr); if (i >= to_func_decl(d)->get_arity()) { SET_ERROR_CODE(Z3_IOB, nullptr); RETURN_Z3(nullptr); diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index cc2a13aed..4b3b85399 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -19,7 +19,7 @@ Revision History: --*/ #include #include "api/api_context.h" -#include "util/version.h" +#include "util/z3_version.h" #include "ast/ast_pp.h" #include "ast/ast_ll_pp.h" #include "api/api_log_macros.h" diff --git a/src/api/api_datalog.cpp b/src/api/api_datalog.cpp index 61c0dc3a5..23c765821 100644 --- a/src/api/api_datalog.cpp +++ b/src/api/api_datalog.cpp @@ -379,10 +379,8 @@ extern "C" { for (unsigned i = 0; i < coll.m_rules.size(); ++i) { to_fixedpoint_ref(d)->add_rule(coll.m_rules[i].get(), coll.m_names[i]); } - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - for (; it != end; ++it) { - to_fixedpoint_ref(d)->ctx().assert_expr(*it); + for (expr * e : ctx.assertions()) { + to_fixedpoint_ref(d)->ctx().assert_expr(e); } return of_ast_vector(v); @@ -717,5 +715,4 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } - }; diff --git a/src/api/api_datalog_spacer.inc b/src/api/api_datalog_spacer.inc deleted file mode 100644 index 1888d4b96..000000000 --- a/src/api/api_datalog_spacer.inc +++ /dev/null @@ -1,113 +0,0 @@ -/*++ -Copyright (c) 2017 Arie Gurfinkel - -Module Name: - - api_datalog_spacer.inc - -Abstract: - - Spacer-specific datalog API - -Author: - - Arie Gurfinkel (arie) - -Notes: - this file is included at the bottom of api_datalog.cpp - ---*/ - Z3_lbool Z3_API Z3_fixedpoint_query_from_lvl (Z3_context c, Z3_fixedpoint d, Z3_ast q, unsigned lvl) { - Z3_TRY; - LOG_Z3_fixedpoint_query_from_lvl (c, d, q, lvl); - RESET_ERROR_CODE(); - lbool r = l_undef; - unsigned timeout = to_fixedpoint(d)->m_params.get_uint("timeout", mk_c(c)->get_timeout()); - unsigned rlimit = to_fixedpoint(d)->m_params.get_uint("rlimit", mk_c(c)->get_rlimit()); - { - scoped_rlimit _rlimit(mk_c(c)->m().limit(), rlimit); - cancel_eh eh(mk_c(c)->m().limit()); - api::context::set_interruptable si(*(mk_c(c)), eh); - scoped_timer timer(timeout, &eh); - try { - r = to_fixedpoint_ref(d)->ctx().query_from_lvl (to_expr(q), lvl); - } - catch (z3_exception& ex) { - mk_c(c)->handle_exception(ex); - r = l_undef; - } - to_fixedpoint_ref(d)->ctx().cleanup(); - } - return of_lbool(r); - Z3_CATCH_RETURN(Z3_L_UNDEF); - } - - Z3_ast Z3_API Z3_fixedpoint_get_ground_sat_answer(Z3_context c, Z3_fixedpoint d) { - Z3_TRY; - LOG_Z3_fixedpoint_get_ground_sat_answer(c, d); - RESET_ERROR_CODE(); - expr* e = to_fixedpoint_ref(d)->ctx().get_ground_sat_answer(); - mk_c(c)->save_ast_trail(e); - RETURN_Z3(of_expr(e)); - Z3_CATCH_RETURN(nullptr); - } - - Z3_ast_vector Z3_API Z3_fixedpoint_get_rules_along_trace( - Z3_context c, - Z3_fixedpoint d) - { - Z3_TRY; - LOG_Z3_fixedpoint_get_rules_along_trace(c, d); - ast_manager& m = mk_c(c)->m(); - Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); - mk_c(c)->save_object(v); - expr_ref_vector rules(m); - svector names; - - to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); - for (unsigned i = 0; i < rules.size(); ++i) { - v->m_ast_vector.push_back(rules[i].get()); - } - RETURN_Z3(of_ast_vector(v)); - Z3_CATCH_RETURN(nullptr); - } - - Z3_symbol Z3_API Z3_fixedpoint_get_rule_names_along_trace( - Z3_context c, - Z3_fixedpoint d) - { - Z3_TRY; - LOG_Z3_fixedpoint_get_rule_names_along_trace(c, d); - ast_manager& m = mk_c(c)->m(); - Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); - mk_c(c)->save_object(v); - expr_ref_vector rules(m); - svector names; - std::stringstream ss; - - to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); - for (unsigned i = 0; i < names.size(); ++i) { - ss << ";" << names[i].str(); - } - RETURN_Z3(of_symbol(symbol(ss.str().substr(1).c_str()))); - Z3_CATCH_RETURN(nullptr); - } - - void Z3_API Z3_fixedpoint_add_invariant(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred, Z3_ast property) { - Z3_TRY; - LOG_Z3_fixedpoint_add_invariant(c, d, pred, property); - RESET_ERROR_CODE(); - to_fixedpoint_ref(d)->ctx ().add_invariant(to_func_decl(pred), to_expr(property)); - Z3_CATCH; - } - - Z3_ast Z3_API Z3_fixedpoint_get_reachable(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred) { - Z3_TRY; - LOG_Z3_fixedpoint_get_reachable(c, d, pred); - RESET_ERROR_CODE(); - expr_ref r = to_fixedpoint_ref(d)->ctx().get_reachable(to_func_decl(pred)); - mk_c(c)->save_ast_trail(r); - RETURN_Z3(of_expr(r.get())); - Z3_CATCH_RETURN(nullptr); - } - diff --git a/src/api/api_goal.cpp b/src/api/api_goal.cpp index cb3bb7478..3d75c3dba 100644 --- a/src/api/api_goal.cpp +++ b/src/api/api_goal.cpp @@ -163,7 +163,7 @@ extern "C" { if (to_goal_ref(g)->mc()) (*to_goal_ref(g)->mc())(m_ref->m_model); RETURN_Z3(of_model(m_ref)); - Z3_CATCH_RETURN(0); + Z3_CATCH_RETURN(nullptr); } Z3_goal Z3_API Z3_goal_translate(Z3_context c, Z3_goal g, Z3_context target) { diff --git a/src/api/api_log.cpp b/src/api/api_log.cpp index 1bdbb8735..0f531b98e 100644 --- a/src/api/api_log.cpp +++ b/src/api/api_log.cpp @@ -19,7 +19,7 @@ Revision History: #include "api/z3.h" #include "api/api_log_macros.h" #include "util/util.h" -#include "util/version.h" +#include "util/z3_version.h" std::ostream * g_z3_log = nullptr; bool g_z3_log_enabled = false; diff --git a/src/api/api_opt.cpp b/src/api/api_opt.cpp index 71f92eeba..0b56b788d 100644 --- a/src/api/api_opt.cpp +++ b/src/api/api_opt.cpp @@ -124,10 +124,16 @@ extern "C" { } - Z3_lbool Z3_API Z3_optimize_check(Z3_context c, Z3_optimize o) { + Z3_lbool Z3_API Z3_optimize_check(Z3_context c, Z3_optimize o, unsigned num_assumptions, Z3_ast const assumptions[]) { Z3_TRY; - LOG_Z3_optimize_check(c, o); + LOG_Z3_optimize_check(c, o, num_assumptions, assumptions); RESET_ERROR_CODE(); + for (unsigned i = 0; i < num_assumptions; i++) { + if (!is_expr(to_ast(assumptions[i]))) { + SET_ERROR_CODE(Z3_INVALID_ARG, "assumption is not an expression"); + return Z3_L_UNDEF; + } + } lbool r = l_undef; cancel_eh eh(mk_c(c)->m().limit()); unsigned timeout = to_optimize_ptr(o)->get_params().get_uint("timeout", mk_c(c)->get_timeout()); @@ -137,7 +143,9 @@ extern "C" { scoped_timer timer(timeout, &eh); scoped_rlimit _rlimit(mk_c(c)->m().limit(), rlimit); try { - r = to_optimize_ptr(o)->optimize(); + expr_ref_vector asms(mk_c(c)->m()); + asms.append(num_assumptions, to_exprs(assumptions)); + r = to_optimize_ptr(o)->optimize(asms); } catch (z3_exception& ex) { if (!mk_c(c)->m().canceled()) { @@ -157,6 +165,22 @@ extern "C" { Z3_CATCH_RETURN(Z3_L_UNDEF); } + Z3_ast_vector Z3_API Z3_optimize_get_unsat_core(Z3_context c, Z3_optimize o) { + Z3_TRY; + LOG_Z3_optimize_get_unsat_core(c, o); + RESET_ERROR_CODE(); + expr_ref_vector core(mk_c(c)->m()); + to_optimize_ptr(o)->get_unsat_core(core); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); + mk_c(c)->save_object(v); + for (expr* e : core) { + v->m_ast_vector.push_back(e); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(nullptr); + } + + Z3_string Z3_API Z3_optimize_get_reason_unknown(Z3_context c, Z3_optimize o) { Z3_TRY; LOG_Z3_optimize_to_string(c, o); @@ -330,10 +354,8 @@ extern "C" { return; } - ptr_vector::const_iterator it = ctx->begin_assertions(); - ptr_vector::const_iterator end = ctx->end_assertions(); - for (; it != end; ++it) { - to_optimize_ptr(opt)->add_hard_constraint(*it); + for (expr * e : ctx->assertions()) { + to_optimize_ptr(opt)->add_hard_constraint(e); } } diff --git a/src/api/api_parsers.cpp b/src/api/api_parsers.cpp index b88f273f9..32c133d2b 100644 --- a/src/api/api_parsers.cpp +++ b/src/api/api_parsers.cpp @@ -71,10 +71,8 @@ extern "C" { SET_ERROR_CODE(Z3_PARSER_ERROR, errstrm.str().c_str()); return of_ast_vector(v); } - ptr_vector::const_iterator it = ctx->begin_assertions(); - ptr_vector::const_iterator end = ctx->end_assertions(); - for (; it != end; ++it) { - v->m_ast_vector.push_back(*it); + for (expr * e : ctx->assertions()) { + v->m_ast_vector.push_back(e); } return of_ast_vector(v); Z3_CATCH_RETURN(nullptr); diff --git a/src/api/api_qe.cpp b/src/api/api_qe.cpp index 167a694aa..0b0c694d7 100644 --- a/src/api/api_qe.cpp +++ b/src/api/api_qe.cpp @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2018 Microsoft Corporation +Copyright (c) Microsoft Corporation, Arie Gurfinkel 2017 Module Name: @@ -70,39 +70,39 @@ extern "C" } Z3_ast Z3_API Z3_qe_model_project_skolem (Z3_context c, - Z3_model m, + Z3_model mdl, unsigned num_bounds, Z3_app const bound[], Z3_ast body, Z3_ast_map map) { Z3_TRY; - LOG_Z3_qe_model_project_skolem (c, m, num_bounds, bound, body, map); + LOG_Z3_qe_model_project_skolem (c, mdl, num_bounds, bound, body, map); RESET_ERROR_CODE(); - ast_manager& man = mk_c(c)->m (); - app_ref_vector vars(man); + ast_manager& m = mk_c(c)->m(); + app_ref_vector vars(m); if (!to_apps(num_bounds, bound, vars)) { RETURN_Z3(nullptr); } - expr_ref result (mk_c(c)->m ()); + expr_ref result (m); result = to_expr (body); - model_ref model (to_model_ref (m)); - expr_map emap (man); + model_ref model (to_model_ref (mdl)); + expr_map emap (m); - spacer::qe_project (mk_c(c)->m (), vars, result, model, emap); - mk_c(c)->save_ast_trail (result.get ()); + spacer::qe_project(m, vars, result, model, emap); + mk_c(c)->save_ast_trail(result); obj_map &map_z3 = to_ast_map_ref(map); - for (expr_map::iterator it = emap.begin(), end = emap.end(); it != end; ++it){ - man.inc_ref(&(it->get_key())); - man.inc_ref(it->get_value()); - map_z3.insert(&(it->get_key()), it->get_value()); + for (auto& kv : emap) { + m.inc_ref(kv.m_key); + m.inc_ref(kv.m_value); + map_z3.insert(kv.m_key, kv.m_value); } - return of_expr (result.get ()); + return of_expr (result); Z3_CATCH_RETURN(nullptr); } @@ -124,9 +124,9 @@ extern "C" expr_ref result (mk_c(c)->m ()); result = mk_and (lits); - mk_c(c)->save_ast_trail (result.get ()); + mk_c(c)->save_ast_trail (result); - return of_expr (result.get ()); + return of_expr (result); Z3_CATCH_RETURN(nullptr); } @@ -138,8 +138,8 @@ extern "C" ast_ref_vector &vVars = to_ast_vector_ref (vars); app_ref_vector vApps (mk_c(c)->m()); - for (unsigned i = 0; i < vVars.size (); ++i) { - app *a = to_app (vVars.get (i)); + for (ast* v : vVars) { + app * a = to_app(v); if (a->get_kind () != AST_APP) { SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); RETURN_Z3(nullptr); @@ -162,7 +162,7 @@ extern "C" } } - mk_c(c)->save_ast_trail (result.get ()); + mk_c(c)->save_ast_trail (result); return of_expr (result); Z3_CATCH_RETURN(nullptr); } diff --git a/src/api/api_quant.cpp b/src/api/api_quant.cpp index 6d6d19d56..49aa09727 100644 --- a/src/api/api_quant.cpp +++ b/src/api/api_quant.cpp @@ -155,7 +155,7 @@ extern "C" { expr_ref result(mk_c(c)->m()); if (num_decls == 0) { SET_ERROR_CODE(Z3_INVALID_USAGE, nullptr); - RETURN_Z3(0); + RETURN_Z3(nullptr); } sort* const* ts = reinterpret_cast(types); @@ -166,7 +166,7 @@ extern "C" { result = mk_c(c)->m().mk_lambda(names.size(), ts, names.c_ptr(), to_expr(body)); mk_c(c)->save_ast_trail(result.get()); return of_ast(result.get()); - Z3_CATCH_RETURN(0); + Z3_CATCH_RETURN(nullptr); } Z3_ast Z3_API Z3_mk_lambda_const(Z3_context c, @@ -178,7 +178,7 @@ extern "C" { RESET_ERROR_CODE(); if (num_decls == 0) { SET_ERROR_CODE(Z3_INVALID_USAGE, nullptr); - RETURN_Z3(0); + RETURN_Z3(nullptr); } svector _names; @@ -196,7 +196,7 @@ extern "C" { result = mk_c(c)->m().mk_lambda(_vars.size(), _vars.c_ptr(), _names.c_ptr(), result); mk_c(c)->save_ast_trail(result.get()); return of_ast(result.get()); - Z3_CATCH_RETURN(0); + Z3_CATCH_RETURN(nullptr); } diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 9ad51aaf4..204370346 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -157,10 +157,8 @@ extern "C" { bool initialized = to_solver(s)->m_solver.get() != nullptr; if (!initialized) init_solver(c, s); - ptr_vector::const_iterator it = ctx->begin_assertions(); - ptr_vector::const_iterator end = ctx->end_assertions(); - for (; it != end; ++it) { - to_solver_ref(s)->assert_expr(*it); + for (expr * e : ctx->assertions()) { + to_solver_ref(s)->assert_expr(e); } to_solver_ref(s)->set_model_converter(ctx->get_model_converter()); } @@ -179,6 +177,7 @@ extern "C" { LOG_Z3_solver_from_file(c, s, file_name); char const* ext = get_extension(file_name); std::ifstream is(file_name); + init_solver(c, s); if (!is) { SET_ERROR_CODE(Z3_FILE_ACCESS_ERROR, nullptr); } @@ -368,7 +367,22 @@ extern "C" { v->m_ast_vector.push_back(f); } RETURN_Z3(of_ast_vector(v)); - Z3_CATCH_RETURN(0); + Z3_CATCH_RETURN(nullptr); + } + + Z3_ast_vector Z3_API Z3_solver_get_non_units(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_non_units(c, s); + RESET_ERROR_CODE(); + init_solver(c, s); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); + mk_c(c)->save_object(v); + expr_ref_vector fmls = to_solver_ref(s)->get_non_units(mk_c(c)->m()); + for (expr* f : fmls) { + v->m_ast_vector.push_back(f); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(nullptr); } static Z3_lbool _solver_check(Z3_context c, Z3_solver s, unsigned num_assumptions, Z3_ast const assumptions[]) { @@ -615,7 +629,7 @@ extern "C" { } catch (z3_exception & ex) { mk_c(c)->handle_exception(ex); - return 0; + return nullptr; } } Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); @@ -628,7 +642,7 @@ extern "C" { to_ast_vector_ref(vs).push_back(a); } RETURN_Z3(of_ast_vector(v)); - Z3_CATCH_RETURN(0); + Z3_CATCH_RETURN(nullptr); } diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index e1f263e17..1b1820909 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -127,6 +127,14 @@ namespace z3 { unsat, sat, unknown }; + enum rounding_mode { + RNA, + RNE, + RTP, + RTN, + RTZ + }; + inline check_result to_check_result(Z3_lbool l) { if (l == Z3_L_TRUE) return sat; else if (l == Z3_L_FALSE) return unsat; @@ -137,12 +145,17 @@ namespace z3 { /** \brief A Context manages all other Z3 objects, global configuration options, etc. */ + + class context { + private: bool m_enable_exceptions; + rounding_mode m_rounding_mode; Z3_context m_ctx; void init(config & c) { m_ctx = Z3_mk_context_rc(c); m_enable_exceptions = true; + m_rounding_mode = RNA; Z3_set_error_handler(m_ctx, 0); Z3_set_ast_print_mode(m_ctx, Z3_PRINT_SMTLIB2_COMPLIANT); } @@ -171,7 +184,7 @@ namespace z3 { } /** - \brief The C++ API uses by defaults exceptions on errors. + \brief The C++ API uses by defaults exceptions on errors. For applications that don't work well with exceptions (there should be only few) you have the ability to turn off exceptions. The tradeoffs are that applications have to be very careful about using check_error() after calls that may result in an @@ -247,6 +260,26 @@ namespace z3 { */ sort array_sort(sort d, sort r); sort array_sort(sort_vector const& d, sort r); + /** + \brief Return a floating point sort. + \c ebits is a number of exponent bits, + \c sbits is a number of significand bits, + \pre where ebits must be larger than 1 and sbits must be larger than 2. + */ + sort fpa_sort(unsigned ebits, unsigned sbits); + /** + \brief Return a FloatingPoint sort with given precision bitwidth (16, 32, 64 or 128). + */ + template + sort fpa_sort(); + /** + \brief Return a RoundingMode sort. + */ + sort fpa_rounding_mode(); + /** + \breif Sets RoundingMode of FloatingPoints. + */ + void set_rounding_mode(rounding_mode rm); /** \brief Return an enumeration sort: enum_names[0], ..., enum_names[n-1]. \c cs and \c ts are output parameters. The method stores in \c cs the constants corresponding to the enumerated elements, @@ -284,6 +317,10 @@ namespace z3 { expr int_const(char const * name); expr real_const(char const * name); expr bv_const(char const * name, unsigned sz); + expr fpa_const(char const * name, unsigned ebits, unsigned sbits); + + template + expr fpa_const(char const * name); expr bool_val(bool b); @@ -307,6 +344,9 @@ namespace z3 { expr bv_val(char const * n, unsigned sz); expr bv_val(unsigned n, bool const* bits); + expr fpa_val(double n); + expr fpa_val(float n); + expr string_val(char const* s); expr string_val(std::string const& s); @@ -465,6 +505,7 @@ namespace z3 { public: sort(context & c):ast(c) {} sort(context & c, Z3_sort s):ast(c, reinterpret_cast(s)) {} + sort(context & c, Z3_ast a):ast(c, a) {} sort(sort const & s):ast(s) {} operator Z3_sort() const { return reinterpret_cast(m_ast); } /** @@ -523,6 +564,10 @@ namespace z3 { \brief Return true if this sort is a Finite domain sort. */ bool is_finite_domain() const { return sort_kind() == Z3_FINITE_DOMAIN_SORT; } + /** + \brief Return true if this sort is a Floating point sort. + */ + bool is_fpa() const { return sort_kind() == Z3_FLOATING_POINT_SORT; } /** \brief Return the size of this Bit-vector sort. @@ -531,6 +576,9 @@ namespace z3 { */ unsigned bv_size() const { assert(is_bv()); unsigned r = Z3_get_bv_sort_size(ctx(), *this); check_error(); return r; } + unsigned fpa_ebits() const { assert(is_fpa()); unsigned r = Z3_fpa_get_ebits(ctx(), *this); check_error(); return r; } + + unsigned fpa_sbits() const { assert(is_fpa()); unsigned r = Z3_fpa_get_sbits(ctx(), *this); check_error(); return r; } /** \brief Return the domain of this Array sort. @@ -634,7 +682,7 @@ namespace z3 { \brief Return true if this is a regular expression. */ bool is_re() const { return get_sort().is_re(); } - + /** \brief Return true if this is a Finite-domain expression. @@ -644,6 +692,10 @@ namespace z3 { */ bool is_finite_domain() const { return get_sort().is_finite_domain(); } + /** + \brief Return true if this is a FloatingPoint expression. . + */ + bool is_fpa() const { return get_sort().is_fpa(); } /** \brief Return true if this expression is a numeral. @@ -696,29 +748,29 @@ namespace z3 { \brief Return true if this expression is well sorted (aka type correct). */ bool is_well_sorted() const { bool r = Z3_is_well_sorted(ctx(), m_ast) != 0; check_error(); return r; } - + /** \brief Return string representation of numeral or algebraic number This method assumes the expression is numeral or algebraic - + \pre is_numeral() || is_algebraic() */ std::string get_decimal_string(int precision) const { assert(is_numeral() || is_algebraic()); return std::string(Z3_get_numeral_decimal_string(ctx(), m_ast, precision)); } - + /** \brief Return int value of numeral, throw if result cannot fit in machine int It only makes sense to use this function if the caller can ensure that - the result is an integer or if exceptions are enabled. + the result is an integer or if exceptions are enabled. If exceptions are disabled, then use the is_numeral_i function. - + \pre is_numeral() */ - int get_numeral_int() const { + int get_numeral_int() const { int result = 0; if (!is_numeral_i(result)) { assert(ctx().enable_exceptions()); @@ -727,13 +779,13 @@ namespace z3 { } return result; } - + /** \brief Return uint value of numeral, throw if result cannot fit in machine uint It only makes sense to use this function if the caller can ensure that - the result is an integer or if exceptions are enabled. + the result is an integer or if exceptions are enabled. If exceptions are disabled, then use the is_numeral_u function. \pre is_numeral() */ @@ -747,11 +799,11 @@ namespace z3 { } return result; } - + /** \brief Return \c int64_t value of numeral, throw if result cannot fit in \c int64_t. - + \pre is_numeral() */ int64_t get_numeral_int64() const { @@ -764,11 +816,11 @@ namespace z3 { } return result; } - + /** \brief Return \c uint64_t value of numeral, throw if result cannot fit in \c uint64_t. - + \pre is_numeral() */ uint64_t get_numeral_uint64() const { @@ -786,7 +838,7 @@ namespace z3 { return Z3_get_bool_value(ctx(), m_ast); } - expr numerator() const { + expr numerator() const { assert(is_numeral()); Z3_ast r = Z3_get_numerator(ctx(), m_ast); check_error(); @@ -794,7 +846,7 @@ namespace z3 { } - expr denominator() const { + expr denominator() const { assert(is_numeral()); Z3_ast r = Z3_get_denominator(ctx(), m_ast); check_error(); @@ -803,6 +855,17 @@ namespace z3 { operator Z3_app() const { assert(is_app()); return reinterpret_cast(m_ast); } + /** + \brief Return a RoundingMode sort. + */ + sort fpa_rounding_mode() { + assert(is_fpa()); + Z3_sort s = ctx().fpa_rounding_mode(); + check_error(); + return sort(ctx(), s); + } + + /** \brief Return the declaration associated with this application. This method assumes the expression is an application. @@ -905,7 +968,7 @@ namespace z3 { bool is_implies() const { return is_app() && Z3_OP_IMPLIES == decl().decl_kind(); } bool is_eq() const { return is_app() && Z3_OP_EQ == decl().decl_kind(); } bool is_ite() const { return is_app() && Z3_OP_ITE == decl().decl_kind(); } - + friend expr distinct(expr_vector const& args); friend expr concat(expr const& a, expr const& b); friend expr concat(expr_vector const& args); @@ -992,23 +1055,34 @@ namespace z3 { friend expr nor(expr const& a, expr const& b); friend expr xnor(expr const& a, expr const& b); + friend expr min(expr const& a, expr const& b); + friend expr max(expr const& a, expr const& b); + expr rotate_left(unsigned i) { Z3_ast r = Z3_mk_rotate_left(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } expr rotate_right(unsigned i) { Z3_ast r = Z3_mk_rotate_right(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } expr repeat(unsigned i) { Z3_ast r = Z3_mk_repeat(ctx(), i, *this); ctx().check_error(); return expr(ctx(), r); } + friend expr abs(expr const & a); + friend expr sqrt(expr const & a, expr const & rm); + friend expr operator~(expr const & a); - expr extract(unsigned hi, unsigned lo) const { Z3_ast r = Z3_mk_extract(ctx(), hi, lo, *this); ctx().check_error(); return expr(ctx(), r); } + expr extract(unsigned hi, unsigned lo) const { Z3_ast r = Z3_mk_extract(ctx(), hi, lo, *this); ctx().check_error(); return expr(ctx(), r); } unsigned lo() const { assert (is_app() && Z3_get_decl_num_parameters(ctx(), decl()) == 2); return static_cast(Z3_get_decl_int_parameter(ctx(), decl(), 1)); } unsigned hi() const { assert (is_app() && Z3_get_decl_num_parameters(ctx(), decl()) == 2); return static_cast(Z3_get_decl_int_parameter(ctx(), decl(), 0)); } + /** + \brief FloatingPoint fused multiply-add. + */ + friend expr fma(expr const& a, expr const& b, expr const& c); + /** \brief sequence and regular expression operations. + is overloaded as sequence concatenation and regular expression union. concat is overloaded to handle sequences and regular expressions */ - expr extract(expr const& offset, expr const& length) const { + expr extract(expr const& offset, expr const& length) const { check_context(*this, offset); check_context(offset, length); - Z3_ast r = Z3_mk_seq_extract(ctx(), *this, offset, length); check_error(); return expr(ctx(), r); + Z3_ast r = Z3_mk_seq_extract(ctx(), *this, offset, length); check_error(); return expr(ctx(), r); } expr replace(expr const& src, expr const& dst) const { check_context(*this, src); check_context(src, dst); @@ -1049,19 +1123,19 @@ namespace z3 { return expr(ctx(), r); } - friend expr range(expr const& lo, expr const& hi); + friend expr range(expr const& lo, expr const& hi); /** \brief create a looping regular expression. */ expr loop(unsigned lo) { - Z3_ast r = Z3_mk_re_loop(ctx(), m_ast, lo, 0); - check_error(); - return expr(ctx(), r); + Z3_ast r = Z3_mk_re_loop(ctx(), m_ast, lo, 0); + check_error(); + return expr(ctx(), r); } expr loop(unsigned lo, unsigned hi) { - Z3_ast r = Z3_mk_re_loop(ctx(), m_ast, lo, hi); - check_error(); - return expr(ctx(), r); + Z3_ast r = Z3_mk_re_loop(ctx(), m_ast, lo, hi); + check_error(); + return expr(ctx(), r); } @@ -1094,7 +1168,7 @@ namespace z3 { inline expr implies(expr const & a, expr const & b) { - assert(a.is_bool() && b.is_bool()); + assert(a.is_bool() && b.is_bool()); _Z3_MK_BIN_(a, b, Z3_mk_implies); } inline expr implies(expr const & a, bool b) { return implies(a, a.ctx().bool_val(b)); } @@ -1109,7 +1183,13 @@ namespace z3 { inline expr mod(expr const & a, int b) { return mod(a, a.ctx().num_val(b, a.get_sort())); } inline expr mod(int a, expr const & b) { return mod(b.ctx().num_val(a, b.get_sort()), b); } - inline expr rem(expr const& a, expr const& b) { _Z3_MK_BIN_(a, b, Z3_mk_rem); } + inline expr rem(expr const& a, expr const& b) { + if (a.is_fpa() && b.is_fpa()) { + _Z3_MK_BIN_(a, b, Z3_mk_fpa_rem); + } else { + _Z3_MK_BIN_(a, b, Z3_mk_rem); + } + } inline expr rem(expr const & a, int b) { return rem(a, a.ctx().num_val(b, a.get_sort())); } inline expr rem(int a, expr const & b) { return rem(b.ctx().num_val(a, b.get_sort()), b); } @@ -1158,8 +1238,8 @@ namespace z3 { a.check_error(); return expr(a.ctx(), r); } - inline expr operator==(expr const & a, int b) { assert(a.is_arith() || a.is_bv()); return a == a.ctx().num_val(b, a.get_sort()); } - inline expr operator==(int a, expr const & b) { assert(b.is_arith() || b.is_bv()); return b.ctx().num_val(a, b.get_sort()) == b; } + inline expr operator==(expr const & a, int b) { assert(a.is_arith() || a.is_bv() || a.is_fpa()); return a == a.ctx().num_val(b, a.get_sort()); } + inline expr operator==(int a, expr const & b) { assert(b.is_arith() || b.is_bv() || b.is_fpa()); return b.ctx().num_val(a, b.get_sort()) == b; } inline expr operator!=(expr const & a, expr const & b) { check_context(a, b); @@ -1168,8 +1248,8 @@ namespace z3 { a.check_error(); return expr(a.ctx(), r); } - inline expr operator!=(expr const & a, int b) { assert(a.is_arith() || a.is_bv()); return a != a.ctx().num_val(b, a.get_sort()); } - inline expr operator!=(int a, expr const & b) { assert(b.is_arith() || b.is_bv()); return b.ctx().num_val(a, b.get_sort()) != b; } + inline expr operator!=(expr const & a, int b) { assert(a.is_arith() || a.is_bv() || a.is_fpa()); return a != a.ctx().num_val(b, a.get_sort()); } + inline expr operator!=(int a, expr const & b) { assert(b.is_arith() || b.is_bv() || b.is_fpa()); return b.ctx().num_val(a, b.get_sort()) != b; } inline expr operator+(expr const & a, expr const & b) { check_context(a, b); @@ -1188,6 +1268,9 @@ namespace z3 { Z3_ast _args[2] = { a, b }; r = Z3_mk_re_union(a.ctx(), 2, _args); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_add(a.ctx(), a.ctx().fpa_rounding_mode(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1208,6 +1291,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvmul(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_mul(a.ctx(), a.ctx().fpa_rounding_mode(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1245,6 +1331,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvsdiv(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_div(a.ctx(), a.ctx().fpa_rounding_mode(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1263,6 +1352,9 @@ namespace z3 { else if (a.is_bv()) { r = Z3_mk_bvneg(a.ctx(), a); } + else if (a.is_fpa()) { + r = Z3_mk_fpa_neg(a.ctx(), a); + } else { // operator is not supported by given arguments. assert(false); @@ -1281,6 +1373,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvsub(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_sub(a.ctx(), a.ctx().fpa_rounding_mode(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1300,6 +1395,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvsle(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_leq(a.ctx(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1322,6 +1420,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvslt(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_lt(a.ctx(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1341,6 +1442,9 @@ namespace z3 { else if (a.is_bv() && b.is_bv()) { r = Z3_mk_bvsgt(a.ctx(), a, b); } + else if (a.is_fpa() && b.is_fpa()) { + r = Z3_mk_fpa_gt(a.ctx(), a, b); + } else { // operator is not supported by given arguments. assert(false); @@ -1366,17 +1470,30 @@ namespace z3 { inline expr nand(expr const& a, expr const& b) { check_context(a, b); Z3_ast r = Z3_mk_bvnand(a.ctx(), a, b); return expr(a.ctx(), r); } inline expr nor(expr const& a, expr const& b) { check_context(a, b); Z3_ast r = Z3_mk_bvnor(a.ctx(), a, b); return expr(a.ctx(), r); } inline expr xnor(expr const& a, expr const& b) { check_context(a, b); Z3_ast r = Z3_mk_bvxnor(a.ctx(), a, b); return expr(a.ctx(), r); } - + inline expr min(expr const& a, expr const& b) { check_context(a, b); Z3_ast r = Z3_mk_fpa_min(a.ctx(), a, b); return expr(a.ctx(), r); } + inline expr max(expr const& a, expr const& b) { check_context(a, b); Z3_ast r = Z3_mk_fpa_max(a.ctx(), a, b); return expr(a.ctx(), r); } + inline expr abs(expr const & a) { Z3_ast r = Z3_mk_fpa_abs(a.ctx(), a); return expr(a.ctx(), r); } + inline expr sqrt(expr const & a, expr const& rm) { + check_context(a, rm); + assert(a.is_fpa()); + Z3_ast r = Z3_mk_fpa_sqrt(a.ctx(), rm, a); + return expr(a.ctx(), r); + } inline expr operator~(expr const & a) { Z3_ast r = Z3_mk_bvnot(a.ctx(), a); return expr(a.ctx(), r); } - + inline expr fma(expr const& a, expr const& b, expr const& c, expr const& rm) { + check_context(a, b); check_context(a, c); check_context(a, rm); + assert(a.is_fpa() && b.is_fpa() && c.is_fpa()); + Z3_ast r = Z3_mk_fpa_fma(a.ctx(), rm, a, b, c); + a.check_error(); + return expr(a.ctx(), r); + } /** \brief Create the if-then-else expression ite(c, t, e) \pre c.is_bool() */ - inline expr ite(expr const & c, expr const & t, expr const & e) { check_context(c, t); check_context(c, e); assert(c.is_bool()); @@ -1453,45 +1570,45 @@ namespace z3 { inline expr smod(expr const & a, expr const & b) { return to_expr(a.ctx(), Z3_mk_bvsmod(a.ctx(), a, b)); } inline expr smod(expr const & a, int b) { return smod(a, a.ctx().num_val(b, a.get_sort())); } inline expr smod(int a, expr const & b) { return smod(b.ctx().num_val(a, b.get_sort()), b); } - + /** \brief unsigned reminder operator for bitvectors */ inline expr urem(expr const & a, expr const & b) { return to_expr(a.ctx(), Z3_mk_bvurem(a.ctx(), a, b)); } inline expr urem(expr const & a, int b) { return urem(a, a.ctx().num_val(b, a.get_sort())); } inline expr urem(int a, expr const & b) { return urem(b.ctx().num_val(a, b.get_sort()), b); } - + /** \brief shift left operator for bitvectors */ inline expr shl(expr const & a, expr const & b) { return to_expr(a.ctx(), Z3_mk_bvshl(a.ctx(), a, b)); } inline expr shl(expr const & a, int b) { return shl(a, a.ctx().num_val(b, a.get_sort())); } inline expr shl(int a, expr const & b) { return shl(b.ctx().num_val(a, b.get_sort()), b); } - + /** \brief logic shift right operator for bitvectors */ inline expr lshr(expr const & a, expr const & b) { return to_expr(a.ctx(), Z3_mk_bvlshr(a.ctx(), a, b)); } inline expr lshr(expr const & a, int b) { return lshr(a, a.ctx().num_val(b, a.get_sort())); } inline expr lshr(int a, expr const & b) { return lshr(b.ctx().num_val(a, b.get_sort()), b); } - + /** \brief arithmetic shift right operator for bitvectors */ inline expr ashr(expr const & a, expr const & b) { return to_expr(a.ctx(), Z3_mk_bvashr(a.ctx(), a, b)); } inline expr ashr(expr const & a, int b) { return ashr(a, a.ctx().num_val(b, a.get_sort())); } inline expr ashr(int a, expr const & b) { return ashr(b.ctx().num_val(a, b.get_sort()), b); } - + /** \brief Extend the given bit-vector with zeros to the (unsigned) equivalent bitvector of size m+i, where m is the size of the given bit-vector. */ inline expr zext(expr const & a, unsigned i) { return to_expr(a.ctx(), Z3_mk_zero_ext(a.ctx(), i, a)); } - + /** \brief Sign-extend of the given bit-vector to the (signed) equivalent bitvector of size m+i, where m is the size of the given bit-vector. */ inline expr sext(expr const & a, unsigned i) { return to_expr(a.ctx(), Z3_mk_sign_ext(a.ctx(), i, a)); } - + template class cast_ast; template<> class cast_ast { @@ -1563,7 +1680,7 @@ namespace z3 { unsigned m_index; public: iterator(ast_vector_tpl const* v, unsigned i): m_vector(v), m_index(i) {} - iterator(iterator& other): m_vector(other.m_vector), m_index(other.m_index) {} + iterator(iterator& other): m_vector(other.m_vector), m_index(other.m_index) {} iterator operator=(iterator const& other) { m_vector = other.m_vector; m_index = other.m_index; return *this; } bool operator==(iterator const& other) { @@ -1773,7 +1890,7 @@ namespace z3 { return expr(ctx, r); } - inline expr mk_or(expr_vector const& args) { + inline expr mk_or(expr_vector const& args) { array _args(args); Z3_ast r = Z3_mk_or(args.ctx(), _args.size(), _args.ptr()); args.check_error(); @@ -1852,7 +1969,7 @@ namespace z3 { model(context & c):object(c) { init(Z3_mk_model(c)); } model(context & c, Z3_model m):object(c) { init(m); } model(model const & s):object(s) { init(s.m_model); } - model(model& src, context& dst, translate) : object(dst) { init(Z3_model_translate(src.ctx(), src, dst)); } + model(model& src, context& dst, translate) : object(dst) { init(Z3_model_translate(src.ctx(), src, dst)); } ~model() { Z3_model_dec_ref(ctx(), m_model); } operator Z3_model() const { return m_model; } model & operator=(model const & s) { @@ -1884,7 +2001,7 @@ namespace z3 { } // returns interpretation of constant declaration c. - // If c is not assigned any value in the model it returns + // If c is not assigned any value in the model it returns // an expression with a null ast reference. expr get_const_interp(func_decl c) const { check_context(*this, c); @@ -1898,7 +2015,7 @@ namespace z3 { check_error(); return func_interp(ctx(), r); } - + // returns true iff the model contains an interpretation // for function f. bool has_interp(func_decl f) const { @@ -1945,7 +2062,7 @@ namespace z3 { bool is_uint(unsigned i) const { Z3_bool r = Z3_stats_is_uint(ctx(), m_stats, i); check_error(); return r != 0; } bool is_double(unsigned i) const { Z3_bool r = Z3_stats_is_double(ctx(), m_stats, i); check_error(); return r != 0; } unsigned uint_value(unsigned i) const { unsigned r = Z3_stats_get_uint_value(ctx(), m_stats, i); check_error(); return r; } - double double_value(unsigned i) const { double r = Z3_stats_get_double_value(ctx(), m_stats, i); check_error(); return r; } + double double_value(unsigned i) const { double r = Z3_stats_get_double_value(ctx(), m_stats, i); check_error(); return r; } friend std::ostream & operator<<(std::ostream & out, stats const & s); }; inline std::ostream & operator<<(std::ostream & out, stats const & s) { out << Z3_stats_to_string(s.ctx(), s); return out; } @@ -2001,7 +2118,7 @@ namespace z3 { void add(expr const & e, char const * p) { add(e, ctx().bool_const(p)); } - // fails for some compilers: + // fails for some compilers: // void add(expr_vector const& v) { check_context(*this, v); for (expr e : v) add(e); } void from_file(char const* file) { Z3_solver_from_file(ctx(), m_solver, file); ctx().check_parser_error(); } void from_string(char const* s) { Z3_solver_from_string(ctx(), m_solver, s); ctx().check_parser_error(); } @@ -2038,6 +2155,8 @@ namespace z3 { stats statistics() const { Z3_stats r = Z3_solver_get_statistics(ctx(), m_solver); check_error(); return stats(ctx(), r); } expr_vector unsat_core() const { Z3_ast_vector r = Z3_solver_get_unsat_core(ctx(), m_solver); check_error(); return expr_vector(ctx(), r); } expr_vector assertions() const { Z3_ast_vector r = Z3_solver_get_assertions(ctx(), m_solver); check_error(); return expr_vector(ctx(), r); } + expr_vector non_units() const { Z3_ast_vector r = Z3_solver_get_non_units(ctx(), m_solver); check_error(); return expr_vector(ctx(), r); } + expr_vector units() const { Z3_ast_vector r = Z3_solver_get_units(ctx(), m_solver); check_error(); return expr_vector(ctx(), r); } expr proof() const { Z3_ast r = Z3_solver_get_proof(ctx(), m_solver); check_error(); return expr(ctx(), r); } friend std::ostream & operator<<(std::ostream & out, solver const & s); @@ -2064,11 +2183,11 @@ namespace z3 { param_descrs get_param_descrs() { return param_descrs(ctx(), Z3_solver_get_param_descrs(ctx(), m_solver)); } - expr_vector cube(expr_vector& vars, unsigned cutoff) { - Z3_ast_vector r = Z3_solver_cube(ctx(), m_solver, vars, cutoff); - check_error(); - return expr_vector(ctx(), r); - } + expr_vector cube(expr_vector& vars, unsigned cutoff) { + Z3_ast_vector r = Z3_solver_cube(ctx(), m_solver, vars, cutoff); + check_error(); + return expr_vector(ctx(), r); + } class cube_iterator { solver& m_solver; @@ -2116,7 +2235,7 @@ namespace z3 { cube_iterator operator++(int) { assert(false); return *this; } expr_vector const * operator->() const { return &(operator*()); } expr_vector const& operator*() const { return m_cube; } - + bool operator==(cube_iterator const& other) { return other.m_end == m_end; }; @@ -2405,7 +2524,7 @@ namespace z3 { class optimize : public object { Z3_optimize m_opt; - + public: class handle { unsigned m_h; @@ -2453,8 +2572,20 @@ namespace z3 { void pop() { Z3_optimize_pop(ctx(), m_opt); } - check_result check() { Z3_lbool r = Z3_optimize_check(ctx(), m_opt); check_error(); return to_check_result(r); } + check_result check() { Z3_lbool r = Z3_optimize_check(ctx(), m_opt, 0, 0); check_error(); return to_check_result(r); } + check_result check(expr_vector const& asms) { + unsigned n = asms.size(); + array _asms(n); + for (unsigned i = 0; i < n; i++) { + check_context(*this, asms[i]); + _asms[i] = asms[i]; + } + Z3_lbool r = Z3_optimize_check(ctx(), m_opt, n, _asms.ptr()); + check_error(); + return to_check_result(r); + } model get_model() const { Z3_model m = Z3_optimize_get_model(ctx(), m_opt); check_error(); return model(ctx(), m); } + expr_vector unsat_core() const { Z3_ast_vector r = Z3_optimize_get_unsat_core(ctx(), m_opt); check_error(); return expr_vector(ctx(), r); } void set(params const & p) { Z3_optimize_set_params(ctx(), m_opt, p); check_error(); } expr lower(handle const& h) { Z3_ast r = Z3_optimize_get_lower(ctx(), m_opt, h.h()); @@ -2481,25 +2612,25 @@ namespace z3 { public: fixedpoint(context& c):object(c) { m_fp = Z3_mk_fixedpoint(c); Z3_fixedpoint_inc_ref(c, m_fp); } ~fixedpoint() { Z3_fixedpoint_dec_ref(ctx(), m_fp); } - operator Z3_fixedpoint() const { return m_fp; } + operator Z3_fixedpoint() const { return m_fp; } void from_string(char const* s) { Z3_fixedpoint_from_string(ctx(), m_fp, s); check_error(); } void from_file(char const* s) { Z3_fixedpoint_from_file(ctx(), m_fp, s); check_error(); } void add_rule(expr& rule, symbol const& name) { Z3_fixedpoint_add_rule(ctx(), m_fp, rule, name); check_error(); } void add_fact(func_decl& f, unsigned * args) { Z3_fixedpoint_add_fact(ctx(), m_fp, f, f.arity(), args); check_error(); } check_result query(expr& q) { Z3_lbool r = Z3_fixedpoint_query(ctx(), m_fp, q); check_error(); return to_check_result(r); } - check_result query(func_decl_vector& relations) { + check_result query(func_decl_vector& relations) { array rs(relations); - Z3_lbool r = Z3_fixedpoint_query_relations(ctx(), m_fp, rs.size(), rs.ptr()); - check_error(); - return to_check_result(r); + Z3_lbool r = Z3_fixedpoint_query_relations(ctx(), m_fp, rs.size(), rs.ptr()); + check_error(); + return to_check_result(r); } expr get_answer() { Z3_ast r = Z3_fixedpoint_get_answer(ctx(), m_fp); check_error(); return expr(ctx(), r); } std::string reason_unknown() { return Z3_fixedpoint_get_reason_unknown(ctx(), m_fp); } void update_rule(expr& rule, symbol const& name) { Z3_fixedpoint_update_rule(ctx(), m_fp, rule, name); check_error(); } unsigned get_num_levels(func_decl& p) { unsigned r = Z3_fixedpoint_get_num_levels(ctx(), m_fp, p); check_error(); return r; } - expr get_cover_delta(int level, func_decl& p) { - Z3_ast r = Z3_fixedpoint_get_cover_delta(ctx(), m_fp, level, p); - check_error(); + expr get_cover_delta(int level, func_decl& p) { + Z3_ast r = Z3_fixedpoint_get_cover_delta(ctx(), m_fp, level, p); + check_error(); return expr(ctx(), r); } void add_cover(int level, func_decl& p, expr& property) { Z3_fixedpoint_add_cover(ctx(), m_fp, level, p, property); check_error(); } @@ -2513,7 +2644,7 @@ namespace z3 { std::string to_string() { return Z3_fixedpoint_to_string(ctx(), m_fp, 0, 0); } std::string to_string(expr_vector const& queries) { array qs(queries); - return Z3_fixedpoint_to_string(ctx(), m_fp, qs.size(), qs.ptr()); + return Z3_fixedpoint_to_string(ctx(), m_fp, qs.size(), qs.ptr()); } void push() { Z3_fixedpoint_push(ctx(), m_fp); check_error(); } void pop() { Z3_fixedpoint_pop(ctx(), m_fp); check_error(); } @@ -2548,11 +2679,37 @@ namespace z3 { inline sort context::string_sort() { Z3_sort s = Z3_mk_string_sort(m_ctx); check_error(); return sort(*this, s); } inline sort context::seq_sort(sort& s) { Z3_sort r = Z3_mk_seq_sort(m_ctx, s); check_error(); return sort(*this, r); } inline sort context::re_sort(sort& s) { Z3_sort r = Z3_mk_re_sort(m_ctx, s); check_error(); return sort(*this, r); } + inline sort context::fpa_sort(unsigned ebits, unsigned sbits) { Z3_sort s = Z3_mk_fpa_sort(m_ctx, ebits, sbits); check_error(); return sort(*this, s); } + + template<> + inline sort context::fpa_sort<16>() { return fpa_sort(5, 11); } + + template<> + inline sort context::fpa_sort<32>() { return fpa_sort(8, 24); } + + template<> + inline sort context::fpa_sort<64>() { return fpa_sort(11, 53); } + + template<> + inline sort context::fpa_sort<128>() { return fpa_sort(15, 113); } + + inline sort context::fpa_rounding_mode() { + switch (m_rounding_mode) { + case RNA: return sort(*this, Z3_mk_fpa_rna(m_ctx)); + case RNE: return sort(*this, Z3_mk_fpa_rne(m_ctx)); + case RTP: return sort(*this, Z3_mk_fpa_rtp(m_ctx)); + case RTN: return sort(*this, Z3_mk_fpa_rtn(m_ctx)); + case RTZ: return sort(*this, Z3_mk_fpa_rtz(m_ctx)); + default: return sort(*this); + } + } + + inline void context::set_rounding_mode(rounding_mode rm) { m_rounding_mode = rm; } inline sort context::array_sort(sort d, sort r) { Z3_sort s = Z3_mk_array_sort(m_ctx, d, r); check_error(); return sort(*this, s); } inline sort context::array_sort(sort_vector const& d, sort r) { array dom(d); - Z3_sort s = Z3_mk_array_sort_n(m_ctx, dom.size(), dom.ptr(), r); check_error(); return sort(*this, s); + Z3_sort s = Z3_mk_array_sort_n(m_ctx, dom.size(), dom.ptr(), r); check_error(); return sort(*this, s); } inline sort context::enumeration_sort(char const * name, unsigned n, char const * const * enum_names, func_decl_vector & cs, func_decl_vector & ts) { array _enum_names(n); @@ -2667,6 +2824,10 @@ namespace z3 { inline expr context::int_const(char const * name) { return constant(name, int_sort()); } inline expr context::real_const(char const * name) { return constant(name, real_sort()); } inline expr context::bv_const(char const * name, unsigned sz) { return constant(name, bv_sort(sz)); } + inline expr context::fpa_const(char const * name, unsigned ebits, unsigned sbits) { return constant(name, fpa_sort(ebits, sbits)); } + + template + inline expr context::fpa_const(char const * name) { return constant(name, fpa_sort()); } inline expr context::bool_val(bool b) { return b ? expr(*this, Z3_mk_true(m_ctx)) : expr(*this, Z3_mk_false(m_ctx)); } @@ -2688,12 +2849,15 @@ namespace z3 { inline expr context::bv_val(int64_t n, unsigned sz) { sort s = bv_sort(sz); Z3_ast r = Z3_mk_int64(m_ctx, n, s); check_error(); return expr(*this, r); } inline expr context::bv_val(uint64_t n, unsigned sz) { sort s = bv_sort(sz); Z3_ast r = Z3_mk_unsigned_int64(m_ctx, n, s); check_error(); return expr(*this, r); } inline expr context::bv_val(char const * n, unsigned sz) { sort s = bv_sort(sz); Z3_ast r = Z3_mk_numeral(m_ctx, n, s); check_error(); return expr(*this, r); } - inline expr context::bv_val(unsigned n, bool const* bits) { + inline expr context::bv_val(unsigned n, bool const* bits) { array _bits(n); for (unsigned i = 0; i < n; ++i) _bits[i] = bits[i] ? 1 : 0; - Z3_ast r = Z3_mk_bv_numeral(m_ctx, n, _bits.ptr()); check_error(); return expr(*this, r); + Z3_ast r = Z3_mk_bv_numeral(m_ctx, n, _bits.ptr()); check_error(); return expr(*this, r); } + inline expr context::fpa_val(double n) { sort s = fpa_sort<64>(); Z3_ast r = Z3_mk_fpa_numeral_double(m_ctx, n, s); check_error(); return expr(*this, r); } + inline expr context::fpa_val(float n) { sort s = fpa_sort<32>(); Z3_ast r = Z3_mk_fpa_numeral_float(m_ctx, n, s); check_error(); return expr(*this, r); } + inline expr context::string_val(char const* s) { Z3_ast r = Z3_mk_string(m_ctx, s); check_error(); return expr(*this, r); } inline expr context::string_val(std::string const& s) { Z3_ast r = Z3_mk_string(m_ctx, s.c_str()); check_error(); return expr(*this, r); } @@ -2817,8 +2981,8 @@ namespace z3 { a.check_error(); return expr(a.ctx(), r); } - inline expr select(expr const & a, int i) { - return select(a, a.ctx().num_val(i, a.get_sort().array_domain())); + inline expr select(expr const & a, int i) { + return select(a, a.ctx().num_val(i, a.get_sort().array_domain())); } inline expr select(expr const & a, expr_vector const & i) { check_context(a, i); @@ -2848,10 +3012,10 @@ namespace z3 { return expr(a.ctx(), r); } - inline expr as_array(func_decl & f) { - Z3_ast r = Z3_mk_as_array(f.ctx(), f); - f.check_error(); - return expr(f.ctx(), r); + inline expr as_array(func_decl & f) { + Z3_ast r = Z3_mk_as_array(f.ctx(), f); + f.check_error(); + return expr(f.ctx(), r); } #define MK_EXPR1(_fn, _arg) \ @@ -2883,21 +3047,21 @@ namespace z3 { inline expr set_del(expr const& s, expr const& e) { MK_EXPR2(Z3_mk_set_del, s, e); - } + } inline expr set_union(expr const& a, expr const& b) { - check_context(a, b); + check_context(a, b); Z3_ast es[2] = { a, b }; Z3_ast r = Z3_mk_set_union(a.ctx(), 2, es); - a.check_error(); + a.check_error(); return expr(a.ctx(), r); } inline expr set_intersect(expr const& a, expr const& b) { - check_context(a, b); + check_context(a, b); Z3_ast es[2] = { a, b }; Z3_ast r = Z3_mk_set_intersect(a.ctx(), 2, es); - a.check_error(); + a.check_error(); return expr(a.ctx(), r); } @@ -2981,10 +3145,10 @@ namespace z3 { MK_EXPR1(Z3_mk_re_complement, a); } inline expr range(expr const& lo, expr const& hi) { - check_context(lo, hi); - Z3_ast r = Z3_mk_re_range(lo.ctx(), lo, hi); - lo.check_error(); - return expr(lo.ctx(), r); + check_context(lo, hi); + Z3_ast r = Z3_mk_re_range(lo.ctx(), lo, hi); + lo.check_error(); + return expr(lo.ctx(), r); } @@ -2995,7 +3159,7 @@ namespace z3 { Z3_ast_vector r = Z3_parse_smtlib2_string(*this, s, 0, 0, 0, 0, 0, 0); check_error(); return expr_vector(*this, r); - + } inline expr_vector context::parse_file(char const* s) { Z3_ast_vector r = Z3_parse_smtlib2_file(*this, s, 0, 0, 0, 0, 0, 0); diff --git a/src/api/dotnet/CMakeLists.txt b/src/api/dotnet/CMakeLists.txt index 50f643c8d..20621e4fc 100644 --- a/src/api/dotnet/CMakeLists.txt +++ b/src/api/dotnet/CMakeLists.txt @@ -147,11 +147,6 @@ if (DOTNET_TOOLCHAIN_IS_WINDOWS) "/nostdlib+" "/reference:mscorlib.dll" ) - # FIXME: This flag only works when the working directory of csc.exe is - # the directory containing the ``libz3`` target. I can't get this to work - # correctly with multi-configuration generators (i.e. Visual Studio) so - # just don't set the flag for now. - #list(APPEND CSC_FLAGS "/linkresource:$") elseif (DOTNET_TOOLCHAIN_IS_MONO) # We need to give the assembly a strong name so that it can be installed # into the GAC. diff --git a/src/api/dotnet/Optimize.cs b/src/api/dotnet/Optimize.cs index 29e5e869a..ce3cc55f7 100644 --- a/src/api/dotnet/Optimize.cs +++ b/src/api/dotnet/Optimize.cs @@ -183,9 +183,9 @@ namespace Microsoft.Z3 /// don't use strict inequalities) meets the objectives. /// /// - public Status Check() + public Status Check(params Expr[] assumptions) { - Z3_lbool r = (Z3_lbool)Native.Z3_optimize_check(Context.nCtx, NativeObject); + Z3_lbool r = (Z3_lbool)Native.Z3_optimize_check(Context.nCtx, NativeObject, (uint)assumptions.Length, AST.ArrayToNative(assumptions)); switch (r) { case Z3_lbool.Z3_L_TRUE: @@ -236,6 +236,25 @@ namespace Microsoft.Z3 } } + /// + /// The unsat core of the last Check. + /// + /// + /// The unsat core is a subset of assumptions + /// The result is empty if Check was not invoked before, + /// if its results was not UNSATISFIABLE, or if core production is disabled. + /// + public BoolExpr[] UnsatCore + { + get + { + Contract.Ensures(Contract.Result() != null); + + ASTVector core = new ASTVector(Context, Native.Z3_optimize_get_unsat_core(Context.nCtx, NativeObject)); + return core.ToBoolExprArray(); + } + } + /// /// Declare an arithmetical maximization objective. /// Return a handle to the objective. The handle is used as diff --git a/src/api/java/Context.java b/src/api/java/Context.java index 57085a09e..08d20dfb2 100644 --- a/src/api/java/Context.java +++ b/src/api/java/Context.java @@ -1976,6 +1976,22 @@ public class Context implements AutoCloseable { { return (SeqExpr) Expr.create(this, Native.mkString(nCtx(), s)); } + + /** + * Convert an integer expression to a string. + */ + public SeqExpr intToString(Expr e) + { + return (SeqExpr) Expr.create(this, Native.mkIntToStr(nCtx(), e.getNativeObject())); + } + + /** + * Convert an integer expression to a string. + */ + public IntExpr stringToInt(Expr e) + { + return (IntExpr) Expr.create(this, Native.mkStrToInt(nCtx(), e.getNativeObject())); + } /** * Concatenate sequences. diff --git a/src/api/java/Optimize.java b/src/api/java/Optimize.java index c5f8f9449..ae50d7f33 100644 --- a/src/api/java/Optimize.java +++ b/src/api/java/Optimize.java @@ -161,9 +161,23 @@ public class Optimize extends Z3Object { * Produce a model that (when the objectives are bounded and * don't use strict inequalities) meets the objectives. **/ - public Status Check() + public Status Check(Expr... assumptions) { - Z3_lbool r = Z3_lbool.fromInt(Native.optimizeCheck(getContext().nCtx(), getNativeObject())); + Z3_lbool r; + if (assumptions == null) { + r = Z3_lbool.fromInt( + Native.optimizeCheck( + getContext().nCtx(), + getNativeObject(), 0, null)); + } + else { + r = Z3_lbool.fromInt( + Native.optimizeCheck( + getContext().nCtx(), + getNativeObject(), + assumptions.length, + AST.arrayToNative(assumptions))); + } switch (r) { case Z3_L_TRUE: return Status.SATISFIABLE; @@ -209,6 +223,21 @@ public class Optimize extends Z3Object { } } + /** + * The unsat core of the last {@code Check}. + * Remarks: The unsat core + * is a subset of {@code Assumptions} The result is empty if + * {@code Check} was not invoked before, if its results was not + * {@code UNSATISFIABLE}, or if core production is disabled. + * + * @throws Z3Exception + **/ + public BoolExpr[] getUnsatCore() + { + ASTVector core = new ASTVector(getContext(), Native.optimizeGetUnsatCore(getContext().nCtx(), getNativeObject())); + return core.ToBoolExprArray(); + } + /** * Declare an arithmetical maximization objective. * Return a handle to the objective. The handle is used as diff --git a/src/api/ml/z3.ml b/src/api/ml/z3.ml index a676f8c43..2ad07aee4 100644 --- a/src/api/ml/z3.ml +++ b/src/api/ml/z3.ml @@ -43,6 +43,14 @@ let mk_list f n = in mk_list' 0 [] +let check_int32 v = v = Int32.to_int (Int32.of_int v) + +let mk_int_expr ctx v ty = + if not (check_int32 v) then + Z3native.mk_numeral ctx (string_of_int v) ty + else + Z3native.mk_int ctx v ty + let mk_context (settings:(string * string) list) = let cfg = Z3native.mk_config () in let f e = Z3native.set_param_value cfg (fst e) (snd e) in @@ -531,7 +539,7 @@ end = struct let mk_fresh_const (ctx:context) (prefix:string) (range:Sort.sort) = Z3native.mk_fresh_const ctx prefix range let mk_app (ctx:context) (f:FuncDecl.func_decl) (args:expr list) = expr_of_func_app ctx f args let mk_numeral_string (ctx:context) (v:string) (ty:Sort.sort) = Z3native.mk_numeral ctx v ty - let mk_numeral_int (ctx:context) (v:int) (ty:Sort.sort) = Z3native.mk_int ctx v ty + let mk_numeral_int (ctx:context) (v:int) (ty:Sort.sort) = mk_int_expr ctx v ty let equal (a:expr) (b:expr) = AST.equal a b let compare (a:expr) (b:expr) = AST.compare a b end @@ -1036,7 +1044,7 @@ struct let mk_mod = Z3native.mk_mod let mk_rem = Z3native.mk_rem let mk_numeral_s (ctx:context) (v:string) = Z3native.mk_numeral ctx v (mk_sort ctx) - let mk_numeral_i (ctx:context) (v:int) = Z3native.mk_int ctx v (mk_sort ctx) + let mk_numeral_i (ctx:context) (v:int) = mk_int_expr ctx v (mk_sort ctx) let mk_int2real = Z3native.mk_int2real let mk_int2bv = Z3native.mk_int2bv end @@ -1061,11 +1069,13 @@ struct let mk_numeral_nd (ctx:context) (num:int) (den:int) = if den = 0 then raise (Error "Denominator is zero") + else if not (check_int32 num) || not (check_int32 den) then + raise (Error "numerals don't fit in 32 bits") else Z3native.mk_real ctx num den let mk_numeral_s (ctx:context) (v:string) = Z3native.mk_numeral ctx v (mk_sort ctx) - let mk_numeral_i (ctx:context) (v:int) = Z3native.mk_int ctx v (mk_sort ctx) + let mk_numeral_i (ctx:context) (v:int) = mk_int_expr ctx v (mk_sort ctx) let mk_is_integer = Z3native.mk_is_int let mk_real2int = Z3native.mk_real2int @@ -1155,11 +1165,6 @@ struct let is_bv_xor3 (x:expr) = (AST.is_app x) && (FuncDecl.get_decl_kind (Expr.get_func_decl x) = OP_XOR3) let get_size (x:Sort.sort) = Z3native.get_bv_sort_size (Sort.gc x) x - let get_int (x:expr) = - match Z3native.get_numeral_int (Expr.gc x) x with - | true, v -> v - | false, _ -> raise (Error "Conversion failed.") - let numeral_to_string (x:expr) = Z3native.get_numeral_string (Expr.gc x) x let mk_const (ctx:context) (name:Symbol.symbol) (size:int) = Expr.mk_const ctx name (mk_sort ctx size) @@ -1810,8 +1815,10 @@ struct | _ -> UNKNOWN let get_model x = - let q = Z3native.solver_get_model (gc x) x in - if Z3native.is_null_model q then None else Some q + try + let q = Z3native.solver_get_model (gc x) x in + if Z3native.is_null_model q then None else Some q + with | _ -> None let get_proof x = let q = Z3native.solver_get_proof (gc x) x in @@ -1940,15 +1947,17 @@ struct let minimize (x:optimize) (e:Expr.expr) = mk_handle x (Z3native.optimize_minimize (gc x) x e) let check (x:optimize) = - let r = lbool_of_int (Z3native.optimize_check (gc x) x) in + let r = lbool_of_int (Z3native.optimize_check (gc x) x) 0 [] in match r with | L_TRUE -> Solver.SATISFIABLE | L_FALSE -> Solver.UNSATISFIABLE | _ -> Solver.UNKNOWN let get_model (x:optimize) = - let q = Z3native.optimize_get_model (gc x) x in - if Z3native.is_null_model q then None else Some q + try + let q = Z3native.optimize_get_model (gc x) x in + if Z3native.is_null_model q then None else Some q + with | _ -> None let get_lower (x:handle) = Z3native.optimize_get_lower (gc x.opt) x.opt x.h let get_upper (x:handle) = Z3native.optimize_get_upper (gc x.opt) x.opt x.h diff --git a/src/api/ml/z3.mli b/src/api/ml/z3.mli index 9b424b508..18ade29bf 100644 --- a/src/api/ml/z3.mli +++ b/src/api/ml/z3.mli @@ -1152,9 +1152,6 @@ sig (** Create a new integer sort. *) val mk_sort : context -> Sort.sort - (** Retrieve the int value. *) - val get_int : Expr.expr -> int - (** Get a big_int from an integer numeral *) val get_big_int : Expr.expr -> Big_int.big_int @@ -1543,9 +1540,6 @@ sig (** The size of a bit-vector sort. *) val get_size : Sort.sort -> int - (** Retrieve the int value. *) - val get_int : Expr.expr -> int - (** Returns a string representation of a numeral. *) val numeral_to_string : Expr.expr -> string @@ -3413,10 +3407,10 @@ sig (** Parse the given string using the SMT-LIB2 parser. @return A conjunction of assertions in the scope (up to push/pop) at the end of the string. *) - val parse_smtlib2_string : context -> string -> Symbol.symbol list -> Sort.sort list -> Symbol.symbol list -> FuncDecl.func_decl list -> Expr.expr + val parse_smtlib2_string : context -> string -> Symbol.symbol list -> Sort.sort list -> Symbol.symbol list -> FuncDecl.func_decl list -> AST.ASTVector.ast_vector (** Parse the given file using the SMT-LIB2 parser. *) - val parse_smtlib2_file : context -> string -> Symbol.symbol list -> Sort.sort list -> Symbol.symbol list -> FuncDecl.func_decl list -> Expr.expr + val parse_smtlib2_file : context -> string -> Symbol.symbol list -> Sort.sort list -> Symbol.symbol list -> FuncDecl.func_decl list -> AST.ASTVector.ast_vector end diff --git a/src/api/python/README.txt b/src/api/python/README.txt index 9831c6fc6..9312b1119 100644 --- a/src/api/python/README.txt +++ b/src/api/python/README.txt @@ -12,8 +12,8 @@ If you are using a 64-bit Python interpreter, you should use msbuild /p:configuration=external /p:platform=x64 -On Linux and OSX, you must install Z3Py, before trying example.py. -To install Z3Py on Linux and OSX, you should execute the following +On Linux and macOS, you must install Z3Py, before trying example.py. +To install Z3Py on Linux and macOS, you should execute the following command in the Z3 root directory sudo make install-z3py diff --git a/src/api/python/setup.py b/src/api/python/setup.py index bce681584..2a750fee6 100644 --- a/src/api/python/setup.py +++ b/src/api/python/setup.py @@ -73,7 +73,7 @@ def _build_z3(): if subprocess.call(['nmake'], env=build_env, cwd=BUILD_DIR) != 0: raise LibError("Unable to build Z3.") - else: # linux and osx + else: # linux and macOS if subprocess.call(['make', '-j', str(multiprocessing.cpu_count())], env=build_env, cwd=BUILD_DIR) != 0: raise LibError("Unable to build Z3.") diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 0eeeeaecc..703105356 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -1258,6 +1258,11 @@ def Consts(names, sort): names = names.split(" ") return [Const(name, sort) for name in names] +def FreshConst(sort, prefix='c'): + """Create a fresh constant of a specified sort""" + ctx = _get_ctx(sort.ctx) + return _to_expr_ref(Z3_mk_fresh_const(ctx.ref(), prefix, sort.ast), ctx) + def Var(idx, s): """Create a Z3 free variable. Free variables are used to create quantified formulas. @@ -1744,7 +1749,9 @@ class QuantifierRef(BoolRef): return Z3_get_ast_id(self.ctx_ref(), self.as_ast()) def sort(self): - """Return the Boolean sort.""" + """Return the Boolean sort or sort of Lambda.""" + if self.is_lambda(): + return _sort(self.ctx, self.as_ast()) return BoolSort(self.ctx) def is_forall(self): @@ -2176,6 +2183,8 @@ class ArithRef(ExprRef): >>> (x * y).sort() Real """ + if isinstance(other, BoolRef): + return If(other, self, 0) a, b = _coerce_exprs(self, other) return ArithRef(_mk_bin(Z3_mk_mul, a, b), self.ctx) @@ -4278,7 +4287,7 @@ def get_map_func(a): _z3_assert(is_map(a), "Z3 array map expression expected.") return FuncDeclRef(Z3_to_func_decl(a.ctx_ref(), Z3_get_decl_ast_parameter(a.ctx_ref(), a.decl().ast, 0)), a.ctx) -def ArraySort(d, r): +def ArraySort(*sig): """Return the Z3 array sort with the given domain and range sorts. >>> A = ArraySort(IntSort(), BoolSort()) @@ -4292,12 +4301,23 @@ def ArraySort(d, r): >>> AA Array(Int, Array(Int, Bool)) """ + sig = _get_args(sig) if __debug__: - _z3_assert(is_sort(d), "Z3 sort expected") - _z3_assert(is_sort(r), "Z3 sort expected") - _z3_assert(d.ctx == r.ctx, "Context mismatch") + _z3_assert(len(sig) > 1, "At least two arguments expected") + arity = len(sig) - 1 + r = sig[arity] + d = sig[0] + if __debug__: + for s in sig: + _z3_assert(is_sort(s), "Z3 sort expected") + _z3_assert(s.ctx == r.ctx, "Context mismatch") ctx = d.ctx - return ArraySortRef(Z3_mk_array_sort(ctx.ref(), d.ast, r.ast), ctx) + if len(sig) == 2: + return ArraySortRef(Z3_mk_array_sort(ctx.ref(), d.ast, r.ast), ctx) + dom = (Sort * arity)() + for i in range(arity): + dom[i] = sig[i].ast + return ArraySortRef(Z3_mk_array_sort_n(ctx.ref(), arity, dom, r.ast), ctx) def Array(name, dom, rng): """Return an array constant named `name` with the given domain and range sorts. @@ -6603,7 +6623,12 @@ class Solver(Z3PPObject): _handle_parse_error(e, self.ctx) def cube(self, vars = None): - """Get set of cubes""" + """Get set of cubes + The method takes an optional set of variables that restrict which + variables may be used as a starting point for cubing. + If vars is not None, then the first case split is based on a variable in + this set. + """ self.cube_vs = AstVector(None, self.ctx) if vars is not None: for v in vars: @@ -6619,19 +6644,15 @@ class Solver(Z3PPObject): return def cube_vars(self): + """Access the set of variables that were touched by the most recently generated cube. + This set of variables can be used as a starting point for additional cubes. + The idea is that variables that appear in clauses that are reduced by the most recent + cube are likely more useful to cube on.""" return self.cube_vs def proof(self): """Return a proof for the last `check()`. Proof construction must be enabled.""" return _to_expr_ref(Z3_solver_get_proof(self.ctx.ref(), self.solver), self.ctx) - - def from_file(self, filename): - """Parse assertions from a file""" - Z3_solver_from_file(self.ctx.ref(), self.solver, filename) - - def from_string(self, s): - """Parse assertions from a string""" - Z3_solver_from_string(self.ctx.ref(), self.solver, s) def assertions(self): """Return an AST vector containing all added constraints. @@ -6652,6 +6673,11 @@ class Solver(Z3PPObject): """ return AstVector(Z3_solver_get_units(self.ctx.ref(), self.solver), self.ctx) + def non_units(self): + """Return an AST vector containing all atomic formulas in solver state that are not units. + """ + return AstVector(Z3_solver_get_non_units(self.ctx.ref(), self.solver), self.ctx) + def statistics(self): """Return statistics for the last `check()`. @@ -7285,10 +7311,15 @@ class Optimize(Z3PPObject): """restore to previously created backtracking point""" Z3_optimize_pop(self.ctx.ref(), self.optimize) - def check(self): + def check(self, *assumptions): """Check satisfiability while optimizing objective functions.""" - return CheckSatResult(Z3_optimize_check(self.ctx.ref(), self.optimize)) - + assumptions = _get_args(assumptions) + num = len(assumptions) + _assumptions = (Ast * num)() + for i in range(num): + _assumptions[i] = assumptions[i].as_ast() + return CheckSatResult(Z3_optimize_check(self.ctx.ref(), self.optimize, num, _assumptions)) + def reason_unknown(self): """Return a string that describes why the last `check()` returned `unknown`.""" return Z3_optimize_get_reason_unknown(self.ctx.ref(), self.optimize) @@ -7300,6 +7331,9 @@ class Optimize(Z3PPObject): except Z3Exception: raise Z3Exception("model is not available") + def unsat_core(self): + return AstVector(Z3_optimize_get_unsat_core(self.ctx.ref(), self.optimize), self.ctx) + def lower(self, obj): if not isinstance(obj, OptimizeObjective): raise Z3Exception("Expecting objective handle returned by maximize/minimize") @@ -8040,7 +8074,7 @@ def substitute(t, *m): """ if isinstance(m, tuple): m1 = _get_args(m) - if isinstance(m1, list): + if isinstance(m1, list) and all(isinstance(p, tuple) for p in m1): m = m1 if __debug__: _z3_assert(is_expr(t), "Z3 expression expected") diff --git a/src/api/z3_api.h b/src/api/z3_api.h index 2657e558d..03bce5d5e 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -6121,6 +6121,14 @@ extern "C" { */ Z3_ast_vector Z3_API Z3_solver_get_units(Z3_context c, Z3_solver s); + + /** + \brief Return the set of non units in the solver state. + + def_API('Z3_solver_get_non_units', AST_VECTOR, (_in(CONTEXT), _in(SOLVER))) + */ + Z3_ast_vector Z3_API Z3_solver_get_non_units(Z3_context c, Z3_solver s); + /** \brief Check whether the assertions in a given solver are consistent or not. diff --git a/src/api/z3_optimization.h b/src/api/z3_optimization.h index f49e1b9ce..8f9e2470c 100644 --- a/src/api/z3_optimization.h +++ b/src/api/z3_optimization.h @@ -117,10 +117,12 @@ extern "C" { \brief Check consistency and produce optimal values. \param c - context \param o - optimization context + \param num_assumptions - number of additional assumptions + \param assumptions - the additional assumptions - def_API('Z3_optimize_check', INT, (_in(CONTEXT), _in(OPTIMIZE))) + def_API('Z3_optimize_check', INT, (_in(CONTEXT), _in(OPTIMIZE), _in(UINT), _in_array(2, AST))) */ - Z3_lbool Z3_API Z3_optimize_check(Z3_context c, Z3_optimize o); + Z3_lbool Z3_API Z3_optimize_check(Z3_context c, Z3_optimize o, unsigned num_assumptions, Z3_ast const assumptions[]); /** @@ -143,6 +145,14 @@ extern "C" { */ Z3_model Z3_API Z3_optimize_get_model(Z3_context c, Z3_optimize o); + /** + \brief Retrieve the unsat core for the last #Z3_optimize_chec + The unsat core is a subset of the assumptions \c a. + + def_API('Z3_optimize_get_unsat_core', AST_VECTOR, (_in(CONTEXT), _in(OPTIMIZE))) + */ + Z3_ast_vector Z3_API Z3_optimize_get_unsat_core(Z3_context c, Z3_optimize o); + /** \brief Set parameters on optimization context. diff --git a/src/ast/arith_decl_plugin.cpp b/src/ast/arith_decl_plugin.cpp index db3604a99..cdf18875c 100644 --- a/src/ast/arith_decl_plugin.cpp +++ b/src/ast/arith_decl_plugin.cpp @@ -21,6 +21,7 @@ Revision History: #include "math/polynomial/algebraic_numbers.h" #include "util/id_gen.h" #include "ast/ast_smt2_pp.h" +#include "util/gparams.h" struct arith_decl_plugin::algebraic_numbers_wrapper { unsynch_mpq_manager m_qmanager; @@ -487,7 +488,7 @@ func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters if (arity != 1 || domain[0] != m_int_decl || num_parameters != 1 || !parameters[0].is_int()) { m_manager->raise_exception("invalid divides application. Expects integer parameter and one argument of sort integer"); } - return m_manager->mk_func_decl(symbol("divides"), 1, &m_int_decl, m_manager->mk_bool_sort(), + return m_manager->mk_func_decl(symbol("divisible"), 1, &m_int_decl, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); } @@ -512,7 +513,7 @@ func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters if (num_args != 1 || m_manager->get_sort(args[0]) != m_int_decl || num_parameters != 1 || !parameters[0].is_int()) { m_manager->raise_exception("invalid divides application. Expects integer parameter and one argument of sort integer"); } - return m_manager->mk_func_decl(symbol("divides"), 1, &m_int_decl, m_manager->mk_bool_sort(), + return m_manager->mk_func_decl(symbol("divisible"), 1, &m_int_decl, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); } if (m_manager->int_real_coercions() && use_coercion(k)) { @@ -549,8 +550,9 @@ void arith_decl_plugin::get_op_names(svector& op_names, symbol con op_names.push_back(builtin_name("*",OP_MUL)); op_names.push_back(builtin_name("/",OP_DIV)); op_names.push_back(builtin_name("div",OP_IDIV)); - // clashes with user-defined functions - // op_names.push_back(builtin_name("divides",OP_IDIVIDES)); + if (gparams::get_value("smtlib2_compliant") == "true") { + op_names.push_back(builtin_name("divisible",OP_IDIVIDES)); + } op_names.push_back(builtin_name("rem",OP_REM)); op_names.push_back(builtin_name("mod",OP_MOD)); op_names.push_back(builtin_name("to_real",OP_TO_REAL)); diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 11a15492c..8750425a8 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -424,7 +424,7 @@ sort * get_sort(expr const * n) { return to_quantifier(n)->get_sort(); default: UNREACHABLE(); - return 0; + return nullptr; } } @@ -1656,6 +1656,12 @@ bool ast_manager::are_distinct(expr* a, expr* b) const { return false; } +func_decl* ast_manager::get_rec_fun_decl(quantifier* q) const { + SASSERT(is_rec_fun_def(q)); + return to_app(to_app(q->get_pattern(0))->get_arg(0))->get_decl(); +} + + void ast_manager::register_plugin(family_id id, decl_plugin * plugin) { SASSERT(m_plugins.get(id, 0) == 0); m_plugins.setx(id, plugin, 0); diff --git a/src/ast/ast.h b/src/ast/ast.h index 89df04961..c1193dfbd 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1632,6 +1632,7 @@ public: bool is_rec_fun_def(quantifier* q) const { return q->get_qid() == m_rec_fun; } bool is_lambda_def(quantifier* q) const { return q->get_qid() == m_lambda_def; } + func_decl* get_rec_fun_decl(quantifier* q) const; symbol const& rec_fun_qid() const { return m_rec_fun; } diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index 2cce53e3a..5e2828adf 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -117,7 +117,7 @@ bool smt_renaming::all_is_legal(char const* s) { } smt_renaming::smt_renaming() { - for (unsigned i = 0; i < ARRAYSIZE(m_predef_names); ++i) { + for (unsigned i = 0; i < Z3_ARRAYSIZE(m_predef_names); ++i) { symbol s(m_predef_names[i]); m_translate.insert(s, sym_b(s, false)); m_rev_translate.insert(s, s); diff --git a/src/ast/format.cpp b/src/ast/format.cpp index 40df437ea..4aeaf183d 100644 --- a/src/ast/format.cpp +++ b/src/ast/format.cpp @@ -152,21 +152,13 @@ namespace format_ns { format * mk_int(ast_manager & m, int i) { char buffer[128]; -#ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%d", i); -#else - sprintf(buffer, "%d", i); -#endif + SPRINTF_D(buffer, i); return mk_string(m, buffer); } format * mk_unsigned(ast_manager & m, unsigned u) { char buffer[128]; -#ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%u", u); -#else - sprintf(buffer, "%u", u); -#endif + SPRINTF_U(buffer, u); return mk_string(m, buffer); } diff --git a/src/ast/fpa/fpa2bv_converter.cpp b/src/ast/fpa/fpa2bv_converter.cpp index dd3f35a2a..1dc13ff9e 100644 --- a/src/ast/fpa/fpa2bv_converter.cpp +++ b/src/ast/fpa/fpa2bv_converter.cpp @@ -21,6 +21,8 @@ Notes: #include "ast/ast_smt2_pp.h" #include "ast/well_sorted.h" #include "ast/rewriter/th_rewriter.h" +#include "ast/used_vars.h" +#include "ast/rewriter/var_subst.h" #include "ast/fpa/fpa2bv_converter.h" #include "ast/rewriter/fpa_rewriter.h" @@ -230,6 +232,42 @@ void fpa2bv_converter::mk_var(unsigned base_inx, sort * srt, expr_ref & result) result = m_util.mk_fp(sgn, e, s); } +expr_ref fpa2bv_converter::extra_quantify(expr * e) +{ + used_vars uv; + unsigned nv; + + ptr_buffer new_decl_sorts; + sbuffer new_decl_names; + expr_ref_buffer subst_map(m); + + uv(e); + nv = uv.get_num_vars(); + subst_map.resize(uv.get_max_found_var_idx_plus_1()); + + if (nv == 0) + return expr_ref(e, m); + + for (unsigned i = 0; i < nv; i++) + { + if (uv.contains(i)) { + TRACE("fpa2bv", tout << "uv[" << i << "] = " << mk_ismt2_pp(uv.get(i), m) << std::endl; ); + sort * s = uv.get(i); + var * v = m.mk_var(i, s); + new_decl_sorts.push_back(s); + new_decl_names.push_back(symbol(i)); + subst_map.set(i, v); + } + } + + expr_ref res(m); + var_subst vsubst(m); + res = vsubst.operator()(e, nv, subst_map.c_ptr()); + TRACE("fpa2bv", tout << "subst'd = " << mk_ismt2_pp(res, m) << std::endl; ); + res = m.mk_forall(nv, new_decl_sorts.c_ptr(), new_decl_names.c_ptr(), res); + return res; +} + void fpa2bv_converter::mk_uf(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { TRACE("fpa2bv", tout << "UF: " << mk_ismt2_pp(f, m) << std::endl; ); @@ -252,7 +290,7 @@ void fpa2bv_converter::mk_uf(func_decl * f, unsigned num, expr * const * args, e m_bv_util.mk_extract(sbits+ebits-2, sbits-1, bv_app), m_bv_util.mk_extract(sbits-2, 0, bv_app)); new_eq = m.mk_eq(fapp, flt_app); - m_extra_assertions.push_back(new_eq); + m_extra_assertions.push_back(extra_quantify(new_eq)); result = flt_app; } else if (m_util.is_rm(rng)) { @@ -263,7 +301,7 @@ void fpa2bv_converter::mk_uf(func_decl * f, unsigned num, expr * const * args, e bv_app = m.mk_app(bv_f, num, args); flt_app = m_util.mk_bv2rm(bv_app); new_eq = m.mk_eq(fapp, flt_app); - m_extra_assertions.push_back(new_eq); + m_extra_assertions.push_back(extra_quantify(new_eq)); result = flt_app; } else diff --git a/src/ast/fpa/fpa2bv_converter.h b/src/ast/fpa/fpa2bv_converter.h index 7637317b0..812c24155 100644 --- a/src/ast/fpa/fpa2bv_converter.h +++ b/src/ast/fpa/fpa2bv_converter.h @@ -220,6 +220,8 @@ private: func_decl * mk_bv_uf(func_decl * f, sort * const * domain, sort * range); expr_ref nan_wrap(expr * n); + + expr_ref extra_quantify(expr * e); }; #endif diff --git a/src/ast/fpa/fpa2bv_rewriter.cpp b/src/ast/fpa/fpa2bv_rewriter.cpp index cc25905f0..b2614e27d 100644 --- a/src/ast/fpa/fpa2bv_rewriter.cpp +++ b/src/ast/fpa/fpa2bv_rewriter.cpp @@ -215,6 +215,12 @@ bool fpa2bv_rewriter_cfg::reduce_quantifier(quantifier * old_q, new_decl_names.push_back(symbol(name_buffer.c_str())); new_decl_sorts.push_back(m_conv.bu().mk_sort(sbits+ebits)); } + else if (m_conv.is_rm(s)) { + name_buffer.reset(); + name_buffer << n << ".bv"; + new_decl_names.push_back(symbol(name_buffer.c_str())); + new_decl_sorts.push_back(m_conv.bu().mk_sort(3)); + } else { new_decl_sorts.push_back(s); new_decl_names.push_back(n); @@ -248,6 +254,11 @@ bool fpa2bv_rewriter_cfg::reduce_var(var * t, expr_ref & result, proof_ref & res m_conv.bu().mk_extract(ebits - 1, 0, new_var), m_conv.bu().mk_extract(sbits+ebits-2, ebits, new_var)); } + else if (m_conv.is_rm(s)) { + expr_ref new_var(m()); + new_var = m().mk_var(t->get_idx(), m_conv.bu().mk_sort(3)); + new_exp = m_conv.fu().mk_bv2rm(new_var); + } else new_exp = m().mk_var(t->get_idx(), s); diff --git a/src/ast/normal_forms/defined_names.cpp b/src/ast/normal_forms/defined_names.cpp index b63c947a9..88ded842f 100644 --- a/src/ast/normal_forms/defined_names.cpp +++ b/src/ast/normal_forms/defined_names.cpp @@ -221,7 +221,7 @@ void defined_names::impl::mk_definition(expr * e, app * n, sort_ref_buffer & var args.push_back(m.mk_var(q->get_num_decls() - i - 1, q->get_decl_sort(i))); } array_util autil(m); - func_decl * f = 0; + func_decl * f = nullptr; if (autil.is_as_array(n2, f)) { n3 = m.mk_app(f, args.size()-1, args.c_ptr() + 1); } diff --git a/src/ast/normal_forms/name_exprs.cpp b/src/ast/normal_forms/name_exprs.cpp index 5e9af1c2d..bb2543b3e 100644 --- a/src/ast/normal_forms/name_exprs.cpp +++ b/src/ast/normal_forms/name_exprs.cpp @@ -127,7 +127,7 @@ class name_nested_formulas : public name_exprs_core { ast_manager & m; expr * m_root; - pred(ast_manager & m):m(m), m_root(0) {} + pred(ast_manager & m):m(m), m_root(nullptr) {} bool operator()(expr * t) override { TRACE("name_exprs", tout << "name_nested_formulas::pred:\n" << mk_ismt2_pp(t, m) << "\n";); diff --git a/src/ast/pattern/expr_pattern_match.cpp b/src/ast/pattern/expr_pattern_match.cpp index 47b3e9203..5cd3542df 100644 --- a/src/ast/pattern/expr_pattern_match.cpp +++ b/src/ast/pattern/expr_pattern_match.cpp @@ -393,10 +393,8 @@ expr_pattern_match::initialize(char const * spec_string) { VERIFY(parse_smt2_commands(ctx, is)); ctx.set_print_success(ps); - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - for (; it != end; ++it) { - compile(*it); + for (expr * e : ctx.assertions()) { + compile(e); } TRACE("expr_pattern_match", display(tout); ); } diff --git a/src/ast/proofs/proof_checker.cpp b/src/ast/proofs/proof_checker.cpp index baea760fa..08d69ff25 100644 --- a/src/ast/proofs/proof_checker.cpp +++ b/src/ast/proofs/proof_checker.cpp @@ -1248,7 +1248,7 @@ void proof_checker::dump_proof(proof const* pr) { void proof_checker::dump_proof(unsigned num_antecedents, expr * const * antecedents, expr * consequent) { char buffer[128]; #ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "proof_lemma_%d.smt2", m_proof_lemma_id); + sprintf_s(buffer, Z3_ARRAYSIZE(buffer), "proof_lemma_%d.smt2", m_proof_lemma_id); #else sprintf(buffer, "proof_lemma_%d.smt2", m_proof_lemma_id); #endif diff --git a/src/ast/rewriter/pb2bv_rewriter.cpp b/src/ast/rewriter/pb2bv_rewriter.cpp index d1def83a1..0faf08515 100644 --- a/src/ast/rewriter/pb2bv_rewriter.cpp +++ b/src/ast/rewriter/pb2bv_rewriter.cpp @@ -458,7 +458,7 @@ struct pb2bv_rewriter::imp { result = m.mk_true(); expr_ref_vector carry(m), new_carry(m); m_base.push_back(bound + rational::one()); - for (rational b_i : m_base) { + for (const rational& b_i : m_base) { unsigned B = b_i.get_unsigned(); unsigned d_i = (bound % b_i).get_unsigned(); bound = div(bound, b_i); diff --git a/src/ast/seq_decl_plugin.cpp b/src/ast/seq_decl_plugin.cpp index 07c6fd06a..06f30cbdf 100644 --- a/src/ast/seq_decl_plugin.cpp +++ b/src/ast/seq_decl_plugin.cpp @@ -842,7 +842,9 @@ void seq_decl_plugin::get_sort_names(svector & sort_names, symbol } app* seq_decl_plugin::mk_string(symbol const& s) { - parameter param(s); + zstring canonStr(s.bare_str()); + symbol canonSym(canonStr.encode().c_str()); + parameter param(canonSym); func_decl* f = m_manager->mk_const_decl(m_stringc_sym, m_string, func_decl_info(m_family_id, OP_STRING_CONST, 1, ¶m)); return m_manager->mk_const(f); diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index e65eb1b32..ea5994ece 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -17,7 +17,7 @@ Notes: --*/ #include "util/gparams.h" #include "util/env_params.h" -#include "util/version.h" +#include "util/z3_version.h" #include "ast/ast_smt_pp.h" #include "ast/ast_smt2_pp.h" #include "ast/ast_pp_dot.h" diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index da495968a..a1c47cdce 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -1315,7 +1315,7 @@ void cmd_context::assert_expr(expr * t) { m().inc_ref(t); m_assertions.push_back(t); if (produce_unsat_cores()) - m_assertion_names.push_back(0); + m_assertion_names.push_back(nullptr); if (m_solver) m_solver->assert_expr(t); } @@ -1491,13 +1491,24 @@ void cmd_context::check_sat(unsigned num_assumptions, expr * const * assumptions scoped_ctrl_c ctrlc(eh); scoped_timer timer(timeout, &eh); scoped_rlimit _rlimit(m().limit(), rlimit); + expr_ref_vector asms(m()); + asms.append(num_assumptions, assumptions); if (!m_processing_pareto) { - ptr_vector cnstr(m_assertions); - cnstr.append(num_assumptions, assumptions); - get_opt()->set_hard_constraints(cnstr); + expr_ref_vector assertions(m()); + unsigned sz = m_assertions.size(); + for (unsigned i = 0; i < sz; ++i) { + if (m_assertion_names.size() > i && m_assertion_names[i]) { + asms.push_back(m_assertion_names[i]); + assertions.push_back(m().mk_implies(m_assertion_names[i], m_assertions[i])); + } + else { + assertions.push_back(m_assertions[i]); + } + } + get_opt()->set_hard_constraints(assertions); } try { - r = get_opt()->optimize(); + r = get_opt()->optimize(asms); if (r == l_true && get_opt()->is_pareto()) { m_processing_pareto = true; } @@ -1805,11 +1816,8 @@ void cmd_context::validate_model() { cancel_eh eh(m().limit()); expr_ref r(m()); scoped_ctrl_c ctrlc(eh); - ptr_vector::const_iterator it = begin_assertions(); - ptr_vector::const_iterator end = end_assertions(); bool invalid_model = false; - for (; it != end; ++it) { - expr * a = *it; + for (expr * a : assertions()) { if (is_ground(a)) { r = nullptr; evaluator(a, r); diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index a0093191d..cb49d1825 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -148,8 +148,8 @@ public: virtual bool empty() = 0; virtual void push() = 0; virtual void pop(unsigned n) = 0; - virtual lbool optimize() = 0; - virtual void set_hard_constraints(ptr_vector & hard) = 0; + virtual lbool optimize(expr_ref_vector const& asms) = 0; + virtual void set_hard_constraints(expr_ref_vector const & hard) = 0; virtual void display_assignment(std::ostream& out) = 0; virtual bool is_pareto() = 0; virtual void set_logic(symbol const& s) = 0; @@ -452,11 +452,8 @@ public: double get_seconds() const { return m_watch.get_seconds(); } - ptr_vector::const_iterator begin_assertions() const { return m_assertions.begin(); } - ptr_vector::const_iterator end_assertions() const { return m_assertions.end(); } - - ptr_vector::const_iterator begin_assertion_names() const { return m_assertion_names.begin(); } - ptr_vector::const_iterator end_assertion_names() const { return m_assertion_names.end(); } + ptr_vector const& assertions() const { return m_assertions; } + ptr_vector const& assertion_names() const { return m_assertion_names; } /** \brief Hack: consume assertions if there are no scopes. diff --git a/src/cmd_context/cmd_context_to_goal.cpp b/src/cmd_context/cmd_context_to_goal.cpp index a66f9e5de..de19805f2 100644 --- a/src/cmd_context/cmd_context_to_goal.cpp +++ b/src/cmd_context/cmd_context_to_goal.cpp @@ -28,20 +28,18 @@ void assert_exprs_from(cmd_context const & ctx, goal & t) { ast_manager & m = t.m(); bool proofs_enabled = t.proofs_enabled(); if (ctx.produce_unsat_cores()) { - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - ptr_vector::const_iterator it2 = ctx.begin_assertion_names(); - SASSERT(end - it == ctx.end_assertion_names() - it2); + ptr_vector::const_iterator it = ctx.assertions().begin(); + ptr_vector::const_iterator end = ctx.assertions().end(); + ptr_vector::const_iterator it2 = ctx.assertion_names().begin(); + SASSERT(ctx.assertions().size() == ctx.assertion_names().size()); for (; it != end; ++it, ++it2) { t.assert_expr(*it, proofs_enabled ? m.mk_asserted(*it) : nullptr, m.mk_leaf(*it2)); } } else { - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - for (; it != end; ++it) { - t.assert_expr(*it, proofs_enabled ? m.mk_asserted(*it) : nullptr, nullptr); + for (expr * e : ctx.assertions()) { + t.assert_expr(e, proofs_enabled ? m.mk_asserted(e) : nullptr, nullptr); } - SASSERT(ctx.begin_assertion_names() == ctx.end_assertion_names()); + SASSERT(ctx.assertion_names().empty()); } } diff --git a/src/cmd_context/pdecl.h b/src/cmd_context/pdecl.h index 12e6399fe..d994a1e8f 100644 --- a/src/cmd_context/pdecl.h +++ b/src/cmd_context/pdecl.h @@ -53,7 +53,7 @@ public: class psort_inst_cache; #if defined(__APPLE__) && defined(__MACH__) -// CMW: for some unknown reason, llvm on OSX does not like the name `psort' +// CMW: for some unknown reason, llvm on macOS does not like the name `psort' #define psort Z3_psort #endif diff --git a/src/math/polynomial/polynomial.cpp b/src/math/polynomial/polynomial.cpp index 35a95ce82..24658fdcf 100644 --- a/src/math/polynomial/polynomial.cpp +++ b/src/math/polynomial/polynomial.cpp @@ -100,7 +100,7 @@ namespace polynomial { struct lt_var { bool operator()(power const & p1, power const & p2) { - // CMW: The assertion below does not hold on OSX, because + // CMW: The assertion below does not hold on macOS, because // their implementation of std::sort will try to compare // two items at the same index instead of comparing // the indices directly. I suspect that the purpose of diff --git a/src/model/model.cpp b/src/model/model.cpp index 83adfc87e..a1fdfc980 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -351,7 +351,7 @@ bool model::can_inline_def(top_sort& ts, func_decl* f) { expr_ref model::cleanup_expr(top_sort& ts, expr* e, unsigned current_partition) { - if (!e) return expr_ref(0, m); + if (!e) return expr_ref(nullptr, m); TRACE("model", tout << "cleaning up:\n" << mk_pp(e, m) << "\n";); @@ -453,7 +453,7 @@ void model::remove_decls(ptr_vector & decls, func_decl_set const & s) expr_ref model::get_inlined_const_interp(func_decl* f) { expr* v = get_const_interp(f); - if (!v) return expr_ref(0, m); + if (!v) return expr_ref(nullptr, m); top_sort st(m); expr_ref result1(v, m); expr_ref result2 = cleanup_expr(st, v, UINT_MAX); diff --git a/src/muz/base/dl_rule_set.cpp b/src/muz/base/dl_rule_set.cpp index 5114fdca4..80af80266 100644 --- a/src/muz/base/dl_rule_set.cpp +++ b/src/muz/base/dl_rule_set.cpp @@ -31,7 +31,7 @@ namespace datalog { rule_dependencies::rule_dependencies(const rule_dependencies & o, bool reversed): m_context(o.m_context) { if (reversed) { - for (auto & kv : o) { + for (auto & kv : o) { func_decl * pred = kv.m_key; item_set & orig_items = *kv.get_value(); @@ -114,8 +114,8 @@ namespace datalog { app* a = to_app(e); d = a->get_decl(); if (m_context.is_predicate(d)) { - // insert d and ensure the invariant - // that every predicate is present as + // insert d and ensure the invariant + // that every predicate is present as // a key in m_data s.insert(d); ensure_key(d); @@ -148,7 +148,7 @@ namespace datalog { item_set& itms = *kv.get_value(); set_intersection(itms, allowed); } - for (func_decl* f : to_remove) + for (func_decl* f : to_remove) remove_m_data_entry(f); } @@ -253,18 +253,18 @@ namespace datalog { // // ----------------------------------- - rule_set::rule_set(context & ctx) - : m_context(ctx), - m_rule_manager(ctx.get_rule_manager()), - m_rules(m_rule_manager), + rule_set::rule_set(context & ctx) + : m_context(ctx), + m_rule_manager(ctx.get_rule_manager()), + m_rules(m_rule_manager), m_deps(ctx), m_stratifier(nullptr), m_refs(ctx.get_manager()) { } - rule_set::rule_set(const rule_set & other) - : m_context(other.m_context), - m_rule_manager(other.m_rule_manager), + rule_set::rule_set(const rule_set & other) + : m_context(other.m_context), + m_rule_manager(other.m_rule_manager), m_rules(m_rule_manager), m_deps(other.m_context), m_stratifier(nullptr), @@ -353,10 +353,27 @@ namespace datalog { break; \ } \ } \ - + DEL_VECTOR(*rules); DEL_VECTOR(m_rules); - } + } + + void rule_set::replace_rule(rule * r, rule * other) { + TRACE("dl", r->display(m_context, tout << "replace:");); + func_decl* d = r->get_decl(); + rule_vector* rules = m_head2rules.find(d); +#define REPLACE_VECTOR(_v) \ + for (unsigned i = (_v).size(); i > 0; ) { \ + --i; \ + if ((_v)[i] == r) { \ + (_v)[i] = other; \ + break; \ + } \ + } \ + + REPLACE_VECTOR(*rules); + REPLACE_VECTOR(m_rules); + } void rule_set::ensure_closed() { if (!is_closed()) { @@ -365,7 +382,7 @@ namespace datalog { } bool rule_set::close() { - SASSERT(!is_closed()); //the rule_set is not already closed + SASSERT(!is_closed()); //the rule_set is not already closed m_deps.populate(*this); m_stratifier = alloc(rule_stratifier, m_deps); if (!stratified_negation()) { @@ -426,7 +443,7 @@ namespace datalog { inherit_predicates(src); } - const rule_vector & rule_set::get_predicate_rules(func_decl * pred) const { + const rule_vector & rule_set::get_predicate_rules(func_decl * pred) const { decl2rules::obj_map_entry * e = m_head2rules.find_core(pred); if (!e) { return m_empty_rule_vector; @@ -519,7 +536,7 @@ namespace datalog { out << "\n"; non_empty = false; } - + for (func_decl * first : *strat) { const func_decl_set & deps = m_deps.get_deps(first); for (func_decl * dep : deps) { @@ -545,8 +562,8 @@ namespace datalog { unsigned rule_stratifier::get_predicate_strat(func_decl * pred) const { unsigned num; if (!m_pred_strat_nums.find(pred, num)) { - //the number of the predicate is not stored, therefore it did not appear - //in the algorithm and therefore it does not depend on anything and nothing + //the number of the predicate is not stored, therefore it did not appear + //in the algorithm and therefore it does not depend on anything and nothing //depends on it. So it is safe to assign zero strate to it, although it is //not strictly true. num = 0; @@ -641,7 +658,7 @@ namespace datalog { } - // We put components whose indegree is zero to m_strats and assign its + // We put components whose indegree is zero to m_strats and assign its // m_components entry to zero. unsigned comp_cnt = m_components.size(); for (unsigned i = 0; i < comp_cnt; i++) { @@ -680,7 +697,7 @@ namespace datalog { strats_index++; } //we have managed to topologicaly order all the components - SASSERT(std::find_if(m_components.begin(), m_components.end(), + SASSERT(std::find_if(m_components.begin(), m_components.end(), std::bind1st(std::not_equal_to(), (item_set*)0)) == m_components.end()); //reverse the strats array, so that the only the later components would depend on earlier ones @@ -713,7 +730,7 @@ namespace datalog { } out << "\n"; } - + } }; diff --git a/src/muz/base/dl_rule_set.h b/src/muz/base/dl_rule_set.h index 736dd8888..e870e369c 100644 --- a/src/muz/base/dl_rule_set.h +++ b/src/muz/base/dl_rule_set.h @@ -77,7 +77,7 @@ namespace datalog { \brief Number of predicates that depend on \c f. */ unsigned out_degree(func_decl * f) const; - + /** \brief If the rependency graph is acyclic, put all elements into \c res ordered so that elements can depend only on elements that are before them. @@ -131,7 +131,7 @@ namespace datalog { it must exist for the whole lifetime of the \c stratifier object. */ rule_stratifier(const rule_dependencies & deps) - : m_deps(deps), m_next_preorder(0) + : m_deps(deps), m_next_preorder(0) { process(); } @@ -145,7 +145,7 @@ namespace datalog { const comp_vector & get_strats() const { return m_strats; } unsigned get_predicate_strat(func_decl * pred) const; - + void display( std::ostream & out ) const; }; @@ -203,6 +203,10 @@ namespace datalog { \brief Remove rule \c r from the rule set. */ void del_rule(rule * r); + /** + \brief Replace a rule \c r with the rule \c other + */ + void replace_rule(rule * r, rule * other); /** \brief Add all rules from a different rule_set. @@ -276,8 +280,7 @@ namespace datalog { inline std::ostream& operator<<(std::ostream& out, rule_set const& r) { r.display(out); return out; } - + }; #endif /* DL_RULE_SET_H_ */ - diff --git a/src/muz/base/dl_util.h b/src/muz/base/dl_util.h index 1a2b64dc4..c6e214f79 100644 --- a/src/muz/base/dl_util.h +++ b/src/muz/base/dl_util.h @@ -276,7 +276,7 @@ namespace datalog { } unsigned n = container.size(); unsigned ofs = 1; - int r_i = 1; + unsigned r_i = 1; for(unsigned i=removed_cols[0]+1; idlctx(); - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - for (; it != end; ++it) { - dlctx.assert_expr(*it); + for (expr * e : ctx.assertions()) { + dlctx.assert_expr(e); } } diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index f0e86f1a5..30b68ebf6 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -493,7 +493,8 @@ lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) : m_pob(nullptr), m_ctp(nullptr), m_lvl(lvl), m_init_lvl(m_lvl), m_bumped(0), m_weakness(WEAKNESS_MAX), - m_external(false), m_blocked(false) { + m_external(false), m_blocked(false), + m_background(false) { SASSERT(m_body); normalize(m_body, m_body); } @@ -505,7 +506,8 @@ lemma::lemma(pob_ref const &p) : m_pob(p), m_ctp(nullptr), m_lvl(p->level()), m_init_lvl(m_lvl), m_bumped(0), m_weakness(p->weakness()), - m_external(false), m_blocked(false) { + m_external(false), m_blocked(false), + m_background(false) { SASSERT(m_pob); m_pob->get_skolems(m_zks); add_binding(m_pob->get_binding()); @@ -519,8 +521,8 @@ lemma::lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl) : m_pob(p), m_ctp(nullptr), m_lvl(p->level()), m_init_lvl(m_lvl), m_bumped(0), m_weakness(p->weakness()), - m_external(false), m_blocked(false) -{ + m_external(false), m_blocked(false), + m_background(false) { if (m_pob) { m_pob->get_skolems(m_zks); add_binding(m_pob->get_binding()); @@ -921,10 +923,10 @@ void pred_transformer::simplify_formulas() {m_frames.simplify_formulas ();} -expr_ref pred_transformer::get_formulas(unsigned level) const +expr_ref pred_transformer::get_formulas(unsigned level, bool bg) const { expr_ref_vector res(m); - m_frames.get_frame_geq_lemmas (level, res); + m_frames.get_frame_geq_lemmas (level, res, bg); return mk_and(res); } @@ -935,6 +937,7 @@ bool pred_transformer::propagate_to_next_level (unsigned src_level) /// \brief adds a lemma to the solver and to child solvers void pred_transformer::add_lemma_core(lemma* lemma, bool ground_only) { + SASSERT(!lemma->is_background()); unsigned lvl = lemma->level(); expr* l = lemma->get_expr(); SASSERT(!lemma->is_ground() || is_clause(m, l)); @@ -975,8 +978,9 @@ void pred_transformer::add_lemma_core(lemma* lemma, bool ground_only) next_level(lvl), ground_only); } } -bool pred_transformer::add_lemma (expr *e, unsigned lvl) { +bool pred_transformer::add_lemma (expr *e, unsigned lvl, bool bg) { lemma_ref lem = alloc(lemma, m, e, lvl); + lem->set_background(bg); return m_frames.add_lemma(lem.get()); } @@ -1217,15 +1221,18 @@ expr_ref pred_transformer::get_origin_summary (model &mdl, } -void pred_transformer::add_cover(unsigned level, expr* property) +void pred_transformer::add_cover(unsigned level, expr* property, bool bg) { + SASSERT(!bg || is_infty_level(level)); // replace bound variables by local constants. expr_ref result(property, m), v(m), c(m); expr_substitution sub(m); + proof_ref pr(m); + pr = m.mk_asserted(m.mk_true()); for (unsigned i = 0; i < sig_size(); ++i) { c = m.mk_const(pm.o2n(sig(i), 0)); v = m.mk_var(i, sig(i)->get_range()); - sub.insert(v, c); + sub.insert(v, c, pr); } scoped_ptr rep = mk_default_expr_replacer(m); rep->set_substitution(&sub); @@ -1236,13 +1243,38 @@ void pred_transformer::add_cover(unsigned level, expr* property) expr_ref_vector lemmas(m); flatten_and(result, lemmas); for (unsigned i = 0, sz = lemmas.size(); i < sz; ++i) { - add_lemma(lemmas.get(i), level); + add_lemma(lemmas.get(i), level, bg); } } void pred_transformer::propagate_to_infinity (unsigned level) {m_frames.propagate_to_infinity (level);} +// compute a conjunction of all background facts +void pred_transformer::get_pred_bg_invs(expr_ref_vector& out) { + expr_ref inv(m), tmp1(m), tmp2(m); + ptr_vector preds; + for (auto kv : m_pt_rules) { + expr* tag = kv.m_value->tag(); + datalog::rule const &r = kv.m_value->rule(); + find_predecessors (r, preds); + + for (unsigned i = 0, preds_sz = preds.size(); i < preds_sz; i++) { + func_decl* pre = preds[i]; + pred_transformer &pt = ctx.get_pred_transformer(pre); + const lemma_ref_vector &invs = pt.get_bg_invs(); + CTRACE("spacer", !invs.empty(), + tout << "add-bg-invariant: " << mk_pp (pre, m) << "\n";); + for (auto inv : invs) { + // tag -> inv1 ... tag -> invn + tmp1 = m.mk_implies(tag, inv->get_expr()); + pm.formula_n2o(tmp1, tmp2, i); + out.push_back(tmp2); + TRACE("spacer", tout << tmp2 << "\n";); + } + } + } +} /// \brief Returns true if the obligation is already blocked by current lemmas @@ -1293,7 +1325,7 @@ bool pred_transformer::is_qblocked (pob &n) { // assert cti s->assert_expr(n.post()); - lbool res = s->check_sat(0, 0); + lbool res = s->check_sat(0, nullptr); // if (res == l_false) { // expr_ref_vector core(m); @@ -1344,6 +1376,14 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, expr_ref_vector post (m), reach_assumps (m); post.push_back (n.post ()); + flatten_and(post); + + // if equality propagation is disabled in arithmetic, expand + // equality literals into two inequalities to increase the space + // for interpolation + if (!ctx.use_eq_prop()) { + expand_literals(m, post); + } // populate reach_assumps if (n.level () > 0 && !m_all_init) { @@ -1472,7 +1512,7 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem, expr_ref lemma_expr(m); lemma_expr = lem->get_expr(); - expr_ref_vector conj(m), aux(m); + expr_ref_vector cand(m), aux(m), conj(m); expr_ref gnd_lemma(m); @@ -1482,8 +1522,8 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem, lemma_expr = gnd_lemma.get(); } - conj.push_back(mk_not(m, lemma_expr)); - flatten_and (conj); + cand.push_back(mk_not(m, lemma_expr)); + flatten_and (cand); prop_solver::scoped_level _sl(*m_solver, level); prop_solver::scoped_subset_core _sc (*m_solver, true); @@ -1494,9 +1534,12 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem, if (ctx.use_ctp()) {mdl_ref_ptr = &mdl;} m_solver->set_core(core); m_solver->set_model(mdl_ref_ptr); - expr * bg = m_extend_lit.get (); - lbool r = m_solver->check_assumptions (conj, aux, m_transition_clause, - 1, &bg, 1); + + conj.push_back(m_extend_lit); + if (ctx.use_bg_invs()) get_pred_bg_invs(conj); + + lbool r = m_solver->check_assumptions (cand, aux, m_transition_clause, + conj.size(), conj.c_ptr(), 1); if (r == l_false) { solver_level = m_solver->uses_level (); lem->reset_ctp(); @@ -1527,6 +1570,7 @@ bool pred_transformer::check_inductive(unsigned level, expr_ref_vector& state, m_solver->set_core(&core); m_solver->set_model (nullptr); expr_ref_vector aux (m); + if (ctx.use_bg_invs()) get_pred_bg_invs(conj); conj.push_back (m_extend_lit); lbool res = m_solver->check_assumptions (state, aux, m_transition_clause, @@ -1941,14 +1985,27 @@ void pred_transformer::update_solver_with_rfs(prop_solver *solver, } /// pred_transformer::frames - - bool pred_transformer::frames::add_lemma(lemma *new_lemma) { TRACE("spacer", tout << "add-lemma: " << pp_level(new_lemma->level()) << " " << m_pt.head()->get_name() << " " << mk_pp(new_lemma->get_expr(), m_pt.get_ast_manager()) << "\n";); + if (new_lemma->is_background()) { + SASSERT (is_infty_level(new_lemma->level())); + + for (auto &l : m_bg_invs) { + if (l->get_expr() == new_lemma->get_expr()) return false; + } + TRACE("spacer", tout << "add-external-lemma: " + << pp_level(new_lemma->level()) << " " + << m_pt.head()->get_name() << " " + << mk_pp(new_lemma->get_expr(), m_pt.get_ast_manager()) << "\n";); + + m_bg_invs.push_back(new_lemma); + return true; + } + unsigned i = 0; for (auto *old_lemma : m_lemmas) { if (old_lemma->get_expr() == new_lemma->get_expr()) { @@ -2295,6 +2352,7 @@ void context::updt_params() { m_use_restarts = m_params.spacer_restarts(); m_restart_initial_threshold = m_params.spacer_restart_initial_threshold(); m_pdr_bfs = m_params.spacer_gpdr_bfs(); + m_use_bg_invs = m_params.spacer_use_bg_invs(); if (m_use_gpdr) { // set options to be compatible with GPDR @@ -2423,36 +2481,38 @@ expr_ref context::get_cover_delta(int level, func_decl* p_orig, func_decl* p) if (m_rels.find(p, pt)) { return pt->get_cover_delta(p_orig, level); } else { - IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); + IF_VERBOSE(10, verbose_stream() << "did not find predicate " + << p->get_name() << "\n";); return expr_ref(m.mk_true(), m); } } -void context::add_cover(int level, func_decl* p, expr* property) +void context::add_cover(int level, func_decl* p, expr* property, bool bg) { + scoped_proof _pf_(m); + pred_transformer* pt = nullptr; if (!m_rels.find(p, pt)) { pt = alloc(pred_transformer, *this, get_manager(), p); m_rels.insert(p, pt); - IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); + IF_VERBOSE(10, verbose_stream() << "did not find predicate " + << p->get_name() << "\n";); } unsigned lvl = (level == -1)?infty_level():((unsigned)level); - pt->add_cover(lvl, property); + pt->add_cover(lvl, property, bg); } void context::add_invariant (func_decl *p, expr *property) -{add_cover (infty_level(), p, property);} +{add_cover (infty_level(), p, property, true);} -expr_ref context::get_reachable(func_decl *p) -{ +expr_ref context::get_reachable(func_decl *p) { pred_transformer* pt = nullptr; if (!m_rels.find(p, pt)) { return expr_ref(m.mk_false(), m); } return pt->get_reachable(); } -bool context::validate() -{ +bool context::validate() { if (!m_validate_result) { return true; } std::stringstream msg; @@ -2483,7 +2543,7 @@ bool context::validate() model_ref model; vector rs; model_converter_ref mc; - get_level_property(m_inductive_lvl, refs, rs); + get_level_property(m_inductive_lvl, refs, rs, use_bg_invs()); inductive_property ex(m, mc, rs); ex.to_model(model); var_subst vs(m, false); @@ -2624,13 +2684,13 @@ void context::init_lemma_generalizers() } void context::get_level_property(unsigned lvl, expr_ref_vector& res, - vector& rs) const { + vector& rs, bool with_bg) const { for (auto const& kv : m_rels) { pred_transformer* r = kv.m_value; if (r->head() == m_query_pred) { continue; } - expr_ref conj = r->get_formulas(lvl); + expr_ref conj = r->get_formulas(lvl, with_bg); m_pm.formula_n2o(0, false, conj); res.push_back(conj); ptr_vector sig(r->head()->get_arity(), r->sig()); @@ -2662,7 +2722,7 @@ lbool context::solve(unsigned from_lvl) IF_VERBOSE(1, { expr_ref_vector refs(m); vector rs; - get_level_property(m_inductive_lvl, refs, rs); + get_level_property(m_inductive_lvl, refs, rs, use_bg_invs()); model_converter_ref mc; inductive_property ex(m, mc, rs); verbose_stream() << ex.to_string(); @@ -2844,7 +2904,7 @@ model_ref context::get_model() model_ref model; expr_ref_vector refs(m); vector rs; - get_level_property(m_inductive_lvl, refs, rs); + get_level_property(m_inductive_lvl, refs, rs, use_bg_invs()); inductive_property ex(m, const_cast(m_mc), rs); ex.to_model (model); return model; @@ -2877,7 +2937,7 @@ expr_ref context::mk_unsat_answer() const { expr_ref_vector refs(m); vector rs; - get_level_property(m_inductive_lvl, refs, rs); + get_level_property(m_inductive_lvl, refs, rs, use_bg_invs()); inductive_property ex(m, const_cast(m_mc), rs); return ex.to_expr(); } diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 48c27f96d..0d8b2daf6 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -128,8 +128,9 @@ class lemma { unsigned m_init_lvl; // level at which lemma was created unsigned m_bumped:16; unsigned m_weakness:16; - unsigned m_external:1; - unsigned m_blocked:1; + unsigned m_external:1; // external lemma from another solver + unsigned m_blocked:1; // blocked by CTP + unsigned m_background:1; // background assumed fact void mk_expr_core(); void mk_cube_core(); @@ -163,6 +164,9 @@ public: void set_external(bool ext){m_external = ext;} bool external() { return m_external;} + void set_background(bool v) {m_background = v;} + bool is_background() {return m_background;} + bool is_blocked() {return m_blocked;} void set_blocked(bool v) {m_blocked=v;} @@ -222,6 +226,7 @@ class pred_transformer { pred_transformer &m_pt; // parent pred_transformer lemma_ref_vector m_pinned_lemmas; // all created lemmas lemma_ref_vector m_lemmas; // active lemmas + lemma_ref_vector m_bg_invs; // background (assumed) invariants unsigned m_size; // num of frames bool m_sorted; // true if m_lemmas is sorted by m_lt @@ -230,7 +235,8 @@ class pred_transformer { void sort (); public: - frames (pred_transformer &pt) : m_pt (pt), m_size(0), m_sorted (true) {} + frames (pred_transformer &pt) : m_pt (pt), + m_size(0), m_sorted (true) {} ~frames() {} void simplify_formulas (); @@ -245,17 +251,25 @@ class pred_transformer { } } } - void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out) const { + void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out, + bool with_bg = false) const { for (auto &lemma : m_lemmas) { if (lemma->level() >= level) { out.push_back(lemma->get_expr()); } } + if (with_bg) { + for (auto &lemma : m_bg_invs) + out.push_back(lemma->get_expr()); + } } - unsigned size () const {return m_size;} - unsigned lemma_size () const {return m_lemmas.size ();} - void add_frame () {m_size++;} + const lemma_ref_vector& get_bg_invs() const {return m_bg_invs;} + unsigned size() const {return m_size;} + unsigned lemma_size() const {return m_lemmas.size ();} + unsigned bg_invs_size() const {return m_bg_invs.size();} + + void add_frame() {m_size++;} void inherit_frames (frames &other) { for (auto &other_lemma : other.m_lemmas) { lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), @@ -265,6 +279,7 @@ class pred_transformer { add_lemma(new_lemma.get()); } m_sorted = false; + m_bg_invs.append(other.m_bg_invs); } bool add_lemma (lemma *new_lemma); @@ -418,6 +433,11 @@ class pred_transformer { app_ref mk_fresh_rf_tag (); + // get tagged formulae of all of the background invariants for all of the + // predecessors of the current transformer + void get_pred_bg_invs(expr_ref_vector &out); + const lemma_ref_vector &get_bg_invs() const {return m_frames.get_bg_invs();} + public: pred_transformer(context& ctx, manager& pm, func_decl* head); ~pred_transformer() {} @@ -448,7 +468,7 @@ public: } unsigned get_num_levels() const {return m_frames.size ();} expr_ref get_cover_delta(func_decl* p_orig, int level); - void add_cover(unsigned level, expr* property); + void add_cover(unsigned level, expr* property, bool bg = false); expr_ref get_reachable(); std::ostream& display(std::ostream& strm) const; @@ -484,7 +504,7 @@ public: bool propagate_to_next_level(unsigned level); void propagate_to_infinity(unsigned level); /// \brief Add a lemma to the current context and all users - bool add_lemma(expr * lemma, unsigned lvl); + bool add_lemma(expr * e, unsigned lvl, bool bg); bool add_lemma(lemma* lem) {return m_frames.add_lemma(lem);} expr* get_reach_case_var (unsigned idx) const; bool has_rfs () const { return !m_reach_facts.empty () ;} @@ -527,7 +547,7 @@ public: bool check_inductive(unsigned level, expr_ref_vector& state, unsigned& assumes_level, unsigned weakness = UINT_MAX); - expr_ref get_formulas(unsigned level) const; + expr_ref get_formulas(unsigned level, bool bg = false) const; void simplify_formulas(); @@ -958,6 +978,7 @@ class context { bool m_simplify_formulas_pre; bool m_simplify_formulas_post; bool m_pdr_bfs; + bool m_use_bg_invs; unsigned m_push_pob_max_depth; unsigned m_max_level; unsigned m_restart_initial_threshold; @@ -992,7 +1013,8 @@ class context { // Generate inductive property void get_level_property(unsigned lvl, expr_ref_vector& res, - vector & rs) const; + vector & rs, + bool with_bg = false) const; // Initialization @@ -1027,18 +1049,20 @@ public: const fp_params &get_params() const { return m_params; } - bool use_native_mbp () {return m_use_native_mbp;} - bool use_ground_pob () {return m_ground_pob;} - bool use_instantiate () {return m_instantiate;} - bool weak_abs() {return m_weak_abs;} - bool use_qlemmas () {return m_use_qlemmas;} - bool use_euf_gen() {return m_use_euf_gen;} - bool simplify_pob() {return m_simplify_pob;} - bool use_ctp() {return m_use_ctp;} - bool use_inc_clause() {return m_use_inc_clause;} - unsigned blast_term_ite_inflation() {return m_blast_term_ite_inflation;} - bool elim_aux() {return m_elim_aux;} - bool reach_dnf() {return m_reach_dnf;} + bool use_eq_prop() const {return m_use_eq_prop;} + bool use_native_mbp() const {return m_use_native_mbp;} + bool use_ground_pob() const {return m_ground_pob;} + bool use_instantiate() const {return m_instantiate;} + bool weak_abs() const {return m_weak_abs;} + bool use_qlemmas() const {return m_use_qlemmas;} + bool use_euf_gen() const {return m_use_euf_gen;} + bool simplify_pob() const {return m_simplify_pob;} + bool use_ctp() const {return m_use_ctp;} + bool use_inc_clause() const {return m_use_inc_clause;} + unsigned blast_term_ite_inflation() const {return m_blast_term_ite_inflation;} + bool elim_aux() const {return m_elim_aux;} + bool reach_dnf() const {return m_reach_dnf;} + bool use_bg_invs() const {return m_use_bg_invs;} ast_manager& get_ast_manager() const {return m;} manager& get_manager() {return m_pm;} @@ -1081,7 +1105,7 @@ public: unsigned get_num_levels(func_decl* p); expr_ref get_cover_delta(int level, func_decl* p_orig, func_decl* p); - void add_cover(int level, func_decl* pred, expr* property); + void add_cover(int level, func_decl* pred, expr* property, bool bg = false); expr_ref get_reachable (func_decl* p); void add_invariant (func_decl *pred, expr* property); model_ref get_model(); diff --git a/src/muz/spacer/spacer_generalizers.cpp b/src/muz/spacer/spacer_generalizers.cpp index b0fb6c2d3..6f9758337 100644 --- a/src/muz/spacer/spacer_generalizers.cpp +++ b/src/muz/spacer/spacer_generalizers.cpp @@ -50,7 +50,7 @@ namespace{ contains_array_op_proc(ast_manager &manager) : m(manager), m_array_fid(m.mk_family_id("array")) {} - virtual bool operator()(expr *e) { + bool operator()(expr *e) override { return is_app(e) && to_app(e)->get_family_id() == m_array_fid; } }; diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index 45ae472bd..e83cc1bc9 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -117,11 +117,11 @@ class lemma_quantifier_generalizer : public lemma_generalizer { int m_offset; public: lemma_quantifier_generalizer(context &ctx, bool normalize_cube = true); - virtual ~lemma_quantifier_generalizer() {} - virtual void operator()(lemma_ref &lemma); + ~lemma_quantifier_generalizer() override {} + void operator()(lemma_ref &lemma) override; - virtual void collect_statistics(statistics& st) const; - virtual void reset_statistics() {m_st.reset();} + void collect_statistics(statistics& st) const override; + void reset_statistics() override {m_st.reset();} private: bool generalize(lemma_ref &lemma, app *term); diff --git a/src/muz/spacer/spacer_proof_utils.cpp b/src/muz/spacer/spacer_proof_utils.cpp index ed02513f1..a73d24d49 100644 --- a/src/muz/spacer/spacer_proof_utils.cpp +++ b/src/muz/spacer/spacer_proof_utils.cpp @@ -600,7 +600,7 @@ namespace spacer { proof* hypothesis_reducer::reduce_core(proof* pf) { SASSERT(m.is_false(m.get_fact(pf))); - proof *res = NULL; + proof *res = nullptr; ptr_vector todo; todo.push_back(pf); diff --git a/src/muz/spacer/spacer_prop_solver.cpp b/src/muz/spacer/spacer_prop_solver.cpp index 004ea6754..4899ad692 100644 --- a/src/muz/spacer/spacer_prop_solver.cpp +++ b/src/muz/spacer/spacer_prop_solver.cpp @@ -94,6 +94,7 @@ void prop_solver::add_level() void prop_solver::ensure_level(unsigned lvl) { + if (is_infty_level(lvl)) return; while (lvl >= level_cnt()) { add_level(); } diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 8da574d0e..0328d8640 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -48,7 +48,8 @@ namespace spacer { } inline bool is_infty_level(unsigned lvl) { - return lvl == infty_level (); + // XXX: level is 16 bits in class pob + return lvl >= 65535; } inline unsigned next_level(unsigned lvl) { diff --git a/src/muz/transforms/CMakeLists.txt b/src/muz/transforms/CMakeLists.txt index e92aa1c2f..62272450c 100644 --- a/src/muz/transforms/CMakeLists.txt +++ b/src/muz/transforms/CMakeLists.txt @@ -25,6 +25,7 @@ z3_add_component(transforms dl_mk_array_eq_rewrite.cpp dl_mk_array_instantiation.cpp dl_mk_elim_term_ite.cpp + dl_mk_synchronize.cpp COMPONENT_DEPENDENCIES dataflow hilbert diff --git a/src/muz/transforms/dl_mk_synchronize.cpp b/src/muz/transforms/dl_mk_synchronize.cpp new file mode 100644 index 000000000..348c9b5de --- /dev/null +++ b/src/muz/transforms/dl_mk_synchronize.cpp @@ -0,0 +1,376 @@ +/*++ +Copyright (c) 2017-2018 Saint-Petersburg State University + +Module Name: + + dl_mk_synchronize.h + +Abstract: + + Rule transformer that attempts to merge recursive iterations + relaxing the shape of the inductive invariant. + +Author: + + Dmitry Mordvinov (dvvrd) 2017-05-24 + Lidiia Chernigovskaia (LChernigovskaya) 2017-10-20 + +Revision History: + +--*/ +#include "muz/transforms/dl_mk_synchronize.h" +#include + +namespace datalog { + + typedef mk_synchronize::item_set_vector item_set_vector; + + mk_synchronize::mk_synchronize(context& ctx, unsigned priority): + rule_transformer::plugin(priority, false), + m_ctx(ctx), + m(ctx.get_manager()), + rm(ctx.get_rule_manager()) + {} + + bool mk_synchronize::is_recursive(rule &r, func_decl &decl) const { + func_decl *hdecl = r.get_head()->get_decl(); + // AG: shouldn't decl appear in the body? + if (hdecl == &decl) return true; + auto & strata = m_stratifier->get_strats(); + unsigned num_of_stratum = m_stratifier->get_predicate_strat(hdecl); + return strata[num_of_stratum]->contains(&decl); + } + + bool mk_synchronize::has_recursive_premise(app * app) const { + func_decl* app_decl = app->get_decl(); + if (m_deps->get_deps(app_decl).contains(app_decl)) { + return true; + } + rule_stratifier::comp_vector const & strata = m_stratifier->get_strats(); + unsigned num_of_stratum = m_stratifier->get_predicate_strat(app_decl); + return strata[num_of_stratum]->size() > 1; + } + + item_set_vector mk_synchronize::add_merged_decls(ptr_vector & apps) { + unsigned sz = apps.size(); + item_set_vector merged_decls; + merged_decls.resize(sz); + auto & strata = m_stratifier->get_strats(); + for (unsigned j = 0; j < sz; ++j) { + unsigned nos; + nos = m_stratifier->get_predicate_strat(apps[j]->get_decl()); + merged_decls[j] = strata[nos]; + } + return merged_decls; + } + + void mk_synchronize::add_new_rel_symbols(unsigned idx, + item_set_vector const & decls, + ptr_vector & decls_buf, + bool & was_added) { + if (idx >= decls.size()) { + string_buffer<> buffer; + ptr_vector domain; + for (auto &d : decls_buf) { + buffer << d->get_name() << "!!"; + domain.append(d->get_arity(), d->get_domain()); + } + + symbol new_name = symbol(buffer.c_str()); + + if (!m_cache.contains(new_name)) { + was_added = true; + func_decl* orig = decls_buf[0]; + func_decl* product_pred = m_ctx.mk_fresh_head_predicate(new_name, + symbol::null, domain.size(), domain.c_ptr(), orig); + m_cache.insert(new_name, product_pred); + } + return; + } + + // -- compute Cartesian product of decls, and create a new + // -- predicate for each element of the product + for (auto &p : *decls[idx]) { + decls_buf[idx] = p; + add_new_rel_symbols(idx + 1, decls, decls_buf, was_added); + } + } + + void mk_synchronize::replace_applications(rule & r, rule_set & rules, + ptr_vector & apps) { + app_ref replacing = product_application(apps); + + ptr_vector new_tail; + svector new_tail_neg; + unsigned n = r.get_tail_size() - apps.size() + 1; + unsigned tail_idx = 0; + new_tail.resize(n); + new_tail_neg.resize(n); + new_tail[0] = replacing; + new_tail_neg[0] = false; + + for (unsigned i = 0; i < r.get_positive_tail_size(); ++i) { + app* tail = r.get_tail(i); + if (!apps.contains(tail)) { + ++tail_idx; + new_tail[tail_idx] = tail; + new_tail_neg[tail_idx] = false; + } + } + for (unsigned i = r.get_positive_tail_size(); i < r.get_uninterpreted_tail_size(); ++i) { + ++tail_idx; + new_tail[tail_idx] = r.get_tail(i); + new_tail_neg[tail_idx] = true; + } + for (unsigned i = r.get_uninterpreted_tail_size(); i < r.get_tail_size(); ++i) { + ++tail_idx; + new_tail[tail_idx] = r.get_tail(i); + new_tail_neg[tail_idx] = false; + } + + rule_ref new_rule(rm); + new_rule = rm.mk(r.get_head(), tail_idx + 1, + new_tail.c_ptr(), new_tail_neg.c_ptr(), symbol::null, false); + rules.replace_rule(&r, new_rule.get()); + } + + rule_ref mk_synchronize::rename_bound_vars_in_rule(rule * r, + unsigned & var_idx) { + // AG: shift all variables in a rule so that lowest var index is var_idx? + // AG: update var_idx in the process? + ptr_vector sorts; + r->get_vars(m, sorts); + expr_ref_vector revsub(m); + revsub.resize(sorts.size()); + for (unsigned i = 0; i < sorts.size(); ++i) { + if (sorts[i]) { + revsub[i] = m.mk_var(var_idx++, sorts[i]); + } + } + + rule_ref new_rule(rm); + new_rule = rm.mk(r); + rm.substitute(new_rule, revsub.size(), revsub.c_ptr()); + return new_rule; + } + + vector mk_synchronize::rename_bound_vars(item_set_vector const & heads, + rule_set & rules) { + // AG: is every item_set in heads corresponds to rules that are merged? + // AG: why are bound variables renamed in the first place? + // AG: the data structure seems too complex + vector result; + unsigned var_idx = 0; + for (auto item : heads) { + rule_ref_vector dst_vector(rm); + for (auto *head : *item) { + for (auto *r : rules.get_predicate_rules(head)) { + rule_ref new_rule = rename_bound_vars_in_rule(r, var_idx); + dst_vector.push_back(new_rule.get()); + } + } + result.push_back(dst_vector); + } + return result; + } + + void mk_synchronize::add_rec_tail(vector< ptr_vector > & recursive_calls, + app_ref_vector & new_tail, + svector & new_tail_neg, + unsigned & tail_idx) { + unsigned max_sz = 0; + for (auto &rc : recursive_calls) + max_sz= std::max(rc.size(), max_sz); + + unsigned n = recursive_calls.size(); + ptr_vector merged_recursive_calls; + + for (unsigned j = 0; j < max_sz; ++j) { + merged_recursive_calls.reset(); + merged_recursive_calls.resize(n); + for (unsigned i = 0; i < n; ++i) { + unsigned sz = recursive_calls[i].size(); + merged_recursive_calls[i] = + j < sz ? recursive_calls[i][j] : recursive_calls[i][sz - 1]; + } + ++tail_idx; + new_tail[tail_idx] = product_application(merged_recursive_calls); + new_tail_neg[tail_idx] = false; + } + } + + void mk_synchronize::add_non_rec_tail(rule & r, app_ref_vector & new_tail, + svector & new_tail_neg, + unsigned & tail_idx) { + for (unsigned i = 0, sz = r.get_positive_tail_size(); i < sz; ++i) { + app* tail = r.get_tail(i); + if (!is_recursive(r, *tail)) { + ++tail_idx; + new_tail[tail_idx] = tail; + new_tail_neg[tail_idx] = false; + } + } + for (unsigned i = r.get_positive_tail_size(), + sz = r.get_uninterpreted_tail_size() ; i < sz; ++i) { + ++tail_idx; + new_tail[tail_idx] = r.get_tail(i); + new_tail_neg[tail_idx] = true; + } + for (unsigned i = r.get_uninterpreted_tail_size(), + sz = r.get_tail_size(); i < sz; ++i) { + ++tail_idx; + new_tail[tail_idx] = r.get_tail(i); + new_tail_neg[tail_idx] = r.is_neg_tail(i); + } + } + + app_ref mk_synchronize::product_application(ptr_vector const &apps) { + unsigned args_num = 0; + string_buffer<> buffer; + + // AG: factor out into mk_name + for (auto *app : apps) { + buffer << app->get_decl()->get_name() << "!!"; + args_num += app->get_num_args(); + } + + symbol name = symbol(buffer.c_str()); + SASSERT(m_cache.contains(name)); + func_decl * pred = m_cache[name]; + + ptr_vector args; + args.resize(args_num); + unsigned idx = 0; + for (auto *a : apps) { + for (unsigned i = 0, sz = a->get_num_args(); i < sz; ++i, ++idx) + args[idx] = a->get_arg(i); + } + + return app_ref(m.mk_app(pred, args_num, args.c_ptr()), m); + } + + rule_ref mk_synchronize::product_rule(rule_ref_vector const & rules) { + unsigned n = rules.size(); + + string_buffer<> buffer; + bool first_rule = true; + for (auto it = rules.begin(); it != rules.end(); ++it, first_rule = false) { + if (!first_rule) { + buffer << "+"; + } + buffer << (*it)->name(); + } + + ptr_vector heads; + heads.resize(n); + for (unsigned i = 0; i < n; ++i) { + heads[i] = rules[i]->get_head(); + } + app_ref product_head = product_application(heads); + unsigned product_tail_length = 0; + bool has_recursion = false; + vector< ptr_vector > recursive_calls; + recursive_calls.resize(n); + for (unsigned i = 0; i < n; ++i) { + rule& rule = *rules[i]; + product_tail_length += rule.get_tail_size(); + for (unsigned j = 0; j < rule.get_positive_tail_size(); ++j) { + app* tail = rule.get_tail(j); + if (is_recursive(rule, *tail)) { + has_recursion = true; + recursive_calls[i].push_back(tail); + } + } + if (recursive_calls[i].empty()) { + recursive_calls[i].push_back(rule.get_head()); + } + } + + app_ref_vector new_tail(m); + svector new_tail_neg; + new_tail.resize(product_tail_length); + new_tail_neg.resize(product_tail_length); + unsigned tail_idx = -1; + if (has_recursion) { + add_rec_tail(recursive_calls, new_tail, new_tail_neg, tail_idx); + } + + for (rule_vector::const_iterator it = rules.begin(); it != rules.end(); ++it) { + rule& rule = **it; + add_non_rec_tail(rule, new_tail, new_tail_neg, tail_idx); + } + + rule_ref new_rule(rm); + new_rule = rm.mk(product_head, tail_idx + 1, + new_tail.c_ptr(), new_tail_neg.c_ptr(), symbol(buffer.c_str()), false); + rm.fix_unbound_vars(new_rule, false); + return new_rule; + } + + void mk_synchronize::merge_rules(unsigned idx, rule_ref_vector & buf, + vector const & merged_rules, + rule_set & all_rules) { + if (idx >= merged_rules.size()) { + rule_ref product = product_rule(buf); + all_rules.add_rule(product.get()); + return; + } + + for (auto *r : merged_rules[idx]) { + buf[idx] = r; + merge_rules(idx + 1, buf, merged_rules, all_rules); + } + } + + void mk_synchronize::merge_applications(rule & r, rule_set & rules) { + ptr_vector non_recursive_preds; + obj_hashtable apps; + for (unsigned i = 0; i < r.get_positive_tail_size(); ++i) { + app* t = r.get_tail(i); + if (!is_recursive(r, *t) && has_recursive_premise(t)) { + apps.insert(t); + } + } + if (apps.size() < 2) return; + for (auto *a : apps) non_recursive_preds.push_back(a); + + item_set_vector merged_decls = add_merged_decls(non_recursive_preds); + + unsigned n = non_recursive_preds.size(); + ptr_vector decls_buf; + decls_buf.resize(n); + bool was_added = false; + add_new_rel_symbols(0, merged_decls, decls_buf, was_added); + if (was_added){ + rule_ref_vector rules_buf(rm); + rules_buf.resize(n); + vector renamed_rules = rename_bound_vars(merged_decls, rules); + merge_rules(0, rules_buf, renamed_rules, rules); + } + + replace_applications(r, rules, non_recursive_preds); + m_deps->populate(rules); + m_stratifier = alloc(rule_stratifier, *m_deps); + } + + rule_set * mk_synchronize::operator()(rule_set const & source) { + rule_set* rules = alloc(rule_set, m_ctx); + rules->inherit_predicates(source); + + for (auto *r : source) { rules->add_rule(r); } + + m_deps = alloc(rule_dependencies, m_ctx); + m_deps->populate(*rules); + m_stratifier = alloc(rule_stratifier, *m_deps); + + unsigned current_rule = 0; + while (current_rule < rules->get_num_rules()) { + rule *r = rules->get_rule(current_rule); + merge_applications(*r, *rules); + ++current_rule; + } + + return rules; + } + +}; diff --git a/src/muz/transforms/dl_mk_synchronize.h b/src/muz/transforms/dl_mk_synchronize.h new file mode 100644 index 000000000..77f45e91f --- /dev/null +++ b/src/muz/transforms/dl_mk_synchronize.h @@ -0,0 +1,134 @@ +/*++ +Copyright (c) 2017-2018 Saint-Petersburg State University + +Module Name: + + dl_mk_synchronize.h + +Abstract: + + Rule transformer that attempts to merge recursive iterations + relaxing the shape of the inductive invariant. + +Example: + + Q(z) :- A(x), B(y), phi1(x,y,z). + A(x) :- A(x'), phi2(x,x'). + A(x) :- phi3(x). + B(y) :- C(y'), phi4(y,y'). + C(y) :- B(y'), phi5(y,y'). + B(y) :- phi6(y). + + Transformed clauses: + + Q(z) :- AB(x,y), phi1(x,y,z). + AB(x,y) :- AC(x',y'), phi2(x,x'), phi4(y,y'). + AC(x,y) :- AB(x',y'), phi2(x,x'), phi5(y,y'). + AB(x,y) :- AC(x, y'), phi3(x), phi4(y,y'). + AC(x,y) :- AB(x, y'), phi3(x), phi5(y,y'). + AB(x,y) :- AB(x',y), phi2(x,x'), phi6(y). + AB(x,y) :- phi3(x), phi6(y). + +Author: + + Dmitry Mordvinov (dvvrd) 2017-05-24 + Lidiia Chernigovskaia (LChernigovskaya) 2017-10-20 + +Revision History: + +--*/ +#ifndef DL_MK_SYNCHRONIZE_H_ +#define DL_MK_SYNCHRONIZE_H_ + +#include"muz/base/dl_context.h" +#include"muz/base/dl_rule_set.h" +#include"util/uint_set.h" +#include"muz/base/dl_rule_transformer.h" +#include"muz/transforms/dl_mk_rule_inliner.h" + +namespace datalog { + + /** + \brief Implements a synchronous product transformation. + */ + class mk_synchronize : public rule_transformer::plugin { + public: + typedef ptr_vector item_set_vector; + private: + context& m_ctx; + ast_manager& m; + rule_manager& rm; + + scoped_ptr m_deps; + scoped_ptr m_stratifier; + + // symbol table that maps new predicate names to corresponding + // func_decl + map m_cache; + + /// returns true if decl is recursive in the given rule + /// requires that decl appears in the tail of r + bool is_recursive(rule &r, func_decl &decl) const; + bool is_recursive(rule &r, expr &e) const { + SASSERT(is_app(&e)); + return is_app(&e) && is_recursive(r, *to_app(&e)->get_decl()); + } + + /// returns true if the top-level predicate of app is recursive + bool has_recursive_premise(app * app) const; + + item_set_vector add_merged_decls(ptr_vector & apps); + + /** + Compute Cartesian product of decls and create a new + predicate for each element. For example, if decls is + + ( (a, b), (c, d) ) ) + + create new predicates: a!!c, a!!d, b!!c, and b!!d + */ + void add_new_rel_symbols(unsigned idx, item_set_vector const & decls, + ptr_vector & buf, bool & was_added); + + /** + Convert vector of predicate apps into a single app. For example, + (Foo a) (Bar b) becomes (Foo!!Bar a b) + */ + app_ref product_application(ptr_vector const & apps); + + /** + Replace a given rule by a rule in which conjunction of + predicates is replaced by a single product predicate + */ + void replace_applications(rule & r, rule_set & rules, + ptr_vector & apps); + + rule_ref rename_bound_vars_in_rule(rule * r, unsigned & var_idx); + vector rename_bound_vars(item_set_vector const & heads, + rule_set & rules); + + void add_rec_tail(vector< ptr_vector > & recursive_calls, + app_ref_vector & new_tail, + svector & new_tail_neg, unsigned & tail_idx); + void add_non_rec_tail(rule & r, app_ref_vector & new_tail, + svector & new_tail_neg, + unsigned & tail_idx); + + rule_ref product_rule(rule_ref_vector const & rules); + + void merge_rules(unsigned idx, rule_ref_vector & buf, + vector const & merged_rules, rule_set & all_rules); + void merge_applications(rule & r, rule_set & rules); + + public: + /** + \brief Create synchronous product transformer. + */ + mk_synchronize(context & ctx, unsigned priority = 22500); + + rule_set * operator()(rule_set const & source) override; + }; + +}; + +#endif /* DL_MK_SYNCHRONIZE_H_ */ diff --git a/src/opt/maxres.cpp b/src/opt/maxres.cpp index f52c56a60..0b5f20db8 100644 --- a/src/opt/maxres.cpp +++ b/src/opt/maxres.cpp @@ -144,7 +144,7 @@ public: } } - virtual ~maxres() {} + ~maxres() override {} bool is_literal(expr* l) { return @@ -332,7 +332,7 @@ public: } - virtual lbool operator()() { + lbool operator()() override { m_defs.reset(); switch(m_st) { case s_primal: @@ -343,7 +343,7 @@ public: return l_undef; } - virtual void collect_statistics(statistics& st) const { + void collect_statistics(statistics& st) const override { st.update("maxres-cores", m_stats.m_num_cores); st.update("maxres-correction-sets", m_stats.m_num_cs); } @@ -781,7 +781,7 @@ public: TRACE("opt", tout << "after remove: " << asms << "\n";); } - virtual void updt_params(params_ref& _p) { + void updt_params(params_ref& _p) override { maxsmt_solver_base::updt_params(_p); opt_params p(_p); m_hill_climb = p.maxres_hill_climb(); @@ -816,7 +816,7 @@ public: return l_true; } - virtual void commit_assignment() { + void commit_assignment() override { if (m_found_feasible_optimum) { TRACE("opt", tout << "Committing feasible solution\n" << m_defs << " " << m_asms;); s().assert_expr(m_defs); diff --git a/src/opt/opt_cmds.cpp b/src/opt/opt_cmds.cpp index d8e301e08..f01df0bdd 100644 --- a/src/opt/opt_cmds.cpp +++ b/src/opt/opt_cmds.cpp @@ -56,33 +56,33 @@ public: m_opt(opt) {} - virtual ~assert_soft_cmd() { + ~assert_soft_cmd() override { } - virtual void reset(cmd_context & ctx) { + void reset(cmd_context & ctx) override { m_idx = 0; m_formula = nullptr; } - virtual char const * get_usage() const { return " [:weight ] [:id ]"; } - virtual char const * get_main_descr() const { return "assert soft constraint with optional weight and identifier"; } + char const * get_usage() const override { return " [:weight ] [:id ]"; } + char const * get_main_descr() const override { return "assert soft constraint with optional weight and identifier"; } // command invocation - virtual void prepare(cmd_context & ctx) { + void prepare(cmd_context & ctx) override { reset(ctx); } - virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { if (m_idx == 0) return CPK_EXPR; return parametric_cmd::next_arg_kind(ctx); } - virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + void init_pdescrs(cmd_context & ctx, param_descrs & p) override { p.insert("weight", CPK_NUMERAL, "(default: 1) penalty of not satisfying constraint."); p.insert("id", CPK_SYMBOL, "(default: null) partition identifier for soft constraints."); } - virtual void set_next_arg(cmd_context & ctx, expr * t) { + void set_next_arg(cmd_context & ctx, expr * t) override { SASSERT(m_idx == 0); if (!ctx.m().is_bool(t)) { throw cmd_exception("Invalid type for expression. Expected Boolean type."); @@ -91,11 +91,11 @@ public: ++m_idx; } - virtual void failure_cleanup(cmd_context & ctx) { + void failure_cleanup(cmd_context & ctx) override { reset(ctx); } - virtual void execute(cmd_context & ctx) { + void execute(cmd_context & ctx) override { if (!m_formula) { throw cmd_exception("assert-soft requires a formulas as argument."); } @@ -107,7 +107,7 @@ public: reset(ctx); } - virtual void finalize(cmd_context & ctx) { + void finalize(cmd_context & ctx) override { } }; @@ -123,14 +123,14 @@ public: m_opt(opt) {} - virtual void reset(cmd_context & ctx) { } - virtual char const * get_usage() const { return ""; } - virtual char const * get_descr(cmd_context & ctx) const { return "check sat modulo objective function";} - virtual unsigned get_arity() const { return 1; } - virtual void prepare(cmd_context & ctx) {} - virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_EXPR; } + void reset(cmd_context & ctx) override { } + char const * get_usage() const override { return ""; } + char const * get_descr(cmd_context & ctx) const override { return "check sat modulo objective function";} + unsigned get_arity() const override { return 1; } + void prepare(cmd_context & ctx) override {} + cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { return CPK_EXPR; } - virtual void set_next_arg(cmd_context & ctx, expr * t) { + void set_next_arg(cmd_context & ctx, expr * t) override { if (!is_app(t)) { throw cmd_exception("malformed objective term: it cannot be a quantifier or bound variable"); } @@ -138,11 +138,11 @@ public: ctx.print_success(); } - virtual void failure_cleanup(cmd_context & ctx) { + void failure_cleanup(cmd_context & ctx) override { reset(ctx); } - virtual void execute(cmd_context & ctx) { + void execute(cmd_context & ctx) override { } }; @@ -154,18 +154,18 @@ public: m_opt(opt) {} - virtual void reset(cmd_context & ctx) { } - virtual char const * get_usage() const { return "(get-objectives)"; } - virtual char const * get_descr(cmd_context & ctx) const { return "retrieve the objective values (after optimization)"; } - virtual unsigned get_arity() const { return 0; } - virtual void prepare(cmd_context & ctx) {} + void reset(cmd_context & ctx) override { } + char const * get_usage() const override { return "(get-objectives)"; } + char const * get_descr(cmd_context & ctx) const override { return "retrieve the objective values (after optimization)"; } + unsigned get_arity() const override { return 0; } + void prepare(cmd_context & ctx) override {} - virtual void failure_cleanup(cmd_context & ctx) { + void failure_cleanup(cmd_context & ctx) override { reset(ctx); } - virtual void execute(cmd_context & ctx) { + void execute(cmd_context & ctx) override { if (!ctx.ignore_check()) { get_opt(ctx, m_opt).display_assignment(ctx.regular_stream()); } diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index 5d4eb3fc5..ff37bfa95 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -79,13 +79,13 @@ namespace opt { m_hard.push_back(hard); } - bool context::scoped_state::set(ptr_vector & hard) { + bool context::scoped_state::set(expr_ref_vector const & hard) { bool eq = hard.size() == m_hard.size(); for (unsigned i = 0; eq && i < hard.size(); ++i) { - eq = hard[i] == m_hard[i].get(); + eq = hard.get(i) == m_hard.get(i); } m_hard.reset(); - m_hard.append(hard.size(), hard.c_ptr()); + m_hard.append(hard); return !eq; } @@ -130,6 +130,7 @@ namespace opt { m_fm(alloc(generic_model_converter, m, "opt")), m_model_fixed(), m_objective_refs(m), + m_core(m), m_enable_sat(false), m_is_clausal(false), m_pp_neat(false), @@ -173,11 +174,10 @@ namespace opt { } void context::get_unsat_core(expr_ref_vector & r) { - throw default_exception("Unsat cores are not supported with optimization"); + r.append(m_core); } - - void context::set_hard_constraints(ptr_vector& fmls) { + void context::set_hard_constraints(expr_ref_vector const& fmls) { if (m_scoped_state.set(fmls)) { clear_state(); } @@ -253,7 +253,7 @@ namespace opt { m_hard_constraints.append(s.m_hard); } - lbool context::optimize() { + lbool context::optimize(expr_ref_vector const& asms) { if (m_pareto) { return execute_pareto(); } @@ -263,7 +263,10 @@ namespace opt { clear_state(); init_solver(); import_scoped_state(); - normalize(); + normalize(asms); + if (m_hard_constraints.size() == 1 && m.is_false(m_hard_constraints.get(0))) { + return l_false; + } internalize(); update_solver(); if (contains_quantifiers()) { @@ -281,7 +284,7 @@ namespace opt { symbol pri = optp.priority(); IF_VERBOSE(1, verbose_stream() << "(optimize:check-sat)\n"); - lbool is_sat = s.check_sat(0,0); + lbool is_sat = s.check_sat(asms.size(),asms.c_ptr()); TRACE("opt", s.display(tout << "initial search result: " << is_sat << "\n");); if (is_sat != l_false) { s.get_model(m_model); @@ -289,7 +292,10 @@ namespace opt { model_updated(m_model.get()); } if (is_sat != l_true) { - TRACE("opt", tout << m_hard_constraints << "\n";); + TRACE("opt", tout << m_hard_constraints << " " << asms << "\n";); + if (!asms.empty()) { + s.get_unsat_core(m_core); + } return is_sat; } IF_VERBOSE(1, verbose_stream() << "(optimize:sat)\n"); @@ -479,7 +485,6 @@ namespace opt { return r; } - expr_ref context::mk_le(unsigned i, model_ref& mdl) { objective const& obj = m_objectives[i]; return mk_cmp(false, mdl, obj); @@ -489,8 +494,7 @@ namespace opt { objective const& obj = m_objectives[i]; return mk_cmp(true, mdl, obj); } - - + expr_ref context::mk_gt(unsigned i, model_ref& mdl) { expr_ref result = mk_le(i, mdl); result = mk_not(m, result); @@ -751,22 +755,25 @@ namespace opt { return m_arith.is_numeral(e, n) || m_bv.is_numeral(e, n, sz); } - void context::normalize() { + void context::normalize(expr_ref_vector const& asms) { expr_ref_vector fmls(m); to_fmls(fmls); - simplify_fmls(fmls); + simplify_fmls(fmls, asms); from_fmls(fmls); } - void context::simplify_fmls(expr_ref_vector& fmls) { + void context::simplify_fmls(expr_ref_vector& fmls, expr_ref_vector const& asms) { if (m_is_clausal) { return; } - goal_ref g(alloc(goal, m, true, false)); + goal_ref g(alloc(goal, m, true, !asms.empty())); for (expr* fml : fmls) { g->assert_expr(fml); } + for (expr * a : asms) { + g->assert_expr(a, a); + } tactic_ref tac0 = and_then(mk_simplify_tactic(m, m_params), mk_propagate_values_tactic(m), @@ -788,6 +795,7 @@ namespace opt { set_simplify(tac0.get()); } goal_ref_buffer result; + TRACE("opt", g->display(tout);); (*m_simplify)(g, result); SASSERT(result.size() == 1); goal* r = result[0]; @@ -795,8 +803,27 @@ namespace opt { fmls.reset(); expr_ref tmp(m); for (unsigned i = 0; i < r->size(); ++i) { - fmls.push_back(r->form(i)); + if (asms.empty()) { + fmls.push_back(r->form(i)); + continue; + } + + ptr_vector deps; + expr_dependency_ref core(r->dep(i), m); + m.linearize(core, deps); + if (!deps.empty()) { + fmls.push_back(m.mk_implies(m.mk_and(deps.size(), deps.c_ptr()), r->form(i))); + } + else { + fmls.push_back(r->form(i)); + } } + if (r->inconsistent()) { + ptr_vector core_elems; + expr_dependency_ref core(r->dep(0), m); + m.linearize(core, core_elems); + m_core.append(core_elems.size(), core_elems.c_ptr()); + } } bool context::is_maximize(expr* fml, app_ref& term, expr_ref& orig_term, unsigned& index) { @@ -1084,11 +1111,7 @@ namespace opt { } term = m_arith.mk_add(args.size(), args.c_ptr()); } - else if (m_arith.is_arith_expr(term) && !is_mul_const(term)) { - TRACE("opt", tout << "Purifying " << term << "\n";); - term = purify(fm, term); - } - else if (m.is_ite(term)) { + else if (m.is_ite(term) || !is_mul_const(term)) { TRACE("opt", tout << "Purifying " << term << "\n";); term = purify(fm, term); } @@ -1399,6 +1422,7 @@ namespace opt { m_box_index = UINT_MAX; m_model.reset(); m_model_fixed.reset(); + m_core.reset(); } void context::set_pareto(pareto_base* p) { diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index 1172e68b6..fe0d4a13e 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -133,7 +133,7 @@ namespace opt { void push(); void pop(); void add(expr* hard); - bool set(ptr_vector & hard); + bool set(expr_ref_vector const& hard); unsigned add(expr* soft, rational const& weight, symbol const& id); unsigned add(app* obj, bool is_max); unsigned get_index(symbol const& id) { return m_indices[id]; } @@ -164,6 +164,7 @@ namespace opt { obj_map m_objective_fns; obj_map m_objective_orig; func_decl_ref_vector m_objective_refs; + expr_ref_vector m_core; tactic_ref m_simplify; bool m_enable_sat; bool m_enable_sls; @@ -186,8 +187,8 @@ namespace opt { void push() override; void pop(unsigned n) override; bool empty() override { return m_scoped_state.m_objectives.empty(); } - void set_hard_constraints(ptr_vector & hard) override; - lbool optimize() override; + void set_hard_constraints(expr_ref_vector const& hard) override; + lbool optimize(expr_ref_vector const& asms) override; void set_model(model_ref& _m) override { m_model = _m; } void get_model_core(model_ref& _m) override; void get_box_model(model_ref& _m, unsigned index) override; @@ -254,7 +255,7 @@ namespace opt { void reset_maxsmts(); void import_scoped_state(); - void normalize(); + void normalize(expr_ref_vector const& asms); void internalize(); bool is_maximize(expr* fml, app_ref& term, expr_ref& orig_term, unsigned& index); bool is_minimize(expr* fml, app_ref& term, expr_ref& orig_term, unsigned& index); @@ -270,7 +271,7 @@ namespace opt { expr* mk_objective_fn(unsigned index, objective_t ty, unsigned sz, expr*const* args); void to_fmls(expr_ref_vector& fmls); void from_fmls(expr_ref_vector const& fmls); - void simplify_fmls(expr_ref_vector& fmls); + void simplify_fmls(expr_ref_vector& fmls, expr_ref_vector const& asms); void mk_atomic(expr_ref_vector& terms); void update_lower() { update_bound(true); } diff --git a/src/opt/opt_pareto.cpp b/src/opt/opt_pareto.cpp index fe92c3ba6..ada5d0d14 100644 --- a/src/opt/opt_pareto.cpp +++ b/src/opt/opt_pareto.cpp @@ -71,7 +71,7 @@ namespace opt { fmls.push_back(mk_or(gt)); fml = mk_and(fmls); IF_VERBOSE(10, verbose_stream() << "dominates: " << fml << "\n";); - TRACE("opt", tout << fml << "\n"; model_smt2_pp(tout, m, *m_model, 0);); + TRACE("opt", model_smt2_pp(tout << fml << "\n", m, *m_model, 0);); m_solver->assert_expr(fml); } diff --git a/src/opt/sortmax.cpp b/src/opt/sortmax.cpp index 1fafa12bd..df361f24a 100644 --- a/src/opt/sortmax.cpp +++ b/src/opt/sortmax.cpp @@ -39,9 +39,9 @@ namespace opt { sortmax(maxsat_context& c, weights_t& ws, expr_ref_vector const& soft): maxsmt_solver_base(c, ws, soft), m_sort(*this), m_trail(m), m_fresh(m) {} - virtual ~sortmax() {} + ~sortmax() override {} - lbool operator()() { + lbool operator()() override { obj_map soft; if (!init()) { return l_false; diff --git a/src/opt/wmax.cpp b/src/opt/wmax.cpp index e4eb7e06b..ee7f92a23 100644 --- a/src/opt/wmax.cpp +++ b/src/opt/wmax.cpp @@ -49,9 +49,9 @@ namespace opt { m_trail(m), m_defs(m) {} - virtual ~wmax() {} + ~wmax() override {} - lbool operator()() { + lbool operator()() override { TRACE("opt", tout << "weighted maxsat\n";); scoped_ensure_theory wth(*this); obj_map soft; diff --git a/src/parsers/smt2/marshal.cpp b/src/parsers/smt2/marshal.cpp index 11244760a..327414300 100644 --- a/src/parsers/smt2/marshal.cpp +++ b/src/parsers/smt2/marshal.cpp @@ -36,11 +36,12 @@ std::string marshal(expr_ref e, ast_manager &m) { expr_ref unmarshal(std::istream &is, ast_manager &m) { cmd_context ctx(false, &m); ctx.set_ignore_check(true); - if (!parse_smt2_commands(ctx, is)) { return expr_ref(nullptr, m); } + if (!parse_smt2_commands(ctx, is)) { + return expr_ref(nullptr, m); + } - ptr_vector::const_iterator it = ctx.begin_assertions(); - ptr_vector::const_iterator end = ctx.end_assertions(); - unsigned size = static_cast(end - it); + ptr_vector::const_iterator it = ctx.assertions().begin(); + unsigned size = ctx.assertions().size(); return expr_ref(mk_and(m, size, it), m); } diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index fe7c048a9..cd259e8f2 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -2614,7 +2614,7 @@ namespace smt2 { check_rparen("invalid get-value command, ')' expected"); model_ref md; - if (!m_ctx.is_model_available(md) || m_ctx.get_check_sat_result() == 0) + if (!m_ctx.is_model_available(md) || m_ctx.get_check_sat_result() == nullptr) throw cmd_exception("model is not available"); if (index != 0) { m_ctx.get_opt()->get_box_model(md, index); diff --git a/src/qe/qe_lite.cpp b/src/qe/qe_lite.cpp index da45d17ca..d900bff5d 100644 --- a/src/qe/qe_lite.cpp +++ b/src/qe/qe_lite.cpp @@ -394,7 +394,7 @@ namespace eq { expr* const* args = &e; if (is_lambda(q)) { r = q; - pr = 0; + pr = nullptr; return; } flatten_args(q, num_args, args); diff --git a/src/qe/qe_solve_plugin.cpp b/src/qe/qe_solve_plugin.cpp index 6ec840de1..dae1faf6f 100644 --- a/src/qe/qe_solve_plugin.cpp +++ b/src/qe/qe_solve_plugin.cpp @@ -156,7 +156,7 @@ namespace qe { std::swap(e1, e2); } // y + -1*x == 0 --> y = x - expr *a0 = 0, *a1 = 0, *x = 0; + expr *a0 = nullptr, *a1 = nullptr, *x = nullptr; if (a.is_zero(e2) && a.is_add(e1, a0, a1)) { if (a.is_times_minus_one(a1, x)) { e1 = a0; diff --git a/src/qe/qsat.cpp b/src/qe/qsat.cpp index c87b1c2eb..2ad5b9b96 100644 --- a/src/qe/qsat.cpp +++ b/src/qe/qsat.cpp @@ -1266,9 +1266,9 @@ namespace qe { in->reset(); in->inc_depth(); result.push_back(in.get()); - if (in->models_enabled()) { + if (in->models_enabled()) { model_converter_ref mc; - mc = model2model_converter(m_model.get()); + mc = model2model_converter(m_model_save.get()); mc = concat(m_pred_abs.fmc(), mc.get()); in->add(mc.get()); } diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index 88de3b6a1..1f9e35f02 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1862,8 +1862,9 @@ namespace sat { return p; } - ba_solver::ba_solver(): m_solver(0), m_lookahead(0), m_unit_walk(0), - m_allocator("ba"), m_constraint_id(0), m_ba(*this), m_sort(m_ba) { + ba_solver::ba_solver() + : m_solver(nullptr), m_lookahead(nullptr), m_unit_walk(nullptr), + m_constraint_id(0), m_ba(*this), m_sort(m_ba) { TRACE("ba", tout << this << "\n";); m_num_propagations_since_pop = 0; } @@ -3838,7 +3839,7 @@ namespace sat { } init_visited(); for (wliteral l : p1) { - SASSERT(m_weights.get(l.second.index(), 0) == 0); + SASSERT(m_weights.size() <= l.second.index() || m_weights[l.second.index()] == 0); m_weights.setx(l.second.index(), l.first, 0); mark_visited(l.second); } @@ -4394,6 +4395,12 @@ namespace sat { } if (slack >= k) { +#if 0 + return active2constraint(); + active2pb(m_A); + std::cout << "not asserting\n"; + display(std::cout, m_A, true); +#endif return nullptr; } diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index 8f9488167..558b22bf3 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -134,7 +134,7 @@ namespace sat { virtual void set_k(unsigned k) { VERIFY(k < 4000000000); m_k = k; } virtual unsigned get_coeff(unsigned i) const { UNREACHABLE(); return 0; } unsigned k() const { return m_k; } - virtual bool well_formed() const; + bool well_formed() const override; }; class card : public pb_base { @@ -146,13 +146,13 @@ namespace sat { literal& operator[](unsigned i) { return m_lits[i]; } literal const* begin() const { return m_lits; } literal const* end() const { return static_cast(m_lits) + m_size; } - virtual void negate(); - virtual void swap(unsigned i, unsigned j) { std::swap(m_lits[i], m_lits[j]); } - virtual literal_vector literals() const { return literal_vector(m_size, m_lits); } - virtual bool is_watching(literal l) const; - virtual literal get_lit(unsigned i) const { return m_lits[i]; } - virtual void set_lit(unsigned i, literal l) { m_lits[i] = l; } - virtual unsigned get_coeff(unsigned i) const { return 1; } + void negate() override; + void swap(unsigned i, unsigned j) override { std::swap(m_lits[i], m_lits[j]); } + literal_vector literals() const override { return literal_vector(m_size, m_lits); } + bool is_watching(literal l) const override; + literal get_lit(unsigned i) const override { return m_lits[i]; } + void set_lit(unsigned i, literal l) override { m_lits[i] = l; } + unsigned get_coeff(unsigned i) const override { return 1; } }; @@ -179,14 +179,14 @@ namespace sat { void update_max_sum(); void set_num_watch(unsigned s) { m_num_watch = s; } bool is_cardinality() const; - virtual void negate(); - virtual void set_k(unsigned k) { m_k = k; VERIFY(k < 4000000000); update_max_sum(); } - virtual void swap(unsigned i, unsigned j) { std::swap(m_wlits[i], m_wlits[j]); } - virtual literal_vector literals() const { literal_vector lits; for (auto wl : *this) lits.push_back(wl.second); return lits; } - virtual bool is_watching(literal l) const; - virtual literal get_lit(unsigned i) const { return m_wlits[i].second; } - virtual void set_lit(unsigned i, literal l) { m_wlits[i].second = l; } - virtual unsigned get_coeff(unsigned i) const { return m_wlits[i].first; } + void negate() override; + void set_k(unsigned k) override { m_k = k; VERIFY(k < 4000000000); update_max_sum(); } + void swap(unsigned i, unsigned j) override { std::swap(m_wlits[i], m_wlits[j]); } + literal_vector literals() const override { literal_vector lits; for (auto wl : *this) lits.push_back(wl.second); return lits; } + bool is_watching(literal l) const override; + literal get_lit(unsigned i) const override { return m_wlits[i].second; } + void set_lit(unsigned i, literal l) override { m_wlits[i].second = l; } + unsigned get_coeff(unsigned i) const override { return m_wlits[i].first; } }; class xr : public constraint { @@ -197,13 +197,13 @@ namespace sat { literal operator[](unsigned i) const { return m_lits[i]; } literal const* begin() const { return m_lits; } literal const* end() const { return begin() + m_size; } - virtual void negate() { m_lits[0].neg(); } - virtual void swap(unsigned i, unsigned j) { std::swap(m_lits[i], m_lits[j]); } - virtual bool is_watching(literal l) const; - virtual literal_vector literals() const { return literal_vector(size(), begin()); } - virtual literal get_lit(unsigned i) const { return m_lits[i]; } - virtual void set_lit(unsigned i, literal l) { m_lits[i] = l; } - virtual bool well_formed() const; + void negate() override { m_lits[0].neg(); } + void swap(unsigned i, unsigned j) override { std::swap(m_lits[i], m_lits[j]); } + bool is_watching(literal l) const override; + literal_vector literals() const override { return literal_vector(size(), begin()); } + literal get_lit(unsigned i) const override { return m_lits[i]; } + void set_lit(unsigned i, literal l) override { m_lits[i] = l; } + bool well_formed() const override; }; @@ -524,44 +524,44 @@ namespace sat { public: ba_solver(); - virtual ~ba_solver(); - virtual void set_solver(solver* s) { m_solver = s; } - virtual void set_lookahead(lookahead* l) { m_lookahead = l; } - virtual void set_unit_walk(unit_walk* u) { m_unit_walk = u; } + ~ba_solver() override; + void set_solver(solver* s) override { m_solver = s; } + void set_lookahead(lookahead* l) override { m_lookahead = l; } + void set_unit_walk(unit_walk* u) override { m_unit_walk = u; } void add_at_least(bool_var v, literal_vector const& lits, unsigned k); void add_pb_ge(bool_var v, svector const& wlits, unsigned k); void add_xr(literal_vector const& lits); - virtual bool propagate(literal l, ext_constraint_idx idx); - virtual lbool resolve_conflict(); - virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r); - virtual void asserted(literal l); - virtual check_result check(); - virtual void push(); - virtual void pop(unsigned n); - virtual void simplify(); - virtual void clauses_modifed(); - virtual lbool get_phase(bool_var v); - virtual bool set_root(literal l, literal r); - virtual void flush_roots(); - virtual std::ostream& display(std::ostream& out) const; - virtual std::ostream& display_justification(std::ostream& out, ext_justification_idx idx) const; - virtual void collect_statistics(statistics& st) const; - virtual extension* copy(solver* s); - virtual extension* copy(lookahead* s, bool learned); - virtual void find_mutexes(literal_vector& lits, vector & mutexes); - virtual void pop_reinit(); - virtual void gc(); - virtual double get_reward(literal l, ext_justification_idx idx, literal_occs_fun& occs) const; - virtual bool is_extended_binary(ext_justification_idx idx, literal_vector & r); - virtual void init_use_list(ext_use_list& ul); - virtual bool is_blocked(literal l, ext_constraint_idx idx); - virtual bool check_model(model const& m) const; + bool propagate(literal l, ext_constraint_idx idx) override; + lbool resolve_conflict() override; + void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) override; + void asserted(literal l) override; + check_result check() override; + void push() override; + void pop(unsigned n) override; + void simplify() override; + void clauses_modifed() override; + lbool get_phase(bool_var v) override; + bool set_root(literal l, literal r) override; + void flush_roots() override; + std::ostream& display(std::ostream& out) const override; + std::ostream& display_justification(std::ostream& out, ext_justification_idx idx) const override; + void collect_statistics(statistics& st) const override; + extension* copy(solver* s) override; + extension* copy(lookahead* s, bool learned) override; + void find_mutexes(literal_vector& lits, vector & mutexes) override; + void pop_reinit() override; + void gc() override; + double get_reward(literal l, ext_justification_idx idx, literal_occs_fun& occs) const override; + bool is_extended_binary(ext_justification_idx idx, literal_vector & r) override; + void init_use_list(ext_use_list& ul) override; + bool is_blocked(literal l, ext_constraint_idx idx) override; + bool check_model(model const& m) const override; ptr_vector const & constraints() const { return m_constraints; } void display(std::ostream& out, constraint const& c, bool values) const; - virtual bool validate(); + bool validate() override; }; diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index 07aacd9ee..a0697c3bf 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -152,6 +152,8 @@ namespace sat { m_gc_burst = p.gc_burst(); m_gc_defrag = p.gc_defrag(); + m_force_cleanup = p.force_cleanup(); + m_minimize_lemmas = p.minimize_lemmas(); m_core_minimize = p.core_minimize(); m_core_minimize_partial = p.core_minimize_partial(); diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index b5cfb9e28..3b2113a0f 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -146,6 +146,8 @@ namespace sat { bool m_gc_burst; bool m_gc_defrag; + bool m_force_cleanup; + bool m_minimize_lemmas; bool m_dyn_sub_res; diff --git a/src/sat/sat_drat.cpp b/src/sat/sat_drat.cpp index 00a3fa076..932e9b35e 100644 --- a/src/sat/sat_drat.cpp +++ b/src/sat/sat_drat.cpp @@ -26,7 +26,7 @@ Notes: namespace sat { drat::drat(solver& s): s(s), - m_out(0), + m_out(nullptr), m_inconsistent(false), m_check_unsat(false), m_check_sat(false), diff --git a/src/sat/sat_local_search.cpp b/src/sat/sat_local_search.cpp index af2447fc2..9b79e2964 100644 --- a/src/sat/sat_local_search.cpp +++ b/src/sat/sat_local_search.cpp @@ -529,7 +529,7 @@ namespace sat { } lbool local_search::check() { - return check(0, 0); + return check(0, nullptr); } #define PROGRESS(tries, flips) \ diff --git a/src/sat/sat_local_search.h b/src/sat/sat_local_search.h index 8a63898c3..5fd69a740 100644 --- a/src/sat/sat_local_search.h +++ b/src/sat/sat_local_search.h @@ -277,7 +277,7 @@ namespace sat { lbool check(); - lbool check(unsigned sz, literal const* assumptions, parallel* p = 0); + lbool check(unsigned sz, literal const* assumptions, parallel* p = nullptr); local_search_config& config() { return m_config; } diff --git a/src/sat/sat_lookahead.cpp b/src/sat/sat_lookahead.cpp index 3833e2a52..72792b182 100644 --- a/src/sat/sat_lookahead.cpp +++ b/src/sat/sat_lookahead.cpp @@ -16,6 +16,8 @@ Author: Notes: + + --*/ #include @@ -31,7 +33,7 @@ namespace sat { } lookahead::scoped_ext::~scoped_ext() { - if (p.m_s.m_ext) p.m_s.m_ext->set_lookahead(0); + if (p.m_s.m_ext) p.m_s.m_ext->set_lookahead(nullptr); } lookahead::scoped_assumptions::scoped_assumptions(lookahead& p, literal_vector const& lits): p(p), lits(lits) { @@ -1215,7 +1217,7 @@ namespace sat { lookahead& lh; public: lookahead_literal_occs_fun(lookahead& lh): lh(lh) {} - double operator()(literal l) { return lh.literal_occs(l); } + double operator()(literal l) override { return lh.literal_occs(l); } }; // Ternary clause managagement: @@ -2055,6 +2057,15 @@ namespace sat { return h; } + bool lookahead::should_cutoff(unsigned depth) { + return depth > 0 && + ((m_config.m_cube_cutoff == depth_cutoff && depth == m_config.m_cube_depth) || + (m_config.m_cube_cutoff == freevars_cutoff && m_freevars.size() <= m_init_freevars * m_config.m_cube_freevars) || + (m_config.m_cube_cutoff == psat_cutoff && psat_heur() >= m_config.m_cube_psat_trigger) || + (m_config.m_cube_cutoff == adaptive_freevars_cutoff && m_freevars.size() < m_cube_state.m_freevars_threshold) || + (m_config.m_cube_cutoff == adaptive_psat_cutoff && psat_heur() >= m_cube_state.m_psat_threshold)); + } + lbool lookahead::cube(bool_var_vector& vars, literal_vector& lits, unsigned backtrack_level) { scoped_ext _scoped_ext(*this); lits.reset(); @@ -2100,22 +2111,13 @@ namespace sat { } backtrack_level = UINT_MAX; depth = m_cube_state.m_cube.size(); - if ((m_config.m_cube_cutoff == depth_cutoff && depth == m_config.m_cube_depth) || - (m_config.m_cube_cutoff == freevars_cutoff && m_freevars.size() <= m_init_freevars * m_config.m_cube_freevars) || - (m_config.m_cube_cutoff == psat_cutoff && psat_heur() >= m_config.m_cube_psat_trigger) || - (m_config.m_cube_cutoff == adaptive_freevars_cutoff && m_freevars.size() < m_cube_state.m_freevars_threshold) || - (m_config.m_cube_cutoff == adaptive_psat_cutoff && psat_heur() >= m_cube_state.m_psat_threshold)) { + if (should_cutoff(depth)) { double dec = (1.0 - pow(m_config.m_cube_fraction, depth)); m_cube_state.m_freevars_threshold *= dec; m_cube_state.m_psat_threshold *= 2.0 - dec; set_conflict(); m_cube_state.inc_cutoff(); -#if 0 - // return cube of all literals, not just the ones in the main cube - lits.append(m_trail.size() - init_trail, m_trail.c_ptr() + init_trail); -#else lits.append(m_cube_state.m_cube); -#endif vars.reset(); for (auto v : m_freevars) if (in_reduced_clause(v)) vars.push_back(v); backtrack(m_cube_state.m_cube, m_cube_state.m_is_decision); @@ -2135,6 +2137,8 @@ namespace sat { if (lit == null_literal) { vars.reset(); for (auto v : m_freevars) if (in_reduced_clause(v)) vars.push_back(v); + m_model.reset(); + init_model(); return l_true; } TRACE("sat", tout << "choose: " << lit << " cube: " << m_cube_state.m_cube << "\n";); diff --git a/src/sat/sat_lookahead.h b/src/sat/sat_lookahead.h index df954bdf1..c2282e779 100644 --- a/src/sat/sat_lookahead.h +++ b/src/sat/sat_lookahead.h @@ -558,6 +558,8 @@ namespace sat { double psat_heur(); + bool should_cutoff(unsigned depth); + public: lookahead(solver& s) : m_s(s), diff --git a/src/sat/sat_par.cpp b/src/sat/sat_par.cpp deleted file mode 100644 index e3d5727ed..000000000 --- a/src/sat/sat_par.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/*++ -Copyright (c) 2017 Microsoft Corporation - -Module Name: - - sat_par.cpp - -Abstract: - - Utilities for parallel SAT solving. - -Author: - - Nikolaj Bjorner (nbjorner) 2017-1-29. - -Revision History: - ---*/ -#include "sat/sat_par.h" - - -namespace sat { - - par::par() {} - - void par::exchange(literal_vector const& in, unsigned& limit, literal_vector& out) { - #pragma omp critical (par_solver) - { - if (limit < m_units.size()) { - // this might repeat some literals. - out.append(m_units.size() - limit, m_units.c_ptr() + limit); - } - for (unsigned i = 0; i < in.size(); ++i) { - literal lit = in[i]; - if (!m_unit_set.contains(lit.index())) { - m_unit_set.insert(lit.index()); - m_units.push_back(lit); - } - } - limit = m_units.size(); - } - } - -}; - diff --git a/src/sat/sat_par.h b/src/sat/sat_par.h deleted file mode 100644 index 001036a98..000000000 --- a/src/sat/sat_par.h +++ /dev/null @@ -1,39 +0,0 @@ -/*++ -Copyright (c) 2017 Microsoft Corporation - -Module Name: - - sat_par.h - -Abstract: - - Utilities for parallel SAT solving. - -Author: - - Nikolaj Bjorner (nbjorner) 2017-1-29. - -Revision History: - ---*/ -#ifndef SAT_PAR_H_ -#define SAT_PAR_H_ - -#include "sat/sat_types.h" -#include "util/hashtable.h" -#include "util/map.h" - -namespace sat { - - class par { - typedef hashtable index_set; - literal_vector m_units; - index_set m_unit_set; - public: - par(); - void exchange(literal_vector const& in, unsigned& limit, literal_vector& out); - }; - -}; - -#endif diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 390cdfa53..a8b23f29c 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -15,7 +15,7 @@ def_module_params('sat', ('restart.margin', DOUBLE, 1.1, 'margin between fast and slow restart factors. For ema'), ('restart.emafastglue', DOUBLE, 3e-2, 'ema alpha factor for fast moving average'), ('restart.emaslowglue', DOUBLE, 1e-5, 'ema alpha factor for slow moving average'), - ('variable_decay', UINT, 110, 'multiplier (divided by 100) for the VSIDS activity increement'), + ('variable_decay', UINT, 110, 'multiplier (divided by 100) for the VSIDS activity increment'), ('inprocess.max', UINT, UINT_MAX, 'maximal number of inprocessing passes'), ('branching.heuristic', SYMBOL, 'vsids', 'branching heuristic vsids, lrb or chb'), ('branching.anti_exploration', BOOL, False, 'apply anti-exploration heuristic for branch selection'), @@ -31,6 +31,7 @@ def_module_params('sat', ('gc.burst', BOOL, False, 'perform eager garbage collection during initialization'), ('gc.defrag', BOOL, True, 'defragment clauses when garbage collecting'), ('simplify.delay', UINT, 0, 'set initial delay of simplification by a conflict count'), + ('force_cleanup', BOOL, False, 'force cleanup to remove tautologies and simplify clauses'), ('minimize_lemmas', BOOL, True, 'minimize learned clauses'), ('dyn_sub_res', BOOL, True, 'dynamic subsumption resolution for minimizing learned clauses'), ('core.minimize', BOOL, False, 'minimize computed core'), @@ -52,17 +53,39 @@ def_module_params('sat', ('unit_walk', BOOL, False, 'use unit-walk search instead of CDCL'), ('unit_walk_threads', UINT, 0, 'number of unit-walk search threads to find satisfiable solution'), ('lookahead.cube.cutoff', SYMBOL, 'depth', 'cutoff type used to create lookahead cubes: depth, freevars, psat, adaptive_freevars, adaptive_psat'), + # - depth: the maximal cutoff is fixed to the value of lookahead.cube.depth. + # So if the value is 10, at most 1024 cubes will be generated of length 10. + # - freevars: cutoff based on a variable fraction of lookahead.cube.freevars. + # Cut if the number of current unassigned variables drops below a fraction of number of initial variables. + # - psat: Let psat_heur := (Sum_{clause C} (psat.clause_base ^ {-|C|+1})) / |freevars|^psat.var_exp + # Cut if the value of psat_heur exceeds psat.trigger + # - adaptive_freevars: Cut if the number of current unassigned variables drops below a fraction of free variables + # at the time of the last conflict. The fraction is increased every time the cutoff is created. + # - adative_psat: Cut based on psat_heur in an adaptive way. ('lookahead.cube.fraction', DOUBLE, 0.4, 'adaptive fraction to create lookahead cubes. Used when lookahead.cube.cutoff is adaptive_freevars or adaptive_psat'), ('lookahead.cube.depth', UINT, 1, 'cut-off depth to create cubes. Used when lookahead.cube.cutoff is depth.'), - ('lookahead.cube.freevars', DOUBLE, 0.8, 'cube free fariable fraction. Used when lookahead.cube.cutoff is freevars'), + ('lookahead.cube.freevars', DOUBLE, 0.8, 'cube free variable fraction. Used when lookahead.cube.cutoff is freevars'), ('lookahead.cube.psat.var_exp', DOUBLE, 1, 'free variable exponent for PSAT cutoff'), ('lookahead.cube.psat.clause_base', DOUBLE, 2, 'clause base for PSAT cutoff'), ('lookahead.cube.psat.trigger', DOUBLE, 5, 'trigger value to create lookahead cubes for PSAT cutoff. Used when lookahead.cube.cutoff is psat'), - ('lookahead_search', BOOL, False, 'use lookahead solver'), ('lookahead.preselect', BOOL, False, 'use pre-selection of subset of variables for branching'), ('lookahead_simplify', BOOL, False, 'use lookahead solver during simplification'), ('lookahead.use_learned', BOOL, False, 'use learned clauses when selecting lookahead literal'), ('lookahead_simplify.bca', BOOL, True, 'add learned binary clauses as part of lookahead simplification'), ('lookahead.global_autarky', BOOL, False, 'prefer to branch on variables that occur in clauses that are reduced'), - ('lookahead.reward', SYMBOL, 'march_cu', 'select lookahead heuristic: ternary, heule_schur (Heule Schur), heuleu (Heule Unit), unit, or march_cu'))) + ('lookahead.reward', SYMBOL, 'march_cu', 'select lookahead heuristic: ternary, heule_schur (Heule Schur), heuleu (Heule Unit), unit, or march_cu')) + # reward function used to determine which literal to cube on. + # - ternary: reward function useful for random 3-SAT instances. Used by Heule and Knuth in March. + # - heule_schur: reward function based on "Schur Number 5", Heule, AAAI 2018 + # The score of a literal lit is: + # Sum_{C in Clauses | lit in C} 2 ^ (- |C|+1) + # * Sum_{lit' in C | lit' != lit} lit_occs(~lit') + # / | C | + # where lit_occs(lit) is the number of clauses containing lit. + # - heuleu: The score of a literal lit is: Sum_{C in Clauses | lit in C} 2 ^ (-|C| + 1) + # - unit: heule_schur + also counts number of unit clauses. + # - march_cu: default reward function used in a version of March + # Each reward function also comes with its own variant of "mix_diff", which + # is the function for combining reward metrics for the positive and negative variant of a literal. + ) diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 065dc1d39..b8e6e7088 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -66,18 +66,20 @@ namespace sat { m_next_simplify = 0; m_num_checkpoints = 0; m_simplifications = 0; - m_ext = 0; + m_ext = nullptr; m_cuber = nullptr; m_mc.set_solver(this); } solver::~solver() { - m_ext = 0; + m_ext = nullptr; SASSERT(check_invariant()); TRACE("sat", tout << "Delete clauses\n";); del_clauses(m_clauses); TRACE("sat", tout << "Delete learned\n";); del_clauses(m_learned); + dealloc(m_cuber); + m_cuber = nullptr; } void solver::del_clauses(clause_vector& clauses) { @@ -1008,14 +1010,39 @@ namespace sat { } lbool solver::cube(bool_var_vector& vars, literal_vector& lits, unsigned backtrack_level) { - if (!m_cuber) { + bool is_first = !m_cuber; + if (is_first) { m_cuber = alloc(lookahead, *this); } lbool result = m_cuber->cube(vars, lits, backtrack_level); m_cuber->update_cube_statistics(m_aux_stats); - if (result == l_false) { + switch (result) { + case l_false: dealloc(m_cuber); m_cuber = nullptr; + if (is_first) { + pop_to_base_level(); + set_conflict(justification()); + } + break; + case l_true: { + lits.reset(); + pop_to_base_level(); + model const& mdl = m_cuber->get_model(); + for (bool_var v = 0; v < mdl.size(); ++v) { + if (value(v) != l_undef) { + continue; + } + literal l(v, false); + if (mdl[v] != l_true) l.neg(); + push(); + assign_core(l, justification()); + } + mk_model(); + break; + } + default: + break; } return result; } @@ -1053,7 +1080,7 @@ namespace sat { init_assumptions(num_lits, lits); propagate(false); if (check_inconsistent()) return l_false; - cleanup(); + cleanup(m_config.m_force_cleanup); if (m_config.m_unit_walk) { return do_unit_walk(); @@ -1127,7 +1154,7 @@ namespace sat { srch.config().set_config(m_config); srch.import(*this, false); scoped_rl.push_child(&srch.rlimit()); - lbool r = srch.check(num_lits, lits, 0); + lbool r = srch.check(num_lits, lits, nullptr); m_model = srch.get_model(); // srch.collect_statistics(m_aux_stats); return r; @@ -1264,7 +1291,7 @@ namespace sat { if (!canceled) { rlimit().reset_cancel(); } - set_par(0, 0); + set_par(nullptr, 0); ls.reset(); uw.reset(); if (finished_id == -1) { @@ -1425,7 +1452,7 @@ namespace sat { if (should_restart()) return l_undef; if (at_base_lvl()) { - cleanup(); // cleaner may propagate frozen clauses + cleanup(false); // cleaner may propagate frozen clauses if (inconsistent()) { TRACE("sat", tout << "conflict at level 0\n";); return l_false; @@ -1621,7 +1648,7 @@ namespace sat { SASSERT(at_base_lvl()); - m_cleaner(); + m_cleaner(m_config.m_force_cleanup); CASSERT("sat_simplify_bug", check_invariant()); m_scc(); @@ -3662,10 +3689,10 @@ namespace sat { // Simplification // // ----------------------- - void solver::cleanup() { + void solver::cleanup(bool force) { if (!at_base_lvl() || inconsistent()) return; - if (m_cleaner() && m_ext) + if (m_cleaner(force) && m_ext) m_ext->clauses_modifed(); } diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h index ad972b2af..fd9af4a02 100644 --- a/src/sat/sat_solver.h +++ b/src/sat/sat_solver.h @@ -37,7 +37,6 @@ Revision History: #include "sat/sat_drat.h" #include "sat/sat_parallel.h" #include "sat/sat_local_search.h" -#include "sat/sat_par.h" #include "util/params.h" #include "util/statistics.h" #include "util/stopwatch.h" @@ -550,7 +549,7 @@ namespace sat { // // ----------------------- public: - void cleanup(); + void cleanup(bool force); void simplify(bool learned = true); void asymmetric_branching(); unsigned scc_bin(); diff --git a/src/sat/sat_solver/CMakeLists.txt b/src/sat/sat_solver/CMakeLists.txt index 14eb4ac25..45a673367 100644 --- a/src/sat/sat_solver/CMakeLists.txt +++ b/src/sat/sat_solver/CMakeLists.txt @@ -8,4 +8,6 @@ z3_add_component(sat_solver core_tactics sat_tactic solver + TACTIC_HEADERS + inc_sat_solver.h ) diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index ff55598c2..df2da82e7 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -259,7 +259,7 @@ public: return m_num_scopes; } - void assert_expr_core2(expr * t, expr * a) override { + void assert_expr_core2(expr * t, expr * a) override { if (a) { m_asmsf.push_back(a); assert_expr_core(m.mk_implies(a, t)); @@ -308,10 +308,19 @@ public: return nullptr; } + expr_ref_vector last_cube(bool is_sat) { + expr_ref_vector result(m); + result.push_back(is_sat ? m.mk_true() : m.mk_false()); + return result; + } + expr_ref_vector cube(expr_ref_vector& vs, unsigned backtrack_level) override { if (!is_internalized()) { lbool r = internalize_formulas(); - if (r != l_true) return expr_ref_vector(m); + if (r != l_true) { + IF_VERBOSE(0, verbose_stream() << "internalize produced " << r << "\n"); + return expr_ref_vector(m); + } } convert_internalized(); obj_hashtable _vs; @@ -323,14 +332,18 @@ public: } sat::literal_vector lits; lbool result = m_solver.cube(vars, lits, backtrack_level); - if (result == l_false || lits.empty()) { - expr_ref_vector result(m); - result.push_back(m.mk_false()); - return result; + switch (result) { + case l_true: + return last_cube(true); + case l_false: + return last_cube(false); + default: + break; } - if (result == l_true) { + if (lits.empty()) { + set_reason_unknown(m_solver.get_reason_unknown()); return expr_ref_vector(m); - } + } expr_ref_vector fmls(m); expr_ref_vector lit2expr(m); lit2expr.resize(m_solver.num_vars() * 2); @@ -473,6 +486,7 @@ public: } void convert_internalized() { + m_solver.pop_to_base_level(); if (!is_internalized() && m_fmls_head > 0) { internalize_formulas(); } diff --git a/src/sat/sat_solver/inc_sat_solver.h b/src/sat/sat_solver/inc_sat_solver.h index 71ec48b99..b1cf7ad37 100644 --- a/src/sat/sat_solver/inc_sat_solver.h +++ b/src/sat/sat_solver/inc_sat_solver.h @@ -28,6 +28,9 @@ solver* mk_inc_sat_solver(ast_manager& m, params_ref const& p, bool incremental_ tactic* mk_psat_tactic(ast_manager& m, params_ref const& p); +/* + ADD_TACTIC('psat', '(try to) solve goal using a parallel SAT solver.', 'mk_psat_tactic(m, p)') +*/ void inc_sat_display(std::ostream& out, solver& s, unsigned sz, expr*const* soft, rational const* _weights); diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index 9d0d29ac0..645ef8a3f 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -72,7 +72,7 @@ struct goal2sat::imp { imp(ast_manager & _m, params_ref const & p, sat::solver & s, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external): m(_m), pb(m), - m_ext(0), + m_ext(nullptr), m_solver(s), m_map(map), m_dep2asm(dep2asm), @@ -452,7 +452,15 @@ struct goal2sat::imp { unsigned sz = m_result_stack.size(); if (root) { m_result_stack.reset(); - m_ext->add_pb_ge(sat::null_bool_var, wlits, k.get_unsigned()); + unsigned k1 = k.get_unsigned(); + if (sign) { + k1 = 1 - k1; + for (wliteral& wl : wlits) { + wl.second.neg(); + k1 += wl.first; + } + } + m_ext->add_pb_ge(sat::null_bool_var, wlits, k1); } else { sat::bool_var v = m_solver.mk_var(true); @@ -477,7 +485,15 @@ struct goal2sat::imp { unsigned sz = m_result_stack.size(); if (root) { m_result_stack.reset(); - m_ext->add_pb_ge(sat::null_bool_var, wlits, k.get_unsigned()); + unsigned k1 = k.get_unsigned(); + if (sign) { + k1 = 1 - k1; + for (wliteral& wl : wlits) { + wl.second.neg(); + k1 += wl.first; + } + } + m_ext->add_pb_ge(sat::null_bool_var, wlits, k1); } else { sat::bool_var v = m_solver.mk_var(true); @@ -1048,7 +1064,7 @@ void sat2goal::mc::insert(sat::bool_var v, app * atom, bool aux) { expr_ref sat2goal::mc::lit2expr(sat::literal l) { if (!m_var2expr.get(l.var())) { - app* aux = m.mk_fresh_const(0, m.mk_bool_sort()); + app* aux = m.mk_fresh_const(nullptr, m.mk_bool_sort()); m_var2expr.set(l.var(), aux); if (!m_gmc) m_gmc = alloc(generic_model_converter, m, "sat2goal"); m_gmc->hide(aux->get_decl()); @@ -1092,7 +1108,7 @@ struct sat2goal::imp { SASSERT(m_lit2expr.get((~l).index()) == 0); app* aux = mc ? mc->var2expr(l.var()) : nullptr; if (!aux) { - aux = m.mk_fresh_const(0, m.mk_bool_sort()); + aux = m.mk_fresh_const(nullptr, m.mk_bool_sort()); if (mc) { mc->insert(l.var(), aux, true); } diff --git a/src/sat/tactic/goal2sat.h b/src/sat/tactic/goal2sat.h index d86b2d7e4..514b65311 100644 --- a/src/sat/tactic/goal2sat.h +++ b/src/sat/tactic/goal2sat.h @@ -86,7 +86,7 @@ public: public: mc(ast_manager& m); - virtual ~mc() {} + ~mc() override {} // flush model converter from SAT solver to this structure. void flush_smc(sat::solver& s, atom2bool_var const& map); void operator()(model_ref& md) override; diff --git a/src/shell/main.cpp b/src/shell/main.cpp index bb1c19b47..3b97d4462 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -26,7 +26,7 @@ Revision History: #include "shell/smtlib_frontend.h" #include "shell/z3_log_frontend.h" #include "util/warning.h" -#include "util/version.h" +#include "util/z3_version.h" #include "shell/dimacs_frontend.h" #include "shell/datalog_frontend.h" #include "shell/opt_frontend.h" @@ -91,7 +91,7 @@ void display_usage() { std::cout << " -pp:name display Z3 parameter description, if 'name' is not provided, then all module names are listed.\n"; std::cout << " --" << " all remaining arguments are assumed to be part of the input file name. This option allows Z3 to read files with strange names such as: -foo.smt2.\n"; std::cout << "\nResources:\n"; - // timeout and memout are now available on Linux and OSX too. + // timeout and memout are now available on Linux and macOS too. std::cout << " -T:timeout set the timeout (in seconds).\n"; std::cout << " -t:timeout set the soft timeout (in milli seconds). It only kills the current query.\n"; std::cout << " -memory:Megabytes set a limit for virtual memory consumption.\n"; diff --git a/src/shell/opt_frontend.cpp b/src/shell/opt_frontend.cpp index 0a2a8f4a1..afa7b79c5 100644 --- a/src/shell/opt_frontend.cpp +++ b/src/shell/opt_frontend.cpp @@ -108,7 +108,8 @@ static unsigned parse_opt(std::istream& in, opt_format f) { unsigned rlimit = std::stoi(gparams::get_value("rlimit")); scoped_timer timer(timeout, &eh); scoped_rlimit _rlimit(m.limit(), rlimit); - lbool r = opt.optimize(); + expr_ref_vector asms(m); + lbool r = opt.optimize(asms); switch (r) { case l_true: std::cout << "sat\n"; break; case l_false: std::cout << "unsat\n"; break; diff --git a/src/smt/arith_eq_solver.cpp b/src/smt/arith_eq_solver.cpp index 128b35dd1..883255b8a 100644 --- a/src/smt/arith_eq_solver.cpp +++ b/src/smt/arith_eq_solver.cpp @@ -12,14 +12,11 @@ Abstract: Author: Nikolaj Bjorner (nbjorner) 2012-02-25 - + --*/ #include "smt/arith_eq_solver.h" -arith_eq_solver::~arith_eq_solver() { -} - arith_eq_solver::arith_eq_solver(ast_manager & m, params_ref const& p): m(m), m_params(p), @@ -93,9 +90,9 @@ void arith_eq_solver::gcd_normalize(vector& values) { if (g.is_zero() || g.is_one()) { return; } - for (unsigned i = 0; i < values.size(); ++i) { - values[i] = values[i] / g; - SASSERT(values[i].is_int()); + for (auto &value : values) { + value /= g; + SASSERT(value.is_int()); } } @@ -116,9 +113,9 @@ unsigned arith_eq_solver::find_abs_min(vector& values) { #ifdef _TRACE static void print_row(std::ostream& out, vector const& row) { - for(unsigned i = 0; i < row.size(); ++i) { - out << row[i] << " "; - } + for(unsigned i = 0; i < row.size(); ++i) { + out << row[i] << " "; + } out << "\n"; } @@ -165,7 +162,7 @@ bool arith_eq_solver::solve_integer_equation( bool& is_fresh ) { - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << "solving: "; print_row(tout, values); ); @@ -174,31 +171,31 @@ bool arith_eq_solver::solve_integer_equation( // // Given: // a1*x1 + a2*x2 + .. + a_n*x_n + a_{n+1} = 0 - // + // // Assume gcd(a1,..,a_n,a_{n+1}) = 1 // Assume gcd(a1,...,a_n) divides a_{n+1} (eg. gcd(a1,..,an) = 1) - // + // // post-condition: values[index] = -1. - // + // // Let a_index be index of least absolute value. // // If |a_index| = 1, then return row and index. // Otherwise: // Let m = |a_index| + 1 // Set - // - // m*x_index' - // = + // + // m*x_index' + // = // ((a1 mod_hat m)*x1 + (a2 mod_hat m)*x2 + .. + (a_n mod_hat m)*x_n + (k mod_hat m)) - // = + // = // (a1'*x1 + a2'*x2 + .. (-)1*x_index + ...) - // + // // <=> Normalize signs so that sign to x_index is -1. // (-)a1'*x1 + (-)a2'*x2 + .. -1*x_index + ... + m*x_index' = 0 - // + // // Return row, where the coefficient to x_index is implicit. // Instead used the coefficient 'm' at position 'index'. - // + // gcd_normalize(values); if (!gcd_test(values)) { @@ -216,8 +213,8 @@ bool arith_eq_solver::solve_integer_equation( return true; } if (a.is_one()) { - for (unsigned i = 0; i < values.size(); ++i) { - values[i].neg(); + for (auto &value : values) { + value.neg(); } } is_fresh = !abs_a.is_one(); @@ -225,19 +222,19 @@ bool arith_eq_solver::solve_integer_equation( if (is_fresh) { numeral m = abs_a + numeral(1); - for (unsigned i = 0; i < values.size(); ++i) { - values[i] = mod_hat(values[i], m); + for (auto &value : values) { + value = mod_hat(value, m); } if (values[index].is_one()) { - for (unsigned i = 0; i < values.size(); ++i) { - values[i].neg(); + for (auto &value : values) { + value.neg(); } } SASSERT(values[index].is_minus_one()); values[index] = m; } - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << "solved at index " << index << ": "; print_row(tout, values); ); @@ -253,7 +250,7 @@ void arith_eq_solver::substitute( ) { SASSERT(1 <= index && index < s.size()); - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << "substitute " << index << ":\n"; print_row(tout, r); print_row(tout, s); @@ -272,21 +269,21 @@ void arith_eq_solver::substitute( // s encodes an equation that contains a variable // with a unit coefficient. // - // Let + // Let // c = r[index] // s = s[index]*x + s'*y = 0 // r = c*x + r'*y = 0 - // - // => + // + // => // // 0 - // = - // -sign(s[index])*c*s + r - // = + // = + // -sign(s[index])*c*s + r + // = // -s[index]*sign(s[index])*c*x - sign(s[index])*c*s'*y + c*x + r'*y - // = + // = // -c*x - sign(s[index])*c*s'*y + c*x + r'*y - // = + // = // -sign(s[index])*c*s'*y + r'*y // numeral sign_s = s[index].is_pos()?numeral(1):numeral(-1); @@ -301,36 +298,36 @@ void arith_eq_solver::substitute( // // s encodes a substitution using an auxiliary variable. // the auxiliary variable is at position 'index'. - // - // Let + // + // Let // c = r[index] // s = s[index]*x + s'*y = 0 // r = c*x + r'*y = 0 // // s encodes : x |-> s[index]*x' + s'*y // - // Set: + // Set: // // r := c*s + r'*y - // + // r[index] = numeral(0); for (unsigned i = 0; i < r.size(); ++i) { r[i] += c*s[i]; - } + } for (unsigned i = r.size(); i < s.size(); ++i) { r.push_back(c*s[i]); } - } + } - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << "result: "; print_row(tout, r); ); } bool arith_eq_solver::solve_integer_equations( - vector& rows, + vector& rows, row& unsat_row ) { @@ -340,10 +337,10 @@ bool arith_eq_solver::solve_integer_equations( // // Naive integer equation solver where only units are eliminated. -// +// bool arith_eq_solver::solve_integer_equations_units( - vector& rows, + vector& rows, row& unsat_row ) { @@ -351,7 +348,7 @@ bool arith_eq_solver::solve_integer_equations_units( TRACE("arith_eq_solver", print_rows(tout << "solving:\n", rows);); unsigned_vector todo, done; - + for (unsigned i = 0; i < rows.size(); ++i) { todo.push_back(i); row& r = rows[i]; @@ -360,9 +357,9 @@ bool arith_eq_solver::solve_integer_equations_units( unsat_row = r; TRACE("arith_eq_solver", print_row(tout << "input is unsat: ", unsat_row); ); return false; - } + } } - for (unsigned i = 0; i < todo.size(); ++i) { + for (unsigned i = 0; i < todo.size(); ++i) { row& r = rows[todo[i]]; gcd_normalize(r); if (!gcd_test(r)) { @@ -388,7 +385,7 @@ bool arith_eq_solver::solve_integer_equations_units( todo.push_back(done[j]); done.erase(done.begin()+j); --j; - } + } } } else { @@ -396,7 +393,7 @@ bool arith_eq_solver::solve_integer_equations_units( } } - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << ((done.size()<=1)?"solved ":"incomplete check ") << done.size() << "\n"; for (unsigned i = 0; i < done.size(); ++i) { print_row(tout, rows[done[i]]); @@ -411,12 +408,12 @@ bool arith_eq_solver::solve_integer_equations_units( // // Partial solver based on the omega test equalities. -// unsatisfiability is not preserved when eliminating +// unsatisfiability is not preserved when eliminating // auxiliary variables. // bool arith_eq_solver::solve_integer_equations_omega( - vector & rows, + vector & rows, row& unsat_row ) { @@ -460,16 +457,16 @@ bool arith_eq_solver::solve_integer_equations_omega( // // solved_row: -x_index + m*sigma + r1 = 0 // unsat_row: k*sigma + r2 = 0 - // - // <=> - // + // + // <=> + // // solved_row: -k*x_index + k*m*sigma + k*r1 = 0 // unsat_row: m*k*sigma + m*r2 = 0 // // => // // m*k*sigma + m*r2 + k*x_index - k*m*sigma - k*r1 = 0 - // + // for (unsigned l = 0; l < unsat_row.size(); ++l) { unsat_row[l] *= m; unsat_row[l] -= k*solved_row[l]; @@ -479,7 +476,7 @@ bool arith_eq_solver::solve_integer_equations_omega( } gcd_normalize(unsat_row); - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << "gcd: "; print_row(tout, solved_row); print_row(tout, unsat_row); @@ -525,18 +522,18 @@ bool arith_eq_solver::solve_integer_equations_omega( // // Eliminate variables by searching for combination of rows where -// the coefficients have gcd = 1. -// +// the coefficients have gcd = 1. +// bool arith_eq_solver::solve_integer_equations_gcd( - vector & rows, + vector & rows, row& unsat_row ) -{ +{ unsigned_vector live, useful, gcd_pos; vector gcds; rational u, v; - + if (rows.empty()) { return true; } @@ -548,7 +545,7 @@ bool arith_eq_solver::solve_integer_equations_gcd( unsat_row = r; TRACE("arith_eq_solver", print_row(tout << "input is unsat: ", unsat_row); ); return false; - } + } } unsigned max_column = rows[0].size(); bool change = true; @@ -579,7 +576,7 @@ bool arith_eq_solver::solve_integer_equations_gcd( if (j == live.size()) { continue; } - + change = true; // found gcd, now identify reduced set of rows with GCD = 1. g = abs(rows[live[j]][i]); @@ -592,7 +589,7 @@ bool arith_eq_solver::solve_integer_equations_gcd( useful.push_back(gcd_pos[j]); g = gcd(g, gcds[j]); SASSERT(j == 0 || gcd(g,gcds[j-1]).is_one()); - } + } } // // we now have a set "useful" of rows whose combined GCD = 1. @@ -600,7 +597,7 @@ bool arith_eq_solver::solve_integer_equations_gcd( // row& r0 = rows[useful[0]]; for (j = 1; j < useful.size(); ++j) { - row& r1 = rows[useful[j]]; + row& r1 = rows[useful[j]]; g = gcd(r0[i], r1[i], u, v); for (unsigned k = 0; k < max_column; ++k) { r0[k] = u*r0[k] + v*r1[k]; @@ -626,7 +623,7 @@ bool arith_eq_solver::solve_integer_equations_gcd( } } - TRACE("arith_eq_solver", + TRACE("arith_eq_solver", tout << ((live.size()<=1)?"solved ":"incomplete check ") << live.size() << "\n"; for (unsigned i = 0; i < live.size(); ++i) { print_row(tout, rows[live[i]]); diff --git a/src/smt/arith_eq_solver.h b/src/smt/arith_eq_solver.h index b2db35ee1..68e58334d 100644 --- a/src/smt/arith_eq_solver.h +++ b/src/smt/arith_eq_solver.h @@ -12,7 +12,7 @@ Abstract: Author: Nikolaj Bjorner (nbjorner) 2012-02-25 - + --*/ #ifndef ARITH_EQ_SOLVER_H_ #define ARITH_EQ_SOLVER_H_ @@ -35,45 +35,45 @@ class arith_eq_solver { void prop_mod_const(expr * e, unsigned depth, numeral const& k, expr_ref& result); - bool gcd_test(vector& value); + bool gcd_test(vector& values); unsigned find_abs_min(vector& values); void gcd_normalize(vector& values); void substitute(vector& r, vector const& s, unsigned index); bool solve_integer_equations_units( - vector > & rows, + vector > & rows, vector& unsat_row ); - + bool solve_integer_equations_omega( - vector > & rows, + vector > & rows, vector& unsat_row ); void compute_hnf(vector >& A); bool solve_integer_equations_hermite( - vector > & rows, + vector > & rows, vector& unsat_row ); bool solve_integer_equations_gcd( - vector > & rows, + vector > & rows, vector& unsat_row ); public: arith_eq_solver(ast_manager & m, params_ref const& p = params_ref()); - ~arith_eq_solver(); + ~arith_eq_solver() = default; // Integer linear solver for a single equation. // The array values contains integer coefficients - // + // // Determine integer solutions to: // // a+k = 0 // // where a = sum_i a_i*k_i - // + // typedef vector row; typedef vector matrix; @@ -90,14 +90,14 @@ public: // a+k = 0 // // where a = sum_i a_i*k_i - // + // // Solution, if there is any, is returned as a substitution. // The return value is "true". // If there is no solution, then return "false". // together with equality "eq_unsat", such that // // eq_unsat = 0 - // + // // is implied and is unsatisfiable over the integers. // diff --git a/src/smt/mam.cpp b/src/smt/mam.cpp index 00e079989..70741fa67 100644 --- a/src/smt/mam.cpp +++ b/src/smt/mam.cpp @@ -2571,6 +2571,7 @@ namespace smt { m_n1 = m_context.get_enode_eq_to(static_cast(m_pc)->m_label, static_cast(m_pc)->m_num_args, m_args.c_ptr()); \ if (m_n1 == 0 || !m_context.is_relevant(m_n1)) \ goto backtrack; \ + update_max_generation(m_n1, nullptr); \ m_registers[static_cast(m_pc)->m_oreg] = m_n1; \ m_pc = m_pc->m_next; \ goto main_loop; diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 3ad20cf90..650afda25 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -63,6 +63,7 @@ void smt_params::updt_params(params_ref const & p) { theory_bv_params::updt_params(p); theory_pb_params::updt_params(p); // theory_array_params::updt_params(p); + theory_datatype_params::updt_params(p); updt_local_params(p); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 3f4105c34..76e9f03b1 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -94,5 +94,6 @@ def_module_params(module_name='smt', ('core.extend_patterns', BOOL, False, 'extend unsat core with literals that trigger (potential) quantifier instances'), ('core.extend_patterns.max_distance', UINT, UINT_MAX, 'limits the distance of a pattern-extended unsat core'), ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body'), - ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none') + ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), + ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy') )) diff --git a/src/smt/params/theory_datatype_params.h b/src/smt/params/theory_datatype_params.h index 9f801e46c..d2d851ffe 100644 --- a/src/smt/params/theory_datatype_params.h +++ b/src/smt/params/theory_datatype_params.h @@ -19,6 +19,8 @@ Revision History: #ifndef THEORY_DATATYPE_PARAMS_H_ #define THEORY_DATATYPE_PARAMS_H_ +#include "smt/params/smt_params_helper.hpp" + struct theory_datatype_params { unsigned m_dt_lazy_splits; @@ -26,11 +28,10 @@ struct theory_datatype_params { m_dt_lazy_splits(1) { } -#if 0 - void register_params(ini_params & p) { - p.register_unsigned_param("dt_lazy_splits", m_dt_lazy_splits, "How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy"); + void updt_params(params_ref const & _p) { + smt_params_helper p(_p); + m_dt_lazy_splits = p.dt_lazy_splits(); } -#endif void display(std::ostream & out) const { out << "m_dt_lazy_splits=" << m_dt_lazy_splits << std::endl; } }; diff --git a/src/smt/smt_arith_value.cpp b/src/smt/smt_arith_value.cpp index ce4c0d9a9..443112ecc 100644 --- a/src/smt/smt_arith_value.cpp +++ b/src/smt/smt_arith_value.cpp @@ -1,4 +1,3 @@ - /*++ Copyright (c) 2018 Microsoft Corporation @@ -17,7 +16,6 @@ Author: Revision History: --*/ -#pragma once; #include "smt/smt_arith_value.h" #include "smt/theory_lra.h" @@ -96,4 +94,10 @@ namespace smt { while (next != n); return false; } + + final_check_status arith_value::final_check() { + family_id afid = a.get_family_id(); + theory * th = m_ctx.get_theory(afid); + return th->final_check_eh(); + } }; diff --git a/src/smt/smt_arith_value.h b/src/smt/smt_arith_value.h index 9b0f833ac..b819b2b9a 100644 --- a/src/smt/smt_arith_value.h +++ b/src/smt/smt_arith_value.h @@ -17,7 +17,7 @@ Author: Revision History: --*/ -#pragma once; +#pragma once #include "ast/arith_decl_plugin.h" #include "smt/smt_context.h" @@ -33,5 +33,6 @@ namespace smt { bool get_lo(expr* e, rational& lo, bool& strict); bool get_up(expr* e, rational& up, bool& strict); bool get_value(expr* e, rational& value); + final_check_status final_check(); }; }; diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index f545f7c6d..8864d6e78 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -565,7 +565,7 @@ namespace smt { return m_asserted_formulas.has_quantifiers(); } - fingerprint * add_fingerprint(void * data, unsigned data_hash, unsigned num_args, enode * const * args, expr* def = 0) { + fingerprint * add_fingerprint(void * data, unsigned data_hash, unsigned num_args, enode * const * args, expr* def = nullptr) { return m_fingerprints.insert(data, data_hash, num_args, args, def); } diff --git a/src/smt/smt_context_pp.cpp b/src/smt/smt_context_pp.cpp index 2a46fd07f..fb67d91d6 100644 --- a/src/smt/smt_context_pp.cpp +++ b/src/smt/smt_context_pp.cpp @@ -426,6 +426,7 @@ namespace smt { std::stringstream strm; strm << "lemma_" << (++m_lemma_id) << ".smt2"; std::ofstream out(strm.str()); + TRACE("lemma", tout << strm.str() << "\n";); display_lemma_as_smt_problem(out, num_antecedents, antecedents, consequent, logic); out.close(); return m_lemma_id; @@ -466,6 +467,7 @@ namespace smt { std::stringstream strm; strm << "lemma_" << (++m_lemma_id) << ".smt2"; std::ofstream out(strm.str()); + TRACE("lemma", tout << strm.str() << "\n";); display_lemma_as_smt_problem(out, num_antecedents, antecedents, num_eq_antecedents, eq_antecedents, consequent, logic); out.close(); return m_lemma_id; diff --git a/src/smt/smt_farkas_util.cpp b/src/smt/smt_farkas_util.cpp index ff415ad0c..f7aaea61b 100644 --- a/src/smt/smt_farkas_util.cpp +++ b/src/smt/smt_farkas_util.cpp @@ -312,6 +312,13 @@ namespace smt { } expr_ref farkas_util::get() { + TRACE("arith", + for (unsigned i = 0; i < m_coeffs.size(); ++i) { + tout << m_coeffs[i] << " * (" << mk_pp(m_ineqs[i].get(), m) << ") "; + } + tout << "\n"; + ); + m_normalize_factor = rational::one(); expr_ref res(m); if (m_coeffs.empty()) { @@ -330,13 +337,12 @@ namespace smt { partition_ineqs(); expr_ref_vector lits(m); unsigned lo = 0; - for (unsigned i = 0; i < m_his.size(); ++i) { - unsigned hi = m_his[i]; + for (unsigned hi : m_his) { lits.push_back(extract_consequence(lo, hi)); lo = hi; } bool_rewriter(m).mk_or(lits.size(), lits.c_ptr(), res); - IF_VERBOSE(2, { if (lits.size() > 1) { verbose_stream() << "combined lemma: " << mk_pp(res, m) << "\n"; } }); + IF_VERBOSE(2, { if (lits.size() > 1) { verbose_stream() << "combined lemma: " << res << "\n"; } }); } else { res = extract_consequence(0, m_coeffs.size()); diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index 1414cb522..618450f9d 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -216,13 +216,17 @@ namespace smt { SASSERT(m_manager.is_bool(n)); if (is_gate(m_manager, n)) { switch(to_app(n)->get_decl_kind()) { - case OP_AND: - UNREACHABLE(); + case OP_AND: { + for (expr * arg : *to_app(n)) { + internalize(arg, true); + literal lit = get_literal(arg); + mk_root_clause(1, &lit, pr); + } + break; + } case OP_OR: { literal_buffer lits; - unsigned num = to_app(n)->get_num_args(); - for (unsigned i = 0; i < num; i++) { - expr * arg = to_app(n)->get_arg(i); + for (expr * arg : *to_app(n)) { internalize(arg, true); lits.push_back(get_literal(arg)); } @@ -294,8 +298,7 @@ namespace smt { sort * s = m_manager.get_sort(n->get_arg(0)); sort_ref u(m_manager.mk_fresh_sort("distinct-elems"), m_manager); func_decl_ref f(m_manager.mk_fresh_func_decl("distinct-aux-f", "", 1, &s, u), m_manager); - for (unsigned i = 0; i < num_args; i++) { - expr * arg = n->get_arg(i); + for (expr * arg : *n) { app_ref fapp(m_manager.mk_app(f, arg), m_manager); app_ref val(m_manager.mk_fresh_const("unique-value", u), m_manager); enode * e = mk_enode(val, false, false, true); @@ -826,9 +829,7 @@ namespace smt { void context::internalize_uninterpreted(app * n) { SASSERT(!e_internalized(n)); // process args - unsigned num = n->get_num_args(); - for (unsigned i = 0; i < num; i++) { - expr * arg = n->get_arg(i); + for (expr * arg : *n) { internalize(arg, false); SASSERT(e_internalized(arg)); } @@ -1542,10 +1543,9 @@ namespace smt { void context::add_and_rel_watches(app * n) { if (relevancy()) { relevancy_eh * eh = m_relevancy_propagator->mk_and_relevancy_eh(n); - unsigned num = n->get_num_args(); - for (unsigned i = 0; i < num; i++) { + for (expr * arg : *n) { // if one child is assigned to false, the and-parent must be notified - literal l = get_literal(n->get_arg(i)); + literal l = get_literal(arg); add_rel_watch(~l, eh); } } @@ -1554,10 +1554,9 @@ namespace smt { void context::add_or_rel_watches(app * n) { if (relevancy()) { relevancy_eh * eh = m_relevancy_propagator->mk_or_relevancy_eh(n); - unsigned num = n->get_num_args(); - for (unsigned i = 0; i < num; i++) { + for (expr * arg : *n) { // if one child is assigned to true, the or-parent must be notified - literal l = get_literal(n->get_arg(i)); + literal l = get_literal(arg); add_rel_watch(l, eh); } } @@ -1588,9 +1587,8 @@ namespace smt { TRACE("mk_and_cnstr", tout << "l: "; display_literal(tout, l); tout << "\n";); literal_buffer buffer; buffer.push_back(l); - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - literal l_arg = get_literal(n->get_arg(i)); + for (expr * arg : *n) { + literal l_arg = get_literal(arg); TRACE("mk_and_cnstr", tout << "l_arg: "; display_literal(tout, l_arg); tout << "\n";); mk_gate_clause(~l, l_arg); buffer.push_back(~l_arg); @@ -1602,9 +1600,8 @@ namespace smt { literal l = get_literal(n); literal_buffer buffer; buffer.push_back(~l); - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - literal l_arg = get_literal(n->get_arg(i)); + for (expr * arg : *n) { + literal l_arg = get_literal(arg); mk_gate_clause(l, ~l_arg); buffer.push_back(l_arg); } diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 0fea4d13d..02b0e16be 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -47,6 +47,7 @@ namespace smt { m_model_finder(mf), m_max_cexs(1), m_iteration_idx(0), + m_has_rec_fun(false), m_curr_model(nullptr), m_pinned_exprs(m) { } @@ -351,9 +352,7 @@ namespace smt { bool model_checker::check_rec_fun(quantifier* q, bool strict_rec_fun) { TRACE("model_checker", tout << mk_pp(q, m) << "\n";); SASSERT(q->get_num_patterns() == 2); // first pattern is the function, second is the body. - expr* fn = to_app(q->get_pattern(0))->get_arg(0); - SASSERT(is_app(fn)); - func_decl* f = to_app(fn)->get_decl(); + func_decl* f = m.get_rec_fun_decl(q); expr_ref_vector args(m); unsigned num_decls = q->get_num_decls(); @@ -433,7 +432,7 @@ namespace smt { TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); m_max_cexs += m_params.m_mbqi_max_cexs; - if (num_failures == 0 && !m_context->validate_model()) { + if (num_failures == 0 && (!m_context->validate_model() || has_rec_under_quantifiers())) { num_failures = 1; // this time force expanding recursive function definitions // that are not forced true in the current model. @@ -450,6 +449,43 @@ namespace smt { return num_failures == 0; } + struct has_rec_fun_proc { + obj_hashtable& m_rec_funs; + bool m_has_rec_fun; + + bool has_rec_fun() const { return m_has_rec_fun; } + + has_rec_fun_proc(obj_hashtable& rec_funs): + m_rec_funs(rec_funs), + m_has_rec_fun(false) {} + + void operator()(app* fn) { + m_has_rec_fun |= m_rec_funs.contains(fn->get_decl()); + } + void operator()(expr*) {} + }; + + bool model_checker::has_rec_under_quantifiers() { + if (!m_has_rec_fun) { + return false; + } + obj_hashtable rec_funs; + for (quantifier * q : *m_qm) { + if (m.is_rec_fun_def(q)) { + rec_funs.insert(m.get_rec_fun_decl(q)); + } + } + expr_fast_mark1 visited; + has_rec_fun_proc proc(rec_funs); + for (quantifier * q : *m_qm) { + if (!m.is_rec_fun_def(q)) { + quick_for_each_expr(proc, visited, q); + if (proc.has_rec_fun()) return true; + } + } + return false; + } + // // (repeated from defined_names.cpp) // NB. The pattern for lambdas is incomplete. @@ -479,6 +515,7 @@ namespace smt { } found_relevant = true; if (m.is_rec_fun_def(q)) { + m_has_rec_fun = true; if (!check_rec_fun(q, strict_rec_fun)) { TRACE("model_checker", tout << "checking recursive function failed\n";); num_failures++; @@ -540,7 +577,7 @@ namespace smt { } if (inst.m_def) { - m_context->internalize_assertion(inst.m_def, 0, gen); + m_context->internalize_assertion(inst.m_def, nullptr, gen); } TRACE("model_checker_bug_detail", tout << "instantiating... q:\n" << mk_pp(q, m) << "\n"; diff --git a/src/smt/smt_model_checker.h b/src/smt/smt_model_checker.h index 40a58efea..57edf3034 100644 --- a/src/smt/smt_model_checker.h +++ b/src/smt/smt_model_checker.h @@ -51,8 +51,10 @@ namespace smt { scoped_ptr m_aux_context; // Auxiliary context used for model checking quantifiers. unsigned m_max_cexs; unsigned m_iteration_idx; + bool m_has_rec_fun; proto_model * m_curr_model; obj_map m_value2expr; + friend class instantiation_set; void init_aux_context(); @@ -64,6 +66,7 @@ namespace smt { bool add_blocking_clause(model * cex, expr_ref_vector & sks); bool check(quantifier * q); bool check_rec_fun(quantifier* q, bool strict_rec_fun); + bool has_rec_under_quantifiers(); void check_quantifiers(bool strict_rec_fun, bool& found_relevant, unsigned& num_failures); struct instance { diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index 9d5a4f49f..b791d890e 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -36,6 +36,7 @@ namespace smt { unsigned_vector m_var2enode_lim; friend class context; + friend class arith_value; protected: virtual void init(context * ctx); diff --git a/src/smt/tactic/smt_tactic.cpp b/src/smt/tactic/smt_tactic.cpp index 50fe47985..6aa365383 100644 --- a/src/smt/tactic/smt_tactic.cpp +++ b/src/smt/tactic/smt_tactic.cpp @@ -217,7 +217,7 @@ public: model_ref md; m_ctx->get_model(md); buffer r; - m_ctx->get_relevant_labels(0, r); + m_ctx->get_relevant_labels(nullptr, r); labels_vec rv; rv.append(r.size(), r.c_ptr()); model_converter_ref mc; @@ -270,7 +270,7 @@ public: model_ref md; m_ctx->get_model(md); buffer r; - m_ctx->get_relevant_labels(0, r); + m_ctx->get_relevant_labels(nullptr, r); labels_vec rv; rv.append(r.size(), r.c_ptr()); in->add(model_and_labels2model_converter(md.get(), rv)); diff --git a/src/smt/theory_arith_aux.h b/src/smt/theory_arith_aux.h index 9526637e9..89737fb42 100644 --- a/src/smt/theory_arith_aux.h +++ b/src/smt/theory_arith_aux.h @@ -1103,6 +1103,7 @@ namespace smt { e = m_util.mk_gt(obj, e); } } + TRACE("opt", tout << e << "\n";); return e; } @@ -1119,6 +1120,8 @@ namespace smt { std::ostringstream strm; strm << val << " <= " << mk_pp(get_enode(v)->get_owner(), get_manager()); app* b = m.mk_const(symbol(strm.str().c_str()), m.mk_bool_sort()); + expr_ref result(b, m); + TRACE("opt", tout << result << "\n";); if (!ctx.b_internalized(b)) { fm.hide(b->get_decl()); bool_var bv = ctx.mk_bool_var(b); @@ -1133,7 +1136,7 @@ namespace smt { TRACE("arith", tout << mk_pp(b, m) << "\n"; display_atom(tout, a, false);); } - return expr_ref(b, m); + return result; } diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 1eb91f1fc..4015fea9a 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -484,7 +484,6 @@ namespace smt { void theory_arith::mk_idiv_mod_axioms(expr * dividend, expr * divisor) { if (!m_util.is_zero(divisor)) { ast_manager & m = get_manager(); - bool is_numeral = m_util.is_numeral(divisor); // if divisor is zero, then idiv and mod are uninterpreted functions. expr_ref div(m), mod(m), zero(m), abs_divisor(m), one(m); expr_ref eqz(m), eq(m), lower(m), upper(m); @@ -504,9 +503,9 @@ namespace smt { tout << "lower: " << lower << "\n"; tout << "upper: " << upper << "\n";); - mk_axiom(eqz, eq, !is_numeral); - mk_axiom(eqz, lower, !is_numeral); - mk_axiom(eqz, upper, !is_numeral); + mk_axiom(eqz, eq, true); + mk_axiom(eqz, lower, false); + mk_axiom(eqz, upper, !m_util.is_numeral(abs_divisor)); rational k; context& ctx = get_context(); (void)ctx; @@ -3311,7 +3310,7 @@ namespace smt { } template - bool theory_arith::get_upper(enode * n, rational& r, bool& is_strict) { + bool theory_arith::get_upper(enode * n, rational& r, bool& is_strict) { theory_var v = n->get_th_var(get_id()); bound* b = (v == null_theory_var) ? nullptr : upper(v); return b && (r = b->get_value().get_rational().to_rational(), is_strict = b->get_value().get_infinitesimal().is_neg(), true); diff --git a/src/smt/theory_arith_int.h b/src/smt/theory_arith_int.h index ee3bd5e2e..afe527a98 100644 --- a/src/smt/theory_arith_int.h +++ b/src/smt/theory_arith_int.h @@ -396,7 +396,9 @@ namespace smt { for (; it != end; ++it) { if (!it->is_dead() && it->m_var != b && is_free(it->m_var)) { theory_var v = it->m_var; - expr * bound = m_util.mk_ge(get_enode(v)->get_owner(), m_util.mk_numeral(rational::zero(), is_int(v))); + expr* e = get_enode(v)->get_owner(); + bool _is_int = m_util.is_int(e); + expr * bound = m_util.mk_ge(e, m_util.mk_numeral(rational::zero(), _is_int)); context & ctx = get_context(); ctx.internalize(bound, true); ctx.mark_as_relevant(bound); diff --git a/src/smt/theory_arith_pp.h b/src/smt/theory_arith_pp.h index 59d386292..b6ddaecf8 100644 --- a/src/smt/theory_arith_pp.h +++ b/src/smt/theory_arith_pp.h @@ -544,7 +544,7 @@ namespace smt { char buffer[128]; static int id = 0; #ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "arith_%d.smt", id); + sprintf_s(buffer, Z3_ARRAYSIZE(buffer), "arith_%d.smt", id); #else sprintf(buffer, "arith_%d.smt", id); #endif diff --git a/src/smt/theory_array_full.cpp b/src/smt/theory_array_full.cpp index d872997c4..d0a37175d 100644 --- a/src/smt/theory_array_full.cpp +++ b/src/smt/theory_array_full.cpp @@ -339,18 +339,14 @@ namespace smt { SASSERT(v != null_theory_var); v = find(v); var_data* d = m_var_data[v]; + TRACE("array", tout << "v" << v << " " << d->m_prop_upward << " " << m_params.m_array_delay_exp_axiom << "\n";); for (enode * store : d->m_stores) { SASSERT(is_store(store)); instantiate_default_store_axiom(store); } if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { - for (enode * store : d->m_parent_stores) { - SASSERT(is_store(store)); - if (!m_params.m_array_cg || store->is_cgr()) { - instantiate_default_store_axiom(store); - } - } + instantiate_parent_stores_default(v); } } @@ -381,14 +377,13 @@ namespace smt { } void theory_array_full::relevant_eh(app* n) { - TRACE("array", tout << mk_pp(n, get_manager()) << "\n";); + TRACE("array", tout << mk_pp(n, get_manager()) << "\n";); theory_array::relevant_eh(n); - if (!is_default(n) && !is_select(n) && !is_map(n) && !is_const(n) && !is_as_array(n)) { + if (!is_default(n) && !is_select(n) && !is_map(n) && !is_const(n) && !is_as_array(n)){ return; } context & ctx = get_context(); enode* node = ctx.get_enode(n); - if (is_select(n)) { enode * arg = ctx.get_enode(n->get_arg(0)); theory_var v = arg->get_th_var(get_id()); @@ -399,14 +394,18 @@ namespace smt { enode * arg = ctx.get_enode(n->get_arg(0)); theory_var v = arg->get_th_var(get_id()); SASSERT(v != null_theory_var); + set_prop_upward(v); add_parent_default(find(v)); } else if (is_const(n)) { instantiate_default_const_axiom(node); + theory_var v = node->get_th_var(get_id()); + set_prop_upward(v); + add_parent_default(find(v)); } else if (is_map(n)) { - for (unsigned i = 0; i < n->get_num_args(); ++i) { - enode* arg = ctx.get_enode(n->get_arg(i)); + for (expr * e : *n) { + enode* arg = ctx.get_enode(e); theory_var v_arg = find(arg->get_th_var(get_id())); add_parent_map(v_arg, node); set_prop_upward(v_arg); @@ -496,7 +495,7 @@ namespace smt { app* map = mp->get_owner(); ast_manager& m = get_manager(); context& ctx = get_context(); - if (!ctx.add_fingerprint(this, 0, 1, &mp)) { + if (!ctx.add_fingerprint(this, m_default_map_fingerprint, 1, &mp)) { return false; } TRACE("array", tout << mk_bounded_pp(map, get_manager()) << "\n";); @@ -521,7 +520,7 @@ namespace smt { bool theory_array_full::instantiate_default_const_axiom(enode* cnst) { context& ctx = get_context(); - if (!ctx.add_fingerprint(this, 0, 1, &cnst)) { + if (!ctx.add_fingerprint(this, m_default_const_fingerprint, 1, &cnst)) { return false; } m_stats.m_num_default_const_axiom++; @@ -542,7 +541,7 @@ namespace smt { return false; #if 0 context& ctx = get_context(); - if (!ctx.add_fingerprint(this, 0, 1, &arr)) { + if (!ctx.add_fingerprint(this, m_default_as_array_fingerprint, 1, &arr)) { return false; } m_stats.m_num_default_as_array_axiom++; @@ -659,7 +658,7 @@ namespace smt { app* store_app = store->get_owner(); context& ctx = get_context(); ast_manager& m = get_manager(); - if (!ctx.add_fingerprint(this, 0, 1, &store)) { + if (!ctx.add_fingerprint(this, m_default_store_fingerprint, 1, &store)) { return false; } @@ -732,6 +731,10 @@ namespace smt { var_data * d = m_var_data[v]; if (d->m_prop_upward && instantiate_axiom_map_for(v)) r = FC_CONTINUE; + if (d->m_prop_upward && !m_params.m_array_weak) { + if (instantiate_parent_stores_default(v)) + r = FC_CONTINUE; + } } } while (!m_eqsv.empty()) { @@ -746,6 +749,22 @@ namespace smt { return r; } + bool theory_array_full::instantiate_parent_stores_default(theory_var v) { + SASSERT(v != null_theory_var); + TRACE("array", tout << "v" << v << "\n";); + v = find(v); + var_data* d = m_var_data[v]; + bool result = false; + for (enode * store : d->m_parent_stores) { + TRACE("array", tout << expr_ref(store->get_owner(), get_manager()) << "\n";); + SASSERT(is_store(store)); + if (!m_params.m_array_cg || store->is_cgr()) { + if (instantiate_default_store_axiom(store)) + result = true; + } + } + return result; + } bool theory_array_full::try_assign_eq(expr* v1, expr* v2) { TRACE("array", diff --git a/src/smt/theory_array_full.h b/src/smt/theory_array_full.h index 3216b4286..b249ec0b4 100644 --- a/src/smt/theory_array_full.h +++ b/src/smt/theory_array_full.h @@ -38,6 +38,11 @@ namespace smt { ast2ast_trailmap m_sort2epsilon; obj_pair_map m_eqs; svector m_eqsv; + + static unsigned const m_default_map_fingerprint = UINT_MAX - 112; + static unsigned const m_default_store_fingerprint = UINT_MAX - 113; + static unsigned const m_default_const_fingerprint = UINT_MAX - 115; + static unsigned const m_default_as_array_fingerprint = UINT_MAX - 116; protected: @@ -70,6 +75,7 @@ namespace smt { bool instantiate_default_store_axiom(enode* store); bool instantiate_default_map_axiom(enode* map); bool instantiate_default_as_array_axiom(enode* arr); + bool instantiate_parent_stores_default(theory_var v); bool has_large_domain(app* array_term); app* mk_epsilon(sort* s); diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 6c66de91d..24309ba88 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -55,7 +55,7 @@ std::ostream& operator<<(std::ostream& out, bound_kind const& k) { } class bound { - smt::bool_var m_bv; + smt::bool_var m_bv; smt::theory_var m_var; bool m_is_int; rational m_value; @@ -129,6 +129,7 @@ class theory_lra::imp { struct scope { unsigned m_bounds_lim; + unsigned m_idiv_lim; unsigned m_asserted_qhead; unsigned m_asserted_atoms_lim; unsigned m_underspecified_lim; @@ -146,7 +147,7 @@ class theory_lra::imp { imp& m_imp; public: resource_limit(imp& i): m_imp(i) { } - virtual bool get_cancel_flag() { return m_imp.m.canceled(); } + bool get_cancel_flag() override { return m_imp.m.canceled(); } }; @@ -154,6 +155,7 @@ class theory_lra::imp { ast_manager& m; theory_arith_params& m_arith_params; arith_util a; + bool m_has_int; arith_eq_adapter m_arith_eq_adapter; vector m_columns; @@ -163,13 +165,13 @@ class theory_lra::imp { expr_ref_vector m_terms; vector m_coeffs; svector m_vars; - rational m_coeff; + rational m_offset; ptr_vector m_terms_to_internalize; internalize_state(ast_manager& m): m_terms(m) {} void reset() { m_terms.reset(); m_coeffs.reset(); - m_coeff.reset(); + m_offset.reset(); m_vars.reset(); m_terms_to_internalize.reset(); } @@ -195,7 +197,7 @@ class theory_lra::imp { expr_ref_vector& terms() { return m_st.m_terms; } vector& coeffs() { return m_st.m_coeffs; } svector& vars() { return m_st.m_vars; } - rational& coeff() { return m_st.m_coeff; } + rational& offset() { return m_st.m_offset; } ptr_vector& terms_to_internalize() { return m_st.m_terms_to_internalize; } void push(expr* e, rational c) { m_st.m_terms.push_back(e); m_st.m_coeffs.push_back(c); } void set_back(unsigned i) { @@ -214,6 +216,10 @@ class theory_lra::imp { svector m_term_index2theory_var; // reverse map from lp_solver variables to theory variables var_coeffs m_left_side; // constraint left side mutable std::unordered_map m_variable_values; // current model + lp::var_index m_one_var; + lp::var_index m_zero_var; + lp::var_index m_rone_var; + lp::var_index m_rzero_var; enum constraint_source { inequality_source, @@ -229,6 +235,7 @@ class theory_lra::imp { svector m_asserted_atoms; expr* m_not_handled; ptr_vector m_underspecified; + ptr_vector m_idiv_terms; unsigned_vector m_var_trail; vector > m_use_list; // bounds where variables are used. @@ -328,6 +335,31 @@ class theory_lra::imp { } } + lp::var_index add_const(int c, lp::var_index& var, bool is_int) { + if (var != UINT_MAX) { + return var; + } + app_ref cnst(a.mk_numeral(rational(c), is_int), m); + TRACE("arith", tout << "add " << cnst << "\n";); + mk_enode(cnst); + theory_var v = mk_var(cnst); + var = m_solver->add_var(v, true); + m_theory_var2var_index.setx(v, var, UINT_MAX); + m_var_index2theory_var.setx(var, v, UINT_MAX); + m_var_trail.push_back(v); + add_def_constraint(m_solver->add_var_bound(var, lp::GE, rational(c))); + add_def_constraint(m_solver->add_var_bound(var, lp::LE, rational(c))); + return var; + } + + lp::var_index get_one(bool is_int) { + return add_const(1, is_int ? m_one_var : m_rone_var, is_int); + } + + lp::var_index get_zero(bool is_int) { + return add_const(0, is_int ? m_zero_var : m_rzero_var, is_int); + } + void found_not_handled(expr* n) { m_not_handled = n; @@ -372,7 +404,7 @@ class theory_lra::imp { expr_ref_vector & terms = st.terms(); svector& vars = st.vars(); vector& coeffs = st.coeffs(); - rational& coeff = st.coeff(); + rational& offset = st.offset(); rational r; expr* n1, *n2; unsigned index = 0; @@ -412,7 +444,7 @@ class theory_lra::imp { ++index; } else if (a.is_numeral(n, r)) { - coeff += coeffs[index]*r; + offset += coeffs[index]*r; ++index; } else if (a.is_uminus(n, n1)) { @@ -424,7 +456,6 @@ class theory_lra::imp { app* t = to_app(n); internalize_args(t); mk_enode(t); - theory_var v = mk_var(n); coeffs[vars.size()] = coeffs[index]; vars.push_back(v); @@ -442,6 +473,7 @@ class theory_lra::imp { } else if (a.is_idiv(n, n1, n2)) { if (!a.is_numeral(n2, r) || r.is_zero()) found_not_handled(n); + m_idiv_terms.push_back(n); app * mod = a.mk_mod(n1, n2); ctx().internalize(mod, false); if (ctx().relevancy()) ctx().add_relevancy_dependency(n, mod); @@ -451,6 +483,7 @@ class theory_lra::imp { if (!is_num) { found_not_handled(n); } +#if 0 else { app_ref div(a.mk_idiv(n1, n2), m); mk_enode(div); @@ -461,7 +494,8 @@ class theory_lra::imp { // abs(r) > v >= 0 assert_idiv_mod_axioms(u, v, w, r); } - if (!ctx().relevancy() && !is_num) mk_idiv_mod_axioms(n1, n2); +#endif + if (!ctx().relevancy()) mk_idiv_mod_axioms(n1, n2); } else if (a.is_rem(n, n1, n2)) { if (!a.is_numeral(n2, r) || r.is_zero()) found_not_handled(n); @@ -544,6 +578,7 @@ class theory_lra::imp { } enode * mk_enode(app * n) { + TRACE("arith", tout << expr_ref(n, m) << "\n";); if (ctx().e_internalized(n)) { return get_enode(n); } @@ -624,6 +659,7 @@ class theory_lra::imp { } if (result == UINT_MAX) { result = m_solver->add_var(v, is_int(v)); + m_has_int |= is_int(v); m_theory_var2var_index.setx(v, result, UINT_MAX); m_var_index2theory_var.setx(result, v, UINT_MAX); m_var_trail.push_back(v); @@ -677,7 +713,7 @@ class theory_lra::imp { m_constraint_sources.setx(index, inequality_source, null_source); m_inequalities.setx(index, lit, null_literal); ++m_stats.m_add_rows; - TRACE("arith", m_solver->print_constraint(index, tout); tout << "\n";); + TRACE("arith", m_solver->print_constraint(index, tout) << "\n";); } void add_def_constraint(lp::constraint_index index) { @@ -692,9 +728,7 @@ class theory_lra::imp { ++m_stats.m_add_rows; } - void internalize_eq(theory_var v1, theory_var v2) { - enode* n1 = get_enode(v1); - enode* n2 = get_enode(v2); + void internalize_eq(theory_var v1, theory_var v2) { app_ref term(m.mk_fresh_const("eq", a.mk_real()), m); scoped_internalize_state st(*this); st.vars().push_back(v1); @@ -707,8 +741,8 @@ class theory_lra::imp { add_def_constraint(m_solver->add_var_bound(vi, lp::GE, rational::zero())); TRACE("arith", { - expr* o1 = n1->get_owner(); - expr* o2 = n2->get_owner(); + expr* o1 = get_enode(v1)->get_owner(); + expr* o2 = get_enode(v2)->get_owner(); tout << "v" << v1 << " = " << "v" << v2 << ": " << mk_pp(o1, m) << " = " << mk_pp(o2, m) << "\n"; }); @@ -733,10 +767,19 @@ class theory_lra::imp { } bool is_unit_var(scoped_internalize_state& st) { - return st.coeff().is_zero() && st.vars().size() == 1 && st.coeffs()[0].is_one(); + return st.offset().is_zero() && st.vars().size() == 1 && st.coeffs()[0].is_one(); + } + + bool is_one(scoped_internalize_state& st) { + return st.offset().is_one() && st.vars().empty(); + } + + bool is_zero(scoped_internalize_state& st) { + return st.offset().is_zero() && st.vars().empty(); } theory_var internalize_def(app* term, scoped_internalize_state& st) { + TRACE("arith", tout << expr_ref(term, m) << "\n";); if (ctx().e_internalized(term)) { IF_VERBOSE(0, verbose_stream() << "repeated term\n";); return mk_var(term, false); @@ -766,13 +809,24 @@ class theory_lra::imp { if (is_unit_var(st)) { return st.vars()[0]; } + else if (is_one(st)) { + return get_one(a.is_int(term)); + } + else if (is_zero(st)) { + return get_zero(a.is_int(term)); + } else { init_left_side(st); theory_var v = mk_var(term); lp::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); - TRACE("arith", tout << mk_pp(term, m) << " " << v << " " << vi << "\n";); + TRACE("arith", tout << mk_pp(term, m) << " v" << v << "\n";); if (vi == UINT_MAX) { - vi = m_solver->add_term(m_left_side, st.coeff()); + rational const& offset = st.offset(); + if (!offset.is_zero()) { + m_left_side.push_back(std::make_pair(offset, get_one(a.is_int(term)))); + } + SASSERT(!m_left_side.empty()); + vi = m_solver->add_term(m_left_side); m_theory_var2var_index.setx(v, vi, UINT_MAX); if (m_solver->is_term(vi)) { m_term_index2theory_var.setx(m_solver->adjust_term_index(vi), v, UINT_MAX); @@ -782,7 +836,7 @@ class theory_lra::imp { } m_var_trail.push_back(v); TRACE("arith_verbose", tout << "v" << v << " := " << mk_pp(term, m) << " slack: " << vi << " scopes: " << m_scopes.size() << "\n"; - m_solver->print_term(m_solver->get_term(vi), tout); tout << "\n";); + m_solver->print_term(m_solver->get_term(vi), tout) << "\n";); } rational val; if (a.is_numeral(term, val)) { @@ -798,9 +852,14 @@ public: th(th), m(m), m_arith_params(ap), a(m), + m_has_int(false), m_arith_eq_adapter(th, ap, a), m_internalize_head(0), - m_not_handled(0), + m_one_var(UINT_MAX), + m_zero_var(UINT_MAX), + m_rone_var(UINT_MAX), + m_rzero_var(UINT_MAX), + m_not_handled(nullptr), m_asserted_qhead(0), m_assume_eq_head(0), m_num_conflicts(0), @@ -871,16 +930,18 @@ public: } return true; } + + bool is_arith(enode* n) { + return n && n->get_th_var(get_id()) != null_theory_var; + } void internalize_eq_eh(app * atom, bool_var) { - expr* lhs = 0, *rhs = 0; + TRACE("arith_verbose", tout << mk_pp(atom, m) << "\n";); + expr* lhs = nullptr, *rhs = nullptr; VERIFY(m.is_eq(atom, lhs, rhs)); enode * n1 = get_enode(lhs); enode * n2 = get_enode(rhs); - if (n1->get_th_var(get_id()) != null_theory_var && - n2->get_th_var(get_id()) != null_theory_var && - n1 != n2) { - TRACE("arith_verbose", tout << mk_pp(atom, m) << "\n";); + if (is_arith(n1) && is_arith(n2) && n1 != n2) { m_arith_eq_adapter.mk_axioms(n1, n2); } } @@ -910,6 +971,7 @@ public: scope& s = m_scopes.back(); s.m_bounds_lim = m_bounds_trail.size(); s.m_asserted_qhead = m_asserted_qhead; + s.m_idiv_lim = m_idiv_terms.size(); s.m_asserted_atoms_lim = m_asserted_atoms.size(); s.m_not_handled = m_not_handled; s.m_underspecified_lim = m_underspecified.size(); @@ -935,6 +997,7 @@ public: } m_theory_var2var_index[m_var_trail[i]] = UINT_MAX; } + m_idiv_terms.shrink(m_scopes[old_size].m_idiv_lim); m_asserted_atoms.shrink(m_scopes[old_size].m_asserted_atoms_lim); m_asserted_qhead = m_scopes[old_size].m_asserted_qhead; m_underspecified.shrink(m_scopes[old_size].m_underspecified_lim); @@ -991,7 +1054,7 @@ public: // to_int (to_real x) = x // to_real(to_int(x)) <= x < to_real(to_int(x)) + 1 void mk_to_int_axiom(app* n) { - expr* x = 0, *y = 0; + expr* x = nullptr, *y = nullptr; VERIFY (a.is_to_int(n, x)); if (a.is_to_real(x, y)) { mk_axiom(th.mk_eq(y, n, false)); @@ -1007,7 +1070,7 @@ public: // is_int(x) <=> to_real(to_int(x)) = x void mk_is_int_axiom(app* n) { - expr* x = 0; + expr* x = nullptr; VERIFY(a.is_is_int(n, x)); literal eq = th.mk_eq(a.mk_to_real(a.mk_to_int(x)), x, false); literal is_int = ctx().get_literal(n); @@ -1030,37 +1093,74 @@ public: add_def_constraint(m_solver->add_var_bound(vi, lp::LE, rational::zero())); add_def_constraint(m_solver->add_var_bound(get_var_index(v), lp::GE, rational::zero())); add_def_constraint(m_solver->add_var_bound(get_var_index(v), lp::LT, abs(r))); + TRACE("arith", m_solver->print_constraints(tout << term << "\n");); } void mk_idiv_mod_axioms(expr * p, expr * q) { if (a.is_zero(q)) { return; } + TRACE("arith", tout << expr_ref(p, m) << " " << expr_ref(q, m) << "\n";); // if q is zero, then idiv and mod are uninterpreted functions. expr_ref div(a.mk_idiv(p, q), m); expr_ref mod(a.mk_mod(p, q), m); expr_ref zero(a.mk_int(0), m); - literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); - literal q_le_0 = mk_literal(a.mk_le(q, zero)); - // literal eqz = th.mk_eq(q, zero, false); literal eq = th.mk_eq(a.mk_add(a.mk_mul(q, div), mod), p, false); literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); - // q >= 0 or p = (p mod q) + q * (p div q) - // q <= 0 or p = (p mod q) + q * (p div q) - // q >= 0 or (p mod q) >= 0 - // q <= 0 or (p mod q) >= 0 - // q <= 0 or (p mod q) < q - // q >= 0 or (p mod q) < -q - // enable_trace("mk_bool_var"); - mk_axiom(q_ge_0, eq); - mk_axiom(q_le_0, eq); - mk_axiom(q_ge_0, mod_ge_0); - mk_axiom(q_le_0, mod_ge_0); - mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); - mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); - rational k; - if (m_arith_params.m_arith_enum_const_mod && a.is_numeral(q, k) && - k.is_pos() && k < rational(8)) { + literal div_ge_0 = mk_literal(a.mk_ge(div, zero)); + literal div_le_0 = mk_literal(a.mk_le(div, zero)); + literal p_ge_0 = mk_literal(a.mk_ge(p, zero)); + literal p_le_0 = mk_literal(a.mk_le(p, zero)); + + rational k(0); + expr_ref upper(m); + + if (a.is_numeral(q, k)) { + if (k.is_pos()) { + upper = a.mk_numeral(k - 1, true); + } + else if (k.is_neg()) { + upper = a.mk_numeral(-k - 1, true); + } + } + else { + k = rational::zero(); + } + + if (!k.is_zero()) { + mk_axiom(eq); + mk_axiom(mod_ge_0); + mk_axiom(mk_literal(a.mk_le(mod, upper))); + if (k.is_pos()) { + mk_axiom(~p_ge_0, div_ge_0); + mk_axiom(~p_le_0, div_le_0); + } + else { + mk_axiom(~p_ge_0, div_le_0); + mk_axiom(~p_le_0, div_ge_0); + } + } + else { + // q >= 0 or p = (p mod q) + q * (p div q) + // q <= 0 or p = (p mod q) + q * (p div q) + // q >= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) >= 0 + // q <= 0 or (p mod q) < q + // q >= 0 or (p mod q) < -q + literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); + literal q_le_0 = mk_literal(a.mk_le(q, zero)); + mk_axiom(q_ge_0, eq); + mk_axiom(q_le_0, eq); + mk_axiom(q_ge_0, mod_ge_0); + mk_axiom(q_le_0, mod_ge_0); + mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); + mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); + mk_axiom(q_le_0, ~p_ge_0, div_ge_0); + mk_axiom(q_le_0, ~p_le_0, div_le_0); + mk_axiom(q_ge_0, ~p_ge_0, div_le_0); + mk_axiom(q_ge_0, ~p_le_0, div_ge_0); + } + if (m_arith_params.m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { unsigned _k = k.get_unsigned(); literal_buffer lits; for (unsigned j = 0; j < _k; ++j) { @@ -1152,7 +1252,6 @@ public: m_todo_terms.pop_back(); if (m_solver->is_term(vi)) { const lp::lar_term& term = m_solver->get_term(vi); - result += term.m_v * coeff; for (const auto & i: term) { m_todo_terms.push_back(std::make_pair(i.var(), coeff * i.coeff())); } @@ -1189,7 +1288,6 @@ public: m_todo_terms.pop_back(); if (m_solver->is_term(wi)) { const lp::lar_term& term = m_solver->get_term(wi); - result += term.m_v * coeff; for (const auto & i : term) { if (m_variable_values.count(i.var()) > 0) { result += m_variable_values[i.var()] * coeff * i.coeff(); @@ -1208,10 +1306,10 @@ public: } void init_variable_values() { + reset_variable_values(); if (!m.canceled() && m_solver.get() && th.get_num_vars() > 0) { - reset_variable_values(); + TRACE("arith", tout << "update variable values\n";); m_solver->get_model(m_variable_values); - TRACE("arith", display(tout);); } } @@ -1314,6 +1412,7 @@ public: } final_check_status final_check_eh() { + IF_VERBOSE(2, verbose_stream() << "final-check " << m_solver->get_status() << "\n"); m_use_nra_model = false; lbool is_sat = l_true; if (m_solver->get_status() != lp::lp_status::OPTIMAL) { @@ -1328,7 +1427,7 @@ public: } if (assume_eqs()) { return FC_CONTINUE; - } + } switch (check_lia()) { case l_true: @@ -1340,7 +1439,7 @@ public: st = FC_CONTINUE; break; } - + switch (check_nra()) { case l_true: break; @@ -1351,7 +1450,7 @@ public: st = FC_GIVEUP; break; } - if (m_not_handled != 0) { + if (m_not_handled != nullptr) { TRACE("arith", tout << "unhandled operator " << mk_pp(m_not_handled, m) << "\n";); st = FC_GIVEUP; } @@ -1378,6 +1477,18 @@ public: u_map coeffs; term2coeffs(term, coeffs, rational::one(), offset); offset.neg(); + TRACE("arith", + m_solver->print_term(term, tout << "term: ") << "\n"; + for (auto const& kv : coeffs) { + tout << "v" << kv.m_key << " * " << kv.m_value << "\n"; + } + tout << offset << "\n"; + rational g(0); + for (auto const& kv : coeffs) { + g = gcd(g, kv.m_value); + } + tout << "gcd: " << g << "\n"; + ); if (is_int) { // 3x + 6y >= 5 -> x + 3y >= 5/3, then x + 3y >= 2 // 3x + 6y <= 5 -> x + 3y <= 1 @@ -1385,10 +1496,12 @@ public: rational g = gcd_reduce(coeffs); if (!g.is_one()) { if (lower_bound) { - offset = div(offset + g - rational::one(), g); + TRACE("arith", tout << "lower: " << offset << " / " << g << " = " << offset / g << " >= " << ceil(offset / g) << "\n";); + offset = ceil(offset / g); } else { - offset = div(offset, g); + TRACE("arith", tout << "upper: " << offset << " / " << g << " = " << offset / g << " <= " << floor(offset / g) << "\n";); + offset = floor(offset / g); } } } @@ -1408,27 +1521,246 @@ public: } TRACE("arith", tout << t << ": " << atom << "\n"; - m_solver->print_term(term, tout << "bound atom: "); tout << (lower_bound?" >= ":" <= ") << k << "\n";); + m_solver->print_term(term, tout << "bound atom: ") << (lower_bound?" >= ":" <= ") << k << "\n";); ctx().internalize(atom, true); ctx().mark_as_relevant(atom.get()); return atom; } + bool make_sure_all_vars_have_bounds() { + if (!m_has_int) { + return true; + } + unsigned nv = std::min(th.get_num_vars(), m_theory_var2var_index.size()); + bool all_bounded = true; + for (unsigned v = 0; v < nv; ++v) { + lp::var_index vi = m_theory_var2var_index[v]; + if (vi == UINT_MAX) + continue; + if (!m_solver->is_term(vi) && !var_has_bound(vi, true) && !var_has_bound(vi, false)) { + lp::lar_term term; + term.add_monomial(rational::one(), vi); + app_ref b = mk_bound(term, rational::zero(), true); + TRACE("arith", tout << "added bound " << b << "\n";); + IF_VERBOSE(2, verbose_stream() << "bound: " << b << "\n"); + all_bounded = false; + } + } + return all_bounded; + } + + /** + * n = (div p q) + * + * (div p q) * q + (mod p q) = p, (mod p q) >= 0 + * + * 0 < q => (p/q <= v(p)/v(q) => n <= floor(v(p)/v(q))) + * 0 < q => (v(p)/v(q) <= p/q => v(p)/v(q) - 1 < n) + * + */ + bool check_idiv_bounds() { + if (m_idiv_terms.empty()) { + return true; + } + bool all_divs_valid = true; + init_variable_values(); + for (expr* n : m_idiv_terms) { + expr* p = nullptr, *q = nullptr; + VERIFY(a.is_idiv(n, p, q)); + theory_var v = mk_var(n); + theory_var v1 = mk_var(p); + rational r1 = get_value(v1); + rational r2; + + if (!r1.is_int() || r1.is_neg()) { + // TBD + // r1 = 223/4, r2 = 2, r = 219/8 + // take ceil(r1), floor(r1), ceil(r2), floor(r2), for floor(r2) > 0 + // then + // p/q <= ceil(r1)/floor(r2) => n <= div(ceil(r1), floor(r2)) + // p/q >= floor(r1)/ceil(r2) => n >= div(floor(r1), ceil(r2)) + continue; + } + + if (a.is_numeral(q, r2) && r2.is_pos()) { + if (get_value(v) == div(r1, r2)) continue; + + rational div_r = div(r1, r2); + // p <= q * div(r1, q) + q - 1 => div(p, q) <= div(r1, r2) + // p >= q * div(r1, q) => div(r1, q) <= div(p, q) + rational mul(1); + rational hi = r2 * div_r + r2 - 1; + rational lo = r2 * div_r; + + // used to normalize inequalities so they + // don't appear as 8*x >= 15, but x >= 2 + expr *n1 = nullptr, *n2 = nullptr; + if (a.is_mul(p, n1, n2) && is_numeral(n1, mul) && mul.is_pos()) { + p = n2; + hi = floor(hi/mul); + lo = ceil(lo/mul); + } + literal p_le_r1 = mk_literal(a.mk_le(p, a.mk_numeral(hi, true))); + literal p_ge_r1 = mk_literal(a.mk_ge(p, a.mk_numeral(lo, true))); + literal n_le_div = mk_literal(a.mk_le(n, a.mk_numeral(div_r, true))); + literal n_ge_div = mk_literal(a.mk_ge(n, a.mk_numeral(div_r, true))); + mk_axiom(~p_le_r1, n_le_div); + mk_axiom(~p_ge_r1, n_ge_div); + + all_divs_valid = false; + + TRACE("arith", + tout << r1 << " div " << r2 << "\n"; + literal_vector lits; + lits.push_back(~p_le_r1); + lits.push_back(n_le_div); + ctx().display_literals_verbose(tout, lits) << "\n\n"; + lits[0] = ~p_ge_r1; + lits[1] = n_ge_div; + ctx().display_literals_verbose(tout, lits) << "\n";); + continue; + } +#if 0 + + // TBD similar for non-linear division. + // better to deal with in nla_solver: + + all_divs_valid = false; + + + // + // p/q <= r1/r2 => n <= div(r1, r2) + // <=> + // p*r2 <= q*r1 => n <= div(r1, r2) + // + // p/q >= r1/r2 => n >= div(r1, r2) + // <=> + // p*r2 >= r1*q => n >= div(r1, r2) + // + expr_ref zero(a.mk_int(0), m); + expr_ref divc(a.mk_numeral(div(r1, r2), true), m); + expr_ref pqr(a.mk_sub(a.mk_mul(a.mk_numeral(r2, true), p), a.mk_mul(a.mk_numeral(r1, true), q)), m); + literal pq_lhs = ~mk_literal(a.mk_le(pqr, zero)); + literal pq_rhs = ~mk_literal(a.mk_ge(pqr, zero)); + literal n_le_div = mk_literal(a.mk_le(n, divc)); + literal n_ge_div = mk_literal(a.mk_ge(n, divc)); + mk_axiom(pq_lhs, n_le_div); + mk_axiom(pq_rhs, n_ge_div); + TRACE("arith", + literal_vector lits; + lits.push_back(pq_lhs); + lits.push_back(n_le_div); + ctx().display_literals_verbose(tout, lits) << "\n"; + lits[0] = pq_rhs; + lits[1] = n_ge_div; + ctx().display_literals_verbose(tout, lits) << "\n";); +#endif + } + + return all_divs_valid; + } + + expr_ref var2expr(lp::var_index v) { + std::ostringstream name; + name << "v" << m_solver->local2external(v); + return expr_ref(m.mk_const(symbol(name.str().c_str()), a.mk_int()), m); + } + + expr_ref multerm(rational const& r, expr* e) { + if (r.is_one()) return expr_ref(e, m); + return expr_ref(a.mk_mul(a.mk_numeral(r, true), e), m); + } + + expr_ref term2expr(lp::lar_term const& term) { + expr_ref t(m); + expr_ref_vector ts(m); + for (auto const& p : term) { + lp::var_index wi = p.var(); + if (m_solver->is_term(wi)) { + ts.push_back(multerm(p.coeff(), term2expr(m_solver->get_term(wi)))); + } + else { + ts.push_back(multerm(p.coeff(), var2expr(wi))); + } + } + if (ts.size() == 1) { + t = ts.back(); + } + else { + t = a.mk_add(ts.size(), ts.c_ptr()); + } + return t; + } + + expr_ref constraint2fml(lp::constraint_index ci) { + lp::lar_base_constraint const& c = *m_solver->constraints()[ci]; + expr_ref fml(m); + expr_ref_vector ts(m); + rational rhs = c.m_right_side; + for (auto cv : c.get_left_side_coefficients()) { + ts.push_back(multerm(cv.first, var2expr(cv.second))); + } + switch (c.m_kind) { + case lp::LE: fml = a.mk_le(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; + case lp::LT: fml = a.mk_lt(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; + case lp::GE: fml = a.mk_ge(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; + case lp::GT: fml = a.mk_gt(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; + case lp::EQ: fml = m.mk_eq(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; + } + return fml; + } + + void dump_cut_lemma(std::ostream& out, lp::lar_term const& term, lp::mpq const& k, lp::explanation const& ex, bool upper) { + m_solver->print_term(term, out << "bound: "); + out << (upper?" <= ":" >= ") << k << "\n"; + for (auto const& p : term) { + lp::var_index wi = p.var(); + out << p.coeff() << " * "; + if (m_solver->is_term(wi)) { + m_solver->print_term(m_solver->get_term(wi), out) << "\n"; + } + else { + out << "v" << m_solver->local2external(wi) << "\n"; + } + } + for (auto const& ev : ex.m_explanation) { + m_solver->print_constraint(ev.second, out << ev.first << ": "); + } + expr_ref_vector fmls(m); + for (auto const& ev : ex.m_explanation) { + fmls.push_back(constraint2fml(ev.second)); + } + expr_ref t(term2expr(term), m); + if (upper) { + fmls.push_back(m.mk_not(a.mk_ge(t, a.mk_numeral(k, true)))); + } + else { + fmls.push_back(m.mk_not(a.mk_le(t, a.mk_numeral(k, true)))); + } + ast_pp_util visitor(m); + visitor.collect(fmls); + visitor.display_decls(out); + visitor.display_asserts(out, fmls, true); + out << "(check-sat)\n"; + } + lbool check_lia() { if (m.canceled()) { TRACE("arith", tout << "canceled\n";); return l_undef; } - lp::lar_term term; - lp::mpq k; - lp::explanation ex; // TBD, this should be streamlined accross different explanations - bool upper; - switch(m_lia->check(term, k, ex, upper)) { + if (!check_idiv_bounds()) { + TRACE("arith", tout << "idiv bounds check\n";); + return l_false; + } + m_explanation.reset(); + switch (m_lia->check()) { case lp::lia_move::sat: return l_true; case lp::lia_move::branch: { TRACE("arith", tout << "branch\n";); - app_ref b = mk_bound(term, k, !upper); + app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); + IF_VERBOSE(2, verbose_stream() << "branch " << b << "\n";); // branch on term >= k + 1 // branch on term <= k // TBD: ctx().force_phase(ctx().get_literal(b)); @@ -1440,11 +1772,13 @@ public: TRACE("arith", tout << "cut\n";); ++m_stats.m_gomory_cuts; // m_explanation implies term <= k - app_ref b = mk_bound(term, k, !upper); + app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); + IF_VERBOSE(2, verbose_stream() << "cut " << b << "\n"); + TRACE("arith", dump_cut_lemma(tout, m_lia->get_term(), m_lia->get_offset(), m_lia->get_explanation(), m_lia->is_upper());); m_eqs.reset(); m_core.reset(); m_params.reset(); - for (auto const& ev : ex.m_explanation) { + for (auto const& ev : m_lia->get_explanation().m_explanation) { if (!ev.first.is_zero()) { set_evidence(ev.second); } @@ -1457,8 +1791,9 @@ public: return l_false; } case lp::lia_move::conflict: + TRACE("arith", tout << "conflict\n";); // ex contains unsat core - m_explanation = ex.m_explanation; + m_explanation = m_lia->get_explanation().m_explanation; set_conflict1(); return l_false; case lp::lia_move::undef: @@ -1744,12 +2079,12 @@ public: m_core2.push_back(~c); } m_core2.push_back(lit); - justification * js = 0; + justification * js = nullptr; if (proofs_enabled()) { js = alloc(theory_lemma_justification, get_id(), ctx(), m_core2.size(), m_core2.c_ptr(), m_params.size(), m_params.c_ptr()); } - ctx().mk_clause(m_core2.size(), m_core2.c_ptr(), js, CLS_AUX_LEMMA, 0); + ctx().mk_clause(m_core2.size(), m_core2.c_ptr(), js, CLS_AUX_LEMMA, nullptr); } else { ctx().assign( @@ -1804,7 +2139,7 @@ public: rational const& k1 = b.get_value(); lp_bounds & bounds = m_bounds[v]; - lp_api::bound* end = 0; + lp_api::bound* end = nullptr; lp_api::bound* lo_inf = end, *lo_sup = end; lp_api::bound* hi_inf = end, *hi_sup = end; @@ -2062,18 +2397,18 @@ public: SASSERT(!bounds.empty()); if (bounds.size() == 1) return; if (m_unassigned_bounds[v] == 0) return; - + bool v_is_int = is_int(v); literal lit1(bv, !is_true); literal lit2 = null_literal; bool find_glb = (is_true == (k == lp_api::lower_t)); + TRACE("arith", tout << "find_glb: " << find_glb << " is_true: " << is_true << " k: " << k << " is_lower: " << (k == lp_api::lower_t) << "\n";); if (find_glb) { rational glb; - lp_api::bound* lb = 0; - for (unsigned i = 0; i < bounds.size(); ++i) { - lp_api::bound* b2 = bounds[i]; + lp_api::bound* lb = nullptr; + for (lp_api::bound* b2 : bounds) { if (b2 == &b) continue; rational const& val2 = b2->get_value(); - if ((is_true ? val2 < val : val2 <= val) && (!lb || glb < val2)) { + if (((is_true || v_is_int) ? val2 < val : val2 <= val) && (!lb || glb < val2)) { lb = b2; glb = val2; } @@ -2084,12 +2419,11 @@ public: } else { rational lub; - lp_api::bound* ub = 0; - for (unsigned i = 0; i < bounds.size(); ++i) { - lp_api::bound* b2 = bounds[i]; + lp_api::bound* ub = nullptr; + for (lp_api::bound* b2 : bounds) { if (b2 == &b) continue; rational const& val2 = b2->get_value(); - if ((is_true ? val < val2 : val <= val2) && (!ub || val2 < lub)) { + if (((is_true || v_is_int) ? val < val2 : val <= val2) && (!ub || val2 < lub)) { ub = b2; lub = val2; } @@ -2107,7 +2441,7 @@ public: m_params.reset(); m_core.reset(); m_eqs.reset(); - m_core.push_back(lit2); + m_core.push_back(lit1); m_params.push_back(parameter(symbol("farkas"))); m_params.push_back(parameter(rational(1))); m_params.push_back(parameter(rational(1))); @@ -2383,6 +2717,18 @@ public: } } + bool var_has_bound(lp::var_index vi, bool is_lower) { + bool is_strict = false; + rational b; + lp::constraint_index ci; + if (is_lower) { + return m_solver->has_lower_bound(vi, ci, b, is_strict); + } + else { + return m_solver->has_upper_bound(vi, ci, b, is_strict); + } + } + bool has_upper_bound(lp::var_index vi, lp::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, false); } bool has_lower_bound(lp::var_index vi, lp::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, true); } @@ -2451,7 +2797,7 @@ public: justification* js = ctx().mk_justification( ext_theory_eq_propagation_justification( - get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), x, y, 0, 0)); + get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), x, y, 0, nullptr)); TRACE("arith", for (unsigned i = 0; i < m_core.size(); ++i) { @@ -2606,7 +2952,7 @@ public: m_todo_terms.push_back(std::make_pair(vi, rational::one())); TRACE("arith", tout << "v" << v << " := w" << vi << "\n"; - m_solver->print_term(m_solver->get_term(vi), tout); tout << "\n";); + m_solver->print_term(m_solver->get_term(vi), tout) << "\n";); m_nra->am().set(r, 0); while (!m_todo_terms.empty()) { @@ -2614,13 +2960,13 @@ public: vi = m_todo_terms.back().first; m_todo_terms.pop_back(); lp::lar_term const& term = m_solver->get_term(vi); - TRACE("arith", m_solver->print_term(term, tout); tout << "\n";); + TRACE("arith", m_solver->print_term(term, tout) << "\n";); scoped_anum r1(m_nra->am()); - rational c1 = term.m_v * wcoeff; + rational c1(0); m_nra->am().set(r1, c1.to_mpq()); m_nra->am().add(r, r1, r); for (auto const & arg : term) { - lp::var_index wi = m_solver->local2external(arg.var()); + lp::var_index wi = arg.var(); c1 = arg.coeff() * wcoeff; if (m_solver->is_term(wi)) { m_todo_terms.push_back(std::make_pair(wi, c1)); @@ -2663,6 +3009,7 @@ public: if (!can_get_bound(v)) return false; lp::var_index vi = m_theory_var2var_index[v]; if (m_solver->has_value(vi, val)) { + TRACE("arith", tout << expr_ref(n->get_owner(), m) << " := " << val << "\n";); if (is_int(n) && !val.is_int()) return false; return true; } @@ -2891,7 +3238,6 @@ public: coeffs.find(w, c0); coeffs.insert(w, c0 + ti.coeff() * coeff); } - offset += coeff * term.m_v; } app_ref coeffs2app(u_map const& coeffs, rational const& offset, bool is_int) { @@ -2931,15 +3277,17 @@ public: rational gcd_reduce(u_map& coeffs) { rational g(0); - for (auto const& kv : coeffs) { - g = gcd(g, kv.m_value); - } - if (!g.is_one() && !g.is_zero()) { - for (auto& kv : coeffs) { - kv.m_value /= g; - } - } - return g; + for (auto const& kv : coeffs) { + g = gcd(g, kv.m_value); + } + if (g.is_zero()) + return rational::one(); + if (!g.is_one()) { + for (auto& kv : coeffs) { + kv.m_value /= g; + } + } + return g; } app_ref mk_obj(theory_var v) { @@ -2967,7 +3315,7 @@ public: } if (!ctx().b_internalized(b)) { fm.hide(b->get_decl()); - bool_var bv = ctx().mk_bool_var(b); + bool_var bv = ctx().mk_bool_var(b); ctx().set_var_theory(bv, get_id()); // ctx().set_enode_flag(bv, true); lp_api::bound_kind bkind = lp_api::bound_kind::lower_t; diff --git a/src/smt/theory_lra.h b/src/smt/theory_lra.h index 811fa1812..23adaa557 100644 --- a/src/smt/theory_lra.h +++ b/src/smt/theory_lra.h @@ -78,9 +78,9 @@ namespace smt { model_value_proc * mk_value(enode * n, model_generator & mg) override; bool get_value(enode* n, expr_ref& r) override; + bool get_value(enode* n, rational& r); bool get_lower(enode* n, expr_ref& r); bool get_upper(enode* n, expr_ref& r); - bool get_value(enode* n, rational& r); bool get_lower(enode* n, rational& r, bool& is_strict); bool get_upper(enode* n, rational& r, bool& is_strict); diff --git a/src/smt/theory_pb.cpp b/src/smt/theory_pb.cpp index e389c819e..90d1a5481 100644 --- a/src/smt/theory_pb.cpp +++ b/src/smt/theory_pb.cpp @@ -759,18 +759,18 @@ namespace smt { card& get_card() { return m_card; } - virtual void get_antecedents(conflict_resolution& cr) { + void get_antecedents(conflict_resolution& cr) override { cr.mark_literal(m_card.lit()); for (unsigned i = m_card.k(); i < m_card.size(); ++i) { cr.mark_literal(~m_card.lit(i)); } } - virtual theory_id get_from_theory() const { + theory_id get_from_theory() const override { return m_fid; } - virtual proof* mk_proof(smt::conflict_resolution& cr) { + proof* mk_proof(smt::conflict_resolution& cr) override { ptr_buffer prs; ast_manager& m = cr.get_context().get_manager(); expr_ref fact(m); @@ -897,7 +897,7 @@ namespace smt { void theory_pb::watch_literal(literal lit, card* c) { init_watch(lit.var()); ptr_vector* cards = m_var_infos[lit.var()].m_lit_cwatch[lit.sign()]; - if (cards == 0) { + if (cards == nullptr) { cards = alloc(ptr_vector); m_var_infos[lit.var()].m_lit_cwatch[lit.sign()] = cards; } @@ -961,13 +961,13 @@ namespace smt { void theory_pb::add_clause(card& c, literal_vector const& lits) { m_stats.m_num_conflicts++; context& ctx = get_context(); - justification* js = 0; + justification* js = nullptr; c.inc_propagations(*this); if (!resolve_conflict(c, lits)) { if (proofs_enabled()) { js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.c_ptr()); } - ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, 0); + ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, nullptr); } SASSERT(ctx.inconsistent()); } @@ -1027,7 +1027,7 @@ namespace smt { } void theory_pb::assign_eh(bool_var v, bool is_true) { - ptr_vector* ineqs = 0; + ptr_vector* ineqs = nullptr; context& ctx = get_context(); literal nlit(v, is_true); init_watch(v); @@ -1060,7 +1060,7 @@ namespace smt { } ptr_vector* cards = m_var_infos[v].m_lit_cwatch[nlit.sign()]; - if (cards != 0 && !cards->empty() && !ctx.inconsistent()) { + if (cards != nullptr && !cards->empty() && !ctx.inconsistent()) { ptr_vector::iterator it = cards->begin(), it2 = it, end = cards->end(); for (; it != end; ++it) { if (ctx.get_assignment((*it)->lit()) != l_true) { @@ -1088,7 +1088,7 @@ namespace smt { } card* crd = m_var_infos[v].m_card; - if (crd != 0 && !ctx.inconsistent()) { + if (crd != nullptr && !ctx.inconsistent()) { crd->init_watch(*this, is_true); } @@ -1527,7 +1527,7 @@ namespace smt { else { z++; clear_watch(*c); - m_var_infos[v].m_card = 0; + m_var_infos[v].m_card = nullptr; dealloc(c); m_card_trail[i] = null_bool_var; ctx.remove_watch(v); @@ -1671,7 +1671,7 @@ namespace smt { if (v != null_bool_var) { card* c = m_var_infos[v].m_card; clear_watch(*c); - m_var_infos[v].m_card = 0; + m_var_infos[v].m_card = nullptr; dealloc(c); } } @@ -1774,11 +1774,11 @@ namespace smt { context& ctx = get_context(); TRACE("pb", tout << "#prop:" << c.m_num_propagations << " - " << lits << "\n"; display(tout, c, true);); - justification* js = 0; + justification* js = nullptr; if (proofs_enabled()) { js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.c_ptr()); } - ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, 0); + ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, nullptr); } @@ -1894,11 +1894,11 @@ namespace smt { break; case b_justification::JUSTIFICATION: { justification* j = js.get_justification(); - card_justification* pbj = 0; + card_justification* pbj = nullptr; if (j->get_from_theory() == get_id()) { pbj = dynamic_cast(j); } - if (pbj != 0) { + if (pbj != nullptr) { card& c2 = pbj->get_card(); result = card2expr(c2); } @@ -2170,11 +2170,11 @@ namespace smt { VERIFY(internalize_card(atl, false)); bool_var abv = ctx.get_bool_var(atl); m_antecedents.push_back(literal(abv)); - justification* js = 0; + justification* js = nullptr; if (proofs_enabled()) { - js = 0; // + js = nullptr; } - ctx.mk_clause(m_antecedents.size(), m_antecedents.c_ptr(), js, CLS_AUX_LEMMA, 0); + ctx.mk_clause(m_antecedents.size(), m_antecedents.c_ptr(), js, CLS_AUX_LEMMA, nullptr); } bool theory_pb::resolve_conflict(card& c, literal_vector const& confl) { @@ -2403,7 +2403,7 @@ namespace smt { } #endif SASSERT(validate_antecedents(m_antecedents)); - ctx.assign(alit, ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), m_antecedents.size(), m_antecedents.c_ptr(), alit, 0, 0))); + ctx.assign(alit, ctx.mk_justification(theory_propagation_justification(get_id(), ctx.get_region(), m_antecedents.size(), m_antecedents.c_ptr(), alit, 0, nullptr))); DEBUG_CODE( m_antecedents.push_back(~alit); diff --git a/src/smt/theory_pb.h b/src/smt/theory_pb.h index 7e9c55a12..3a0ee723f 100644 --- a/src/smt/theory_pb.h +++ b/src/smt/theory_pb.h @@ -258,7 +258,7 @@ namespace smt { card_watch* m_lit_cwatch[2]; card* m_card; - var_info(): m_var_watch(0), m_ineq(0), m_card(0) + var_info(): m_var_watch(nullptr), m_ineq(nullptr), m_card(nullptr) { m_lit_watch[0] = nullptr; m_lit_watch[1] = nullptr; diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index b00c1565c..093a47146 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -1120,7 +1120,7 @@ bool theory_seq::find_better_rep(expr_ref_vector const& ls, expr_ref_vector cons break; } if (flag) { - expr* nl_fst = 0; + expr* nl_fst = nullptr; if (e.rs().size()>1 && is_var(e.rs().get(0))) nl_fst = e.rs().get(0); if (nl_fst && nl_fst != r_fst) { @@ -1173,7 +1173,7 @@ bool theory_seq::find_better_rep(expr_ref_vector const& ls, expr_ref_vector cons break; } if (flag) { - expr* nl_fst = 0; + expr* nl_fst = nullptr; if (e.rs().size()>1 && is_var(e.rs().get(0))) nl_fst = e.rs().get(0); if (nl_fst && nl_fst != r_fst) { @@ -1375,8 +1375,8 @@ bool theory_seq::branch_variable_mb() { continue; } rational l1, l2; - for (auto elem : len1) l1 += elem; - for (auto elem : len2) l2 += elem; + for (const auto& elem : len1) l1 += elem; + for (const auto& elem : len2) l2 += elem; if (l1 != l2) { TRACE("seq", tout << "lengths are not compatible\n";); expr_ref l = mk_concat(e.ls()); @@ -4588,10 +4588,10 @@ bool theory_seq::lower_bound2(expr* _e, rational& lo) { theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); if (!tha) { theory_i_arith* thi = get_th_arith(ctx, m_autil.get_family_id(), e); - if (!thi || !thi->get_lower(ctx.get_enode(e), _lo)) return false; + if (!thi || !thi->get_lower(ctx.get_enode(e), _lo) || !m_autil.is_numeral(_lo, lo)) return false; } enode *ee = ctx.get_enode(e); - if (!tha->get_lower(ee, _lo) || m_autil.is_numeral(_lo, lo)) { + if (tha && (!tha->get_lower(ee, _lo) || m_autil.is_numeral(_lo, lo))) { enode *next = ee->get_next(); bool flag = false; while (next != ee) { diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index f2432b4fb..ec192cd89 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -110,7 +110,7 @@ namespace smt { public: seq_expr_solver(ast_manager& m, smt_params& fp): m_kernel(m, fp) {} - virtual lbool check_sat(expr* e) { + lbool check_sat(expr* e) override { m_kernel.push(); m_kernel.assert_expr(e); lbool r = m_kernel.check(); @@ -1388,7 +1388,7 @@ namespace smt { // (but don't introduce it if it isn't already in the instance) expr_ref haystack(ex->get_arg(0), m), needle(ex->get_arg(1), m), startIdx(ex->get_arg(2), m); expr_ref zeroAst(mk_int(0), m); - // (H contains N) <==> (H indexof N, i) >= 0 + // (H contains N) <==> (H indexof N, 0) >= 0 expr_ref premise(u.str.mk_contains(haystack, needle), m); ctx.internalize(premise, false); expr_ref conclusion(m_autil.mk_ge(ex, zeroAst), m); @@ -1482,14 +1482,23 @@ namespace smt { { // heuristic: integrate with str.contains information // (but don't introduce it if it isn't already in the instance) - // (H contains N) <==> (H indexof N, i) >= 0 + // (0 <= i < len(H)) ==> (H contains N) <==> (H indexof N, i) >= 0 + expr_ref precondition1(m_autil.mk_gt(i, minus_one), m); + //expr_ref precondition2(m_autil.mk_lt(i, mk_strlen(H)), m); + expr_ref precondition2(m.mk_not(m_autil.mk_ge(m_autil.mk_add(i, m_autil.mk_mul(minus_one, mk_strlen(H))), zero)), m); + expr_ref _precondition(m.mk_and(precondition1, precondition2), m); + expr_ref precondition(_precondition); + th_rewriter rw(m); + rw(precondition); + expr_ref premise(u.str.mk_contains(H, N), m); ctx.internalize(premise, false); expr_ref conclusion(m_autil.mk_ge(e, zero), m); expr_ref containsAxiom(ctx.mk_eq_atom(premise, conclusion), m); - SASSERT(containsAxiom); + expr_ref finalAxiom(rewrite_implication(precondition, containsAxiom), m); + SASSERT(finalAxiom); // we can't assert this during init_search as it breaks an invariant if the instance becomes inconsistent - m_delayed_assertions_todo.push_back(containsAxiom); + m_delayed_assertions_todo.push_back(finalAxiom); } } @@ -1829,6 +1838,14 @@ namespace smt { u.str.is_string(range1, range1val); u.str.is_string(range2, range2val); return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); + } else if (u.re.is_loop(a_regex)) { + expr * body; + unsigned lo, hi; + u.re.is_loop(a_regex, body, lo, hi); + rational rLo(lo); + rational rHi(hi); + zstring bodyStr = get_std_regex_str(body); + return zstring("(") + bodyStr + zstring("{") + zstring(rLo.to_string().c_str()) + zstring(",") + zstring(rHi.to_string().c_str()) + zstring("})"); } else if (u.re.is_full_seq(a_regex)) { return zstring("(.*)"); } else if (u.re.is_full_char(a_regex)) { @@ -4818,41 +4835,25 @@ namespace smt { return n; } - // from Z3: theory_seq.cpp - - static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { - theory* th = ctx.get_theory(afid); - if (th && ctx.e_internalized(e)) { - return dynamic_cast(th); - } - else { - return nullptr; - } - } - bool theory_str::get_arith_value(expr* e, rational& val) const { - context& ctx = get_context(); - ast_manager & m = get_manager(); - // safety - if (!ctx.e_internalized(e)) { - return false; - } - // if an integer constant exists in the eqc, it should be the root - enode * en_e = ctx.get_enode(e); - enode * root_e = en_e->get_root(); - if (m_autil.is_numeral(root_e->get_owner(), val) && val.is_int()) { - TRACE("str", tout << mk_pp(e, m) << " ~= " << mk_pp(root_e->get_owner(), m) << std::endl;); - return true; - } else { - TRACE("str", tout << "root of eqc of " << mk_pp(e, m) << " is not a numeral" << std::endl;); - return false; - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); - if (!tha) return false; - expr_ref val_e(m); - if (tha->get_value(root_e, val_e) && m_autil.is_numeral(val_e, val) && val.is_int()) return true; - return false; - } - } + context& ctx = get_context(); + ast_manager & m = get_manager(); + if (!ctx.e_internalized(e)) { + return false; + } + // check root of the eqc for an integer constant + // if an integer constant exists in the eqc, it should be the root + enode * en_e = ctx.get_enode(e); + enode * root_e = en_e->get_root(); + if (m_autil.is_numeral(root_e->get_owner(), val) && val.is_int()) { + TRACE("str", tout << mk_pp(e, m) << " ~= " << mk_pp(root_e->get_owner(), m) << std::endl;); + return true; + } else { + TRACE("str", tout << "root of eqc of " << mk_pp(e, m) << " is not a numeral" << std::endl;); + return false; + } + + } bool theory_str::lower_bound(expr* _e, rational& lo) { if (opt_DisableIntegerTheoryIntegration) { @@ -4860,12 +4861,9 @@ namespace smt { return false; } - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _lo(m); - if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; - return m_autil.is_numeral(_lo, lo) && lo.is_int(); + arith_value v(get_context()); + bool strict; + return v.get_lo(_e, lo, strict); } bool theory_str::upper_bound(expr* _e, rational& hi) { @@ -4874,12 +4872,9 @@ namespace smt { return false; } - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _hi(m); - if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; - return m_autil.is_numeral(_hi, hi) && hi.is_int(); + arith_value v(get_context()); + bool strict; + return v.get_up(_e, hi, strict); } bool theory_str::get_len_value(expr* e, rational& val) { @@ -4891,17 +4886,6 @@ namespace smt { context& ctx = get_context(); ast_manager & m = get_manager(); - theory* th = ctx.get_theory(m_autil.get_family_id()); - if (!th) { - TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); - return false; - } - theory_mi_arith* tha = dynamic_cast(th); - if (!tha) { - TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); - return false; - } - TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); rational val1; @@ -6644,6 +6628,7 @@ namespace smt { ENSURE(u.is_re(re)); expr * sub1; expr * sub2; + unsigned lo, hi; if (u.re.is_to_re(re, sub1)) { if (!u.str.is_string(sub1)) throw default_exception("regular expressions must be built from string literals"); @@ -6663,6 +6648,9 @@ namespace smt { } else if (u.re.is_star(re, sub1) || u.re.is_plus(re, sub1)) { unsigned cx = estimate_regex_complexity(sub1); return _qmul(2, cx); + } else if (u.re.is_loop(re, sub1, lo, hi)) { + unsigned cx = estimate_regex_complexity(sub1); + return _qadd(lo, cx); } else if (u.re.is_range(re, sub1, sub2)) { SASSERT(u.str.is_string(sub1)); SASSERT(u.str.is_string(sub2)); @@ -6684,6 +6672,7 @@ namespace smt { ENSURE(u.is_re(re)); expr * sub1; expr * sub2; + unsigned lo, hi; if (u.re.is_to_re(re, sub1)) { SASSERT(u.str.is_string(sub1)); zstring str; @@ -6702,7 +6691,7 @@ namespace smt { unsigned cx1 = estimate_regex_complexity_under_complement(sub1); unsigned cx2 = estimate_regex_complexity_under_complement(sub2); return _qmul(cx1, cx2); - } else if (u.re.is_star(re, sub1) || u.re.is_plus(re, sub1)) { + } else if (u.re.is_star(re, sub1) || u.re.is_plus(re, sub1) || u.re.is_loop(re, sub1, lo, hi)) { unsigned cx = estimate_regex_complexity_under_complement(sub1); return _qmul(2, cx); } else if (u.re.is_range(re, sub1, sub2)) { @@ -6723,8 +6712,8 @@ namespace smt { } unsigned theory_str::estimate_automata_intersection_difficulty(eautomaton * aut1, eautomaton * aut2) { - ENSURE(aut1 != NULL); - ENSURE(aut2 != NULL); + ENSURE(aut1 != nullptr); + ENSURE(aut2 != nullptr); return _qmul(aut1->num_states(), aut2->num_states()); } @@ -6736,6 +6725,7 @@ namespace smt { bool theory_str::check_regex_length_linearity_helper(expr * re, bool already_star) { expr * sub1; expr * sub2; + unsigned lo, hi; if (u.re.is_to_re(re)) { return true; } else if (u.re.is_concat(re, sub1, sub2)) { @@ -6757,6 +6747,8 @@ namespace smt { } else if (u.re.is_complement(re)) { // TODO can we do better? return false; + } else if (u.re.is_loop(re, sub1, lo, hi)) { + return check_regex_length_linearity_helper(sub1, already_star); } else { TRACE("str", tout << "WARNING: unknown regex term " << mk_pp(re, get_manager()) << std::endl;); UNREACHABLE(); return false; @@ -6767,6 +6759,7 @@ namespace smt { void theory_str::check_subterm_lengths(expr * re, integer_set & lens) { expr * sub1; expr * sub2; + unsigned lo, hi; if (u.re.is_to_re(re, sub1)) { SASSERT(u.str.is_string(sub1)); zstring str; @@ -6821,6 +6814,14 @@ namespace smt { lens.reset(); } else if (u.re.is_complement(re)) { lens.reset(); + } else if (u.re.is_loop(re, sub1, lo, hi)) { + integer_set lens_1; + check_subterm_lengths(sub1, lens_1); + for (unsigned i = lo; i <= hi; ++i) { + for (auto j : lens_1) { + lens.insert(i * j); + } + } } else { TRACE("str", tout << "WARNING: unknown regex term " << mk_pp(re, get_manager()) << std::endl;); lens.reset(); @@ -6842,6 +6843,7 @@ namespace smt { ast_manager & m = get_manager(); expr * sub1; expr * sub2; + unsigned lo, hi; if (u.re.is_to_re(re, sub1)) { if (!u.str.is_string(sub1)) throw default_exception("regular expressions must be built from string literals"); @@ -6911,6 +6913,22 @@ namespace smt { expr_ref retval(ctx.mk_eq_atom(lenVar, m_autil.mk_add_simplify(sum_terms)), m); return retval; } + } else if (u.re.is_loop(re, sub1, lo, hi)) { + expr * v1 = mk_int_var("rlen"); + freeVariables.push_back(v1); + expr_ref r1 = infer_all_regex_lengths(v1, sub1, freeVariables); + expr_ref_vector v1_choices(m); + for (unsigned i = lo; i <= hi; ++i) { + rational rI(i); + expr_ref v1_i(ctx.mk_eq_atom(lenVar, m_autil.mk_mul(m_autil.mk_numeral(rI, true), v1)), m); + v1_choices.push_back(v1_i); + } + expr_ref_vector finalResult(m); + finalResult.push_back(r1); + finalResult.push_back(mk_or(v1_choices)); + expr_ref retval(mk_and(finalResult), m); + SASSERT(retval); + return retval; } else if (u.re.is_range(re, sub1, sub2)) { SASSERT(u.str.is_string(sub1)); SASSERT(u.str.is_string(sub2)); @@ -6948,7 +6966,7 @@ namespace smt { * and the equality with 0 is based on whether solutions of length 0 are allowed. */ void theory_str::find_automaton_initial_bounds(expr * str_in_re, eautomaton * aut) { - ENSURE(aut != NULL); + ENSURE(aut != nullptr); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -7000,7 +7018,7 @@ namespace smt { * if it exists, or -1 otherwise. */ bool theory_str::refine_automaton_lower_bound(eautomaton * aut, rational current_lower_bound, rational & refined_lower_bound) { - ENSURE(aut != NULL); + ENSURE(aut != nullptr); if (aut->final_states().size() < 1) { // no solutions at all @@ -7110,7 +7128,7 @@ namespace smt { * if a shorter solution exists, or -1 otherwise. */ bool theory_str::refine_automaton_upper_bound(eautomaton * aut, rational current_upper_bound, rational & refined_upper_bound) { - ENSURE(aut != NULL); + ENSURE(aut != nullptr); if (aut->final_states().empty()) { // no solutions at all! @@ -7229,7 +7247,7 @@ namespace smt { return retval; } else { TRACE("str", tout << "ERROR: unrecognized automaton path constraint " << mk_pp(cond, m) << ", cannot translate" << std::endl;); - retval = NULL; + retval = nullptr; return retval; } } @@ -7242,7 +7260,7 @@ namespace smt { * are returned in `characterConstraints`. */ expr_ref theory_str::generate_regex_path_constraints(expr * stringTerm, eautomaton * aut, rational lenVal, expr_ref & characterConstraints) { - ENSURE(aut != NULL); + ENSURE(aut != nullptr); context & ctx = get_context(); ast_manager & m = get_manager(); @@ -8094,31 +8112,7 @@ namespace smt { // BEGIN new_eq_handler() in strTheory - { - rational nn1Len, nn2Len; - bool nn1Len_exists = get_len_value(lhs, nn1Len); - bool nn2Len_exists = get_len_value(rhs, nn2Len); - expr_ref emptyStr(mk_string(""), m); - - if (nn1Len_exists && nn1Len.is_zero()) { - if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - - if (nn2Len_exists && nn2Len.is_zero()) { - if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - } - + check_eqc_empty_string(lhs, rhs); instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); // group terms by equivalence class (groupNodeInEqc()) @@ -8170,52 +8164,7 @@ namespace smt { ); // step 1: Concat == Concat - int hasCommon = 0; - if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { - std::set::iterator itor1 = eqc_concat_lhs.begin(); - std::set::iterator itor2 = eqc_concat_rhs.begin(); - for (; itor1 != eqc_concat_lhs.end(); itor1++) { - if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { - hasCommon = 1; - break; - } - } - for (; itor2 != eqc_concat_rhs.end(); itor2++) { - if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { - hasCommon = 1; - break; - } - } - if (hasCommon == 0) { - if (opt_ConcatOverlapAvoid) { - bool found = false; - // check each pair and take the first ones that won't immediately overlap - for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { - expr * concat_lhs = *itor1; - for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { - expr * concat_rhs = *itor2; - if (will_result_in_overlap(concat_lhs, concat_rhs)) { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); - } else { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); - simplify_concat_equality(concat_lhs, concat_rhs); - found = true; - break; - } - } - } - if (!found) { - TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } else { - // default behaviour - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } - } + check_eqc_concat_concat(eqc_concat_lhs, eqc_concat_rhs); // step 2: Concat == Constant @@ -8268,6 +8217,86 @@ namespace smt { } + // Check that a string's length can be 0 iff it is the empty string. + void theory_str::check_eqc_empty_string(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + rational nn1Len, nn2Len; + bool nn1Len_exists = get_len_value(lhs, nn1Len); + bool nn2Len_exists = get_len_value(rhs, nn2Len); + expr_ref emptyStr(mk_string(""), m); + + if (nn1Len_exists && nn1Len.is_zero()) { + if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + + if (nn2Len_exists && nn2Len.is_zero()) { + if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + } + + void theory_str::check_eqc_concat_concat(std::set & eqc_concat_lhs, std::set & eqc_concat_rhs) { + ast_manager & m = get_manager(); + + int hasCommon = 0; + if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { + hasCommon = 1; + break; + } + } + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { + hasCommon = 1; + break; + } + } + if (hasCommon == 0) { + if (opt_ConcatOverlapAvoid) { + bool found = false; + // check each pair and take the first ones that won't immediately overlap + for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { + expr * concat_lhs = *itor1; + for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { + expr * concat_rhs = *itor2; + if (will_result_in_overlap(concat_lhs, concat_rhs)) { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); + } else { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); + simplify_concat_equality(concat_lhs, concat_rhs); + found = true; + break; + } + } + } + if (!found) { + TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } else { + // default behaviour + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } + } + } + void theory_str::set_up_axioms(expr * ex) { ast_manager & m = get_manager(); context & ctx = get_context(); @@ -9779,6 +9808,732 @@ namespace smt { } } + void theory_str::solve_regex_automata() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // TODO since heuristics might fail, the "no progress" flag might need to be handled specially here + bool regex_axiom_add = false; + for (obj_hashtable::iterator it = regex_terms.begin(); it != regex_terms.end(); ++it) { + expr * str_in_re = *it; + expr * str = nullptr; + expr * re = nullptr; + u.str.is_in_re(str_in_re, str, re); + lbool current_assignment = ctx.get_assignment(str_in_re); + TRACE("str", tout << "regex term: " << mk_pp(str, m) << " in " << mk_pp(re, m) << " : " << current_assignment << std::endl;); + if (current_assignment == l_undef) { + continue; + } + + if (!regex_terms_with_length_constraints.contains(str_in_re)) { + if (current_assignment == l_true && check_regex_length_linearity(re)) { + TRACE("str", tout << "regex length constraints expected to be linear -- generating and asserting them" << std::endl;); + + if (regex_term_to_length_constraint.contains(str_in_re)) { + // use existing length constraint + expr * top_level_length_constraint = nullptr; + regex_term_to_length_constraint.find(str_in_re, top_level_length_constraint); + + ptr_vector extra_length_vars; + regex_term_to_extra_length_vars.find(str_in_re, extra_length_vars); + + assert_axiom(top_level_length_constraint); + for(ptr_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { + expr * v = *it; + refresh_theory_var(v); + expr_ref len_constraint(m_autil.mk_ge(v, m_autil.mk_numeral(rational::zero(), true)), m); + assert_axiom(len_constraint); + } + } else { + // generate new length constraint + expr_ref_vector extra_length_vars(m); + expr_ref _top_level_length_constraint = infer_all_regex_lengths(mk_strlen(str), re, extra_length_vars); + expr_ref top_level_length_constraint(_top_level_length_constraint, m); + th_rewriter rw(m); + rw(top_level_length_constraint); + TRACE("str", tout << "top-level length constraint: " << mk_pp(top_level_length_constraint, m) << std::endl;); + // assert and track length constraint + assert_axiom(top_level_length_constraint); + for(expr_ref_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { + expr * v = *it; + expr_ref len_constraint(m_autil.mk_ge(v, m_autil.mk_numeral(rational::zero(), true)), m); + assert_axiom(len_constraint); + } + + regex_term_to_length_constraint.insert(str_in_re, top_level_length_constraint); + ptr_vector vtmp; + for(expr_ref_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { + vtmp.push_back(*it); + } + regex_term_to_extra_length_vars.insert(str_in_re, vtmp); + } + + regex_terms_with_length_constraints.insert(str_in_re); + m_trail_stack.push(insert_obj_trail(regex_terms_with_length_constraints, str_in_re)); + regex_axiom_add = true; + } + } // re not in regex_terms_with_length_constraints + + rational exact_length_value; + if (get_len_value(str, exact_length_value)) { + TRACE("str", tout << "exact length of " << mk_pp(str, m) << " is " << exact_length_value << std::endl;); + + if (regex_terms_with_path_constraints.contains(str_in_re)) { + TRACE("str", tout << "term " << mk_pp(str_in_re, m) << " already has path constraints set up" << std::endl;); + continue; + } + + // find a consistent automaton for this term + bool found = false; + regex_automaton_under_assumptions assumption; + if (regex_automaton_assumptions.contains(re) && + !regex_automaton_assumptions[re].empty()){ + for (svector::iterator it = regex_automaton_assumptions[re].begin(); + it != regex_automaton_assumptions[re].end(); ++it) { + regex_automaton_under_assumptions autA = *it; + rational assumed_upper_bound, assumed_lower_bound; + bool assumes_upper_bound = autA.get_upper_bound(assumed_upper_bound); + bool assumes_lower_bound = autA.get_lower_bound(assumed_lower_bound); + if (!assumes_upper_bound && !assumes_lower_bound) { + // automaton with no assumptions is always usable + assumption = autA; + found = true; + break; + } + // TODO check consistency of bounds assumptions + } // foreach(a in regex_automaton_assumptions) + } + if (found) { + if (exact_length_value.is_zero()) { + // check consistency of 0-length solution with automaton + eautomaton * aut = assumption.get_automaton(); + bool zero_solution = false; + unsigned initial_state = aut->init(); + if (aut->is_final_state(initial_state)) { + zero_solution = true; + } else { + unsigned_vector eps_states; + aut->get_epsilon_closure(initial_state, eps_states); + for (unsigned_vector::iterator it = eps_states.begin(); it != eps_states.end(); ++it) { + unsigned state = *it; + if (aut->is_final_state(state)) { + zero_solution = true; + break; + } + } + } + + // now check polarity of automaton wrt. original term + if ( (current_assignment == l_true && !assumption.get_polarity()) + || (current_assignment == l_false && assumption.get_polarity())) { + // invert sense + zero_solution = !zero_solution; + } + + if (zero_solution) { + TRACE("str", tout << "zero-length solution OK -- asserting empty path constraint" << std::endl;); + expr_ref_vector lhs_terms(m); + if (current_assignment == l_true) { + lhs_terms.push_back(str_in_re); + } else { + lhs_terms.push_back(m.mk_not(str_in_re)); + } + lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); + expr_ref lhs(mk_and(lhs_terms), m); + expr_ref rhs(ctx.mk_eq_atom(str, mk_string("")), m); + assert_implication(lhs, rhs); + regex_terms_with_path_constraints.insert(str_in_re); + m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); + } else { + TRACE("str", tout << "zero-length solution not admitted by this automaton -- asserting conflict clause" << std::endl;); + expr_ref_vector lhs_terms(m); + if (current_assignment == l_true) { + lhs_terms.push_back(str_in_re); + } else { + lhs_terms.push_back(m.mk_not(str_in_re)); + } + lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); + expr_ref lhs(mk_and(lhs_terms), m); + expr_ref conflict(m.mk_not(lhs), m); + assert_axiom(conflict); + } + regex_axiom_add = true; + regex_inc_counter(regex_length_attempt_count, re); + continue; + } else { + expr_ref pathConstraint(m); + expr_ref characterConstraints(m); + pathConstraint = generate_regex_path_constraints(str, assumption.get_automaton(), exact_length_value, characterConstraints); + TRACE("str", tout << "generated regex path constraint " << mk_pp(pathConstraint, m) << std::endl;); + TRACE("str", tout << "character constraints are " << mk_pp(characterConstraints, m) << std::endl;); + + expr_ref_vector lhs_terms(m); + if (current_assignment == l_true) { + lhs_terms.push_back(str_in_re); + } else { + lhs_terms.push_back(m.mk_not(str_in_re)); + } + lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); + expr_ref lhs(mk_and(lhs_terms), m); + + // If the path constraint comes out as "false", this means there are no paths of that length + // in the automaton. If the polarity is the same, we can assert a conflict clause. + // If the polarity is opposite, we ignore the path constraint. + + if (m.is_false(pathConstraint)) { + if ( (current_assignment == l_true && assumption.get_polarity()) + || (current_assignment == l_false && !assumption.get_polarity())) { + // automaton and constraint have same polarity -- assert conflict clause + TRACE("str", tout << "path constraint is false with matching polarity; asserting conflict clause" << std::endl;); + expr_ref conflict(m.mk_not(mk_and(lhs_terms)), m); + assert_axiom(conflict); + // don't set up "regex_terms_with_path_constraints" as a conflict clause is not a path constraint + } else { + // automaton and constraint have opposite polarity -- ignore path constraint + TRACE("str", tout << "path constraint is false with opposite polarity; ignoring path constraint" << std::endl;); + assert_implication(lhs, characterConstraints); + regex_terms_with_path_constraints.insert(str_in_re); + m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); + } + regex_axiom_add = true; + } else { + // If the automaton was built with the same polarity as the constraint, + // assert directly. Otherwise, negate the path constraint + if ( (current_assignment == l_true && assumption.get_polarity()) + || (current_assignment == l_false && !assumption.get_polarity())) { + TRACE("str", tout << "automaton and regex term have same polarity" << std::endl;); + expr_ref rhs(m.mk_and(pathConstraint, characterConstraints), m); + assert_implication(lhs, rhs); + } else { + TRACE("str", tout << "automaton and regex term have opposite polarity" << std::endl;); + expr_ref rhs(m.mk_and(m.mk_not(pathConstraint), characterConstraints), m); + assert_implication(lhs, rhs); + } + regex_terms_with_path_constraints.insert(str_in_re); + m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); + regex_axiom_add = true; + } + + // increment LengthAttemptCount + regex_inc_counter(regex_length_attempt_count, re); + + TRACE("str", + { + unsigned v = regex_get_counter(regex_length_attempt_count, re); + tout << "length attempt count for " << mk_pp(re, m) << " is " << v << std::endl; + }); + + continue; + } + } else { + // no automata available, or else all bounds assumptions are invalid + unsigned expected_complexity = estimate_regex_complexity(re); + if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold) { + CTRACE("str", regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold, + tout << "failed automaton threshold reached for " << mk_pp(str_in_re, m) << " -- automatically constructing full automaton" << std::endl;); + eautomaton * aut = m_mk_aut(re); + aut->compress(); + regex_automata.push_back(aut); + regex_automaton_under_assumptions new_aut(re, aut, true); + if (!regex_automaton_assumptions.contains(re)) { + regex_automaton_assumptions.insert(re, svector()); + } + regex_automaton_assumptions[re].push_back(new_aut); + TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); + regex_axiom_add = true; + find_automaton_initial_bounds(str_in_re, aut); + } else { + regex_inc_counter(regex_fail_count, str_in_re); + } + continue; + } + } // get_len_value() + expr_ref str_len(mk_strlen(str), m); + rational lower_bound_value; + rational upper_bound_value; + bool lower_bound_exists = lower_bound(str_len, lower_bound_value); + bool upper_bound_exists = upper_bound(str_len, upper_bound_value); + CTRACE("str", lower_bound_exists, tout << "lower bound of " << mk_pp(str, m) << " is " << lower_bound_value << std::endl;); + CTRACE("str", upper_bound_exists, tout << "upper bound of " << mk_pp(str, m) << " is " << upper_bound_value << std::endl;); + + bool new_lower_bound_info = true; + bool new_upper_bound_info = true; + // check last seen lower/upper bound to avoid performing duplicate work + if (regex_last_lower_bound.contains(str)) { + rational last_lb_value; + regex_last_lower_bound.find(str, last_lb_value); + if (last_lb_value == lower_bound_value) { + new_lower_bound_info = false; + } + } + if (regex_last_upper_bound.contains(str)) { + rational last_ub_value; + regex_last_upper_bound.find(str, last_ub_value); + if (last_ub_value == upper_bound_value) { + new_upper_bound_info = false; + } + } + + if (new_lower_bound_info) { + regex_last_lower_bound.insert(str, lower_bound_value); + } + if (new_upper_bound_info) { + regex_last_upper_bound.insert(str, upper_bound_value); + } + + if (upper_bound_exists && new_upper_bound_info) { + // check current assumptions + if (regex_automaton_assumptions.contains(re) && + !regex_automaton_assumptions[re].empty()){ + // one or more existing assumptions. + // see if the (current best) upper bound can be refined + // (note that if we have an automaton with no assumption, + // this automatically counts as best) + bool need_assumption = true; + regex_automaton_under_assumptions last_assumption; + rational last_ub = rational::minus_one(); + for (svector::iterator it = regex_automaton_assumptions[re].begin(); + it != regex_automaton_assumptions[re].end(); ++it) { + regex_automaton_under_assumptions autA = *it; + if ((current_assignment == l_true && autA.get_polarity() == false) + || (current_assignment == l_false && autA.get_polarity() == true)) { + // automaton uses incorrect polarity + continue; + } + rational this_ub; + if (autA.get_upper_bound(this_ub)) { + if (last_ub == rational::minus_one() || this_ub < last_ub) { + last_ub = this_ub; + last_assumption = autA; + } + } else { + need_assumption = false; + last_assumption = autA; + break; + } + } + if (!last_ub.is_minus_one() || !need_assumption) { + CTRACE("str", !need_assumption, tout << "using automaton with full length information" << std::endl;); + CTRACE("str", need_assumption, tout << "using automaton with assumed upper bound of " << last_ub << std::endl;); + + rational refined_upper_bound; + bool solution_at_upper_bound = refine_automaton_upper_bound(last_assumption.get_automaton(), + upper_bound_value, refined_upper_bound); + TRACE("str", tout << "refined upper bound is " << refined_upper_bound << + (solution_at_upper_bound?", solution at upper bound":", no solution at upper bound") << std::endl;); + + expr_ref_vector lhs(m); + if (current_assignment == l_false) { + lhs.push_back(m.mk_not(str_in_re)); + } else { + lhs.push_back(str_in_re); + } + if (need_assumption) { + lhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(last_ub, true))); + } + lhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(upper_bound_value, true))); + + expr_ref_vector rhs(m); + + if (solution_at_upper_bound) { + if (refined_upper_bound.is_minus_one()) { + // If there are solutions at the upper bound but not below it, make the bound exact. + rhs.push_back(ctx.mk_eq_atom(str_len, m_autil.mk_numeral(upper_bound_value, true))); + } else { + // If there are solutions at and below the upper bound, add an additional bound. + rhs.push_back(m.mk_or( + ctx.mk_eq_atom(str_len, m_autil.mk_numeral(upper_bound_value, true)), + m_autil.mk_le(str_len, m_autil.mk_numeral(refined_upper_bound, true)) + )); + } + } else { + if (refined_upper_bound.is_minus_one()) { + // If there are no solutions at or below the upper bound, assert a conflict clause. + rhs.push_back(m.mk_not(m_autil.mk_le(str_len, m_autil.mk_numeral(upper_bound_value, true)))); + } else { + // If there are solutions below the upper bound but not at it, refine the bound. + rhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(refined_upper_bound, true))); + } + } + + if (!rhs.empty()) { + expr_ref lhs_terms(mk_and(lhs), m); + expr_ref rhs_terms(mk_and(rhs), m); + assert_implication(lhs_terms, rhs_terms); + regex_axiom_add = true; + } + } + } else { + // no existing automata/assumptions. + // if it's easy to construct a full automaton for R, do so + unsigned expected_complexity = estimate_regex_complexity(re); + bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); + if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || failureThresholdExceeded) { + eautomaton * aut = m_mk_aut(re); + aut->compress(); + regex_automata.push_back(aut); + regex_automaton_under_assumptions new_aut(re, aut, true); + if (!regex_automaton_assumptions.contains(re)) { + regex_automaton_assumptions.insert(re, svector()); + } + regex_automaton_assumptions[re].push_back(new_aut); + TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); + regex_axiom_add = true; + find_automaton_initial_bounds(str_in_re, aut); + } else { + // TODO check negation? + // TODO construct a partial automaton for R to the given upper bound? + if (false) { + + } else { + regex_inc_counter(regex_fail_count, str_in_re); + } + } + continue; + } + // if we have *any* automaton for R, and the upper bound is not too large, + // finitize the automaton (if we have not already done so) and assert all solutions + if (upper_bound_value < 50) { // TODO better metric for threshold + // NOT_IMPLEMENTED_YET(); // TODO(mtrberzi) + } + } else { // !upper_bound_exists + // no upper bound information + if (lower_bound_exists && !lower_bound_value.is_zero() && new_lower_bound_info) { + // nonzero lower bound, no upper bound + + // check current assumptions + if (regex_automaton_assumptions.contains(re) && + !regex_automaton_assumptions[re].empty()){ + // one or more existing assumptions. + // see if the (current best) lower bound can be refined + // (note that if we have an automaton with no assumption, + // this automatically counts as best) + bool need_assumption = true; + regex_automaton_under_assumptions last_assumption; + rational last_lb = rational::zero(); // the default + for (svector::iterator it = regex_automaton_assumptions[re].begin(); + it != regex_automaton_assumptions[re].end(); ++it) { + regex_automaton_under_assumptions autA = *it; + if ((current_assignment == l_true && autA.get_polarity() == false) + || (current_assignment == l_false && autA.get_polarity() == true)) { + // automaton uses incorrect polarity + continue; + } + rational this_lb; + if (autA.get_lower_bound(this_lb)) { + if (this_lb > last_lb) { + last_lb = this_lb; + last_assumption = autA; + } + } else { + need_assumption = false; + last_assumption = autA; + break; + } + } + if (!last_lb.is_zero() || !need_assumption) { + CTRACE("str", !need_assumption, tout << "using automaton with full length information" << std::endl;); + CTRACE("str", need_assumption, tout << "using automaton with assumed lower bound of " << last_lb << std::endl;); + rational refined_lower_bound; + bool solution_at_lower_bound = refine_automaton_lower_bound(last_assumption.get_automaton(), + lower_bound_value, refined_lower_bound); + TRACE("str", tout << "refined lower bound is " << refined_lower_bound << + (solution_at_lower_bound?", solution at lower bound":", no solution at lower bound") << std::endl;); + + expr_ref_vector lhs(m); + if (current_assignment == l_false) { + lhs.push_back(m.mk_not(str_in_re)); + } else { + lhs.push_back(str_in_re); + } + if (need_assumption) { + lhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(last_lb, true))); + } + lhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(lower_bound_value, true))); + + expr_ref_vector rhs(m); + + if (solution_at_lower_bound) { + if (refined_lower_bound.is_minus_one()) { + // If there are solutions at the lower bound but not above it, make the bound exact. + rhs.push_back(ctx.mk_eq_atom(str_len, m_autil.mk_numeral(lower_bound_value, true))); + } else { + // If there are solutions at and above the lower bound, add an additional bound. + // DISABLED as this is causing non-termination in the integer solver. --mtrberzi + /* + rhs.push_back(m.mk_or( + ctx.mk_eq_atom(str_len, m_autil.mk_numeral(lower_bound_value, true)), + m_autil.mk_ge(str_len, m_autil.mk_numeral(refined_lower_bound, true)) + )); + */ + } + } else { + if (refined_lower_bound.is_minus_one()) { + // If there are no solutions at or above the lower bound, assert a conflict clause. + rhs.push_back(m.mk_not(m_autil.mk_ge(str_len, m_autil.mk_numeral(lower_bound_value, true)))); + } else { + // If there are solutions above the lower bound but not at it, refine the bound. + rhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(refined_lower_bound, true))); + } + } + + if (!rhs.empty()) { + expr_ref lhs_terms(mk_and(lhs), m); + expr_ref rhs_terms(mk_and(rhs), m); + assert_implication(lhs_terms, rhs_terms); + regex_axiom_add = true; + } + } + } else { + // no existing automata/assumptions. + // if it's easy to construct a full automaton for R, do so + unsigned expected_complexity = estimate_regex_complexity(re); + bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); + if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || failureThresholdExceeded) { + eautomaton * aut = m_mk_aut(re); + aut->compress(); + regex_automata.push_back(aut); + regex_automaton_under_assumptions new_aut(re, aut, true); + if (!regex_automaton_assumptions.contains(re)) { + regex_automaton_assumptions.insert(re, svector()); + } + regex_automaton_assumptions[re].push_back(new_aut); + TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); + regex_axiom_add = true; + find_automaton_initial_bounds(str_in_re, aut); + } else { + // TODO check negation? + // TODO construct a partial automaton for R to the given lower bound? + if (false) { + + } else { + regex_inc_counter(regex_fail_count, str_in_re); + } + } + continue; + } + } else { // !lower_bound_exists + // no bounds information + // check for existing automata; + // try to construct an automaton if we don't have one yet + // and doing so without bounds is not difficult + bool existingAutomata = (regex_automaton_assumptions.contains(re) && !regex_automaton_assumptions[re].empty()); + bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); + if (!existingAutomata) { + unsigned expected_complexity = estimate_regex_complexity(re); + if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold + || failureThresholdExceeded) { + eautomaton * aut = m_mk_aut(re); + aut->compress(); + regex_automata.push_back(aut); + regex_automaton_under_assumptions new_aut(re, aut, true); + if (!regex_automaton_assumptions.contains(re)) { + regex_automaton_assumptions.insert(re, svector()); + } + regex_automaton_assumptions[re].push_back(new_aut); + TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); + regex_axiom_add = true; + find_automaton_initial_bounds(str_in_re, aut); + } else { + regex_inc_counter(regex_fail_count, str_in_re); + } + } else { + regex_inc_counter(regex_fail_count, str_in_re); + } + } + } + } // foreach (entry in regex_terms) + + for (obj_map >::iterator it = regex_terms_by_string.begin(); + it != regex_terms_by_string.end(); ++it) { + // TODO do we need to check equivalence classes of strings here? + + expr * str = it->m_key; + ptr_vector str_in_re_terms = it->m_value; + + svector intersect_constraints; + // we may find empty intersection before checking every constraint; + // this vector keeps track of which ones actually take part in intersection + svector used_intersect_constraints; + + // choose an automaton/assumption for each assigned (str.in.re) + // that's consistent with the current length information + for (ptr_vector::iterator term_it = str_in_re_terms.begin(); + term_it != str_in_re_terms.end(); ++term_it) { + expr * _unused = nullptr; + expr * re = nullptr; + SASSERT(u.str.is_in_re(*term_it)); + u.str.is_in_re(*term_it, _unused, re); + + rational exact_len; + bool has_exact_len = get_len_value(str, exact_len); + + rational lb, ub; + bool has_lower_bound = lower_bound(mk_strlen(str), lb); + bool has_upper_bound = upper_bound(mk_strlen(str), ub); + + if (regex_automaton_assumptions.contains(re) && + !regex_automaton_assumptions[re].empty()){ + for (svector::iterator aut_it = regex_automaton_assumptions[re].begin(); + aut_it != regex_automaton_assumptions[re].end(); ++aut_it) { + regex_automaton_under_assumptions aut = *aut_it; + rational aut_ub; + bool assume_ub = aut.get_upper_bound(aut_ub); + rational aut_lb; + bool assume_lb = aut.get_lower_bound(aut_lb); + bool consistent = true; + + if (assume_ub) { + // check consistency of assumed upper bound + if (has_exact_len) { + if (exact_len > aut_ub) { + consistent = false; + } + } else { + if (has_upper_bound && ub > aut_ub) { + consistent = false; + } + } + } + + if (assume_lb) { + // check consistency of assumed lower bound + if (has_exact_len) { + if (exact_len < aut_lb) { + consistent = false; + } + } else { + if (has_lower_bound && lb < aut_lb) { + consistent = false; + } + } + } + + if (consistent) { + intersect_constraints.push_back(aut); + break; + } + } + } + } // foreach(term in str_in_re_terms) + + eautomaton * aut_inter = NULL; + CTRACE("str", !intersect_constraints.empty(), tout << "check intersection of automata constraints for " << mk_pp(str, m) << std::endl;); + for (svector::iterator aut_it = intersect_constraints.begin(); + aut_it != intersect_constraints.end(); ++aut_it) { + regex_automaton_under_assumptions aut = *aut_it; + if (aut_inter == NULL) { + // start somewhere + aut_inter = aut.get_automaton(); + used_intersect_constraints.push_back(aut); + continue; + } + + TRACE("str", + { + unsigned v = regex_get_counter(regex_length_attempt_count, aut.get_regex_term()); + tout << "length attempt count of " << mk_pp(aut.get_regex_term(), m) << " is " << v + << ", threshold is " << m_params.m_RegexAutomata_LengthAttemptThreshold << std::endl; + }); + + if (regex_get_counter(regex_length_attempt_count, aut.get_regex_term()) >= m_params.m_RegexAutomata_LengthAttemptThreshold) { + unsigned intersectionDifficulty = estimate_automata_intersection_difficulty(aut_inter, aut.get_automaton()); + TRACE("str", tout << "intersection difficulty is " << intersectionDifficulty << std::endl;); + if (intersectionDifficulty <= m_params.m_RegexAutomata_IntersectionDifficultyThreshold + || regex_get_counter(regex_intersection_fail_count, aut.get_regex_term()) >= m_params.m_RegexAutomata_FailedIntersectionThreshold) { + + expr * str_in_re_term(u.re.mk_in_re(str, aut.get_regex_term())); + lbool current_assignment = ctx.get_assignment(str_in_re_term); + // if the assignment is consistent with our assumption, use the automaton directly; + // otherwise, complement it (and save that automaton for next time) + // TODO we should cache these intermediate results + // TODO do we need to push the intermediates into a vector for deletion anyway? + if ( (current_assignment == l_true && aut.get_polarity()) + || (current_assignment == l_false && !aut.get_polarity())) { + aut_inter = m_mk_aut.mk_product(aut_inter, aut.get_automaton()); + m_automata.push_back(aut_inter); + } else { + // need to complement first + expr_ref rc(u.re.mk_complement(aut.get_regex_term()), m); + eautomaton * aut_c = m_mk_aut(rc); + regex_automata.push_back(aut_c); + // TODO is there any way to build a complement automaton from an existing one? + // this discards length information + aut_inter = m_mk_aut.mk_product(aut_inter, aut_c); + m_automata.push_back(aut_inter); + } + used_intersect_constraints.push_back(aut); + if (aut_inter->is_empty()) { + break; + } + } else { + // failed intersection + regex_inc_counter(regex_intersection_fail_count, aut.get_regex_term()); + } + } + } // foreach(entry in intersect_constraints) + if (aut_inter != NULL) { + aut_inter->compress(); + } + TRACE("str", tout << "intersected " << used_intersect_constraints.size() << " constraints" << std::endl;); + + expr_ref_vector conflict_terms(m); + expr_ref conflict_lhs(m); + for (svector::iterator aut_it = used_intersect_constraints.begin(); + aut_it != used_intersect_constraints.end(); ++aut_it) { + regex_automaton_under_assumptions aut = *aut_it; + expr * str_in_re_term(u.re.mk_in_re(str, aut.get_regex_term())); + lbool current_assignment = ctx.get_assignment(str_in_re_term); + if (current_assignment == l_true) { + conflict_terms.push_back(str_in_re_term); + } else if (current_assignment == l_false) { + conflict_terms.push_back(m.mk_not(str_in_re_term)); + } + // add length assumptions, if any + rational ub; + if (aut.get_upper_bound(ub)) { + expr_ref ub_term(m_autil.mk_le(mk_strlen(str), m_autil.mk_numeral(ub, true)), m); + conflict_terms.push_back(ub_term); + } + rational lb; + if (aut.get_lower_bound(lb)) { + expr_ref lb_term(m_autil.mk_ge(mk_strlen(str), m_autil.mk_numeral(lb, true)), m); + conflict_terms.push_back(lb_term); + } + } + conflict_lhs = mk_and(conflict_terms); + + if (used_intersect_constraints.size() > 1 && aut_inter != NULL) { + // check whether the intersection is only the empty string + unsigned initial_state = aut_inter->init(); + if (aut_inter->final_states().size() == 1 && aut_inter->is_final_state(initial_state)) { + // initial state is final and it is the only final state + // if there are no moves from the initial state, + // the only solution is the empty string + if (aut_inter->get_moves_from(initial_state).empty()) { + TRACE("str", tout << "product automaton only accepts empty string" << std::endl;); + expr_ref rhs1(ctx.mk_eq_atom(str, mk_string("")), m); + expr_ref rhs2(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(rational::zero(), true)), m); + expr_ref rhs(m.mk_and(rhs1, rhs2), m); + assert_implication(conflict_lhs, rhs); + regex_axiom_add = true; + } + } + } + + if (aut_inter != NULL && aut_inter->is_empty()) { + TRACE("str", tout << "product automaton is empty; asserting conflict clause" << std::endl;); + expr_ref conflict_clause(m.mk_not(mk_and(conflict_terms)), m); + assert_axiom(conflict_clause); + add_persisted_axiom(conflict_clause); + regex_axiom_add = true; + } + } // foreach (entry in regex_terms_by_string) + if (regex_axiom_add) { + //return FC_CONTINUE; + } + } + final_check_status theory_str::final_check_eh() { context & ctx = get_context(); ast_manager & m = get_manager(); @@ -9888,8 +10643,10 @@ namespace smt { if (!u.str.is_string(concat_lhs)) { lhs_terms.push_back(ctx.mk_eq_atom(concat_lhs, concat_lhs_str)); } + if (!u.str.is_string(concat_rhs)) { lhs_terms.push_back(ctx.mk_eq_atom(concat_rhs, concat_rhs_str)); + } if (lhs_terms.empty()) { @@ -9926,726 +10683,7 @@ namespace smt { // regex automata if (m_params.m_RegexAutomata) { - // TODO since heuristics might fail, the "no progress" flag might need to be handled specially here - bool regex_axiom_add = false; - for (obj_hashtable::iterator it = regex_terms.begin(); it != regex_terms.end(); ++it) { - expr * str_in_re = *it; - expr * str = nullptr; - expr * re = nullptr; - u.str.is_in_re(str_in_re, str, re); - lbool current_assignment = ctx.get_assignment(str_in_re); - TRACE("str", tout << "regex term: " << mk_pp(str, m) << " in " << mk_pp(re, m) << " : " << current_assignment << std::endl;); - if (current_assignment == l_undef) { - continue; - } - - if (!regex_terms_with_length_constraints.contains(str_in_re)) { - if (current_assignment == l_true && check_regex_length_linearity(re)) { - TRACE("str", tout << "regex length constraints expected to be linear -- generating and asserting them" << std::endl;); - - if (regex_term_to_length_constraint.contains(str_in_re)) { - // use existing length constraint - expr * top_level_length_constraint = nullptr; - regex_term_to_length_constraint.find(str_in_re, top_level_length_constraint); - - ptr_vector extra_length_vars; - regex_term_to_extra_length_vars.find(str_in_re, extra_length_vars); - - assert_axiom(top_level_length_constraint); - for(ptr_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { - expr * v = *it; - refresh_theory_var(v); - expr_ref len_constraint(m_autil.mk_ge(v, m_autil.mk_numeral(rational::zero(), true)), m); - assert_axiom(len_constraint); - } - } else { - // generate new length constraint - expr_ref_vector extra_length_vars(m); - expr_ref _top_level_length_constraint = infer_all_regex_lengths(mk_strlen(str), re, extra_length_vars); - expr_ref top_level_length_constraint(_top_level_length_constraint, m); - th_rewriter rw(m); - rw(top_level_length_constraint); - TRACE("str", tout << "top-level length constraint: " << mk_pp(top_level_length_constraint, m) << std::endl;); - // assert and track length constraint - assert_axiom(top_level_length_constraint); - for(expr_ref_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { - expr * v = *it; - expr_ref len_constraint(m_autil.mk_ge(v, m_autil.mk_numeral(rational::zero(), true)), m); - assert_axiom(len_constraint); - } - - regex_term_to_length_constraint.insert(str_in_re, top_level_length_constraint); - ptr_vector vtmp; - for(expr_ref_vector::iterator it = extra_length_vars.begin(); it != extra_length_vars.end(); ++it) { - vtmp.push_back(*it); - } - regex_term_to_extra_length_vars.insert(str_in_re, vtmp); - } - - regex_terms_with_length_constraints.insert(str_in_re); - m_trail_stack.push(insert_obj_trail(regex_terms_with_length_constraints, str_in_re)); - regex_axiom_add = true; - } - } // re not in regex_terms_with_length_constraints - - rational exact_length_value; - if (get_len_value(str, exact_length_value)) { - TRACE("str", tout << "exact length of " << mk_pp(str, m) << " is " << exact_length_value << std::endl;); - - if (regex_terms_with_path_constraints.contains(str_in_re)) { - TRACE("str", tout << "term " << mk_pp(str_in_re, m) << " already has path constraints set up" << std::endl;); - continue; - } - - // find a consistent automaton for this term - bool found = false; - regex_automaton_under_assumptions assumption; - if (regex_automaton_assumptions.contains(re) && - !regex_automaton_assumptions[re].empty()){ - for (svector::iterator it = regex_automaton_assumptions[re].begin(); - it != regex_automaton_assumptions[re].end(); ++it) { - regex_automaton_under_assumptions autA = *it; - rational assumed_upper_bound, assumed_lower_bound; - bool assumes_upper_bound = autA.get_upper_bound(assumed_upper_bound); - bool assumes_lower_bound = autA.get_lower_bound(assumed_lower_bound); - if (!assumes_upper_bound && !assumes_lower_bound) { - // automaton with no assumptions is always usable - assumption = autA; - found = true; - break; - } - // TODO check consistency of bounds assumptions - } // foreach(a in regex_automaton_assumptions) - } - if (found) { - if (exact_length_value.is_zero()) { - // check consistency of 0-length solution with automaton - eautomaton * aut = assumption.get_automaton(); - bool zero_solution = false; - unsigned initial_state = aut->init(); - if (aut->is_final_state(initial_state)) { - zero_solution = true; - } else { - unsigned_vector eps_states; - aut->get_epsilon_closure(initial_state, eps_states); - for (unsigned_vector::iterator it = eps_states.begin(); it != eps_states.end(); ++it) { - unsigned state = *it; - if (aut->is_final_state(state)) { - zero_solution = true; - break; - } - } - } - - // now check polarity of automaton wrt. original term - if ( (current_assignment == l_true && !assumption.get_polarity()) - || (current_assignment == l_false && assumption.get_polarity())) { - // invert sense - zero_solution = !zero_solution; - } - - if (zero_solution) { - TRACE("str", tout << "zero-length solution OK -- asserting empty path constraint" << std::endl;); - expr_ref_vector lhs_terms(m); - if (current_assignment == l_true) { - lhs_terms.push_back(str_in_re); - } else { - lhs_terms.push_back(m.mk_not(str_in_re)); - } - lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); - expr_ref lhs(mk_and(lhs_terms), m); - expr_ref rhs(ctx.mk_eq_atom(str, mk_string("")), m); - assert_implication(lhs, rhs); - regex_terms_with_path_constraints.insert(str_in_re); - m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); - } else { - TRACE("str", tout << "zero-length solution not admitted by this automaton -- asserting conflict clause" << std::endl;); - expr_ref_vector lhs_terms(m); - if (current_assignment == l_true) { - lhs_terms.push_back(str_in_re); - } else { - lhs_terms.push_back(m.mk_not(str_in_re)); - } - lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); - expr_ref lhs(mk_and(lhs_terms), m); - expr_ref conflict(m.mk_not(lhs), m); - assert_axiom(conflict); - } - regex_axiom_add = true; - regex_inc_counter(regex_length_attempt_count, re); - continue; - } else { - expr_ref pathConstraint(m); - expr_ref characterConstraints(m); - pathConstraint = generate_regex_path_constraints(str, assumption.get_automaton(), exact_length_value, characterConstraints); - TRACE("str", tout << "generated regex path constraint " << mk_pp(pathConstraint, m) << std::endl;); - TRACE("str", tout << "character constraints are " << mk_pp(characterConstraints, m) << std::endl;); - - expr_ref_vector lhs_terms(m); - if (current_assignment == l_true) { - lhs_terms.push_back(str_in_re); - } else { - lhs_terms.push_back(m.mk_not(str_in_re)); - } - lhs_terms.push_back(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(exact_length_value, true))); - expr_ref lhs(mk_and(lhs_terms), m); - - // If the path constraint comes out as "false", this means there are no paths of that length - // in the automaton. If the polarity is the same, we can assert a conflict clause. - // If the polarity is opposite, we ignore the path constraint. - - if (m.is_false(pathConstraint)) { - if ( (current_assignment == l_true && assumption.get_polarity()) - || (current_assignment == l_false && !assumption.get_polarity())) { - // automaton and constraint have same polarity -- assert conflict clause - TRACE("str", tout << "path constraint is false with matching polarity; asserting conflict clause" << std::endl;); - expr_ref conflict(m.mk_not(mk_and(lhs_terms)), m); - assert_axiom(conflict); - // don't set up "regex_terms_with_path_constraints" as a conflict clause is not a path constraint - } else { - // automaton and constraint have opposite polarity -- ignore path constraint - TRACE("str", tout << "path constraint is false with opposite polarity; ignoring path constraint" << std::endl;); - assert_implication(lhs, characterConstraints); - regex_terms_with_path_constraints.insert(str_in_re); - m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); - } - regex_axiom_add = true; - } else { - // If the automaton was built with the same polarity as the constraint, - // assert directly. Otherwise, negate the path constraint - if ( (current_assignment == l_true && assumption.get_polarity()) - || (current_assignment == l_false && !assumption.get_polarity())) { - TRACE("str", tout << "automaton and regex term have same polarity" << std::endl;); - expr_ref rhs(m.mk_and(pathConstraint, characterConstraints), m); - assert_implication(lhs, rhs); - } else { - TRACE("str", tout << "automaton and regex term have opposite polarity" << std::endl;); - expr_ref rhs(m.mk_and(m.mk_not(pathConstraint), characterConstraints), m); - assert_implication(lhs, rhs); - } - regex_terms_with_path_constraints.insert(str_in_re); - m_trail_stack.push(insert_obj_trail(regex_terms_with_path_constraints, str_in_re)); - regex_axiom_add = true; - } - - // increment LengthAttemptCount - regex_inc_counter(regex_length_attempt_count, re); - - TRACE("str", - { - unsigned v = regex_get_counter(regex_length_attempt_count, re); - tout << "length attempt count for " << mk_pp(re, m) << " is " << v << std::endl; - }); - - continue; - } - } else { - // no automata available, or else all bounds assumptions are invalid - unsigned expected_complexity = estimate_regex_complexity(re); - if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold) { - CTRACE("str", regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold, - tout << "failed automaton threshold reached for " << mk_pp(str_in_re, m) << " -- automatically constructing full automaton" << std::endl;); - eautomaton * aut = m_mk_aut(re); - aut->compress(); - regex_automata.push_back(aut); - regex_automaton_under_assumptions new_aut(re, aut, true); - if (!regex_automaton_assumptions.contains(re)) { - regex_automaton_assumptions.insert(re, svector()); - } - regex_automaton_assumptions[re].push_back(new_aut); - TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); - regex_axiom_add = true; - find_automaton_initial_bounds(str_in_re, aut); - } else { - regex_inc_counter(regex_fail_count, str_in_re); - } - continue; - } - } // get_len_value() - expr_ref str_len(mk_strlen(str), m); - rational lower_bound_value; - rational upper_bound_value; - bool lower_bound_exists = lower_bound(str_len, lower_bound_value); - bool upper_bound_exists = upper_bound(str_len, upper_bound_value); - CTRACE("str", lower_bound_exists, tout << "lower bound of " << mk_pp(str, m) << " is " << lower_bound_value << std::endl;); - CTRACE("str", upper_bound_exists, tout << "upper bound of " << mk_pp(str, m) << " is " << upper_bound_value << std::endl;); - - bool new_lower_bound_info = true; - bool new_upper_bound_info = true; - // check last seen lower/upper bound to avoid performing duplicate work - if (regex_last_lower_bound.contains(str)) { - rational last_lb_value; - regex_last_lower_bound.find(str, last_lb_value); - if (last_lb_value == lower_bound_value) { - new_lower_bound_info = false; - } - } - if (regex_last_upper_bound.contains(str)) { - rational last_ub_value; - regex_last_upper_bound.find(str, last_ub_value); - if (last_ub_value == upper_bound_value) { - new_upper_bound_info = false; - } - } - - if (new_lower_bound_info) { - regex_last_lower_bound.insert(str, lower_bound_value); - } - if (new_upper_bound_info) { - regex_last_upper_bound.insert(str, upper_bound_value); - } - - if (upper_bound_exists && new_upper_bound_info) { - // check current assumptions - if (regex_automaton_assumptions.contains(re) && - !regex_automaton_assumptions[re].empty()){ - // one or more existing assumptions. - // see if the (current best) upper bound can be refined - // (note that if we have an automaton with no assumption, - // this automatically counts as best) - bool need_assumption = true; - regex_automaton_under_assumptions last_assumption; - rational last_ub = rational::minus_one(); - for (svector::iterator it = regex_automaton_assumptions[re].begin(); - it != regex_automaton_assumptions[re].end(); ++it) { - regex_automaton_under_assumptions autA = *it; - if ((current_assignment == l_true && autA.get_polarity() == false) - || (current_assignment == l_false && autA.get_polarity() == true)) { - // automaton uses incorrect polarity - continue; - } - rational this_ub; - if (autA.get_upper_bound(this_ub)) { - if (last_ub == rational::minus_one() || this_ub < last_ub) { - last_ub = this_ub; - last_assumption = autA; - } - } else { - need_assumption = false; - last_assumption = autA; - break; - } - } - if (!last_ub.is_minus_one() || !need_assumption) { - CTRACE("str", !need_assumption, tout << "using automaton with full length information" << std::endl;); - CTRACE("str", need_assumption, tout << "using automaton with assumed upper bound of " << last_ub << std::endl;); - - rational refined_upper_bound; - bool solution_at_upper_bound = refine_automaton_upper_bound(last_assumption.get_automaton(), - upper_bound_value, refined_upper_bound); - TRACE("str", tout << "refined upper bound is " << refined_upper_bound << - (solution_at_upper_bound?", solution at upper bound":", no solution at upper bound") << std::endl;); - - expr_ref_vector lhs(m); - if (current_assignment == l_false) { - lhs.push_back(m.mk_not(str_in_re)); - } else { - lhs.push_back(str_in_re); - } - if (need_assumption) { - lhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(last_ub, true))); - } - lhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(upper_bound_value, true))); - - expr_ref_vector rhs(m); - - if (solution_at_upper_bound) { - if (refined_upper_bound.is_minus_one()) { - // If there are solutions at the upper bound but not below it, make the bound exact. - rhs.push_back(ctx.mk_eq_atom(str_len, m_autil.mk_numeral(upper_bound_value, true))); - } else { - // If there are solutions at and below the upper bound, add an additional bound. - rhs.push_back(m.mk_or( - ctx.mk_eq_atom(str_len, m_autil.mk_numeral(upper_bound_value, true)), - m_autil.mk_le(str_len, m_autil.mk_numeral(refined_upper_bound, true)) - )); - } - } else { - if (refined_upper_bound.is_minus_one()) { - // If there are no solutions at or below the upper bound, assert a conflict clause. - rhs.push_back(m.mk_not(m_autil.mk_le(str_len, m_autil.mk_numeral(upper_bound_value, true)))); - } else { - // If there are solutions below the upper bound but not at it, refine the bound. - rhs.push_back(m_autil.mk_le(str_len, m_autil.mk_numeral(refined_upper_bound, true))); - } - } - - if (!rhs.empty()) { - expr_ref lhs_terms(mk_and(lhs), m); - expr_ref rhs_terms(mk_and(rhs), m); - assert_implication(lhs_terms, rhs_terms); - regex_axiom_add = true; - } - } - } else { - // no existing automata/assumptions. - // if it's easy to construct a full automaton for R, do so - unsigned expected_complexity = estimate_regex_complexity(re); - bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); - if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || failureThresholdExceeded) { - eautomaton * aut = m_mk_aut(re); - aut->compress(); - regex_automata.push_back(aut); - regex_automaton_under_assumptions new_aut(re, aut, true); - if (!regex_automaton_assumptions.contains(re)) { - regex_automaton_assumptions.insert(re, svector()); - } - regex_automaton_assumptions[re].push_back(new_aut); - TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); - regex_axiom_add = true; - find_automaton_initial_bounds(str_in_re, aut); - } else { - // TODO check negation? - // TODO construct a partial automaton for R to the given upper bound? - if (false) { - - } else { - regex_inc_counter(regex_fail_count, str_in_re); - } - } - continue; - } - // if we have *any* automaton for R, and the upper bound is not too large, - // finitize the automaton (if we have not already done so) and assert all solutions - if (upper_bound_value < 50) { // TODO better metric for threshold - // NOT_IMPLEMENTED_YET(); // TODO(mtrberzi) - } - } else { // !upper_bound_exists - // no upper bound information - if (lower_bound_exists && !lower_bound_value.is_zero() && new_lower_bound_info) { - // nonzero lower bound, no upper bound - - // check current assumptions - if (regex_automaton_assumptions.contains(re) && - !regex_automaton_assumptions[re].empty()){ - // one or more existing assumptions. - // see if the (current best) lower bound can be refined - // (note that if we have an automaton with no assumption, - // this automatically counts as best) - bool need_assumption = true; - regex_automaton_under_assumptions last_assumption; - rational last_lb = rational::zero(); // the default - for (svector::iterator it = regex_automaton_assumptions[re].begin(); - it != regex_automaton_assumptions[re].end(); ++it) { - regex_automaton_under_assumptions autA = *it; - if ((current_assignment == l_true && autA.get_polarity() == false) - || (current_assignment == l_false && autA.get_polarity() == true)) { - // automaton uses incorrect polarity - continue; - } - rational this_lb; - if (autA.get_lower_bound(this_lb)) { - if (this_lb > last_lb) { - last_lb = this_lb; - last_assumption = autA; - } - } else { - need_assumption = false; - last_assumption = autA; - break; - } - } - if (!last_lb.is_zero() || !need_assumption) { - CTRACE("str", !need_assumption, tout << "using automaton with full length information" << std::endl;); - CTRACE("str", need_assumption, tout << "using automaton with assumed lower bound of " << last_lb << std::endl;); - rational refined_lower_bound; - bool solution_at_lower_bound = refine_automaton_lower_bound(last_assumption.get_automaton(), - lower_bound_value, refined_lower_bound); - TRACE("str", tout << "refined lower bound is " << refined_lower_bound << - (solution_at_lower_bound?", solution at lower bound":", no solution at lower bound") << std::endl;); - - expr_ref_vector lhs(m); - if (current_assignment == l_false) { - lhs.push_back(m.mk_not(str_in_re)); - } else { - lhs.push_back(str_in_re); - } - if (need_assumption) { - lhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(last_lb, true))); - } - lhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(lower_bound_value, true))); - - expr_ref_vector rhs(m); - - if (solution_at_lower_bound) { - if (refined_lower_bound.is_minus_one()) { - // If there are solutions at the lower bound but not above it, make the bound exact. - rhs.push_back(ctx.mk_eq_atom(str_len, m_autil.mk_numeral(lower_bound_value, true))); - } else { - // If there are solutions at and above the lower bound, add an additional bound. - // DISABLED as this is causing non-termination in the integer solver. --mtrberzi - /* - rhs.push_back(m.mk_or( - ctx.mk_eq_atom(str_len, m_autil.mk_numeral(lower_bound_value, true)), - m_autil.mk_ge(str_len, m_autil.mk_numeral(refined_lower_bound, true)) - )); - */ - } - } else { - if (refined_lower_bound.is_minus_one()) { - // If there are no solutions at or above the lower bound, assert a conflict clause. - rhs.push_back(m.mk_not(m_autil.mk_ge(str_len, m_autil.mk_numeral(lower_bound_value, true)))); - } else { - // If there are solutions above the lower bound but not at it, refine the bound. - rhs.push_back(m_autil.mk_ge(str_len, m_autil.mk_numeral(refined_lower_bound, true))); - } - } - - if (!rhs.empty()) { - expr_ref lhs_terms(mk_and(lhs), m); - expr_ref rhs_terms(mk_and(rhs), m); - assert_implication(lhs_terms, rhs_terms); - regex_axiom_add = true; - } - } - } else { - // no existing automata/assumptions. - // if it's easy to construct a full automaton for R, do so - unsigned expected_complexity = estimate_regex_complexity(re); - bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); - if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold || failureThresholdExceeded) { - eautomaton * aut = m_mk_aut(re); - aut->compress(); - regex_automata.push_back(aut); - regex_automaton_under_assumptions new_aut(re, aut, true); - if (!regex_automaton_assumptions.contains(re)) { - regex_automaton_assumptions.insert(re, svector()); - } - regex_automaton_assumptions[re].push_back(new_aut); - TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); - regex_axiom_add = true; - find_automaton_initial_bounds(str_in_re, aut); - } else { - // TODO check negation? - // TODO construct a partial automaton for R to the given lower bound? - if (false) { - - } else { - regex_inc_counter(regex_fail_count, str_in_re); - } - } - continue; - } - } else { // !lower_bound_exists - // no bounds information - // check for existing automata; - // try to construct an automaton if we don't have one yet - // and doing so without bounds is not difficult - bool existingAutomata = (regex_automaton_assumptions.contains(re) && !regex_automaton_assumptions[re].empty()); - bool failureThresholdExceeded = (regex_get_counter(regex_fail_count, str_in_re) >= m_params.m_RegexAutomata_FailedAutomatonThreshold); - if (!existingAutomata) { - unsigned expected_complexity = estimate_regex_complexity(re); - if (expected_complexity <= m_params.m_RegexAutomata_DifficultyThreshold - || failureThresholdExceeded) { - eautomaton * aut = m_mk_aut(re); - aut->compress(); - regex_automata.push_back(aut); - regex_automaton_under_assumptions new_aut(re, aut, true); - if (!regex_automaton_assumptions.contains(re)) { - regex_automaton_assumptions.insert(re, svector()); - } - regex_automaton_assumptions[re].push_back(new_aut); - TRACE("str", tout << "add new automaton for " << mk_pp(re, m) << ": no assumptions" << std::endl;); - regex_axiom_add = true; - find_automaton_initial_bounds(str_in_re, aut); - } else { - regex_inc_counter(regex_fail_count, str_in_re); - } - } else { - regex_inc_counter(regex_fail_count, str_in_re); - } - } - } - } // foreach (entry in regex_terms) - - for (obj_map >::iterator it = regex_terms_by_string.begin(); - it != regex_terms_by_string.end(); ++it) { - // TODO do we need to check equivalence classes of strings here? - - expr * str = it->m_key; - ptr_vector str_in_re_terms = it->m_value; - - svector intersect_constraints; - // we may find empty intersection before checking every constraint; - // this vector keeps track of which ones actually take part in intersection - svector used_intersect_constraints; - - // choose an automaton/assumption for each assigned (str.in.re) - // that's consistent with the current length information - for (ptr_vector::iterator term_it = str_in_re_terms.begin(); - term_it != str_in_re_terms.end(); ++term_it) { - expr * _unused = nullptr; - expr * re = nullptr; - SASSERT(u.str.is_in_re(*term_it)); - u.str.is_in_re(*term_it, _unused, re); - - rational exact_len; - bool has_exact_len = get_len_value(str, exact_len); - - rational lb, ub; - bool has_lower_bound = lower_bound(mk_strlen(str), lb); - bool has_upper_bound = upper_bound(mk_strlen(str), ub); - - if (regex_automaton_assumptions.contains(re) && - !regex_automaton_assumptions[re].empty()){ - for (svector::iterator aut_it = regex_automaton_assumptions[re].begin(); - aut_it != regex_automaton_assumptions[re].end(); ++aut_it) { - regex_automaton_under_assumptions aut = *aut_it; - rational aut_ub; - bool assume_ub = aut.get_upper_bound(aut_ub); - rational aut_lb; - bool assume_lb = aut.get_lower_bound(aut_lb); - bool consistent = true; - - if (assume_ub) { - // check consistency of assumed upper bound - if (has_exact_len) { - if (exact_len > aut_ub) { - consistent = false; - } - } else { - if (has_upper_bound && ub > aut_ub) { - consistent = false; - } - } - } - - if (assume_lb) { - // check consistency of assumed lower bound - if (has_exact_len) { - if (exact_len < aut_lb) { - consistent = false; - } - } else { - if (has_lower_bound && lb < aut_lb) { - consistent = false; - } - } - } - - if (consistent) { - intersect_constraints.push_back(aut); - break; - } - } - } - } // foreach(term in str_in_re_terms) - - eautomaton * aut_inter = NULL; - CTRACE("str", !intersect_constraints.empty(), tout << "check intersection of automata constraints for " << mk_pp(str, m) << std::endl;); - for (svector::iterator aut_it = intersect_constraints.begin(); - aut_it != intersect_constraints.end(); ++aut_it) { - regex_automaton_under_assumptions aut = *aut_it; - if (aut_inter == NULL) { - // start somewhere - aut_inter = aut.get_automaton(); - used_intersect_constraints.push_back(aut); - continue; - } - - TRACE("str", - { - unsigned v = regex_get_counter(regex_length_attempt_count, aut.get_regex_term()); - tout << "length attempt count of " << mk_pp(aut.get_regex_term(), m) << " is " << v - << ", threshold is " << m_params.m_RegexAutomata_LengthAttemptThreshold << std::endl; - }); - - if (regex_get_counter(regex_length_attempt_count, aut.get_regex_term()) >= m_params.m_RegexAutomata_LengthAttemptThreshold) { - unsigned intersectionDifficulty = estimate_automata_intersection_difficulty(aut_inter, aut.get_automaton()); - TRACE("str", tout << "intersection difficulty is " << intersectionDifficulty << std::endl;); - if (intersectionDifficulty <= m_params.m_RegexAutomata_IntersectionDifficultyThreshold - || regex_get_counter(regex_intersection_fail_count, aut.get_regex_term()) >= m_params.m_RegexAutomata_FailedIntersectionThreshold) { - - expr * str_in_re_term(u.re.mk_in_re(str, aut.get_regex_term())); - lbool current_assignment = ctx.get_assignment(str_in_re_term); - // if the assignment is consistent with our assumption, use the automaton directly; - // otherwise, complement it (and save that automaton for next time) - // TODO we should cache these intermediate results - // TODO do we need to push the intermediates into a vector for deletion anyway? - if ( (current_assignment == l_true && aut.get_polarity()) - || (current_assignment == l_false && !aut.get_polarity())) { - aut_inter = m_mk_aut.mk_product(aut_inter, aut.get_automaton()); - m_automata.push_back(aut_inter); - } else { - // need to complement first - expr_ref rc(u.re.mk_complement(aut.get_regex_term()), m); - eautomaton * aut_c = m_mk_aut(rc); - regex_automata.push_back(aut_c); - // TODO is there any way to build a complement automaton from an existing one? - // this discards length information - aut_inter = m_mk_aut.mk_product(aut_inter, aut_c); - m_automata.push_back(aut_inter); - } - used_intersect_constraints.push_back(aut); - if (aut_inter->is_empty()) { - break; - } - } else { - // failed intersection - regex_inc_counter(regex_intersection_fail_count, aut.get_regex_term()); - } - } - } // foreach(entry in intersect_constraints) - if (aut_inter != NULL) { - aut_inter->compress(); - } - TRACE("str", tout << "intersected " << used_intersect_constraints.size() << " constraints" << std::endl;); - - expr_ref_vector conflict_terms(m); - expr_ref conflict_lhs(m); - for (svector::iterator aut_it = used_intersect_constraints.begin(); - aut_it != used_intersect_constraints.end(); ++aut_it) { - regex_automaton_under_assumptions aut = *aut_it; - expr * str_in_re_term(u.re.mk_in_re(str, aut.get_regex_term())); - lbool current_assignment = ctx.get_assignment(str_in_re_term); - if (current_assignment == l_true) { - conflict_terms.push_back(str_in_re_term); - } else if (current_assignment == l_false) { - conflict_terms.push_back(m.mk_not(str_in_re_term)); - } - // add length assumptions, if any - rational ub; - if (aut.get_upper_bound(ub)) { - expr_ref ub_term(m_autil.mk_le(mk_strlen(str), m_autil.mk_numeral(ub, true)), m); - conflict_terms.push_back(ub_term); - } - rational lb; - if (aut.get_lower_bound(lb)) { - expr_ref lb_term(m_autil.mk_ge(mk_strlen(str), m_autil.mk_numeral(lb, true)), m); - conflict_terms.push_back(lb_term); - } - } - conflict_lhs = mk_and(conflict_terms); - - if (used_intersect_constraints.size() > 1 && aut_inter != NULL) { - // check whether the intersection is only the empty string - unsigned initial_state = aut_inter->init(); - if (aut_inter->final_states().size() == 1 && aut_inter->is_final_state(initial_state)) { - // initial state is final and it is the only final state - // if there are no moves from the initial state, - // the only solution is the empty string - if (aut_inter->get_moves_from(initial_state).empty()) { - TRACE("str", tout << "product automaton only accepts empty string" << std::endl;); - expr_ref rhs1(ctx.mk_eq_atom(str, mk_string("")), m); - expr_ref rhs2(ctx.mk_eq_atom(mk_strlen(str), m_autil.mk_numeral(rational::zero(), true)), m); - expr_ref rhs(m.mk_and(rhs1, rhs2), m); - assert_implication(conflict_lhs, rhs); - regex_axiom_add = true; - } - } - } - - if (aut_inter != NULL && aut_inter->is_empty()) { - TRACE("str", tout << "product automaton is empty; asserting conflict clause" << std::endl;); - expr_ref conflict_clause(m.mk_not(mk_and(conflict_terms)), m); - assert_axiom(conflict_clause); - add_persisted_axiom(conflict_clause); - regex_axiom_add = true; - } - } // foreach (entry in regex_terms_by_string) - if (regex_axiom_add) { - //return FC_CONTINUE; - } + solve_regex_automata(); } // RegexAutomata bool needToAssignFreeVars = false; @@ -12180,7 +12218,7 @@ namespace smt { // - in the same EQC as freeVar if (term_appears_as_subterm(freeVar, re_str)) { TRACE("str", tout << "prevent value testing on free var " << mk_pp(freeVar, m) << " as it belongs to one or more regex constraints." << std::endl;); - return NULL; + return nullptr; } } } diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 419084091..d93c73bed 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -30,6 +30,7 @@ #include "smt/params/theory_str_params.h" #include "smt/proto_model/value_factory.h" #include "smt/smt_model_generator.h" +#include "smt/smt_arith_value.h" #include #include #include @@ -164,7 +165,7 @@ protected: rational upper_bound; public: regex_automaton_under_assumptions() : - re_term(NULL), aut(NULL), polarity(false), + re_term(nullptr), aut(nullptr), polarity(false), assume_lower_bound(false), assume_upper_bound(false) {} regex_automaton_under_assumptions(expr * re_term, eautomaton * aut, bool polarity) : @@ -546,6 +547,7 @@ protected: void process_concat_eq_unroll(expr * concat, expr * unroll); // regex automata and length-aware regex + void solve_regex_automata(); unsigned estimate_regex_complexity(expr * re); unsigned estimate_regex_complexity_under_complement(expr * re); unsigned estimate_automata_intersection_difficulty(eautomaton * aut1, eautomaton * aut2); @@ -580,6 +582,8 @@ protected: bool can_concat_eq_str(expr * concat, zstring& str); bool can_concat_eq_concat(expr * concat1, expr * concat2); bool check_concat_len_in_eqc(expr * concat); + void check_eqc_empty_string(expr * lhs, expr * rhs); + void check_eqc_concat_concat(std::set & eqc_concat_lhs, std::set & eqc_concat_rhs); bool check_length_consistency(expr * n1, expr * n2); bool check_length_const_string(expr * n1, expr * constStr); bool check_length_eq_var_concat(expr * n1, expr * n2); diff --git a/src/smt/watch_list.cpp b/src/smt/watch_list.cpp index 9835142f6..778e93021 100644 --- a/src/smt/watch_list.cpp +++ b/src/smt/watch_list.cpp @@ -36,10 +36,10 @@ namespace smt { void watch_list::expand() { if (m_data == nullptr) { - unsigned size = DEFAULT_WATCH_LIST_SIZE + HEADER_SIZE; + unsigned size = DEFAULT_WATCH_LIST_SIZE + HEADER_SIZE; unsigned * mem = reinterpret_cast(alloc_svect(char, size)); #ifdef _AMD64_ - ++mem; // make sure data is aligned in 64 bit machines + ++mem; // make sure data is aligned in 64 bit machines #endif *mem = 0; ++mem; @@ -62,9 +62,9 @@ namespace smt { unsigned * mem = reinterpret_cast(alloc_svect(char, new_capacity + HEADER_SIZE)); unsigned curr_end_cls = end_cls_core(); #ifdef _AMD64_ - ++mem; // make sure data is aligned in 64 bit machines + ++mem; // make sure data is aligned in 64 bit machines #endif - *mem = curr_end_cls; + *mem = curr_end_cls; ++mem; SASSERT(bin_bytes <= new_capacity); unsigned new_begin_bin = new_capacity - bin_bytes; diff --git a/src/solver/mus.cpp b/src/solver/mus.cpp index 094b27ed3..4ae93a52e 100644 --- a/src/solver/mus.cpp +++ b/src/solver/mus.cpp @@ -217,13 +217,12 @@ struct mus::imp { } expr_set mss_set; - for (unsigned i = 0; i < mss.size(); ++i) { - mss_set.insert(mss[i]); + for (expr* e : mss) { + mss_set.insert(e); } - expr_set::iterator it = min_core.begin(), end = min_core.end(); - for (; it != end; ++it) { - if (mss_set.contains(*it) && min_lit != *it) { - unknown.push_back(*it); + for (expr * e : min_core) { + if (mss_set.contains(e) && min_lit != e) { + unknown.push_back(e); } } core_literal = min_lit; diff --git a/src/solver/parallel_tactic.cpp b/src/solver/parallel_tactic.cpp index 142ba4bb8..4ee93ff37 100644 --- a/src/solver/parallel_tactic.cpp +++ b/src/solver/parallel_tactic.cpp @@ -672,11 +672,11 @@ public: init(); } - void operator ()(const goal_ref & g,goal_ref_buffer & result) { + void operator ()(const goal_ref & g,goal_ref_buffer & result) override { fail_if_proof_generation("parallel-tactic", g); ast_manager& m = g->m(); solver* s = m_solver->translate(m, m_params); - solver_state* st = alloc(solver_state, 0, s, m_params); + solver_state* st = alloc(solver_state, nullptr, s, m_params); m_queue.add_task(st); expr_ref_vector clauses(m); ptr_vector assumptions; @@ -719,29 +719,29 @@ public: return pp.conquer_batch_size(); } - void cleanup() { + void cleanup() override { m_queue.reset(); } - tactic* translate(ast_manager& m) { + tactic* translate(ast_manager& m) override { solver* s = m_solver->translate(m, m_params); return alloc(parallel_tactic, s, m_params); } - virtual void updt_params(params_ref const & p) { + void updt_params(params_ref const & p) override { m_params.copy(p); parallel_params pp(p); m_conquer_delay = pp.conquer_delay(); } - virtual void collect_statistics(statistics & st) const { + void collect_statistics(statistics & st) const override { st.copy(m_stats); st.update("par unsat", m_num_unsat); st.update("par models", m_models.size()); st.update("par progress", m_progress); } - virtual void reset_statistics() { + void reset_statistics() override { m_stats.reset(); } }; diff --git a/src/solver/solver.cpp b/src/solver/solver.cpp index 4044c4a85..0e2128990 100644 --- a/src/solver/solver.cpp +++ b/src/solver/solver.cpp @@ -178,10 +178,19 @@ lbool solver::preferred_sat(expr_ref_vector const& asms, vector return check_sat(0, nullptr); } -bool solver::is_literal(ast_manager& m, expr* e) { - return is_uninterp_const(e) || (m.is_not(e, e) && is_uninterp_const(e)); + +static bool is_m_atom(ast_manager& m, expr* f) { + if (!is_app(f)) return true; + app* _f = to_app(f); + family_id bfid = m.get_basic_family_id(); + if (_f->get_family_id() != bfid) return true; + if (_f->get_num_args() > 0 && m.is_bool(_f->get_arg(0))) return false; + return m.is_eq(f) || m.is_distinct(f); } +bool solver::is_literal(ast_manager& m, expr* e) { + return is_m_atom(m, e) || (m.is_not(e, e) && is_m_atom(m, e)); +} void solver::assert_expr(expr* f) { expr_ref fml(f, get_manager()); @@ -256,3 +265,40 @@ expr_ref_vector solver::get_units(ast_manager& m) { return result; } + + +expr_ref_vector solver::get_non_units(ast_manager& m) { + expr_ref_vector result(m), fmls(m); + get_assertions(fmls); + family_id bfid = m.get_basic_family_id(); + expr_mark marked; + unsigned sz0 = fmls.size(); + for (unsigned i = 0; i < fmls.size(); ++i) { + expr* f = fmls.get(i); + if (marked.is_marked(f)) continue; + marked.mark(f); + if (!is_app(f)) { + if (i >= sz0) result.push_back(f); + continue; + } + app* _f = to_app(f); + if (_f->get_family_id() == bfid) { + // basic objects are true/false/and/or/not/=/distinct + // and proof objects (that are not Boolean). + if (i < sz0 && m.is_not(f) && is_m_atom(m, _f->get_arg(0))) { + marked.mark(_f->get_arg(0)); + } + else if (_f->get_num_args() > 0 && m.is_bool(_f->get_arg(0))) { + fmls.append(_f->get_num_args(), _f->get_args()); + } + else if (i >= sz0 && is_m_atom(m, f)) { + result.push_back(f); + } + } + + else { + if (i >= sz0) result.push_back(f); + } + } + return result; +} diff --git a/src/solver/solver.h b/src/solver/solver.h index c371be284..5329161cd 100644 --- a/src/solver/solver.h +++ b/src/solver/solver.h @@ -236,6 +236,8 @@ public: */ expr_ref_vector get_units(ast_manager& m); + expr_ref_vector get_non_units(ast_manager& m); + class scoped_push { solver& s; bool m_nopop; diff --git a/src/solver/solver_na2as.cpp b/src/solver/solver_na2as.cpp index 41853b19a..a3fcd0e0b 100644 --- a/src/solver/solver_na2as.cpp +++ b/src/solver/solver_na2as.cpp @@ -31,7 +31,7 @@ solver_na2as::solver_na2as(ast_manager & m): solver_na2as::~solver_na2as() {} void solver_na2as::assert_expr_core2(expr * t, expr * a) { - if (a == 0) { + if (a == nullptr) { assert_expr_core(t); } else { diff --git a/src/solver/tactic2solver.cpp b/src/solver/tactic2solver.cpp index cf0c6f9bc..3a905bafd 100644 --- a/src/solver/tactic2solver.cpp +++ b/src/solver/tactic2solver.cpp @@ -79,6 +79,7 @@ public: expr_ref_vector cube(expr_ref_vector& vars, unsigned ) override { + set_reason_unknown("cubing is not supported on tactics"); return expr_ref_vector(get_manager()); } diff --git a/src/tactic/arith/degree_shift_tactic.cpp b/src/tactic/arith/degree_shift_tactic.cpp index c15285703..b713c3055 100644 --- a/src/tactic/arith/degree_shift_tactic.cpp +++ b/src/tactic/arith/degree_shift_tactic.cpp @@ -204,7 +204,7 @@ class degree_shift_tactic : public tactic { for (auto const& kv : m_var2degree) { SASSERT(kv.m_value.is_int()); SASSERT(kv.m_value >= rational(2)); - app * fresh = m.mk_fresh_const(0, kv.m_key->get_decl()->get_range()); + app * fresh = m.mk_fresh_const(nullptr, kv.m_key->get_decl()->get_range()); m_pinned.push_back(fresh); m_var2var.insert(kv.m_key, fresh); if (m_produce_models) { diff --git a/src/tactic/arith/lia2card_tactic.cpp b/src/tactic/arith/lia2card_tactic.cpp index ec18ae2bf..027b6d91c 100644 --- a/src/tactic/arith/lia2card_tactic.cpp +++ b/src/tactic/arith/lia2card_tactic.cpp @@ -36,7 +36,7 @@ class lia2card_tactic : public tactic { expr* m_expr; bound(unsigned lo, unsigned hi, expr* b): m_lo(lo), m_hi(hi), m_expr(b) {} - bound(): m_lo(0), m_hi(0), m_expr(0) {} + bound(): m_lo(0), m_hi(0), m_expr(nullptr) {} }; struct lia_rewriter_cfg : public default_rewriter_cfg { @@ -167,7 +167,8 @@ public: m_mc.reset(); expr_ref_vector axioms(m); expr_safe_replace rep(m); - + + TRACE("pb", g->display(tout);); tactic_report report("lia2card", *g); bound_manager bounds(m); diff --git a/src/tactic/core/symmetry_reduce_tactic.cpp b/src/tactic/core/symmetry_reduce_tactic.cpp index d288adbc7..784686a5e 100644 --- a/src/tactic/core/symmetry_reduce_tactic.cpp +++ b/src/tactic/core/symmetry_reduce_tactic.cpp @@ -40,7 +40,7 @@ public: void operator()(goal_ref const & g, goal_ref_buffer & result) override; - virtual void cleanup() override; + void cleanup() override; }; class ac_rewriter { diff --git a/src/tactic/dependency_converter.cpp b/src/tactic/dependency_converter.cpp index 34c864526..dcd50717b 100644 --- a/src/tactic/dependency_converter.cpp +++ b/src/tactic/dependency_converter.cpp @@ -27,15 +27,15 @@ public: unit_dependency_converter(expr_dependency_ref& d) : m_dep(d) {} - virtual expr_dependency_ref operator()() { return m_dep; } + expr_dependency_ref operator()() override { return m_dep; } - virtual dependency_converter * translate(ast_translation & translator) { + dependency_converter * translate(ast_translation & translator) override { expr_dependency_translation tr(translator); expr_dependency_ref d(tr(m_dep), translator.to()); return alloc(unit_dependency_converter, d); } - virtual void display(std::ostream& out) { + void display(std::ostream& out) override { out << m_dep.get() << "\n"; } }; @@ -47,18 +47,18 @@ public: concat_dependency_converter(dependency_converter* c1, dependency_converter* c2) : m_dc1(c1), m_dc2(c2) {} - virtual expr_dependency_ref operator()() { + expr_dependency_ref operator()() override { expr_dependency_ref d1 = (*m_dc1)(); expr_dependency_ref d2 = (*m_dc2)(); ast_manager& m = d1.get_manager(); return expr_dependency_ref(m.mk_join(d1, d2), m); } - virtual dependency_converter * translate(ast_translation & translator) { + dependency_converter * translate(ast_translation & translator) override { return alloc(concat_dependency_converter, m_dc1->translate(translator), m_dc2->translate(translator)); } - virtual void display(std::ostream& out) { + void display(std::ostream& out) override { m_dc1->display(out); m_dc2->display(out); } @@ -73,7 +73,7 @@ public: for (unsigned i = 0; i < n; ++i) m_goals.push_back(goals[i]); } - virtual expr_dependency_ref operator()() { + expr_dependency_ref operator()() override { expr_dependency_ref result(m.mk_empty_dependencies(), m); for (goal_ref g : m_goals) { dependency_converter_ref dc = g->dc(); @@ -81,13 +81,13 @@ public: } return result; } - virtual dependency_converter * translate(ast_translation & translator) { + dependency_converter * translate(ast_translation & translator) override { goal_ref_buffer goals; for (goal_ref g : m_goals) goals.push_back(g->translate(translator)); return alloc(goal_dependency_converter, goals.size(), goals.c_ptr()); } - virtual void display(std::ostream& out) { out << "goal-dep\n"; } + void display(std::ostream& out) override { out << "goal-dep\n"; } }; diff --git a/src/tactic/fd_solver/enum2bv_solver.cpp b/src/tactic/fd_solver/enum2bv_solver.cpp index a864d9631..185f23d13 100644 --- a/src/tactic/fd_solver/enum2bv_solver.cpp +++ b/src/tactic/fd_solver/enum2bv_solver.cpp @@ -148,7 +148,7 @@ public: // translate enumeration constants to bit-vectors. for (expr* v : vars) { - func_decl* f = 0; + func_decl* f = nullptr; if (is_app(v) && is_uninterp_const(v) && m_rewriter.enum2bv().find(to_app(v)->get_decl(), f)) { bvars.push_back(m.mk_const(f)); } diff --git a/src/tactic/generic_model_converter.h b/src/tactic/generic_model_converter.h index c67dd5eff..750a22cd4 100644 --- a/src/tactic/generic_model_converter.h +++ b/src/tactic/generic_model_converter.h @@ -41,11 +41,11 @@ class generic_model_converter : public model_converter { public: generic_model_converter(ast_manager & m, char const* orig) : m(m), m_orig(orig) {} - virtual ~generic_model_converter(); + ~generic_model_converter() override; void hide(expr* e) { SASSERT(is_app(e) && to_app(e)->get_num_args() == 0); hide(to_app(e)->get_decl()); } - void hide(func_decl * f) { m_entries.push_back(entry(f, 0, m, HIDE)); } + void hide(func_decl * f) { m_entries.push_back(entry(f, nullptr, m, HIDE)); } void add(func_decl * d, expr* e); diff --git a/src/tactic/portfolio/solver2lookahead.cpp b/src/tactic/portfolio/solver2lookahead.cpp index 0c18ab079..63b28793d 100644 --- a/src/tactic/portfolio/solver2lookahead.cpp +++ b/src/tactic/portfolio/solver2lookahead.cpp @@ -20,5 +20,5 @@ Notes: #include "solver/solver.h" solver * mk_solver2lookahead(solver* s) { - return 0; + return nullptr; } diff --git a/src/tactic/proof_converter.cpp b/src/tactic/proof_converter.cpp index f1a209487..faa9a36db 100644 --- a/src/tactic/proof_converter.cpp +++ b/src/tactic/proof_converter.cpp @@ -130,7 +130,7 @@ proof_ref apply(ast_manager & m, proof_converter_ref & pc1, proof_converter_ref_ for (unsigned i = 0; i < sz; i++) { proof_ref pr(m); SASSERT(pc2s[i]); // proof production is enabled - pr = pc2s[i]->operator()(m, 0, 0); + pr = pc2s[i]->operator()(m, 0, nullptr); prs.push_back(pr); } return (*pc1)(m, sz, prs.c_ptr()); diff --git a/src/tactic/sine_filter.cpp b/src/tactic/sine_filter.cpp index b1db12964..1612670a4 100644 --- a/src/tactic/sine_filter.cpp +++ b/src/tactic/sine_filter.cpp @@ -54,7 +54,7 @@ public: TRACE("sine", tout << new_forms.size();); g->reset(); for (unsigned i = 0; i < new_forms.size(); i++) { - g->assert_expr(new_forms.get(i), 0, 0); + g->assert_expr(new_forms.get(i), nullptr, nullptr); } g->inc_depth(); g->updt_prec(goal::OVER); diff --git a/src/tactic/tactical.cpp b/src/tactic/tactical.cpp index 43087f50f..98f6be343 100644 --- a/src/tactic/tactical.cpp +++ b/src/tactic/tactical.cpp @@ -636,7 +636,7 @@ public: else { SASSERT(is_decided_unsat(r2)); - if (cores_enabled && r2[0]->dep(0) != 0) { + if (cores_enabled && r2[0]->dep(0) != nullptr) { expr_dependency_ref * new_dep = alloc(expr_dependency_ref, new_m); *new_dep = r2[0]->dep(0); core_buffer.set(i, new_dep); @@ -674,7 +674,7 @@ public: ast_translation translator(*(managers[i]), m, false); goal_ref_buffer * r = goals_vect[i]; unsigned j = result.size(); - if (r != 0) { + if (r != nullptr) { for (unsigned k = 0; k < r->size(); k++) { result.push_back((*r)[k]->translate(translator)); } @@ -744,7 +744,7 @@ public: SASSERT(t); } - virtual ~unary_tactical() { } + ~unary_tactical() override { } void operator()(goal_ref const & in, goal_ref_buffer& result) override { m_t->operator()(in, result); @@ -1003,7 +1003,7 @@ public: SASSERT(m_p); } - virtual ~cond_tactical() {} + ~cond_tactical() override {} void operator()(goal_ref const & in, goal_ref_buffer & result) override { if (m_p->operator()(*(in.get())).is_true()) @@ -1035,7 +1035,7 @@ public: SASSERT(m_p); } - virtual ~fail_if_tactic() {} + ~fail_if_tactic() override {} void cleanup() override {} diff --git a/src/test/arith_rewriter.cpp b/src/test/arith_rewriter.cpp index d0a110c4f..da49e58dc 100644 --- a/src/test/arith_rewriter.cpp +++ b/src/test/arith_rewriter.cpp @@ -1,4 +1,3 @@ - /*++ Copyright (c) 2015 Microsoft Corporation @@ -26,8 +25,8 @@ static expr_ref parse_fml(ast_manager& m, char const* str) { << "(assert " << str << ")\n"; std::istringstream is(buffer.str()); VERIFY(parse_smt2_commands(ctx, is)); - ENSURE(ctx.begin_assertions() != ctx.end_assertions()); - result = *ctx.begin_assertions(); + ENSURE(!ctx.assertions().empty()); + result = ctx.assertions().get(0); return result; } diff --git a/src/test/bit_blaster.cpp b/src/test/bit_blaster.cpp index c6853ae1e..b4c7a8e76 100644 --- a/src/test/bit_blaster.cpp +++ b/src/test/bit_blaster.cpp @@ -27,7 +27,7 @@ void mk_bits(ast_manager & m, char const * prefix, unsigned sz, expr_ref_vector for (unsigned i = 0; i < sz; ++i) { char buffer[128]; #ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%s%d.smt", prefix, i); + sprintf_s(buffer, Z3_ARRAYSIZE(buffer), "%s%d.smt", prefix, i); #else sprintf(buffer, "%s%d.smt", prefix, i); #endif diff --git a/src/test/for_each_file.cpp b/src/test/for_each_file.cpp index eeec69156..7b7a45042 100644 --- a/src/test/for_each_file.cpp +++ b/src/test/for_each_file.cpp @@ -21,6 +21,7 @@ Revision History: #include #include #include +#include "util/util.h" #include "test/for_each_file.h" bool for_each_file(for_each_file_proc& proc, const char* base, const char* suffix) @@ -36,7 +37,7 @@ bool for_each_file(for_each_file_proc& proc, const char* base, const char* suffi while (h != INVALID_HANDLE_VALUE) { - StringCchPrintfA(buffer, ARRAYSIZE(buffer), "%s\\%s", base, data.cFileName); + StringCchPrintfA(buffer, Z3_ARRAYSIZE(buffer), "%s\\%s", base, data.cFileName); if (!proc(buffer)) { return false; diff --git a/src/test/lp/argument_parser.h b/src/test/lp/argument_parser.h index c8566ce34..ce59632d2 100644 --- a/src/test/lp/argument_parser.h +++ b/src/test/lp/argument_parser.h @@ -112,7 +112,7 @@ public: std::string usage_string() { std::string ret = ""; std::vector unknown_options; - for (auto t : m_free_args) { + for (const auto & t : m_free_args) { if (starts_with(t, "-") || starts_with(t, "\\")) { unknown_options.push_back(t); } @@ -120,7 +120,7 @@ public: if (unknown_options.size()) { ret = "Unknown options:"; } - for (auto unknownOption : unknown_options) { + for (const auto & unknownOption : unknown_options) { ret += unknownOption; ret += ","; } @@ -140,7 +140,7 @@ public: return; } std::cout << "options are: " << std::endl; - for (std::string s : m_used_options) { + for (const std::string & s : m_used_options) { std::cout << s << std::endl; } for (auto & t : m_used_options_with_after_string) { diff --git a/src/test/lp/gomory_test.h b/src/test/lp/gomory_test.h index 03ad5b187..972466dc3 100644 --- a/src/test/lp/gomory_test.h +++ b/src/test/lp/gomory_test.h @@ -1,4 +1,5 @@ namespace lp { +#include "util/lp/lp_utils.h" struct gomory_test { gomory_test( std::function name_function_p, @@ -88,7 +89,7 @@ struct gomory_test { lp_assert(is_int(x_j)); lp_assert(!a.is_int()); lp_assert(f_0 > zero_of_type() && f_0 < one_of_type()); - mpq f_j = int_solver::fractional_part(a); + mpq f_j = fractional_part(a); TRACE("gomory_cut_detail", tout << a << " x_j = " << x_j << ", k = " << k << "\n"; tout << "f_j: " << f_j << "\n"; @@ -184,7 +185,6 @@ struct gomory_test { } void print_term(lar_term & t, std::ostream & out) { - lp_assert(is_zero(t.m_v)); vector> row; for (auto p : t.m_coeffs) row.push_back(std::make_pair(p.second, p.first)); @@ -206,7 +206,7 @@ struct gomory_test { unsigned x_j; mpq a; bool some_int_columns = false; - mpq f_0 = int_solver::fractional_part(get_value(inf_col)); + mpq f_0 = fractional_part(get_value(inf_col)); mpq one_min_f_0 = 1 - f_0; for ( auto pp : row) { a = pp.first; diff --git a/src/test/lp/lp.cpp b/src/test/lp/lp.cpp index 6e418fe68..ffdbb5af8 100644 --- a/src/test/lp/lp.cpp +++ b/src/test/lp/lp.cpp @@ -1161,14 +1161,14 @@ void setup_solver(unsigned max_iterations, unsigned time_limit, bool look_for_mi bool values_are_one_percent_close(double a, double b); void print_x(mps_reader & reader, lp_solver * solver) { - for (auto name : reader.column_names()) { + for (const auto & name : reader.column_names()) { std::cout << name << "=" << solver->get_column_value_by_name(name) << ' '; } std::cout << std::endl; } void compare_solutions(mps_reader & reader, lp_solver * solver, lp_solver * solver0) { - for (auto name : reader.column_names()) { + for (const auto & name : reader.column_names()) { double a = solver->get_column_value_by_name(name); double b = solver0->get_column_value_by_name(name); if (!values_are_one_percent_close(a, b)) { @@ -1299,7 +1299,7 @@ void solve_mps_in_rational(std::string file_name, bool dual, argument_parser & / std::cout << "status is " << lp_status_to_string(solver->get_status()) << std::endl; if (solver->get_status() == lp_status::OPTIMAL) { if (reader.column_names().size() < 20) { - for (auto name : reader.column_names()) { + for (const auto & name : reader.column_names()) { std::cout << name << "=" << solver->get_column_value_by_name(name).get_double() << ' '; } } @@ -1414,7 +1414,7 @@ void solve_mps_with_known_solution(std::string file_name, std::unordered_map get_solution_map(lp_solver * lps, mps_reader & reader) { std::unordered_map ret; - for (auto it : reader.column_names()) { + for (const auto & it : reader.column_names()) { ret[it] = lps->get_column_value_by_name(it); } return ret; @@ -2487,7 +2487,7 @@ void test_lar_solver(argument_parser & args_parser) { std::string file_list = args_parser.get_option_value("--filelist"); if (file_list.size() > 0) { - for (std::string fn : get_file_names_from_file_list(file_list)) + for (const std::string & fn : get_file_names_from_file_list(file_list)) test_lar_on_file(fn, args_parser); return; } @@ -2667,13 +2667,20 @@ void test_term() { lar_solver solver; unsigned _x = 0; unsigned _y = 1; + unsigned _one = 2; var_index x = solver.add_var(_x, false); var_index y = solver.add_var(_y, false); + var_index one = solver.add_var(_one, false); + + vector> term_one; + term_one.push_back(std::make_pair((int)1, one)); + solver.add_constraint(term_one, lconstraint_kind::EQ, mpq(0)); vector> term_ls; term_ls.push_back(std::pair((int)1, x)); term_ls.push_back(std::pair((int)1, y)); - var_index z = solver.add_term(term_ls, mpq(3)); + term_ls.push_back(std::make_pair((int)3, one)); + var_index z = solver.add_term(term_ls); vector> ls; ls.push_back(std::pair((int)1, x)); @@ -2743,10 +2750,10 @@ void test_bound_propagation_one_small_sample1() { vector> coeffs; coeffs.push_back(std::pair(1, a)); coeffs.push_back(std::pair(-1, c)); - ls.add_term(coeffs, zero_of_type()); + ls.add_term(coeffs); coeffs.pop_back(); coeffs.push_back(std::pair(-1, b)); - ls.add_term(coeffs, zero_of_type()); + ls.add_term(coeffs); coeffs.clear(); coeffs.push_back(std::pair(1, a)); coeffs.push_back(std::pair(-1, b)); @@ -3485,12 +3492,12 @@ void test_maximize_term() { vector> term_ls; term_ls.push_back(std::pair((int)1, x)); term_ls.push_back(std::pair((int)-1, y)); - unsigned term_x_min_y = solver.add_term(term_ls, mpq(0)); + unsigned term_x_min_y = solver.add_term(term_ls); term_ls.clear(); term_ls.push_back(std::pair((int)2, x)); term_ls.push_back(std::pair((int)2, y)); - unsigned term_2x_pl_2y = solver.add_term(term_ls, mpq(0)); + unsigned term_2x_pl_2y = solver.add_term(term_ls); solver.add_var_bound(term_x_min_y, LE, zero_of_type()); solver.add_var_bound(term_2x_pl_2y, LE, mpq((int)5)); solver.find_feasible_solution(); @@ -3502,8 +3509,7 @@ void test_maximize_term() { std::cout<< "v[" << p.first << "] = " << p.second << std::endl; } std::cout << "calling int_solver\n"; - lar_term t; mpq k; explanation ex; bool upper; - lia_move lm = i_solver.check(t, k, ex, upper); + lia_move lm = i_solver.check(); VERIFY(lm == lia_move::sat); impq term_max; lp_status st = solver.maximize_term(term_2x_pl_2y, term_max); @@ -3618,7 +3624,7 @@ void test_lp_local(int argn, char**argv) { } std::string file_list = args_parser.get_option_value("--filelist"); if (file_list.size() > 0) { - for (std::string fn : get_file_names_from_file_list(file_list)) + for (const std::string & fn : get_file_names_from_file_list(file_list)) solve_mps(fn, args_parser); return finalize(0); } diff --git a/src/test/main.cpp b/src/test/main.cpp index d330610d9..811b67407 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -78,11 +78,11 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t int i = 1; while (i < argc) { char * arg = argv[i]; - char * eq_pos = 0; + char * eq_pos = nullptr; if (arg[0] == '-' || arg[0] == '/') { char * opt_name = arg + 1; - char * opt_arg = 0; + char * opt_arg = nullptr; char * colon = strchr(arg, ':'); if (colon) { opt_arg = colon + 1; @@ -97,7 +97,7 @@ void parse_cmd_line_args(int argc, char ** argv, bool& do_display_usage, bool& t else if (strcmp(opt_name, "v") == 0) { if (!opt_arg) error("option argument (/v:level) is missing."); - long lvl = strtol(opt_arg, 0, 10); + long lvl = strtol(opt_arg, nullptr, 10); set_verbosity_level(lvl); } else if (strcmp(opt_name, "w") == 0) { diff --git a/src/test/pb2bv.cpp b/src/test/pb2bv.cpp index 493d81bb7..623f7c9f1 100644 --- a/src/test/pb2bv.cpp +++ b/src/test/pb2bv.cpp @@ -18,6 +18,7 @@ Copyright (c) 2015 Microsoft Corporation #include "ast/rewriter/th_rewriter.h" #include "tactic/fd_solver/fd_solver.h" #include "solver/solver.h" +#include "ast/arith_decl_plugin.h" static void test1() { ast_manager m; @@ -194,9 +195,20 @@ static void test3() { } } +static void test4() { + ast_manager m; + reg_decl_plugins(m); + arith_util arith(m); + expr_ref a(m.mk_const(symbol("a"), arith.mk_int()), m); + expr_ref b(m.mk_const(symbol("b"), arith.mk_int()), m); + expr_ref eq(m.mk_eq(a,b), m); + std::cout << "is_atom: " << is_atom(m, eq) << "\n"; +} + void tst_pb2bv() { test1(); test2(); test3(); + test4(); } diff --git a/src/test/polynorm.cpp b/src/test/polynorm.cpp index ef01bd27d..58481938b 100644 --- a/src/test/polynorm.cpp +++ b/src/test/polynorm.cpp @@ -25,8 +25,7 @@ static expr_ref parse_fml(ast_manager& m, char const* str) { << "(assert " << str << ")\n"; std::istringstream is(buffer.str()); VERIFY(parse_smt2_commands(ctx, is)); - ENSURE(ctx.begin_assertions() != ctx.end_assertions()); - result = *ctx.begin_assertions(); + result = ctx.assertions().get(0); return result; } diff --git a/src/test/qe_arith.cpp b/src/test/qe_arith.cpp index 031912c46..2b1b9686c 100644 --- a/src/test/qe_arith.cpp +++ b/src/test/qe_arith.cpp @@ -37,8 +37,7 @@ static expr_ref parse_fml(ast_manager& m, char const* str) { << "(assert " << str << ")\n"; std::istringstream is(buffer.str()); VERIFY(parse_smt2_commands(ctx, is)); - ENSURE(ctx.begin_assertions() != ctx.end_assertions()); - result = *ctx.begin_assertions(); + result = ctx.assertions().get(0); return result; } diff --git a/src/test/quant_solve.cpp b/src/test/quant_solve.cpp index 04a75c42e..d0c616166 100644 --- a/src/test/quant_solve.cpp +++ b/src/test/quant_solve.cpp @@ -131,8 +131,7 @@ static expr_ref parse_fml(ast_manager& m, char const* str) { << "(assert " << str << ")\n"; std::istringstream is(buffer.str()); VERIFY(parse_smt2_commands(ctx, is)); - ENSURE(ctx.begin_assertions() != ctx.end_assertions()); - result = *ctx.begin_assertions(); + result = ctx.assertions().get(0); return result; } diff --git a/src/test/smt2print_parse.cpp b/src/test/smt2print_parse.cpp index 99ffb726c..81b7ba1e9 100644 --- a/src/test/smt2print_parse.cpp +++ b/src/test/smt2print_parse.cpp @@ -19,7 +19,7 @@ void test_print(Z3_context ctx, Z3_ast_vector av) { Z3_ast a = Z3_mk_and(ctx, Z3_ast_vector_size(ctx, av), args); Z3_inc_ref(ctx, a); delete[] args; - char const* spec1 = Z3_benchmark_to_smtlib_string(ctx, "test", 0, 0, 0, 0, 0, a); + char const* spec1 = Z3_benchmark_to_smtlib_string(ctx, "test", nullptr, nullptr, nullptr, 0, nullptr, a); Z3_dec_ref(ctx, a); std::cout << "spec1: benchmark->string\n" << spec1 << "\n"; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 85b6f955c..b6abb785f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,11 +1,12 @@ -if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/version.h") - message(FATAL_ERROR "\"${CMAKE_CURRENT_SOURCE_DIR}/version.h\"" +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/z3_version.h") + message(FATAL_ERROR "\"${CMAKE_CURRENT_SOURCE_DIR}/z3_version.h\"" ${z3_polluted_tree_msg} ) endif() set(Z3_FULL_VERSION "\"${Z3_FULL_VERSION_STR}\"") -configure_file(version.h.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +configure_file(z3_version.h.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/z3_version.h) + z3_add_component(util SOURCES diff --git a/src/util/hwf.cpp b/src/util/hwf.cpp index b8f481329..32b1343e3 100644 --- a/src/util/hwf.cpp +++ b/src/util/hwf.cpp @@ -45,7 +45,7 @@ Revision History: // For SSE2, it is best to use compiler intrinsics because this makes it completely // clear to the compiler what instructions should be used. E.g., for sqrt(), the Windows compiler selects // the x87 FPU, even when /arch:SSE2 is on. -// Luckily, these are kind of standardized, at least for Windows/Linux/OSX. +// Luckily, these are kind of standardized, at least for Windows/Linux/macOS. #ifdef __clang__ #undef USE_INTRINSICS #endif @@ -75,14 +75,14 @@ hwf_manager::hwf_manager() : #endif #endif #else - // OSX/Linux: Nothing. + // macOS/Linux: Nothing. #endif // We only set the precision of the FPU here in the constructor. At the moment, there are no // other parts of the code that could overwrite this, and Windows takes care of context switches. // CMW: I'm not sure what happens on CPUs with hyper-threading (since the FPU is shared). - // I have yet to discover whether Linux and OSX save the FPU state when switching context. + // I have yet to discover whether Linux and macOS save the FPU state when switching context. // As long as we stick to using the SSE2 FPU though, there shouldn't be any problems with respect // to the precision (not sure about the rounding modes though). } @@ -279,7 +279,7 @@ void hwf_manager::fma(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf co #endif #endif #else - // Linux, OSX + // Linux, macOS o.value = ::fma(x.value, y.value, z.value); #endif #endif @@ -331,7 +331,7 @@ void hwf_manager::round_to_integral(mpf_rounding_mode rm, hwf const & x, hwf & o } #endif #else - // Linux, OSX. + // Linux, macOS. o.value = nearbyint(x.value); #endif } @@ -623,7 +623,7 @@ void hwf_manager::set_rounding_mode(mpf_rounding_mode rm) UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware! } #endif -#else // OSX/Linux +#else // macOS/Linux switch (rm) { case MPF_ROUND_NEAREST_TEVEN: SETRM(FE_TONEAREST); diff --git a/src/util/lp/CMakeLists.txt b/src/util/lp/CMakeLists.txt index bde6ed93a..edb73fdab 100644 --- a/src/util/lp/CMakeLists.txt +++ b/src/util/lp/CMakeLists.txt @@ -2,10 +2,11 @@ z3_add_component(lp SOURCES binary_heap_priority_queue.cpp binary_heap_upair_queue.cpp - bound_propagator.cpp + lp_bound_propagator.cpp core_solver_pretty_printer.cpp dense_matrix.cpp eta_matrix.cpp + gomory.cpp indexed_vector.cpp int_solver.cpp lar_solver.cpp diff --git a/src/util/lp/column_info.h b/src/util/lp/column_info.h index 407f40dfc..2a38900c1 100644 --- a/src/util/lp/column_info.h +++ b/src/util/lp/column_info.h @@ -69,16 +69,6 @@ public: m_column_index(static_cast(-1)) {} - column_info(unsigned column_index) : - m_lower_bound_is_set(false), - m_lower_bound_is_strict(false), - m_upper_bound_is_set (false), - m_upper_bound_is_strict (false), - m_is_fixed(false), - m_cost(numeric_traits::zero()), - m_column_index(column_index) { - } - column_info(const column_info & ci) { m_name = ci.m_name; m_lower_bound_is_set = ci.m_lower_bound_is_set; diff --git a/src/util/lp/column_namer.h b/src/util/lp/column_namer.h index e6e8e53a2..51baf445f 100644 --- a/src/util/lp/column_namer.h +++ b/src/util/lp/column_namer.h @@ -33,29 +33,6 @@ public: print_linear_combination_of_column_indices(coeff, out); } - template - void print_linear_combination_of_column_indices_only(const vector> & coeffs, std::ostream & out) const { - bool first = true; - for (const auto & it : coeffs) { - auto val = it.first; - if (first) { - first = false; - } else { - if (numeric_traits::is_pos(val)) { - out << " + "; - } else { - out << " - "; - val = -val; - } - } - if (val == -numeric_traits::one()) - out << " - "; - else if (val != numeric_traits::one()) - out << T_to_string(val); - - out << "v" << it.second; - } - } template diff --git a/src/util/lp/gomory.cpp b/src/util/lp/gomory.cpp new file mode 100644 index 000000000..ad1c02625 --- /dev/null +++ b/src/util/lp/gomory.cpp @@ -0,0 +1,341 @@ +/*++ + Copyright (c) 2017 Microsoft Corporation + + Module Name: + + + + Abstract: + + + + Author: + Nikolaj Bjorner (nbjorner) + Lev Nachmanson (levnach) + + Revision History: + + + --*/ +#include "util/lp/gomory.h" +#include "util/lp/int_solver.h" +#include "util/lp/lar_solver.h" +#include "util/lp/lp_utils.h" +namespace lp { + +class gomory::imp { + lar_term & m_t; // the term to return in the cut + mpq & m_k; // the right side of the cut + explanation& m_ex; // the conflict explanation + unsigned m_inf_col; // a basis column which has to be an integer but has a non integral value + const row_strip& m_row; + const int_solver& m_int_solver; + mpq m_lcm_den; + mpq m_f; + mpq m_one_minus_f; + mpq m_fj; + mpq m_one_minus_fj; + + const impq & get_value(unsigned j) const { return m_int_solver.get_value(j); } + bool is_real(unsigned j) const { return m_int_solver.is_real(j); } + bool at_lower(unsigned j) const { return m_int_solver.at_lower(j); } + bool at_upper(unsigned j) const { return m_int_solver.at_upper(j); } + const impq & lower_bound(unsigned j) const { return m_int_solver.lower_bound(j); } + const impq & upper_bound(unsigned j) const { return m_int_solver.upper_bound(j); } + constraint_index column_lower_bound_constraint(unsigned j) const { return m_int_solver.column_lower_bound_constraint(j); } + constraint_index column_upper_bound_constraint(unsigned j) const { return m_int_solver.column_upper_bound_constraint(j); } + bool column_is_fixed(unsigned j) const { return m_int_solver.m_lar_solver->column_is_fixed(j); } + + void int_case_in_gomory_cut(unsigned j) { + lp_assert(is_int(j) && m_fj.is_pos()); + TRACE("gomory_cut_detail", + tout << " k = " << m_k; + tout << ", fj: " << m_fj << ", "; + tout << (at_lower(j)?"at_lower":"at_upper")<< std::endl; + ); + mpq new_a; + if (at_lower(j)) { + new_a = m_fj <= m_one_minus_f ? m_fj / m_one_minus_f : ((1 - m_fj) / m_f); + lp_assert(new_a.is_pos()); + m_k.addmul(new_a, lower_bound(j).x); + m_ex.push_justification(column_lower_bound_constraint(j)); + } + else { + lp_assert(at_upper(j)); + // the upper terms are inverted: therefore we have the minus + new_a = - (m_fj <= m_f ? m_fj / m_f : ((1 - m_fj) / m_one_minus_f)); + lp_assert(new_a.is_neg()); + m_k.addmul(new_a, upper_bound(j).x); + m_ex.push_justification(column_upper_bound_constraint(j)); + } + m_t.add_monomial(new_a, j); + m_lcm_den = lcm(m_lcm_den, denominator(new_a)); + TRACE("gomory_cut_detail", tout << "v" << j << " new_a = " << new_a << ", k = " << m_k << ", m_lcm_den = " << m_lcm_den << "\n";); + } + + void real_case_in_gomory_cut(const mpq & a, unsigned j) { + TRACE("gomory_cut_detail_real", tout << "real\n";); + mpq new_a; + if (at_lower(j)) { + if (a.is_pos()) { + new_a = a / m_one_minus_f; + } + else { + new_a = - a / m_f; + } + m_k.addmul(new_a, lower_bound(j).x); // is it a faster operation than + // k += lower_bound(j).x * new_a; + m_ex.push_justification(column_lower_bound_constraint(j)); + } + else { + lp_assert(at_upper(j)); + if (a.is_pos()) { + new_a = - a / m_f; + } + else { + new_a = a / m_one_minus_f; + } + m_k.addmul(new_a, upper_bound(j).x); // k += upper_bound(j).x * new_a; + m_ex.push_justification(column_upper_bound_constraint(j)); + } + TRACE("gomory_cut_detail_real", tout << a << "*v" << j << " k: " << m_k << "\n";); + m_t.add_monomial(new_a, j); + } + + lia_move report_conflict_from_gomory_cut() { + lp_assert(m_k.is_pos()); + // conflict 0 >= k where k is positive + m_k.neg(); // returning 0 <= -k + return lia_move::conflict; + } + + void adjust_term_and_k_for_some_ints_case_gomory() { + lp_assert(!m_t.is_empty()); + // k = 1 + sum of m_t at bounds + auto pol = m_t.coeffs_as_vector(); + m_t.clear(); + if (pol.size() == 1) { + TRACE("gomory_cut_detail", tout << "pol.size() is 1" << std::endl;); + unsigned v = pol[0].second; + lp_assert(is_int(v)); + const mpq& a = pol[0].first; + m_k /= a; + if (a.is_pos()) { // we have av >= k + if (!m_k.is_int()) + m_k = ceil(m_k); + // switch size + m_t.add_monomial(- mpq(1), v); + m_k.neg(); + } else { + if (!m_k.is_int()) + m_k = floor(m_k); + m_t.add_monomial(mpq(1), v); + } + } else { + m_lcm_den = lcm(m_lcm_den, denominator(m_k)); + lp_assert(m_lcm_den.is_pos()); + TRACE("gomory_cut_detail", tout << "pol.size() > 1 den: " << m_lcm_den << std::endl;); + if (!m_lcm_den.is_one()) { + // normalize coefficients of integer parameters to be integers. + for (auto & pi: pol) { + pi.first *= m_lcm_den; + SASSERT(!is_int(pi.second) || pi.first.is_int()); + } + m_k *= m_lcm_den; + } + // negate everything to return -pol <= -m_k + for (const auto & pi: pol) { + TRACE("gomory_cut", tout << pi.first << "* " << "v" << pi.second << "\n";); + m_t.add_monomial(-pi.first, pi.second); + } + m_k.neg(); + } + TRACE("gomory_cut_detail", tout << "k = " << m_k << std::endl;); + lp_assert(m_k.is_int()); + } + + std::string var_name(unsigned j) const { + return std::string("x") + std::to_string(j); + } + + std::ostream& dump_coeff_val(std::ostream & out, const mpq & a) const { + if (a.is_int()) { + out << a; + } + else if ( a >= zero_of_type()) + out << "(/ " << numerator(a) << " " << denominator(a) << ")"; + else { + out << "(- ( / " << numerator(-a) << " " << denominator(-a) << "))"; + } + return out; + } + + template + void dump_coeff(std::ostream & out, const T& c) const { + out << "( * "; + dump_coeff_val(out, c.coeff()); + out << " " << var_name(c.var()) << ")"; + } + + std::ostream& dump_row_coefficients(std::ostream & out) const { + mpq lc(1); + for (const auto& p : m_row) { + lc = lcm(lc, denominator(p.coeff())); + } + for (const auto& p : m_row) { + dump_coeff_val(out << " (* ", p.coeff()*lc) << " " << var_name(p.var()) << ")"; + } + return out; + } + + void dump_the_row(std::ostream& out) const { + out << "; the row, excluding fixed vars\n"; + out << "(assert ( = ( +"; + dump_row_coefficients(out) << ") 0))\n"; + } + + void dump_declaration(std::ostream& out, unsigned v) const { + out << "(declare-const " << var_name(v) << (is_int(v) ? " Int" : " Real") << ")\n"; + } + + void dump_declarations(std::ostream& out) const { + // for a column j the var name is vj + for (const auto & p : m_row) { + dump_declaration(out, p.var()); + } + for (const auto& p : m_t) { + unsigned v = p.var(); + if (m_int_solver.m_lar_solver->is_term(v)) { + dump_declaration(out, v); + } + } + } + + void dump_lower_bound_expl(std::ostream & out, unsigned j) const { + out << "(assert (>= " << var_name(j) << " " << lower_bound(j).x << "))\n"; + } + void dump_upper_bound_expl(std::ostream & out, unsigned j) const { + out << "(assert (<= " << var_name(j) << " " << upper_bound(j).x << "))\n"; + } + + void dump_explanations(std::ostream& out) const { + for (const auto & p : m_row) { + unsigned j = p.var(); + if (j == m_inf_col || (!is_real(j) && p.coeff().is_int())) { + continue; + } + else if (at_lower(j)) { + dump_lower_bound_expl(out, j); + } else { + lp_assert(at_upper(j)); + dump_upper_bound_expl(out, j); + } + } + } + + std::ostream& dump_term_coefficients(std::ostream & out) const { + for (const auto& p : m_t) { + dump_coeff(out, p); + } + return out; + } + + std::ostream& dump_term_sum(std::ostream & out) const { + return dump_term_coefficients(out << "(+ ") << ")"; + } + + std::ostream& dump_term_le_k(std::ostream & out) const { + return dump_term_sum(out << "(<= ") << " " << m_k << ")"; + } + + void dump_the_cut_assert(std::ostream & out) const { + dump_term_le_k(out << "(assert (not ") << "))\n"; + } + + + void dump_cut_and_constraints_as_smt_lemma(std::ostream& out) const { + dump_declarations(out); + dump_the_row(out); + dump_explanations(out); + dump_the_cut_assert(out); + out << "(check-sat)\n"; + } +public: + lia_move create_cut() { + TRACE("gomory_cut", + tout << "applying cut at:\n"; print_linear_combination_of_column_indices_only(m_row, tout); tout << std::endl; + for (auto & p : m_row) { + m_int_solver.m_lar_solver->m_mpq_lar_core_solver.m_r_solver.print_column_info(p.var(), tout); + } + tout << "inf_col = " << m_inf_col << std::endl; + ); + + // gomory will be t <= k and the current solution has a property t > k + m_k = 1; + m_t.clear(); + mpq m_lcm_den(1); + bool some_int_columns = false; + mpq m_f = fractional_part(get_value(m_inf_col)); + TRACE("gomory_cut_detail", tout << "m_f: " << m_f << ", "; + tout << "1 - m_f: " << 1 - m_f << ", get_value(m_inf_col).x - m_f = " << get_value(m_inf_col).x - m_f;); + lp_assert(m_f.is_pos() && (get_value(m_inf_col).x - m_f).is_int()); + + mpq one_min_m_f = 1 - m_f; + for (const auto & p : m_row) { + unsigned j = p.var(); + if (j == m_inf_col) { + lp_assert(p.coeff() == one_of_type()); + TRACE("gomory_cut_detail", tout << "seeing basic var";); + continue; + } + + // use -p.coeff() to make the format compatible with the format used in: Integrating Simplex with DPLL(T) + if (is_real(j)) { + real_case_in_gomory_cut(- p.coeff(), j); + } else { + if (p.coeff().is_int()) { + // m_fj will be zero and no monomial will be added + continue; + } + some_int_columns = true; + m_fj = fractional_part(-p.coeff()); + m_one_minus_fj = 1 - m_fj; + int_case_in_gomory_cut(j); + } + } + + if (m_t.is_empty()) + return report_conflict_from_gomory_cut(); + if (some_int_columns) + adjust_term_and_k_for_some_ints_case_gomory(); + lp_assert(m_int_solver.current_solution_is_inf_on_cut()); + TRACE("gomory_cut_detail", dump_cut_and_constraints_as_smt_lemma(tout);); + m_int_solver.m_lar_solver->subs_term_columns(m_t); + TRACE("gomory_cut", print_linear_combination_of_column_indices_only(m_t, tout << "gomory cut:"); tout << " <= " << m_k << std::endl;); + return lia_move::cut; + } + + imp(lar_term & t, mpq & k, explanation& ex, unsigned basic_inf_int_j, const row_strip& row, const int_solver& int_slv ) : + m_t(t), + m_k(k), + m_ex(ex), + m_inf_col(basic_inf_int_j), + m_row(row), + m_int_solver(int_slv), + m_lcm_den(1), + m_f(fractional_part(get_value(basic_inf_int_j).x)), + m_one_minus_f(1 - m_f) {} + +}; + +lia_move gomory::create_cut() { + return m_imp->create_cut(); +} + +gomory::gomory(lar_term & t, mpq & k, explanation& ex, unsigned basic_inf_int_j, const row_strip& row, const int_solver& s) { + m_imp = alloc(imp, t, k, ex, basic_inf_int_j, row, s); +} + +gomory::~gomory() { dealloc(m_imp); } + +} diff --git a/src/util/lp/gomory.h b/src/util/lp/gomory.h new file mode 100644 index 000000000..b7946d6b0 --- /dev/null +++ b/src/util/lp/gomory.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation + +Module Name: + + + +Abstract: + + + +Author: + Nikolaj Bjorner (nbjorner) + Lev Nachmanson (levnach) + +Revision History: + + +--*/ +#pragma once +#include "util/lp/lar_term.h" +#include "util/lp/lia_move.h" +#include "util/lp/explanation.h" +#include "util/lp/static_matrix.h" + +namespace lp { +class int_solver; +class gomory { + class imp; + imp *m_imp; +public : + gomory(lar_term & t, mpq & k, explanation& ex, unsigned basic_inf_int_j, const row_strip& row, const int_solver& s); + lia_move create_cut(); + ~gomory(); +}; +} diff --git a/src/util/lp/int_solver.cpp b/src/util/lp/int_solver.cpp index a77c202a0..0967c6cc6 100644 --- a/src/util/lp/int_solver.cpp +++ b/src/util/lp/int_solver.cpp @@ -8,6 +8,7 @@ #include "util/lp/lp_utils.h" #include #include "util/lp/monomial.h" +#include "util/lp/gomory.h" namespace lp { @@ -101,10 +102,7 @@ bool int_solver::is_gomory_cut_target(const row_strip& row) { unsigned j; for (const auto & p : row) { j = p.var(); - if (is_base(j)) continue; - if (!at_bound(j)) - return false; - if (!is_zero(get_value(j).y)) { + if (!is_base(j) && (!at_bound(j) || !is_zero(get_value(j).y))) { TRACE("gomory_cut", tout << "row is not gomory cut target:\n"; display_column(tout, j); tout << "infinitesimal: " << !is_zero(get_value(j).y) << "\n";); @@ -115,36 +113,6 @@ bool int_solver::is_gomory_cut_target(const row_strip& row) { } -void int_solver::real_case_in_gomory_cut(const mpq & a, unsigned x_j, const mpq& f_0, const mpq& one_minus_f_0) { - TRACE("gomory_cut_detail_real", tout << "real\n";); - mpq new_a; - if (at_low(x_j)) { - if (a.is_pos()) { - new_a = a / one_minus_f_0; - } - else { - new_a = a / f_0; - new_a.neg(); - } - m_k->addmul(new_a, lower_bound(x_j).x); // is it a faster operation than - // k += lower_bound(x_j).x * new_a; - m_ex->push_justification(column_lower_bound_constraint(x_j), new_a); - } - else { - lp_assert(at_upper(x_j)); - if (a.is_pos()) { - new_a = a / f_0; - new_a.neg(); // the upper terms are inverted. - } - else { - new_a = a / one_minus_f_0; - } - m_k->addmul(new_a, upper_bound(x_j).x); // k += upper_bound(x_j).x * new_a; - m_ex->push_justification(column_upper_bound_constraint(x_j), new_a); - } - TRACE("gomory_cut_detail_real", tout << a << "*v" << x_j << " k: " << *m_k << "\n";); - m_t->add_monomial(new_a, x_j); -} constraint_index int_solver::column_upper_bound_constraint(unsigned j) const { return m_lar_solver->get_column_upper_bound_witness(j); @@ -155,221 +123,31 @@ constraint_index int_solver::column_lower_bound_constraint(unsigned j) const { } -void int_solver::int_case_in_gomory_cut(const mpq & a, unsigned x_j, - mpq & lcm_den, const mpq& f_0, const mpq& one_minus_f_0) { - lp_assert(is_int(x_j)); - lp_assert(!a.is_int()); - mpq f_j = fractional_part(a); - TRACE("gomory_cut_detail", - tout << a << " x_j" << x_j << " k = " << *m_k << "\n"; - tout << "f_j: " << f_j << "\n"; - tout << "f_0: " << f_0 << "\n"; - tout << "1 - f_0: " << 1 - f_0 << "\n"; - tout << "at_low(" << x_j << ") = " << at_low(x_j) << std::endl; - ); - lp_assert (!f_j.is_zero()); - mpq new_a; - if (at_low(x_j)) { - if (f_j <= one_minus_f_0) { - new_a = f_j / one_minus_f_0; - } - else { - new_a = (1 - f_j) / f_0; - } - m_k->addmul(new_a, lower_bound(x_j).x); - m_ex->push_justification(column_lower_bound_constraint(x_j), new_a); - } - else { - lp_assert(at_upper(x_j)); - if (f_j <= f_0) { - new_a = f_j / f_0; - } - else { - new_a = (mpq(1) - f_j) / one_minus_f_0; - } - new_a.neg(); // the upper terms are inverted - m_k->addmul(new_a, upper_bound(x_j).x); - m_ex->push_justification(column_upper_bound_constraint(x_j), new_a); - } - TRACE("gomory_cut_detail", tout << "new_a: " << new_a << " k: " << *m_k << "\n";); - m_t->add_monomial(new_a, x_j); - lcm_den = lcm(lcm_den, denominator(new_a)); -} - -lia_move int_solver::report_conflict_from_gomory_cut() { - TRACE("empty_pol",); - lp_assert(m_k->is_pos()); - // conflict 0 >= k where k is positive - m_k->neg(); // returning 0 <= -k - return lia_move::conflict; -} - -void int_solver::gomory_cut_adjust_t_and_k(vector> & pol, - lar_term & t, - mpq &k, - bool some_ints, - mpq & lcm_den) { - if (!some_ints) - return; - - t.clear(); - if (pol.size() == 1) { - unsigned v = pol[0].second; - lp_assert(is_int(v)); - bool k_is_int = k.is_int(); - const mpq& a = pol[0].first; - k /= a; - if (a.is_pos()) { // we have av >= k - if (!k_is_int) - k = ceil(k); - // switch size - t.add_monomial(- mpq(1), v); - k.neg(); - } else { - if (!k_is_int) - k = floor(k); - t.add_monomial(mpq(1), v); - } - } else if (some_ints) { - lcm_den = lcm(lcm_den, denominator(k)); - lp_assert(lcm_den.is_pos()); - if (!lcm_den.is_one()) { - // normalize coefficients of integer parameters to be integers. - for (auto & pi: pol) { - pi.first *= lcm_den; - SASSERT(!is_int(pi.second) || pi.first.is_int()); - } - k *= lcm_den; - } - // negate everything to return -pol <= -k - for (const auto & pi: pol) - t.add_monomial(-pi.first, pi.second); - k.neg(); - } -} - bool int_solver::current_solution_is_inf_on_cut() const { const auto & x = m_lar_solver->m_mpq_lar_core_solver.m_r_x; - impq v = m_t->apply(x); - mpq sign = *m_upper ? one_of_type() : -one_of_type(); - CTRACE("current_solution_is_inf_on_cut", v * sign <= (*m_k) * sign, - tout << "m_upper = " << *m_upper << std::endl; - tout << "v = " << v << ", k = " << (*m_k) << std::endl; + impq v = m_t.apply(x); + mpq sign = m_upper ? one_of_type() : -one_of_type(); + CTRACE("current_solution_is_inf_on_cut", v * sign <= m_k * sign, + tout << "m_upper = " << m_upper << std::endl; + tout << "v = " << v << ", k = " << m_k << std::endl; ); - return v * sign > (*m_k) * sign; + return v * sign > m_k * sign; } -void int_solver::adjust_term_and_k_for_some_ints_case_gomory(mpq &lcm_den) { - lp_assert(!m_t->is_empty()); - auto pol = m_t->coeffs_as_vector(); - m_t->clear(); - if (pol.size() == 1) { - TRACE("gomory_cut_detail", tout << "pol.size() is 1" << std::endl;); - unsigned v = pol[0].second; - lp_assert(is_int(v)); - const mpq& a = pol[0].first; - (*m_k) /= a; - if (a.is_pos()) { // we have av >= k - if (!(*m_k).is_int()) - (*m_k) = ceil((*m_k)); - // switch size - m_t->add_monomial(- mpq(1), v); - (*m_k).neg(); - } else { - if (!(*m_k).is_int()) - (*m_k) = floor((*m_k)); - m_t->add_monomial(mpq(1), v); - } - } else { - TRACE("gomory_cut_detail", tout << "pol.size() > 1" << std::endl;); - lcm_den = lcm(lcm_den, denominator((*m_k))); - lp_assert(lcm_den.is_pos()); - if (!lcm_den.is_one()) { - // normalize coefficients of integer parameters to be integers. - for (auto & pi: pol) { - pi.first *= lcm_den; - SASSERT(!is_int(pi.second) || pi.first.is_int()); - } - (*m_k) *= lcm_den; - } - // negate everything to return -pol <= -(*m_k) - for (const auto & pi: pol) - m_t->add_monomial(-pi.first, pi.second); - (*m_k).neg(); - } - TRACE("gomory_cut_detail", tout << "k = " << (*m_k) << std::endl;); - lp_assert((*m_k).is_int()); -} - - - - lia_move int_solver::mk_gomory_cut( unsigned inf_col, const row_strip & row) { - lp_assert(column_is_int_inf(inf_col)); - TRACE("gomory_cut", - tout << "applying cut at:\n"; m_lar_solver->print_row(row, tout); tout << std::endl; - for (auto & p : row) { - m_lar_solver->m_mpq_lar_core_solver.m_r_solver.print_column_info(p.var(), tout); - } - tout << "inf_col = " << inf_col << std::endl; - ); - - // gomory will be t <= k and the current solution has a property t > k - *m_k = 1; - mpq lcm_den(1); - unsigned x_j; - mpq a; - bool some_int_columns = false; - mpq f_0 = int_solver::fractional_part(get_value(inf_col)); - mpq one_min_f_0 = 1 - f_0; - for (const auto & p : row) { - x_j = p.var(); - if (x_j == inf_col) - continue; - // make the format compatible with the format used in: Integrating Simplex with DPLL(T) - a = p.coeff(); - a.neg(); - if (is_real(x_j)) - real_case_in_gomory_cut(a, x_j, f_0, one_min_f_0); - else if (!a.is_int()) { // f_j will be zero and no monomial will be added - some_int_columns = true; - int_case_in_gomory_cut(a, x_j, lcm_den, f_0, one_min_f_0); - } - } - - if (m_t->is_empty()) - return report_conflict_from_gomory_cut(); - if (some_int_columns) - adjust_term_and_k_for_some_ints_case_gomory(lcm_den); - - lp_assert(current_solution_is_inf_on_cut()); - m_lar_solver->subs_term_columns(*m_t); - TRACE("gomory_cut", tout<<"precut:"; m_lar_solver->print_term(*m_t, tout); tout << " <= " << *m_k << std::endl;); - return lia_move::cut; -} - -int int_solver::find_free_var_in_gomory_row(const row_strip& row) { - unsigned j; - for (const auto & p : row) { - j = p.var(); - if (!is_base(j) && is_free(j)) - return static_cast(j); - } - return -1; + gomory gc(m_t, m_k, m_ex, inf_col, row, *this); + return gc.create_cut(); } lia_move int_solver::proceed_with_gomory_cut(unsigned j) { const row_strip& row = m_lar_solver->get_row(row_of_basic_column(j)); - if (-1 != find_free_var_in_gomory_row(row)) - return lia_move::undef; - if (!is_gomory_cut_target(row)) - return lia_move::undef; + return create_branch_on_column(j); - *m_upper = true; + m_upper = true; return mk_gomory_cut(j, row); } @@ -394,17 +172,19 @@ typedef monomial mono; // this will allow to enable and disable tracking of the pivot rows -struct pivoted_rows_tracking_control { - lar_solver * m_lar_solver; - bool m_track_pivoted_rows; - pivoted_rows_tracking_control(lar_solver* ls) : +struct check_return_helper { + lar_solver * m_lar_solver; + const lia_move & m_r; + bool m_track_pivoted_rows; + check_return_helper(lar_solver* ls, const lia_move& r) : m_lar_solver(ls), + m_r(r), m_track_pivoted_rows(ls->get_track_pivoted_rows()) { TRACE("pivoted_rows", tout << "pivoted rows = " << ls->m_mpq_lar_core_solver.m_r_solver.m_pivoted_rows->size() << std::endl;); m_lar_solver->set_track_pivoted_rows(false); } - ~pivoted_rows_tracking_control() { + ~check_return_helper() { TRACE("pivoted_rows", tout << "pivoted rows = " << m_lar_solver->m_mpq_lar_core_solver.m_r_solver.m_pivoted_rows->size() << std::endl;); m_lar_solver->set_track_pivoted_rows(m_track_pivoted_rows); } @@ -589,21 +369,21 @@ lia_move int_solver::make_hnf_cut() { #else vector x0; #endif - lia_move r = m_hnf_cutter.create_cut(*m_t, *m_k, *m_ex, *m_upper, x0); + lia_move r = m_hnf_cutter.create_cut(m_t, m_k, m_ex, m_upper, x0); if (r == lia_move::cut) { TRACE("hnf_cut", - m_lar_solver->print_term(*m_t, tout << "cut:"); - tout << " <= " << *m_k << std::endl; + m_lar_solver->print_term(m_t, tout << "cut:"); + tout << " <= " << m_k << std::endl; for (unsigned i : m_hnf_cutter.constraints_for_explanation()) { m_lar_solver->print_constraint(i, tout); } ); lp_assert(current_solution_is_inf_on_cut()); settings().st().m_hnf_cuts++; - m_ex->clear(); + m_ex.clear(); for (unsigned i : m_hnf_cutter.constraints_for_explanation()) { - m_ex->push_justification(i); + m_ex.push_justification(i); } } return r; @@ -619,14 +399,17 @@ lia_move int_solver::hnf_cut() { return lia_move::undef; } -lia_move int_solver::check(lar_term& t, mpq& k, explanation& ex, bool & upper) { +lia_move int_solver::check() { if (!has_inf_int()) return lia_move::sat; - m_t = &t; m_k = &k; m_ex = &ex; m_upper = &upper; + m_t.clear(); + m_k.reset(); + m_ex.clear(); + m_upper = false; lia_move r = run_gcd_test(); if (r != lia_move::undef) return r; - pivoted_rows_tracking_control pc(m_lar_solver); + check_return_helper pc(m_lar_solver, r); if(settings().m_int_pivot_fixed_vars_from_basis) m_lar_solver->pivot_fixed_vars_from_basis(); @@ -862,8 +645,8 @@ bool int_solver::gcd_test_for_row(static_matrix> & A, uns void int_solver::add_to_explanation_from_fixed_or_boxed_column(unsigned j) { constraint_index lc, uc; m_lar_solver->get_bound_constraint_witnesses_for_column(j, lc, uc); - m_ex->m_explanation.push_back(std::make_pair(mpq(1), lc)); - m_ex->m_explanation.push_back(std::make_pair(mpq(1), uc)); + m_ex.m_explanation.push_back(std::make_pair(mpq(1), lc)); + m_ex.m_explanation.push_back(std::make_pair(mpq(1), uc)); } void int_solver::fill_explanation_from_fixed_columns(const row_strip & row) { for (const auto & c : row) { @@ -1126,7 +909,7 @@ bool int_solver::at_bound(unsigned j) const { } } -bool int_solver::at_low(unsigned j) const { +bool int_solver::at_lower(unsigned j) const { auto & mpq_solver = m_lar_solver->m_mpq_lar_core_solver.m_r_solver; switch (mpq_solver.m_column_types[j] ) { case column_type::fixed: @@ -1258,20 +1041,20 @@ const impq& int_solver::lower_bound(unsigned j) const { lia_move int_solver::create_branch_on_column(int j) { TRACE("check_main_int", tout << "branching" << std::endl;); - lp_assert(m_t->is_empty()); + lp_assert(m_t.is_empty()); lp_assert(j != -1); - m_t->add_monomial(mpq(1), m_lar_solver->adjust_column_index_to_term_index(j)); + m_t.add_monomial(mpq(1), m_lar_solver->adjust_column_index_to_term_index(j)); if (is_free(j)) { - *m_upper = true; - *m_k = mpq(0); + m_upper = true; + m_k = mpq(0); } else { - *m_upper = left_branch_is_more_narrow_than_right(j); - *m_k = *m_upper? floor(get_value(j)) : ceil(get_value(j)); + m_upper = left_branch_is_more_narrow_than_right(j); + m_k = m_upper? floor(get_value(j)) : ceil(get_value(j)); } TRACE("arith_int", tout << "branching v" << j << " = " << get_value(j) << "\n"; display_column(tout, j); - tout << "k = " << *m_k << std::endl; + tout << "k = " << m_k << std::endl; ); return lia_move::branch; diff --git a/src/util/lp/int_solver.h b/src/util/lp/int_solver.h index ec708918d..17ce20481 100644 --- a/src/util/lp/int_solver.h +++ b/src/util/lp/int_solver.h @@ -39,20 +39,31 @@ public: // fields lar_solver *m_lar_solver; unsigned m_number_of_calls; - lar_term *m_t; // the term to return in the cut - mpq *m_k; // the right side of the cut - explanation *m_ex; // the conflict explanation - bool *m_upper; // we have a cut m_t*x <= k if m_upper is true nad m_t*x >= k otherwise + lar_term m_t; // the term to return in the cut + mpq m_k; // the right side of the cut + explanation m_ex; // the conflict explanation + bool m_upper; // we have a cut m_t*x <= k if m_upper is true nad m_t*x >= k otherwise hnf_cutter m_hnf_cutter; // methods int_solver(lar_solver* lp); // main function to check that the solution provided by lar_solver is valid for integral values, // or provide a way of how it can be adjusted. - lia_move check(lar_term& t, mpq& k, explanation& ex, bool & upper); + lia_move check(); + lar_term const& get_term() const { return m_t; } + mpq const& get_offset() const { return m_k; } + explanation const& get_explanation() const { return m_ex; } + bool is_upper() const { return m_upper; } + bool move_non_basic_column_to_bounds(unsigned j); - lia_move check_wrapper(lar_term& t, mpq& k, explanation& ex); bool is_base(unsigned j) const; + bool is_real(unsigned j) const; + const impq & lower_bound(unsigned j) const; + const impq & upper_bound(unsigned j) const; + bool is_int(unsigned j) const; + const impq & get_value(unsigned j) const; + bool at_lower(unsigned j) const; + bool at_upper(unsigned j) const; private: @@ -79,10 +90,7 @@ private: void add_to_explanation_from_fixed_or_boxed_column(unsigned j); lia_move patch_nbasic_columns(); bool get_freedom_interval_for_column(unsigned j, bool & inf_l, impq & l, bool & inf_u, impq & u, mpq & m); - const impq & lower_bound(unsigned j) const; - const impq & upper_bound(unsigned j) const; - bool is_int(unsigned j) const; - bool is_real(unsigned j) const; +private: bool is_boxed(unsigned j) const; bool is_fixed(unsigned j) const; bool is_free(unsigned j) const; @@ -91,7 +99,6 @@ private: void set_value_for_nbasic_column_ignore_old_values(unsigned j, const impq & new_val); bool non_basic_columns_are_at_bounds() const; bool is_feasible() const; - const impq & get_value(unsigned j) const; bool column_is_int_inf(unsigned j) const; void trace_inf_rows() const; lia_move branch_or_sat(); @@ -104,39 +111,22 @@ private: bool move_non_basic_columns_to_bounds(); void branch_infeasible_int_var(unsigned); lia_move mk_gomory_cut(unsigned inf_col, const row_strip& row); - lia_move report_conflict_from_gomory_cut(); - void adjust_term_and_k_for_some_ints_case_gomory(mpq& lcm_den); lia_move proceed_with_gomory_cut(unsigned j); - int find_free_var_in_gomory_row(const row_strip& ); bool is_gomory_cut_target(const row_strip&); bool at_bound(unsigned j) const; - bool at_low(unsigned j) const; - bool at_upper(unsigned j) const; bool has_low(unsigned j) const; bool has_upper(unsigned j) const; unsigned row_of_basic_column(unsigned j) const; - inline static bool is_rational(const impq & n) { - return is_zero(n.y); - } public: void display_column(std::ostream & out, unsigned j) const; - inline static - mpq fractional_part(const impq & n) { - lp_assert(is_rational(n)); - return n.x - floor(n.x); - } -private: - void real_case_in_gomory_cut(const mpq & a, unsigned x_j, const mpq& f_0, const mpq& one_minus_f_0); - void int_case_in_gomory_cut(const mpq & a, unsigned x_j, mpq & lcm_den, const mpq& f_0, const mpq& one_minus_f_0); constraint_index column_upper_bound_constraint(unsigned j) const; constraint_index column_lower_bound_constraint(unsigned j) const; - void display_row_info(std::ostream & out, unsigned row_index) const; - void gomory_cut_adjust_t_and_k(vector> & pol, lar_term & t, mpq &k, bool num_ints, mpq &lcm_den); bool current_solution_is_inf_on_cut() const; -public: + bool shift_var(unsigned j, unsigned range); private: + void display_row_info(std::ostream & out, unsigned row_index) const; unsigned random(); bool has_inf_int() const; lia_move create_branch_on_column(int j); @@ -161,5 +151,5 @@ public: bool hnf_has_var_with_non_integral_value() const; bool hnf_cutter_is_full() const; void patch_nbasic_column(unsigned j, bool patch_only_int_vals); -}; + }; } diff --git a/src/util/lp/lar_constraints.h b/src/util/lp/lar_constraints.h index ac15028bb..6305089b4 100644 --- a/src/util/lp/lar_constraints.h +++ b/src/util/lp/lar_constraints.h @@ -75,7 +75,7 @@ struct lar_term_constraint: public lar_base_constraint { } unsigned size() const override { return m_term->size();} lar_term_constraint(const lar_term *t, lconstraint_kind kind, const mpq& right_side) : lar_base_constraint(kind, right_side), m_term(t) { } - mpq get_free_coeff_of_left_side() const override { return m_term->m_v;} + // mpq get_free_coeff_of_left_side() const override { return m_term->m_v;} }; diff --git a/src/util/lp/lar_core_solver.h b/src/util/lp/lar_core_solver.h index 904550339..52290c69a 100644 --- a/src/util/lp/lar_core_solver.h +++ b/src/util/lp/lar_core_solver.h @@ -817,7 +817,7 @@ public: } - const bool column_is_bounded(unsigned j) const { + bool column_is_bounded(unsigned j) const { switch(m_column_types()[j]) { case column_type::fixed: case column_type::boxed: diff --git a/src/util/lp/lar_solver.cpp b/src/util/lp/lar_solver.cpp index 654eb7017..31e9d2654 100644 --- a/src/util/lp/lar_solver.cpp +++ b/src/util/lp/lar_solver.cpp @@ -27,7 +27,7 @@ void clear() {lp_assert(false); // not implemented } -lar_solver::lar_solver() : m_status(lp_status::OPTIMAL), +lar_solver::lar_solver() : m_status(lp_status::UNKNOWN), m_infeasible_column_index(-1), m_terms_start_index(1000000), m_mpq_lar_core_solver(m_settings, *this), @@ -71,7 +71,7 @@ bool lar_solver::sizes_are_correct() const { } -void lar_solver::print_implied_bound(const implied_bound& be, std::ostream & out) const { +std::ostream& lar_solver::print_implied_bound(const implied_bound& be, std::ostream & out) const { out << "implied bound\n"; unsigned v = be.m_j; if (is_term(v)) { @@ -83,6 +83,7 @@ void lar_solver::print_implied_bound(const implied_bound& be, std::ostream & out } out << " " << lconstraint_kind_string(be.kind()) << " " << be.m_bound << std::endl; out << "end of implied bound" << std::endl; + return out; } bool lar_solver::implied_bound_is_correctly_explained(implied_bound const & be, const vector> & explanation) const { @@ -136,7 +137,7 @@ bool lar_solver::implied_bound_is_correctly_explained(implied_bound const & be, kind = static_cast(-kind); } rs_of_evidence /= ratio; - rs_of_evidence += t->m_v * ratio; + // rs_of_evidence += t->m_v * ratio; } return kind == be.kind() && rs_of_evidence == be.m_bound; @@ -376,7 +377,7 @@ void lar_solver::pop(unsigned k) { m_settings.simplex_strategy() = m_simplex_strategy; lp_assert(sizes_are_correct()); lp_assert((!m_settings.use_tableau()) || m_mpq_lar_core_solver.m_r_solver.reduced_costs_are_correct_tableau()); - m_status = m_mpq_lar_core_solver.m_r_solver.current_x_is_feasible()? lp_status::OPTIMAL: lp_status::UNKNOWN; + set_status(lp_status::UNKNOWN); } vector lar_solver::get_all_constraint_indices() const { @@ -601,7 +602,7 @@ void lar_solver::register_monoid_in_map(std::unordered_map & coe void lar_solver::substitute_terms_in_linear_expression(const vector>& left_side_with_terms, - vector> &left_side, mpq & free_coeff) const { + vector> &left_side) const { std::unordered_map coeffs; for (auto & t : left_side_with_terms) { unsigned j = t.second; @@ -612,7 +613,6 @@ void lar_solver::substitute_terms_in_linear_expression(const vector & ci) { return false; } -column_type lar_solver::get_column_type(const column_info & ci) { - auto ret = ci.get_column_type_no_flipping(); - if (ret == column_type::boxed) { // changing boxed to fixed because of the no span - if (ci.get_lower_bound() == ci.get_upper_bound()) - ret = column_type::fixed; - } - return ret; +column_type lar_solver::get_column_type(unsigned j) const{ + return m_mpq_lar_core_solver.m_column_types[j]; } std::string lar_solver::get_column_name(unsigned j) const { @@ -954,9 +949,9 @@ bool lar_solver::constraint_holds(const lar_base_constraint & constr, std::unord mpq left_side_val = get_left_side_val(constr, var_map); switch (constr.m_kind) { case LE: return left_side_val <= constr.m_right_side; - case LT: return left_side_val < constr.m_right_side; + case LT: return left_side_val < constr.m_right_side; case GE: return left_side_val >= constr.m_right_side; - case GT: return left_side_val > constr.m_right_side; + case GT: return left_side_val > constr.m_right_side; case EQ: return left_side_val == constr.m_right_side; default: lp_unreachable(); @@ -975,8 +970,10 @@ bool lar_solver::the_relations_are_of_same_type(const vectorm_kind); if (kind == GT || kind == LT) strict = true; - if (kind == GE || kind == GT) n_of_G++; - else if (kind == LE || kind == LT) n_of_L++; + if (kind == GE || kind == GT) + n_of_G++; + else if (kind == LE || kind == LT) + n_of_L++; } the_kind_of_sum = n_of_G ? GE : (n_of_L ? LE : EQ); if (strict) @@ -1116,7 +1113,7 @@ bool lar_solver::has_upper_bound(var_index var, constraint_index& ci, mpq& value bool lar_solver::has_value(var_index var, mpq& value) const { if (is_term(var)) { lar_term const& t = get_term(var); - value = t.m_v; + value = 0; for (auto const& cv : t) { impq const& r = get_column_value(cv.var()); if (!numeric_traits::is_zero(r.y)) return false; @@ -1177,6 +1174,7 @@ void lar_solver::get_model(std::unordered_map & variable_values) std::unordered_set set_of_different_pairs; std::unordered_set set_of_different_singles; delta = m_mpq_lar_core_solver.find_delta_for_strict_bounds(delta); + TRACE("get_model", tout << "delta=" << delta << "size = " << m_mpq_lar_core_solver.m_r_x.size() << std::endl;); for (i = 0; i < m_mpq_lar_core_solver.m_r_x.size(); i++ ) { const numeric_pair & rp = m_mpq_lar_core_solver.m_r_x[i]; set_of_different_pairs.insert(rp); @@ -1208,42 +1206,40 @@ std::string lar_solver::get_variable_name(var_index vi) const { } // ********** print region start -void lar_solver::print_constraint(constraint_index ci, std::ostream & out) const { +std::ostream& lar_solver::print_constraint(constraint_index ci, std::ostream & out) const { if (ci >= m_constraints.size()) { out << "constraint " << T_to_string(ci) << " is not found"; out << std::endl; - return; + return out; } - print_constraint(m_constraints[ci], out); + return print_constraint(m_constraints[ci], out); } -void lar_solver::print_constraints(std::ostream& out) const { +std::ostream& lar_solver::print_constraints(std::ostream& out) const { out << "number of constraints = " << m_constraints.size() << std::endl; for (auto c : m_constraints) { print_constraint(c, out); } + return out; } -void lar_solver::print_terms(std::ostream& out) const { +std::ostream& lar_solver::print_terms(std::ostream& out) const { for (auto it : m_terms) { - print_term(*it, out); - out << "\n"; + print_term(*it, out) << "\n"; } + return out; } -void lar_solver::print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const { +std::ostream& lar_solver::print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const { print_linear_combination_of_column_indices(c->get_left_side_coefficients(), out); mpq free_coeff = c->get_free_coeff_of_left_side(); if (!is_zero(free_coeff)) out << " + " << free_coeff; - + return out; } -void lar_solver::print_term(lar_term const& term, std::ostream & out) const { - if (!numeric_traits::is_zero(term.m_v)) { - out << term.m_v << " + "; - } +std::ostream& lar_solver::print_term(lar_term const& term, std::ostream & out) const { bool first = true; for (const auto p : term) { mpq val = p.coeff(); @@ -1263,14 +1259,12 @@ void lar_solver::print_term(lar_term const& term, std::ostream & out) const { out << T_to_string(val); out << this->get_column_name(p.var()); } - + return out; } -void lar_solver::print_term_as_indices(lar_term const& term, std::ostream & out) const { - if (!numeric_traits::is_zero(term.m_v)) { - out << term.m_v << " + "; - } - print_linear_combination_of_column_indices_only(term.coeffs_as_vector(), out); +std::ostream& lar_solver::print_term_as_indices(lar_term const& term, std::ostream & out) const { + print_linear_combination_of_column_indices_only(term, out); + return out; } mpq lar_solver::get_left_side_val(const lar_base_constraint & cns, const std::unordered_map & var_map) const { @@ -1284,9 +1278,10 @@ mpq lar_solver::get_left_side_val(const lar_base_constraint & cns, const std::u return ret; } -void lar_solver::print_constraint(const lar_base_constraint * c, std::ostream & out) const { +std::ostream& lar_solver::print_constraint(const lar_base_constraint * c, std::ostream & out) const { print_left_side_of_constraint(c, out); out << " " << lconstraint_kind_string(c->m_kind) << " " << c->m_right_side << std::endl; + return out; } void lar_solver::fill_var_set_for_random_update(unsigned sz, var_index const * vars, vector& column_list) { @@ -1492,7 +1487,7 @@ bool lar_solver::term_is_int(const lar_term * t) const { for (auto const & p : t->m_coeffs) if (! (column_is_int(p.first) && p.second.is_int())) return false; - return t->m_v.is_int(); + return true; } bool lar_solver::var_is_int(var_index v) const { @@ -1593,17 +1588,13 @@ void lar_solver::add_new_var_to_core_fields_for_mpq(bool register_in_basis) { } -var_index lar_solver::add_term_undecided(const vector> & coeffs, - const mpq &m_v) { - push_and_register_term(new lar_term(coeffs, m_v)); +var_index lar_solver::add_term_undecided(const vector> & coeffs) { + push_and_register_term(new lar_term(coeffs)); return m_terms_start_index + m_terms.size() - 1; } #if Z3DEBUG_CHECK_UNIQUE_TERMS -bool lar_solver::term_coeffs_are_ok(const vector> & coeffs, const mpq& v) { - if (coeffs.empty()) { - return is_zero(v); - } +bool lar_solver::term_coeffs_are_ok(const vector> & coeffs) { for (const auto & p : coeffs) { if (column_is_real(p.second)) @@ -1638,12 +1629,11 @@ void lar_solver::push_and_register_term(lar_term* t) { } // terms -var_index lar_solver::add_term(const vector> & coeffs, - const mpq &m_v) { +var_index lar_solver::add_term(const vector> & coeffs) { if (strategy_is_undecided()) - return add_term_undecided(coeffs, m_v); + return add_term_undecided(coeffs); - push_and_register_term(new lar_term(coeffs, m_v)); + push_and_register_term(new lar_term(coeffs)); unsigned adjusted_term_index = m_terms.size() - 1; var_index ret = m_terms_start_index + adjusted_term_index; if (use_tableau() && !coeffs.empty()) { @@ -1656,7 +1646,7 @@ var_index lar_solver::add_term(const vector> & coeffs, } void lar_solver::add_row_from_term_no_constraint(const lar_term * term, unsigned term_ext_index) { - TRACE("dump_terms", print_term(*term, tout); tout << std::endl;); + TRACE("dump_terms", print_term(*term, tout) << std::endl;); register_new_ext_var_index(term_ext_index, term_is_int(term)); // j will be a new variable unsigned j = A_r().column_count(); @@ -1738,9 +1728,8 @@ void lar_solver::add_var_bound_on_constraint_for_term(var_index j, lconstraint_k // lp_assert(!term_is_int(m_terms[adjusted_term_index]) || right_side.is_int()); unsigned term_j; if (m_var_register.external_is_used(j, term_j)) { - mpq rs = right_side - m_terms[adjusted_term_index]->m_v; m_constraints.push_back(new lar_term_constraint(m_terms[adjusted_term_index], kind, right_side)); - update_column_type_and_bound(term_j, kind, rs, ci); + update_column_type_and_bound(term_j, kind, right_side, ci); } else { add_constraint_from_term_and_create_new_column_row(j, m_terms[adjusted_term_index], kind, right_side); @@ -1749,11 +1738,10 @@ void lar_solver::add_var_bound_on_constraint_for_term(var_index j, lconstraint_k constraint_index lar_solver::add_constraint(const vector>& left_side_with_terms, lconstraint_kind kind_par, const mpq& right_side_parm) { vector> left_side; - mpq rs = -right_side_parm; - substitute_terms_in_linear_expression(left_side_with_terms, left_side, rs); - unsigned term_index = add_term(left_side, zero_of_type()); + substitute_terms_in_linear_expression(left_side_with_terms, left_side); + unsigned term_index = add_term(left_side); constraint_index ci = m_constraints.size(); - add_var_bound_on_constraint_for_term(term_index, kind_par, -rs, ci); + add_var_bound_on_constraint_for_term(term_index, kind_par, right_side_parm, ci); return ci; } @@ -1762,7 +1750,7 @@ void lar_solver::add_constraint_from_term_and_create_new_column_row(unsigned ter add_row_from_term_no_constraint(term, term_j); unsigned j = A_r().column_count() - 1; - update_column_type_and_bound(j, kind, right_side - term->m_v, m_constraints.size()); + update_column_type_and_bound(j, kind, right_side, m_constraints.size()); m_constraints.push_back(new lar_term_constraint(term, kind, right_side)); lp_assert(A_r().column_count() == m_mpq_lar_core_solver.m_r_solver.m_costs.size()); } @@ -1964,7 +1952,6 @@ void lar_solver::update_boxed_column_type_and_bound(var_index j, lconstraint_kin if (up < m_mpq_lar_core_solver.m_r_lower_bounds[j]) { m_status = lp_status::INFEASIBLE; - lp_assert(false); m_infeasible_column_index = j; } else { @@ -2261,6 +2248,7 @@ void lar_solver::set_cut_strategy(unsigned cut_frequency) { } } + } // namespace lp diff --git a/src/util/lp/lar_solver.h b/src/util/lp/lar_solver.h index 283c13c38..7af9cfacb 100644 --- a/src/util/lp/lar_solver.h +++ b/src/util/lp/lar_solver.h @@ -94,7 +94,6 @@ class lar_solver : public column_namer { var_register m_var_register; stacked_vector m_columns_to_ul_pairs; vector m_constraints; -private: stacked_value m_constraint_count; // the set of column indices j such that bounds have changed for j int_set m_columns_with_changed_bound; @@ -164,13 +163,11 @@ public: // terms - var_index add_term(const vector> & coeffs, - const mpq &m_v); + var_index add_term(const vector> & coeffs); - var_index add_term_undecided(const vector> & coeffs, - const mpq &m_v); + var_index add_term_undecided(const vector> & coeffs); - bool term_coeffs_are_ok(const vector> & coeffs, const mpq& v); + bool term_coeffs_are_ok(const vector> & coeffs); void push_and_register_term(lar_term* t); void add_row_for_term(const lar_term * term, unsigned term_ext_index); @@ -208,7 +205,10 @@ public: void update_lower_bound_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci); void update_fixed_column_type_and_bound(var_index j, lconstraint_kind kind, const mpq & right_side, constraint_index ci); + //end of init region + + lp_settings & settings(); lp_settings const & settings() const; @@ -227,9 +227,7 @@ public: bool use_lu() const; bool sizes_are_correct() const; - - void print_implied_bound(const implied_bound& be, std::ostream & out) const; - + bool implied_bound_is_correctly_explained(implied_bound const & be, const vector> & explanation) const; void analyze_new_bounds_on_row( @@ -238,8 +236,7 @@ public: void analyze_new_bounds_on_row_tableau( unsigned row_index, - bound_propagator & bp - ); + bound_propagator & bp); void substitute_basis_var_in_terms_for_row(unsigned i); @@ -331,7 +328,7 @@ public: void substitute_terms_in_linear_expression( const vector>& left_side_with_terms, - vector> &left_side, mpq & free_coeff) const; + vector> &left_side) const; void detect_rows_of_bound_change_column_for_nbasic_column(unsigned j); @@ -397,9 +394,9 @@ public: bool try_to_set_fixed(column_info & ci); - column_type get_column_type(const column_info & ci); + column_type get_column_type(unsigned j) const; - std::string get_column_name(unsigned j) const; + std::string get_column_name(unsigned j) const override; bool all_constrained_variables_are_registered(const vector>& left_side); @@ -436,30 +433,33 @@ public: int inf_sign) const; - void get_model(std::unordered_map & variable_values) const; void get_model_do_not_care_about_diff_vars(std::unordered_map & variable_values) const; std::string get_variable_name(var_index vi) const; - // ********** print region start - void print_constraint(constraint_index ci, std::ostream & out) const; + // print utilities - void print_constraints(std::ostream& out) const ; + std::ostream& print_constraint(constraint_index ci, std::ostream & out) const; - void print_terms(std::ostream& out) const; + std::ostream& print_constraints(std::ostream& out) const ; - void print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const; + std::ostream& print_terms(std::ostream& out) const; - void print_term(lar_term const& term, std::ostream & out) const; + std::ostream& print_left_side_of_constraint(const lar_base_constraint * c, std::ostream & out) const; - void print_term_as_indices(lar_term const& term, std::ostream & out) const; + std::ostream& print_term(lar_term const& term, std::ostream & out) const; + std::ostream& print_term_as_indices(lar_term const& term, std::ostream & out) const; + + std::ostream& print_constraint(const lar_base_constraint * c, std::ostream & out) const; + + std::ostream& print_implied_bound(const implied_bound& be, std::ostream & out) const; + + mpq get_left_side_val(const lar_base_constraint & cns, const std::unordered_map & var_map) const; - void print_constraint(const lar_base_constraint * c, std::ostream & out) const; - void fill_var_set_for_random_update(unsigned sz, var_index const * vars, vector& column_list); void random_update(unsigned sz, var_index const * vars); diff --git a/src/util/lp/lar_term.h b/src/util/lp/lar_term.h index 519847848..e9259b8c0 100644 --- a/src/util/lp/lar_term.h +++ b/src/util/lp/lar_term.h @@ -21,9 +21,9 @@ #include "util/lp/indexed_vector.h" namespace lp { struct lar_term { - // the term evaluates to sum of m_coeffs + m_v + // the term evaluates to sum of m_coeffs std::unordered_map m_coeffs; - mpq m_v; + // mpq m_v; lar_term() {} void add_monomial(const mpq& c, unsigned j) { auto it = m_coeffs.find(j); @@ -37,7 +37,7 @@ struct lar_term { } bool is_empty() const { - return m_coeffs.size() == 0 && is_zero(m_v); + return m_coeffs.size() == 0; // && is_zero(m_v); } unsigned size() const { return static_cast(m_coeffs.size()); } @@ -46,8 +46,7 @@ struct lar_term { return m_coeffs; } - lar_term(const vector>& coeffs, - const mpq & v) : m_v(v) { + lar_term(const vector>& coeffs) { for (const auto & p : coeffs) { add_monomial(p.first, p.second); } @@ -87,7 +86,7 @@ struct lar_term { template T apply(const vector& x) const { - T ret = T(m_v); + T ret(0); for (const auto & t : m_coeffs) { ret += t.second * x[t.first]; } @@ -96,7 +95,6 @@ struct lar_term { void clear() { m_coeffs.clear(); - m_v = zero_of_type(); } struct ival { diff --git a/src/util/lp/bound_propagator.cpp b/src/util/lp/lp_bound_propagator.cpp similarity index 93% rename from src/util/lp/bound_propagator.cpp rename to src/util/lp/lp_bound_propagator.cpp index c4fa2aefa..a5c7c976a 100644 --- a/src/util/lp/bound_propagator.cpp +++ b/src/util/lp/lp_bound_propagator.cpp @@ -17,10 +17,6 @@ const impq & bound_propagator::get_upper_bound(unsigned j) const { } void bound_propagator::try_add_bound(mpq v, unsigned j, bool is_low, bool coeff_before_j_is_pos, unsigned row_or_term_index, bool strict) { j = m_lar_solver.adjust_column_index_to_term_index(j); - if (m_lar_solver.is_term(j)) { - // lp treats terms as not having a free coefficient, restoring it below for the outside consumption - v += m_lar_solver.get_term(j).m_v; - } lconstraint_kind kind = is_low? GE : LE; if (strict) diff --git a/src/util/lp/lp_core_solver_base.cpp b/src/util/lp/lp_core_solver_base.cpp index 00c1322c2..e71f65d5d 100644 --- a/src/util/lp/lp_core_solver_base.cpp +++ b/src/util/lp/lp_core_solver_base.cpp @@ -84,6 +84,7 @@ template lp::lp_core_solver_base >::lp_core_s const vector >&); template bool lp::lp_core_solver_base >::print_statistics_with_cost_and_check_that_the_time_is_over(lp::numeric_pair, std::ostream&); template void lp::lp_core_solver_base >::snap_xN_to_bounds_and_fill_xB(); +template void lp::lp_core_solver_base >::solve_Ax_eq_b(); template void lp::lp_core_solver_base >::solve_Bd(unsigned int); template bool lp::lp_core_solver_base >::update_basis_and_x(int, int, lp::numeric_pair const&); template void lp::lp_core_solver_base >::update_x(unsigned int, const lp::numeric_pair&); diff --git a/src/util/lp/lp_core_solver_base.h b/src/util/lp/lp_core_solver_base.h index 41b6fe31d..9a6549917 100644 --- a/src/util/lp/lp_core_solver_base.h +++ b/src/util/lp/lp_core_solver_base.h @@ -577,7 +577,7 @@ public: } void print_column_info(unsigned j, std::ostream & out) const { - out << "j = " << j << ", name = "<< column_name(j); + out << "j = " << j << ",\tname = "<< column_name(j) << "\t"; switch (m_column_types[j]) { case column_type::fixed: case column_type::boxed: @@ -596,11 +596,11 @@ public: lp_assert(false); } // out << "basis heading = " << m_basis_heading[j] << std::endl; - out << " x = " << m_x[j]; + out << "\tx = " << m_x[j]; if (m_basis_heading[j] >= 0) out << " base\n"; else - out << " nbas\n"; + out << " \n"; } bool column_is_free(unsigned j) const { return this->m_column_type[j] == free; } diff --git a/src/util/lp/lp_primal_core_solver_def.h b/src/util/lp/lp_primal_core_solver_def.h index 1e9edbd31..872922f60 100644 --- a/src/util/lp/lp_primal_core_solver_def.h +++ b/src/util/lp/lp_primal_core_solver_def.h @@ -1238,6 +1238,7 @@ template void lp_primal_core_solver::print_column break; case column_type::free_column: out << "( _" << this->m_x[j] << "_)" << std::endl; + break; default: lp_unreachable(); } diff --git a/src/util/lp/lp_settings.h b/src/util/lp/lp_settings.h index dd19df23a..1bbefd154 100644 --- a/src/util/lp/lp_settings.h +++ b/src/util/lp/lp_settings.h @@ -357,7 +357,7 @@ public: } #ifdef Z3DEBUG - static unsigned ddd; // used for debugging +static unsigned ddd; // used for debugging #endif }; // end of lp_settings class @@ -433,7 +433,15 @@ inline void ensure_increasing(vector & v) { } } +inline static bool is_rational(const impq & n) { return is_zero(n.y); } +inline static mpq fractional_part(const impq & n) { + lp_assert(is_rational(n)); + return n.x - floor(n.x); +} +inline static mpq fractional_part(const mpq & n) { + return n - floor(n); +} #if Z3DEBUG bool D(); diff --git a/src/util/lp/lp_solver_def.h b/src/util/lp/lp_solver_def.h index 10c7a6feb..9b385dee6 100644 --- a/src/util/lp/lp_solver_def.h +++ b/src/util/lp/lp_solver_def.h @@ -24,7 +24,7 @@ Revision History: namespace lp { template column_info * lp_solver::get_or_create_column_info(unsigned column) { auto it = m_map_from_var_index_to_column_info.find(column); - return (it == m_map_from_var_index_to_column_info.end())? (m_map_from_var_index_to_column_info[column] = new column_info(static_cast(-1))) : it->second; + return (it == m_map_from_var_index_to_column_info.end())? (m_map_from_var_index_to_column_info[column] = new column_info()) : it->second; } template diff --git a/src/util/lp/lp_utils.h b/src/util/lp/lp_utils.h index e776092a2..573fc319c 100644 --- a/src/util/lp/lp_utils.h +++ b/src/util/lp/lp_utils.h @@ -50,11 +50,34 @@ bool contains(const std::unordered_map & map, const A& key) { namespace lp { - - inline void throw_exception(std::string && str) { - throw default_exception(std::move(str)); +template +void print_linear_combination_of_column_indices_only(const T & coeffs, std::ostream & out) { + bool first = true; + for (const auto & it : coeffs) { + auto val = it.coeff(); + if (first) { + first = false; + } else { + if (val.is_pos()) { + out << " + "; + } else { + out << " - "; + val = -val; + } + } + if (val == 1) + out << " "; + else + out << T_to_string(val); + + out << "x" << it.var(); } - typedef z3_exception exception; +} + +inline void throw_exception(std::string && str) { + throw default_exception(std::move(str)); +} +typedef z3_exception exception; #define lp_assert(_x_) { SASSERT(_x_); } inline void lp_unreachable() { lp_assert(false); } diff --git a/src/util/lp/nra_solver.cpp b/src/util/lp/nra_solver.cpp index 0d28058e8..200bdd2bc 100644 --- a/src/util/lp/nra_solver.cpp +++ b/src/util/lp/nra_solver.cpp @@ -154,7 +154,7 @@ namespace nra { polynomial::polynomial* ps[1] = { p }; bool even[1] = { false }; nlsat::literal lit = m_nlsat->mk_ineq_literal(nlsat::atom::kind::EQ, 1, ps, even); - m_nlsat->mk_clause(1, &lit, 0); + m_nlsat->mk_clause(1, &lit, nullptr); } void add_constraint(unsigned idx) { diff --git a/src/util/lp/numeric_pair.h b/src/util/lp/numeric_pair.h index e98d76cbb..ed740d645 100644 --- a/src/util/lp/numeric_pair.h +++ b/src/util/lp/numeric_pair.h @@ -57,10 +57,10 @@ public: template <> class numeric_traits { public: static bool precise() { return true; } - static int const zero() { return 0; } - static int const one() { return 1; } + static int zero() { return 0; } + static int one() { return 1; } static bool is_zero(int v) { return v == 0; } - static double const get_double(int const & d) { return d; } + static double get_double(int const & d) { return d; } static bool is_int(int) {return true;} static bool is_pos(int j) {return j > 0;} static bool is_neg(int j) {return j < 0;} diff --git a/src/util/memory_manager.h b/src/util/memory_manager.h index 5cb7dc467..94b969531 100644 --- a/src/util/memory_manager.h +++ b/src/util/memory_manager.h @@ -27,8 +27,9 @@ Revision History: # define __has_builtin(x) 0 #endif + #ifdef __GNUC__ -# if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 || __has_builtin(returns_nonnull) +# if ((__GNUC__ * 100 + __GNUC_MINOR__) >= 409 || __has_builtin(returns_nonnull)) && !defined(__INTEL_COMPILER) # define GCC_RET_NON_NULL __attribute__((returns_nonnull)) # else # define GCC_RET_NON_NULL diff --git a/src/util/mpz.h b/src/util/mpz.h index 65f89f078..2df03666f 100644 --- a/src/util/mpz.h +++ b/src/util/mpz.h @@ -152,7 +152,7 @@ class mpz_manager { // make sure that n is a big number and has capacity equal to at least c. void allocate_if_needed(mpz & n, unsigned c) { - c = std::max(c, m_init_cell_capacity); + if (m_init_cell_capacity > c) c = m_init_cell_capacity; if (n.m_ptr == nullptr || capacity(n) < c) { deallocate(n); n.m_val = 1; diff --git a/src/util/obj_ref.h b/src/util/obj_ref.h index 885601375..8908f2cdd 100644 --- a/src/util/obj_ref.h +++ b/src/util/obj_ref.h @@ -115,7 +115,7 @@ public: */ T * steal() { T * r = m_obj; - m_obj = 0; + m_obj = nullptr; return r; } }; diff --git a/src/util/scoped_timer.cpp b/src/util/scoped_timer.cpp index 8078c46ee..9850699be 100644 --- a/src/util/scoped_timer.cpp +++ b/src/util/scoped_timer.cpp @@ -27,7 +27,7 @@ Revision History: // Windows #include #elif defined(__APPLE__) && defined(__MACH__) -// Mac OS X +// macOS #include #include #include @@ -59,7 +59,7 @@ struct scoped_timer::imp { HANDLE m_timer; bool m_first; #elif defined(__APPLE__) && defined(__MACH__) - // Mac OS X + // macOS pthread_t m_thread_id; pthread_attr_t m_attributes; unsigned m_interval; @@ -89,7 +89,7 @@ struct scoped_timer::imp { } } #elif defined(__APPLE__) && defined(__MACH__) - // Mac OS X + // macOS static void * thread_func(void * arg) { scoped_timer::imp * st = static_cast(arg); @@ -153,7 +153,7 @@ struct scoped_timer::imp { ms, WT_EXECUTEINTIMERTHREAD); #elif defined(__APPLE__) && defined(__MACH__) - // Mac OS X + // macOS m_interval = ms?ms:0xFFFFFFFF; if (pthread_attr_init(&m_attributes) != 0) throw default_exception("failed to initialize timer thread attributes"); @@ -194,7 +194,7 @@ struct scoped_timer::imp { m_timer, INVALID_HANDLE_VALUE); #elif defined(__APPLE__) && defined(__MACH__) - // Mac OS X + // macOS // If the waiting-thread is not up and waiting yet, // we can make sure that it finishes quickly by diff --git a/src/util/stopwatch.h b/src/util/stopwatch.h index 83e03a2f7..2c7551df0 100644 --- a/src/util/stopwatch.h +++ b/src/util/stopwatch.h @@ -23,8 +23,6 @@ Revision History: #if defined(_WINDOWS) || defined(_CYGWIN) // Does this redefinition work? -#define ARRAYSIZE_TEMP ARRAYSIZE -#undef ARRAYSIZE #include @@ -68,13 +66,11 @@ public: } }; -#undef ARRAYSIZE -#define ARRAYSIZE ARRAYSIZE_TEMP #undef max #undef min -#elif defined(__APPLE__) && defined (__MACH__) // Mac OS X +#elif defined(__APPLE__) && defined (__MACH__) // macOS #include #include diff --git a/src/util/string_buffer.h b/src/util/string_buffer.h index 28ce84fc4..6b94e3686 100644 --- a/src/util/string_buffer.h +++ b/src/util/string_buffer.h @@ -44,6 +44,8 @@ class string_buffer { m_buffer = new_buffer; } + static const unsigned c_buffer_size = 24; + public: string_buffer(): m_buffer(m_initial_buffer), @@ -80,29 +82,21 @@ public: } void append(int n) { - char buffer[24]; -#ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%d", n); -#else - sprintf(buffer, "%d", n); -#endif + char buffer[c_buffer_size]; + SPRINTF_D(buffer, n); append(buffer); } void append(unsigned n) { - char buffer[24]; -#ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%d", n); -#else - sprintf(buffer, "%d", n); -#endif + char buffer[c_buffer_size]; + SPRINTF_U(buffer, n); append(buffer); } void append(long n) { - char buffer[24]; + char buffer[c_buffer_size]; #ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "%ld", n); + sprintf_s(buffer, Z3_ARRAYSIZE(buffer), "%ld", n); #else sprintf(buffer, "%ld", n); #endif diff --git a/src/util/union_find.h b/src/util/union_find.h index 9dd0b6fbd..f8ae0402f 100644 --- a/src/util/union_find.h +++ b/src/util/union_find.h @@ -122,8 +122,10 @@ public: TRACE("union_find", tout << "merging " << r1 << " " << r2 << "\n";); if (r1 == r2) return; - if (m_size[r1] > m_size[r2]) + if (m_size[r1] > m_size[r2]) { std::swap(r1, r2); + std::swap(v1, v2); + } m_ctx.merge_eh(r2, r1, v2, v1); m_find[r1] = r2; m_size[r2] += m_size[r1]; diff --git a/src/util/util.h b/src/util/util.h index 12fd2fe3b..b9f3bdc28 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -48,13 +48,19 @@ static_assert(sizeof(int64_t) == 8, "64 bits"); #ifdef _WINDOWS #define SSCANF sscanf_s -#define SPRINTF sprintf_s +// #define SPRINTF sprintf_s +#define SPRINTF_D(_buffer_, _i_) sprintf_s(_buffer_, Z3_ARRAYSIZE(_buffer_), "%d", _i_) +#define SPRINTF_U(_buffer_, _u_) sprintf_s(_buffer_, Z3_ARRAYSIZE(_buffer_), "%u", _u_) #define _Exit exit #else #define SSCANF sscanf -#define SPRINTF sprintf +// #define SPRINTF sprintf +#define SPRINTF_D(_buffer_, _i_) sprintf(_buffer_, "%d", _i_) +#define SPRINTF_U(_buffer_, _u_) sprintf(_buffer_, "%u", _u_) #endif + + #define VEC2PTR(_x_) ((_x_).size() ? &(_x_)[0] : 0) #ifdef _WINDOWS @@ -142,9 +148,7 @@ static inline uint64_t shift_left(uint64_t x, uint64_t y) { template char (*ArraySizer(T (&)[N]))[N]; // For determining the length of an array. See ARRAYSIZE() macro. This function is never actually called. -#ifndef ARRAYSIZE -#define ARRAYSIZE(a) sizeof(*ArraySizer(a)) -#endif +#define Z3_ARRAYSIZE(a) sizeof(*ArraySizer(a)) template void display(std::ostream & out, const IT & begin, const IT & end, const char * sep, bool & first) { diff --git a/src/util/version.h.cmake.in b/src/util/z3_version.h.cmake.in similarity index 100% rename from src/util/version.h.cmake.in rename to src/util/z3_version.h.cmake.in diff --git a/src/util/version.h.in b/src/util/z3_version.h.in similarity index 100% rename from src/util/version.h.in rename to src/util/z3_version.h.in