forked from microsoft/mcBV
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBoundsTheory.fs
280 lines (232 loc) · 11.4 KB
/
BoundsTheory.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
module BoundsTheory
open Microsoft.Z3
open GlobalOptions
open Util
open Numeral
open Literal
open BooleanValuation
open BitVectorValuation
open BoundsValuation
open NumeralDB
open Clause
open Trail
open State
open TheoryRelation
open BitVector
open Learning
open Interval
open Z3Check
open BoundsOperations
type BoundType =
| Lower
| Upper
| Interval
// CMW: Global Note: All the functions in this file implement some bounds-specific
// function. The idea is that they can reason over bounds _only_. Quick sanity check:
// If one of the functions has a BitVectorEvaluation as an argument, it automatically
// depends on RLEBVTheory, which we don't want. We rather want the Interval[] in
// s.partAssignment.bounds
// We can exploit the combination of the two theories later (e.g., in tDecide and
// tGeneralize). For now, we can have all Boolean combinations of RLEBV constraints
// (PAs) and bounds constraints (Intervals), which does not require any implementation
// work, it will happen automatically once each of the theories has generated a few
// clauses (over their theory only), and the Boolean engine starts to resolve them with
// each other.
// Further theory dependency check: change the order of RLEBVTheory.fs and
// BoundsTheory.fs in the project, everything should compile regardless of the
// order of those two files.
let tbndsHolds (r:TheoryRelation) (pBounds:Ref<BoundsValuation>) (pNumerals:Ref<NumeralDB>) =
let relOp =
(fun (xBnds:Interval) (yBnds:Interval) ->
match r.getRelationOP with
| Z3_decl_kind.Z3_OP_EQ when xBnds.isSingleton && yBnds.isSingleton ->
if BitVector.bvEQ (xBnds.Singleton) (yBnds.Singleton) then True else False
| Z3_decl_kind.Z3_OP_ULEQ when xBnds.isSingleton && yBnds.isSingleton ->
if BitVector.bvULEQ (xBnds.Singleton) (yBnds.Singleton) then True else False
| Z3_decl_kind.Z3_OP_EQ ->
if (xBnds.isSingleton) then
if not (yBnds.Contains(xBnds.Singleton)) then False else Undefined
elif (yBnds.isSingleton) then
if not (xBnds.Contains(yBnds.Singleton)) then False else Undefined
else
Undefined
| Z3_decl_kind.Z3_OP_ULEQ ->
if (BitVector.bvULEQ xBnds.Upper yBnds.Lower) then
True
elif not (BitVector.bvULEQ xBnds.Lower yBnds.Upper) then
False
else
Undefined
| _ -> assert (false)
Undefined) // Error
let rhs = getRhsBounds r pBounds pNumerals
let lhs = getLhsBounds r pBounds pNumerals
if rhs.isEmpty || lhs.isEmpty then
False
else
let res = relOp lhs rhs
// dbg <| (lazy (sprintf " | %s in %s %s %s in %s => %s"
// (if (r.isSimpleRelation) then
// (r.getArgumentString 0)
// elif r.numArguments = 2 then
// (r.getBVOP.ToString()) + " " +
// r.getArgumentString 0 + " " +
// r.getArgumentString 1
// elif r.numArguments = 1 then
// (r.getBVOP.ToString()) + " " +
// r.getArgumentString 0
// else
// assert(false)
// "")
// (lhs.ToString())
// (if r.getRelationOP = Z3_decl_kind.Z3_OP_EQ then "=" else "<=")
// (if r.isRhsNumeral then ((!pNumerals).getNumeral (- r.getRhs)).ToString() else (r.getRhs.ToString()) + ":bv")
// (rhs.ToString())
// (res.ToString())))
res
let tbndsGetAntecedents (s:State) (tRel:Ref<TheoryRelation>) : Literal list =
let lex = (!s.bounds).L_explanation
let uex = (!s.bounds).U_explanation
// The situation is tRel: (op a0 a1) = rhs
// the antecedents of tRel are the bounds for a0, a1, and rhs,
// i.e., a maximum of 6 different Boolean variables (negated).
// Note: BV variables occurring in bounds constraints can have
// the same bounds constraint's boolVar as a reason (e.g., when they
// appear on decision level 0).
let nbv = Negate ((!tRel).getBoolVar)
let mutable t = []
for i in 0 .. (!tRel).numArguments do
if not ((!tRel).isArgumentNumeral i) then
let ll = Negate lex.[(!tRel).getArgument i]
let ul = Negate uex.[(!tRel).getArgument i]
for lit in [ll; ul] do
if lit <> 0 && lit <> nbv &&
not (List.exists (fun x -> x = lit) t) then
t <- lit :: t
t
let tbndsGetImplication (s:State) (tRel:Ref<TheoryRelation>) (holds:Val) =
// Whenever this function is called, we know that tbndsHolds is True or False.
assert (holds <> Undefined)
// CMW: The name of this function is not great, here's a description:
// Suppose there is a conflict between (...) -> boolVar and
// (only bounds) -> -boolVar. We need to make the latter explicit by
// providing a clause that contains all antecedents, and which
// implies -boolVar (swap polarity when holds = False)
let boolVar = (!tRel).getBoolVar
let ants = (tbndsGetAntecedents s tRel)
let lit = if holds = True then var2lit boolVar else Negate boolVar
let ants_contains_lit = (List.tryFind(fun x -> x = lit) ants).IsSome
assert(not ants_contains_lit)
let cl = newClauseFromList (lit :: ants)
checkExplanation s.trail s.database s.bvVal s.bounds cl false false true
(cl, lit)
let tbndsGeneralize (s:State) (expl:Clause) : Clause =
// TODO: Bounds generalization
expl
//let tbndsCheckConsistency (s:State) (wRel:Ref<TheoryRelation>) =
// let holds = tbndsHolds (!wRel) s.bounds s.numeralDB
// let valB = (!s.bVal).getValueB (!wRel).getBoolVar
// match (holds,valB) with
// | (True,False)
// | (False,True) ->
// let cnflctCls = tbndsExplainConflict s wRel
// s.conflict <- Some (ref cnflctCls)
// | _ -> ()
let implyBoundPredicate (s:State) (antecedants:Literal list) (boundPred:Ref<TheoryRelation>) =
assert(not antecedants.IsEmpty)
let lbVar = (!boundPred).getBoolVar
let imp = newClauseFromList (lbVar :: antecedants)
match (!s.bVal).getValueB lbVar with
| True ->
// AZ: If the predicates are normalized then this should work
// CMW: So, are they normalized?
()
| False -> s.SetConflict (Some (ref imp))
| Undefined ->
checkExplanation s.trail s.database s.bvVal s.bounds imp false false true
s.Push (Imp (ref imp, lbVar))
//assert(not s.IsConflicted) AZ: cross theory conflcits can occur
let tbndsImplyNewInterval (s:State) (antecedants:Literal list) (bvVar:Var) (newIntvl:Interval) =
// The situation is newInterval.lower < bvVar < newInterval.upper.
// See tImplyNewValue in RLEBVTheory.
// For the bounds, the difference is that we need to introduce
// "interval-Predicates" instead of MAPredicates.
let oldIntvl = (!s.bounds).get bvVar
if not (BitVector.bvEQ oldIntvl.Lower newIntvl.Lower) then
let lbPredicate = getLowerBoundPredicate s.database bvVar newIntvl.Lower s.bVal s.bvVal s.bounds
implyBoundPredicate s antecedants (ref lbPredicate)
if s.IsSearch then
if not (BitVector.bvEQ oldIntvl.Upper newIntvl.Upper) then
let ubPredicate = getUpperBoundPredicate s.database bvVar newIntvl.Upper s.bVal s.bvVal s.bounds
implyBoundPredicate s antecedants (ref ubPredicate)
let tbndsGetImpliedBounds (s:State) (tRel:Ref<TheoryRelation>) (polarity:Val) =
assert ((!s.bVal).getValueB ((!tRel).getBoolVar) <> Undefined)
let boolVar = (!tRel).getBoolVar
let varBoundPairs = tbndsGetNewValues (!tRel) polarity s.bounds s.numeralDB
let expl = (if polarity = True then Negate boolVar else boolVar) :: (tbndsGetAntecedents s tRel)
for (bvVar, newBounds) in varBoundPairs do
let oldBounds = (!s.bounds).get bvVar
if s.IsConflicted || bvVar = 0 || (Interval.Equal newBounds oldBounds) then
()
else if newBounds.isEmpty then
verbose <| (lazy (sprintf " | ==> Domain of %s:bv is empty; %s => False" (bvVar.ToString()) (clauseToString (newClauseFromList expl))))
let cc = newClauseFromList (expl)
s.SetConflict (Some (ref cc))
else
// CMW: bvVar is assigned new bounds (newBnds), but that new Interval
// does not necessarily depend on all the literals in `expl`, so
// this can be optimized. (Maybe add explanation finding to evalBounds?)
tbndsImplyNewInterval s expl bvVar newBounds
let tbndsEvaluateAtLeast1U (s:State) (tRel:Ref<TheoryRelation>) =
let nums = s.numeralDB
let pVal = s.bVal
let rel = !tRel
let boolVar = rel.getBoolVar
let bVal = (!pVal).getValueB boolVar
if bVal = Undefined then
let holds = tbndsHolds (!tRel) s.bounds s.numeralDB
if holds <> Undefined then
let (expl, l) = tbndsGetImplication s tRel holds
s.Push (Imp (ref expl, l))
assert(not s.IsConflicted || s.isInTempMode)
else
tbndsGetImpliedBounds s tRel bVal
// CMW: this is an optimization to get conflicts earlier.
// tbndsCheckConsistency s tRel
let tbndsEvaluate0U (s:State) (tRel:Ref<TheoryRelation>) =
// The TRel can be fully evaluated and checked against the trail.
// If it agrees with the trail, it's valueT is set
// If it contradicts the trail, a conflict is detected.
// If it is not present on the trail, it is propagated.
let pVal = s.bVal
let boolVar = (!tRel).getBoolVar
let holds = tbndsHolds (!tRel) s.bounds s.numeralDB
let bval = ((!pVal).getValueB boolVar)
match (holds, bval) with
| (True, True) -> (!pVal).setValueT boolVar (!s.trail).getNumDecisions |>ignore
| (False, False) -> (!pVal).setValueT (Negate boolVar) (!s.trail).getNumDecisions |>ignore
| (True, False)
| (False, True) ->
let (expl, l) = tbndsGetImplication s tRel holds
s.SetConflict (Some (ref expl))
| (True, Undefined)
| (False, Undefined) ->
let (expl, l) = tbndsGetImplication s tRel holds
s.Push (Imp (ref expl, l))
| (Undefined, _) ->
// While this case can't happen in the RLEBV theory, it can be triggered
// in the bounds theory when the bounds are not strong enough.
()
let tbndsEvaluate (s:State) (tRel:Ref<TheoryRelation>) =
// match (numUnboundedVariables !tRel s.bounds) with
// | 0 ->
// // Problem: 0 unbounded variables means this is called very
// // often, with small chance of success.
// tbndsEvaluate0U s tRel
// | _ ->
// tbndsEvaluateAtLeast1U s tRel
// CMW: In each iteration, first run bounds update,
// then check for conflict/implication/etc. "numUnboundedVariables"
// is not a good indicator for whether we will see bounds updates.
tbndsEvaluateAtLeast1U s tRel
tbndsEvaluate0U s tRel