Skip to content

Commit 1582d36

Browse files
authored
Fix Context/Solver interactions (#89)
We'd never really tested them before.
1 parent 3a2de1e commit 1582d36

9 files changed

Lines changed: 149 additions & 84 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
with:
1919
options: "--check --verbose"
2020
src: "cvc5_pythonic_api"
21-
version: "23.3.0"
21+
version: "23.7.0"
2222

2323
- uses: actions/checkout@v2
2424
with:

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ check:
77
pyright ./cvc5_pythonic_api
88

99
fmt:
10-
black --required-version 23.3.0 ./cvc5_pythonic_api
10+
black --required-version 23.7.0 ./cvc5_pythonic_api
1111

1212
check-fmt:
13-
black --check --verbose --required-version 23.3.0 ./cvc5_pythonic_api
13+
black --check --verbose --required-version 23.7.0 ./cvc5_pythonic_api
1414

1515
coverage:
1616
coverage run test_unit.py && coverage report && coverage html

cvc5_pythonic_api/cvc5_pythonic.py

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -127,32 +127,32 @@ def _get_args(args):
127127
class Context(object):
128128
"""A Context manages all terms, configurations, etc.
129129
130-
In cvc5's API, these are managed by a solver, but we keep the Context class
131-
(which just wraps a solver) for compatiblity.
132-
133-
It's only responsibilities are:
130+
In z3's API, a context owns terms, sorts, and solvers. It is used to create
131+
them. Terms/sorts/solvers from one context cannot be implicitly used in
132+
another. Items *can* be explicitly transfered between contexts. The result
133+
is that essentially all functions have a context threaded through them.
134+
There is a default "main" context.
135+
136+
In cvc5's API, terms and sorts are created using a solver, but stored
137+
globally and are freely transferable.
138+
139+
Our compatibility solution is:
140+
* a context wraps a solver, so that a context can be used to create terms
141+
* also, a context does these things:
134142
* making fresh identifiers for a given sort
135143
* looking up previously defined constants
136144
"""
137145

138-
def __init__(self, *args, **kws):
139-
self.reset()
140-
141-
def __del__(self):
142-
self.solver = None
143-
144-
def reset(self):
145-
"""Fully reset the context. This actually destroys the solver object and
146-
recreates it. **This invalidates all objects created within this context
147-
and using them will most likely crash.**
148-
"""
149-
self.solver = pc.Solver()
150-
self.solver.setOption("produce-models", "true")
146+
def __init__(self, solver=None):
147+
self.solver = solver.solver if solver is not None else SimpleSolver().solver
151148
# Map from (name, sort) pairs to constant terms
152149
self.vars = {}
153150
# An increasing identifier used to make fresh identifiers
154151
self.next_fresh_var = 0
155152

153+
def __del__(self):
154+
self.solver = None
155+
156156
def get_var(self, name, sort):
157157
"""Get the variable identified by `name`.
158158
@@ -633,7 +633,6 @@ def eq(self, other):
633633
True
634634
"""
635635
instance_check(other, SortRef)
636-
assert self.ctx == other.ctx
637636
return self.as_ast() == other.as_ast()
638637

639638
def hash(self):
@@ -1754,6 +1753,16 @@ def is_string(self):
17541753
"""
17551754
return isinstance(self, StringSortRef)
17561755

1756+
def elem_sort(self):
1757+
"""Get the element sort for this sequence
1758+
1759+
>>> SeqSort(IntSort()).elem_sort()
1760+
Int
1761+
>>> SeqSort(SeqSort(IntSort())).elem_sort()
1762+
(Seq Int)
1763+
"""
1764+
return _to_sort_ref(self.ast.getSequenceElementSort(), self.ctx)
1765+
17571766

17581767
class SeqRef(ExprRef):
17591768
"""Sequence Expressions"""
@@ -1990,13 +1999,13 @@ def Empty(s):
19901999
True
19912000
>>> e3 = Empty(SeqSort(IntSort()))
19922001
>>> print(e3)
1993-
(as seq.empty (Seq (Seq Int)))()
2002+
(as seq.empty (Seq Int))()
19942003
"""
19952004
if isinstance(s, ReSortRef):
19962005
return ReRef(s.ctx.solver.mkRegexpNone(), s.ctx)
19972006
if isinstance(s, StringSortRef):
19982007
return StringRef(s.ctx.solver.mkString(""), s.ctx)
1999-
return _to_expr_ref(s.ctx.solver.mkEmptySequence(s.ast), s.ctx)
2008+
return _to_expr_ref(s.ctx.solver.mkEmptySequence(s.elem_sort().ast), s.ctx)
20002009

20012010

20022011
def is_seq(a):
@@ -5968,20 +5977,27 @@ class Solver(object):
59685977
* get-model,
59695978
* etc."""
59705979

5971-
def __init__(self, solver=None, ctx=None, logFile=None):
5972-
assert solver is None or ctx is not None
5973-
self.ctx = _get_ctx(ctx)
5974-
self.solver = self.ctx.solver
5980+
def __init__(self, logic=None, ctx=None, logFile=None):
5981+
# save logic so that we can re-build the solver if needed.
5982+
self.logic = logic
5983+
# ignore ctx (the paramter is kept for z3 compatibility)
5984+
self.solver = None
5985+
self.initFromLogic()
59755986
self.scopes = 0
59765987
self.assertions_ = [[]]
59775988
self.last_result = None
59785989
self.resetAssertions()
59795990

5991+
def initFromLogic(self):
5992+
"""Create the base-API solver from the logic"""
5993+
self.solver = pc.Solver()
5994+
if self.logic is not None:
5995+
self.solver.setLogic(self.logic)
5996+
self.solver.setOption("produce-models", "true")
5997+
59805998
def __del__(self):
59815999
if self.solver is not None:
59826000
self.solver = None
5983-
if self.ctx is not None:
5984-
self.ctx = None
59856001

59866002
def push(self):
59876003
"""Create a backtracking point.
@@ -6081,9 +6097,7 @@ def reset(self):
60816097
>>> s.reset()
60826098
>>> s.setOption(incremental=True)
60836099
"""
6084-
self.ctx.reset()
6085-
self.solver = self.ctx.solver
6086-
self.resetAssertions()
6100+
self.initFromLogic()
60876101

60886102
def assert_exprs(self, *args):
60896103
"""Assert constraints into the solver.
@@ -6095,7 +6109,7 @@ def assert_exprs(self, *args):
60956109
[x > 0, x < 2]
60966110
"""
60976111
args = _get_args(args)
6098-
s = BoolSort(self.ctx)
6112+
s = BoolSort()
60996113
for arg in args:
61006114
arg = s.cast(arg)
61016115
self.assertions_[-1].append(arg)
@@ -6184,7 +6198,7 @@ def model(self):
61846198
>>> s.model()
61856199
[a = -2]
61866200
"""
6187-
return ModelRef(self, self.ctx)
6201+
return ModelRef(self)
61886202

61896203
def assertions(self):
61906204
"""Return an AST vector containing all added constraints.
@@ -6229,7 +6243,6 @@ def sexpr(self):
62296243
def set(self, *args, **kwargs):
62306244
"""Set an option on the solver. Wraps ``setOption()``.
62316245
6232-
>>> main_ctx().reset()
62336246
>>> s = Solver()
62346247
>>> s.set(incremental=True)
62356248
>>> s.set('incremental', 'true')
@@ -6244,7 +6257,6 @@ def setOption(self, name=None, value=None, **kwargs):
62446257
properly converted manually, all other types are convertes using
62456258
``str()``.
62466259
6247-
>>> main_ctx().reset()
62486260
>>> s = Solver()
62496261
>>> s.setOption('incremental', True)
62506262
>>> s.setOption(incremental='true')
@@ -6263,7 +6275,6 @@ def getOption(self, name):
62636275
"""Get the current value of an option from the solver. The value is
62646276
returned as a string. For type-safe querying use ``getOptionInfo()``.
62656277
6266-
>>> main_ctx().reset()
62676278
>>> s = Solver()
62686279
>>> s.setOption(incremental=True)
62696280
>>> s.getOption("incremental")
@@ -6285,12 +6296,10 @@ def getOptionInfo(self, name):
62856296
"""Get the current value of an option from the solver. The value is
62866297
returned as a string. For type-safe querying use ``getOptionInfo()``.
62876298
6288-
>>> main_ctx().reset()
62896299
>>> s = Solver()
62906300
>>> s.setOption(incremental=False)
62916301
>>> s.getOptionInfo("incremental")
62926302
{'name': 'incremental', 'aliases': [], 'setByUser': True, 'type': <class 'bool'>, 'current': False, 'default': True}
6293-
>>> main_ctx().reset()
62946303
"""
62956304
return self.solver.getOptionInfo(name)
62966305

@@ -6305,7 +6314,7 @@ def statistics(self):
63056314
sat
63066315
>>> stats = s.statistics()
63076316
>>> stats['cvc5::CONSTANT']
6308-
{'defaulted': False, 'internal': False, 'value': {'integer type': 1}}
6317+
{'defaulted': True, 'internal': False, 'value': {}}
63096318
>>> len(stats.get()) < 10
63106319
True
63116320
>>> len(stats.get(True, True)) > 30
@@ -6331,11 +6340,7 @@ def SolverFor(logic, ctx=None, logFile=None):
63316340
# sat
63326341
# >>> s.model()
63336342
# [x = 1]
6334-
# """
6335-
solver = pc.Solver()
6336-
solver.setLogic(logic)
6337-
ctx = _get_ctx(ctx)
6338-
return Solver(solver, ctx, logFile)
6343+
return Solver(logic=logic, ctx=ctx, logFile=logFile)
63396344

63406345

63416346
def SimpleSolver(ctx=None, logFile=None):
@@ -6347,8 +6352,7 @@ def SimpleSolver(ctx=None, logFile=None):
63476352
>>> s.check()
63486353
sat
63496354
"""
6350-
ctx = _get_ctx(ctx)
6351-
return Solver(None, ctx, logFile)
6355+
return Solver(ctx=ctx, logFile=logFile)
63526356

63536357

63546358
#########################################
@@ -6571,17 +6575,13 @@ def is_tautology(taut):
65716575
class ModelRef:
65726576
"""Model/Solution of a satisfiability problem (aka system of constraints)."""
65736577

6574-
def __init__(self, solver, ctx):
6578+
def __init__(self, solver):
65756579
assert solver is not None
6576-
assert ctx is not None
65776580
self.solver = solver
6578-
self.ctx = ctx
65796581

65806582
def __del__(self):
65816583
if self.solver is not None:
65826584
self.solver = None
6583-
if self.ctx is not None:
6584-
self.ctx = None
65856585

65866586
def vars(self):
65876587
"""Returns the free constants in an assertion, as terms"""
@@ -6629,7 +6629,7 @@ def eval(self, t, model_completion=False):
66296629
>>> m.eval(x == 1)
66306630
True
66316631
"""
6632-
return _to_expr_ref(self.solver.solver.getValue(t.ast), self.solver.ctx)
6632+
return _to_expr_ref(self.solver.solver.getValue(t.ast), Context(self.solver))
66336633

66346634
def evaluate(self, t, model_completion=False):
66356635
"""Alias for `eval`.
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
False
2-
[s = (seq.unit 1)(), t = (seq.unit 2)()]
3-
[s = (seq.unit (- 1))(), t = (seq.unit (- 1))()]
4-
[s = (as seq.empty (Seq Int))()]
5-
[s = (as seq.empty (Seq Int))()]
6-
[s = (seq.++ (seq.unit 1) (seq.unit 2))()]
7-
[s = (as seq.empty (Seq Int))()]
8-
[s = (seq.++ (seq.unit 1) (seq.unit 2))()]
9-
[s = (seq.++ (seq.unit 1) (seq.unit 1))(), t = (seq.++ (seq.unit 1) (seq.unit 1))()]
10-
[s = (as seq.empty (Seq Int))(), t = (as seq.empty (Seq Int))(), u = (as seq.empty (Seq Int))()]
11-
[s = (seq.++ (seq.unit (- 2)) (seq.unit (- 1)))(), t = (seq.unit (- 1))()]
12-
[s = (seq.++ (seq.unit (- 3)) (seq.unit (- 2)) (seq.unit 4))(), t = (seq.unit 4)()]
13-
no solution
2+
sat
3+
sat
4+
sat
5+
sat
6+
sat
7+
sat
8+
sat
9+
sat
10+
sat
11+
sat
12+
sat
13+
sat
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
True
2-
[s = (seq.++ (seq.unit 1) (seq.unit 2))(), t = (as seq.empty (Seq Int))()]
3-
[a = "heAAA"]
2+
sat
3+
sat
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[x = 0, y = 0]
2+
sat
3+
0
4+
The logic was specified as QF_BV, which doesn't include THEORY_ARITH, but got a theory atom for that theory.
5+
The atom:
6+
(= x (* 2 y))
7+
Can't solve integer problems with QF_BV solver
8+
unsat
9+
unsat
10+
Can't eagerly BB with models and ALL

test/pgms/example_sequence.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
from cvc5_pythonic_api import *
2-
s, t, u = Consts('s t u', SeqSort(IntSort()))
2+
3+
s, t, u = Consts("s t u", SeqSort(IntSort()))
34
print(s.is_string())
4-
solve(Concat(s, Unit(IntVal(2))) == Concat(Unit(IntVal(1)), t))
5-
solve(PrefixOf(s.at(0),t),Length(s)>0)
6-
solve(s[0]> 5)
7-
solve(PrefixOf(s,Concat(Unit(IntVal(1)),Unit(IntVal(2)))))
8-
solve(PrefixOf(Concat(Unit(IntVal(1)),Unit(IntVal(2))),s))
9-
solve(SuffixOf(s,Concat(Unit(IntVal(1)),Unit(IntVal(2)))))
10-
solve(SuffixOf(Concat(Unit(IntVal(1)),Unit(IntVal(2))),s))
11-
solve(Contains(s,t),Length(t)>1)
12-
solve(Replace(s,t,u)==Replace(s,u,t))
13-
solve(IndexOf(s,t) == 1 )
14-
solve(IndexOf(s,t,1) > 1 )
5+
6+
7+
def mysolve(*args):
8+
s = Solver()
9+
print(s.check(*args))
10+
11+
12+
mysolve(Concat(s, Unit(IntVal(2))) == Concat(Unit(IntVal(1)), t))
13+
mysolve(PrefixOf(s.at(0), t), Length(s) > 0)
14+
mysolve(s[0] > 5)
15+
mysolve(PrefixOf(s, Concat(Unit(IntVal(1)), Unit(IntVal(2)))))
16+
mysolve(PrefixOf(Concat(Unit(IntVal(1)), Unit(IntVal(2))), s))
17+
mysolve(SuffixOf(s, Concat(Unit(IntVal(1)), Unit(IntVal(2)))))
18+
mysolve(SuffixOf(Concat(Unit(IntVal(1)), Unit(IntVal(2))), s))
19+
mysolve(Contains(s, t), Length(t) > 1)
20+
mysolve(Replace(s, t, u) == Replace(s, u, t))
21+
mysolve(IndexOf(s, t) == 1)
22+
mysolve(IndexOf(s, t, 1) > 1)
1523
e = Empty(SeqSort(IntSort()))
16-
solve(PrefixOf(s,e))
24+
mysolve(PrefixOf(s, e))
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
from cvc5_pythonic_api import *
2-
s,t = Consts('s t',SeqSort(IntSort()))
2+
3+
4+
def mysolve(*args):
5+
s = Solver()
6+
print(s.check(*args))
7+
8+
9+
s, t = Consts("s t", SeqSort(IntSort()))
310
print(is_seq(s))
4-
solve(SeqUpdate(s,t,0) == Concat(Unit(IntVal(1)),Unit(IntVal(2))))
5-
a = String('a')
6-
solve(SeqUpdate(a,StringVal('llo'),2) == 'hello')
11+
mysolve(SeqUpdate(s, t, 0) == Concat(Unit(IntVal(1)), Unit(IntVal(2))))
12+
a = String("a")
13+
mysolve(SeqUpdate(a, StringVal("llo"), 2) == "hello")

0 commit comments

Comments
 (0)