Skip to content

Commit 0bb7f09

Browse files
authored
Unsat core (#91)
1 parent c1c83ba commit 0bb7f09

File tree

3 files changed

+179
-2
lines changed

3 files changed

+179
-2
lines changed

cvc5_pythonic_api/cvc5_pythonic.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,7 +1819,7 @@ def as_string(self):
18191819
18201820
>>> s = Unit(IntVal(1)) + Unit(IntVal(2))
18211821
>>> s.as_string()
1822-
'(str.++ (seq.unit 1) (seq.unit 2))'
1822+
'(seq.++ (seq.unit 1) (seq.unit 2))'
18231823
>>> x = Unit(RealVal(1.5))
18241824
>>> print(x.as_string())
18251825
(seq.unit (/ 3 2))
@@ -6316,14 +6316,40 @@ def statistics(self):
63166316
sat
63176317
>>> stats = s.statistics()
63186318
>>> stats['cvc5::CONSTANT']
6319-
{'defaulted': True, 'internal': False, 'value': {}}
6319+
{'default': True, 'internal': False, 'value': {}}
63206320
>>> len(stats.get()) < 10
63216321
True
63226322
>>> len(stats.get(True, True)) > 30
63236323
True
63246324
"""
63256325
return self.solver.getStatistics()
63266326

6327+
def unsat_core(self):
6328+
"""Return a subset (as a list of Bool expressions) of the assumptions provided to the last check().
6329+
6330+
These are the unsat ("failed") assumptions.
6331+
6332+
To enable this, set the option "produce-unsat-assumptions" to true.
6333+
6334+
>>> a,b,c = Bools('a b c')
6335+
>>> s = Solver()
6336+
>>> s.set('produce-unsat-assumptions','true')
6337+
>>> s.add(Or(a,b),Or(b,c),Not(c))
6338+
>>> s.check(a,b,c)
6339+
unsat
6340+
>>> core = s.unsat_core()
6341+
>>> a in core
6342+
False
6343+
>>> b in core
6344+
False
6345+
>>> c in core
6346+
True
6347+
>>> s.check(a,b)
6348+
sat
6349+
"""
6350+
core = self.solver.getUnsatAssumptions()
6351+
return [BoolRef(c) for c in core]
6352+
63276353

63286354
def SolverFor(logic, ctx=None, logFile=None):
63296355
"""Create a solver customized for the given logic.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
success

test/pgms/unsat_assumptions.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from cvc5_pythonic_api import *
2+
3+
4+
def reset_solver(s):
5+
s.reset()
6+
s.set('produce-unsat-assumptions','true')
7+
8+
def validate_unsat_assumptions(assumptions, core):
9+
# checks that the produced unsat assumptions (core) match the assumptions (assumptions) sent to the check function
10+
return sum([c in assumptions for c in core]) == len(core)
11+
12+
13+
def check_unsat_assumptions(assertions, core):
14+
# This function checks wether, given assertions, the produced unsat assumptions (core) also lead to unsat result
15+
slvr = Solver()
16+
slvr.set('produce-unsat-assumptions','true')
17+
for a in assertions:
18+
slvr.add(a)
19+
return s.check(*core) == unsat
20+
21+
22+
# To make make sure the unsat_core function works there should be at least one nontrivial solution - a solution that doesn't contain all the assumptions sent in the check function.
23+
nontrivial_counter = 0
24+
25+
p1, p2, p3 = Bools('p1 p2 p3')
26+
x, y = Ints('x y')
27+
s = Solver()
28+
reset_solver(s)
29+
assertions = [Implies(p1, x > 0), Implies(p2, y > x), Implies(p2, y < 1), Implies(p3, y > -3)]
30+
31+
for a in assertions:
32+
s.add(a)
33+
34+
assumptions = [p1,p2,p3]
35+
36+
s.check(*assumptions)
37+
38+
core = s.unsat_core()
39+
40+
41+
assert validate_unsat_assumptions(assumptions,core)
42+
assert check_unsat_assumptions(assertions,core)
43+
if len(core) < len(assumptions):
44+
nontrivial_counter += 1
45+
46+
# example 2 - booleans
47+
48+
reset_solver(s)
49+
50+
a, b, c = Bools('a b c')
51+
52+
# Add constraints
53+
54+
assertions = [Or(a, b), Or(Not(a), c), Not(c) ]
55+
for c in assertions:
56+
s.add(c)
57+
58+
59+
# Check satisfiability
60+
assumptions = [a,b,c]
61+
result = s.check(*assumptions)
62+
63+
unsat_core = s.unsat_core()
64+
65+
assert validate_unsat_assumptions(assumptions,unsat_core)
66+
assert check_unsat_assumptions(assertions,assumptions)
67+
if len(unsat_core) < len(assumptions):
68+
nontrivial_counter += 1
69+
70+
# example 3 - booleans
71+
72+
73+
reset_solver(s)
74+
75+
a, b, c = Bools('a b c')
76+
d = Bool('d')
77+
# Add constraints with boolean operators
78+
assertions = [And(a, b, Not(c)), Or(a, d), Not(And(a, d)) ]
79+
for a in assertions:
80+
s.add(a)
81+
82+
# Check satisfiability
83+
assumptions = [a,b,c,d]
84+
result = s.check(*assumptions)
85+
86+
unsat_core = s.unsat_core()
87+
88+
assert validate_unsat_assumptions(assumptions,unsat_core)
89+
assert check_unsat_assumptions(assertions,assumptions)
90+
if len(unsat_core) < len(assumptions):
91+
nontrivial_counter += 1
92+
93+
# example 4 - reals
94+
95+
96+
97+
reset_solver(s)
98+
99+
x = Real('x')
100+
y = Real('y')
101+
z = Real('z')
102+
103+
assertions = [x + y == 5, y - z > 2, z > 3 ]
104+
for a in assertions:
105+
s.add(a)
106+
107+
# Check satisfiability
108+
assumptions = [x > 0, y > 0, z > 0]
109+
result = s.check(*assumptions)
110+
111+
unsat_core = s.unsat_core()
112+
113+
assert validate_unsat_assumptions(assumptions,unsat_core)
114+
assert check_unsat_assumptions(assertions,assumptions)
115+
if len(unsat_core) < len(assumptions):
116+
nontrivial_counter += 1
117+
118+
119+
# example 5 - strings
120+
121+
122+
reset_solver(s)
123+
124+
125+
# Define string variables
126+
s1 = String('s1')
127+
s2 = String('s2')
128+
129+
# Add string constraints
130+
assertions = [Or(s1 == "hello", s1 == "world"), s1 + s2 == "helloworld"]
131+
for a in assertions:
132+
s.add(a)
133+
134+
# Check satisfiability
135+
136+
result = s.check( Length(s2) < 2)
137+
138+
unsat_core = s.unsat_core()
139+
140+
assert validate_unsat_assumptions([Length(s2) < 2], unsat_core)
141+
assert check_unsat_assumptions(assertions,[ Length(s2) < 2 ])
142+
if len(unsat_core) < len([ Length(s2) < 2 ]):
143+
nontrivial_counter += 1
144+
145+
# check that there is at least one nontrivial unsat core
146+
assert nontrivial_counter >= 1
147+
148+
print('success')
149+
150+

0 commit comments

Comments
 (0)