@@ -16,8 +16,8 @@ trait Logic extends Debugging {
16
16
import PatternMatchingStats ._
17
17
18
18
private def max (xs : Seq [Int ]) = if (xs isEmpty) 0 else xs max
19
- private def alignedColumns (cols : Seq [AnyRef ]): Seq [String ] = {
20
- def toString (x : AnyRef ) = if (x eq null ) " " else x.toString
19
+ private def alignedColumns (cols : Seq [Any ]): Seq [String ] = {
20
+ def toString (x : Any ) = if (x == null ) " " else x.toString
21
21
if (cols.isEmpty || cols.tails.isEmpty) cols map toString
22
22
else {
23
23
val colLens = cols map (c => toString(c).length)
@@ -32,7 +32,7 @@ trait Logic extends Debugging {
32
32
}
33
33
}
34
34
35
- def alignAcrossRows (xss : List [List [AnyRef ]], sep : String , lineSep : String = " \n " ): String = {
35
+ def alignAcrossRows (xss : List [List [Any ]], sep : String , lineSep : String = " \n " ): String = {
36
36
val maxLen = max(xss map (_.length))
37
37
val padded = xss map (xs => xs ++ List .fill(maxLen - xs.length)(null ))
38
38
padded.transpose.map(alignedColumns).transpose map (_.mkString(sep)) mkString(lineSep)
@@ -46,7 +46,7 @@ trait Logic extends Debugging {
46
46
type Tree
47
47
48
48
class Prop
49
- case class Eq (p : Var , q : Const ) extends Prop
49
+ final case class Eq (p : Var , q : Const ) extends Prop
50
50
51
51
type Const
52
52
@@ -105,43 +105,146 @@ trait Logic extends Debugging {
105
105
106
106
// would be nice to statically check whether a prop is equational or pure,
107
107
// but that requires typing relations like And(x: Tx, y: Ty) : (if(Tx == PureProp && Ty == PureProp) PureProp else Prop)
108
- case class And (a : Prop , b : Prop ) extends Prop
109
- case class Or (a : Prop , b : Prop ) extends Prop
110
- case class Not (a : Prop ) extends Prop
108
+ final case class And (ops : Set [Prop ]) extends Prop
109
+ object And {
110
+ def apply (ops : Prop * ) = new And (ops.toSet)
111
+ }
112
+
113
+ final case class Or (ops : Set [Prop ]) extends Prop
114
+ object Or {
115
+ def apply (ops : Prop * ) = new Or (ops.toSet)
116
+ }
117
+
118
+ final case class Not (a : Prop ) extends Prop
111
119
112
120
case object True extends Prop
113
121
case object False extends Prop
114
122
115
123
// symbols are propositions
116
- abstract case class Sym ( variable : Var , const : Const ) extends Prop {
124
+ final class Sym private [ PropositionalLogic ] ( val variable : Var , val const : Const ) extends Prop {
117
125
private val id : Int = Sym .nextSymId
118
126
119
- override def toString = variable + " =" + const + " #" + id
127
+ override def toString = variable + " =" + const + " #" + id
120
128
}
121
- class UniqueSym ( variable : Var , const : Const ) extends Sym (variable, const)
129
+
122
130
object Sym {
123
131
private val uniques : HashSet [Sym ] = new HashSet (" uniques" , 512 )
124
132
def apply (variable : Var , const : Const ): Sym = {
125
- val newSym = new UniqueSym (variable, const)
133
+ val newSym = new Sym (variable, const)
126
134
(uniques findEntryOrUpdate newSym)
127
135
}
128
- private def nextSymId = {_symId += 1 ; _symId}; private var _symId = 0
136
+ def nextSymId = {_symId += 1 ; _symId}; private var _symId = 0
129
137
implicit val SymOrdering : Ordering [Sym ] = Ordering .by(_.id)
130
138
}
131
139
132
- def / \ (props : Iterable [Prop ]) = if (props.isEmpty) True else props.reduceLeft(And (_, _))
133
- def \/ (props : Iterable [Prop ]) = if (props.isEmpty) False else props.reduceLeft(Or (_, _))
140
+ def / \ (props : Iterable [Prop ]) = if (props.isEmpty) True else And (props.toSeq: _* )
141
+ def \/ (props : Iterable [Prop ]) = if (props.isEmpty) False else Or (props.toSeq: _* )
142
+
143
+ /**
144
+ * Simplifies propositional formula according to the following rules:
145
+ * - eliminate double negation (avoids unnecessary Tseitin variables)
146
+ * - flatten trees of same connectives (avoids unnecessary Tseitin variables)
147
+ * - removes constants and connectives that are in fact constant because of their operands
148
+ * - eliminates duplicate operands
149
+ * - convert formula into NNF: all sub-expressions have a positive polarity
150
+ * which makes them amenable for the subsequent Plaisted transformation
151
+ * and increases chances to figure out that the formula is already in CNF
152
+ *
153
+ * Complexity: DFS over formula tree
154
+ *
155
+ * See http://www.decision-procedures.org/slides/propositional_logic-2x3.pdf
156
+ */
157
+ def simplify (f : Prop ): Prop = {
158
+
159
+ // limit size to avoid blow up
160
+ def hasImpureAtom (ops : Seq [Prop ]): Boolean = ops.size < 10 &&
161
+ ops.combinations(2 ).exists {
162
+ case Seq (a, Not (b)) if a == b => true
163
+ case Seq (Not (a), b) if a == b => true
164
+ case _ => false
165
+ }
166
+
167
+ // push negation inside formula
168
+ def negationNormalFormNot (p : Prop ): Prop = p match {
169
+ case And (ops) => Or (ops.map(negationNormalFormNot)) // De'Morgan
170
+ case Or (ops) => And (ops.map(negationNormalFormNot)) // De'Morgan
171
+ case Not (p) => negationNormalForm(p)
172
+ case True => False
173
+ case False => True
174
+ case s : Sym => Not (s)
175
+ }
176
+
177
+ def negationNormalForm (p : Prop ): Prop = p match {
178
+ case And (ops) => And (ops.map(negationNormalForm))
179
+ case Or (ops) => Or (ops.map(negationNormalForm))
180
+ case Not (negated) => negationNormalFormNot(negated)
181
+ case True
182
+ | False
183
+ | (_ : Sym ) => p
184
+ }
185
+
186
+ def simplifyProp (p : Prop ): Prop = p match {
187
+ case And (fv) =>
188
+ // recurse for nested And (pulls all Ands up)
189
+ val ops = fv.map(simplifyProp) - True // ignore `True`
190
+
191
+ // build up Set in order to remove duplicates
192
+ val opsFlattened = ops.flatMap {
193
+ case And (fv) => fv
194
+ case f => Set (f)
195
+ }.toSeq
196
+
197
+ if (hasImpureAtom(opsFlattened) || opsFlattened.contains(False )) {
198
+ False
199
+ } else {
200
+ opsFlattened match {
201
+ case Seq () => True
202
+ case Seq (f) => f
203
+ case ops => And (ops : _* )
204
+ }
205
+ }
206
+ case Or (fv) =>
207
+ // recurse for nested Or (pulls all Ors up)
208
+ val ops = fv.map(simplifyProp) - False // ignore `False`
209
+
210
+ val opsFlattened = ops.flatMap {
211
+ case Or (fv) => fv
212
+ case f => Set (f)
213
+ }.toSeq
214
+
215
+ if (hasImpureAtom(opsFlattened) || opsFlattened.contains(True )) {
216
+ True
217
+ } else {
218
+ opsFlattened match {
219
+ case Seq () => False
220
+ case Seq (f) => f
221
+ case ops => Or (ops : _* )
222
+ }
223
+ }
224
+ case Not (Not (a)) =>
225
+ simplify(a)
226
+ case Not (p) =>
227
+ Not (simplify(p))
228
+ case p =>
229
+ p
230
+ }
231
+
232
+ val nnf = negationNormalForm(f)
233
+ simplifyProp(nnf)
234
+ }
134
235
135
236
trait PropTraverser {
136
237
def apply (x : Prop ): Unit = x match {
137
- case And (a, b ) => apply(a); apply(b)
138
- case Or (a, b ) => apply(a); apply(b)
238
+ case And (ops ) => ops foreach apply
239
+ case Or (ops ) => ops foreach apply
139
240
case Not (a) => apply(a)
140
241
case Eq (a, b) => applyVar(a); applyConst(b)
242
+ case s : Sym => applySymbol(s)
141
243
case _ =>
142
244
}
143
245
def applyVar (x : Var ): Unit = {}
144
246
def applyConst (x : Const ): Unit = {}
247
+ def applySymbol (x : Sym ): Unit = {}
145
248
}
146
249
147
250
def gatherVariables (p : Prop ): Set [Var ] = {
@@ -152,36 +255,27 @@ trait Logic extends Debugging {
152
255
vars.toSet
153
256
}
154
257
258
+ def gatherSymbols (p : Prop ): Set [Sym ] = {
259
+ val syms = new mutable.HashSet [Sym ]()
260
+ (new PropTraverser {
261
+ override def applySymbol (s : Sym ) = syms += s
262
+ })(p)
263
+ syms.toSet
264
+ }
265
+
155
266
trait PropMap {
156
267
def apply (x : Prop ): Prop = x match { // TODO: mapConserve
157
- case And (a, b ) => And (apply(a), apply(b) )
158
- case Or (a, b ) => Or (apply(a), apply(b) )
268
+ case And (ops ) => And (ops map apply)
269
+ case Or (ops ) => Or (ops map apply)
159
270
case Not (a) => Not (apply(a))
160
271
case p => p
161
272
}
162
273
}
163
274
164
- // to govern how much time we spend analyzing matches for unreachability/exhaustivity
165
- object AnalysisBudget {
166
- private val budgetProp = scala.sys.Prop [String ](" scalac.patmat.analysisBudget" )
167
- private val budgetOff = " off"
168
- val max : Int = {
169
- val DefaultBudget = 256
170
- budgetProp.option match {
171
- case Some (`budgetOff`) =>
172
- Integer .MAX_VALUE
173
- case Some (x) =>
174
- x.toInt
175
- case None =>
176
- DefaultBudget
177
- }
178
- }
179
-
180
- abstract class Exception (val advice : String ) extends RuntimeException (" CNF budget exceeded" )
181
-
182
- object exceeded extends Exception (
183
- s " (The analysis required more space than allowed. Please try with scalac -D ${budgetProp.key}= ${AnalysisBudget .max* 2 } or -D ${budgetProp.key}= ${budgetOff}.) " )
184
-
275
+ // TODO: remove since deprecated
276
+ val budgetProp = scala.sys.Prop [String ](" scalac.patmat.analysisBudget" )
277
+ if (budgetProp.isSet) {
278
+ reportWarning(s " Please remove -D ${budgetProp.key}, it is ignored. " )
185
279
}
186
280
187
281
// convert finite domain propositional logic with subtyping to pure boolean propositional logic
@@ -202,7 +296,7 @@ trait Logic extends Debugging {
202
296
// TODO: for V1 representing x1 and V2 standing for x1.head, encode that
203
297
// V1 = Nil implies -(V2 = Ci) for all Ci in V2's domain (i.e., it is unassignable)
204
298
// may throw an AnalysisBudget.Exception
205
- def removeVarEq (props : List [Prop ], modelNull : Boolean = false ): (Formula , List [Formula ]) = {
299
+ def removeVarEq (props : List [Prop ], modelNull : Boolean = false ): (Prop , List [Prop ]) = {
206
300
val start = if (Statistics .canEnable) Statistics .startTimer(patmatAnaVarEq) else null
207
301
208
302
val vars = new mutable.HashSet [Var ]
@@ -226,10 +320,10 @@ trait Logic extends Debugging {
226
320
props foreach gatherEqualities.apply
227
321
if (modelNull) vars foreach (_.registerNull())
228
322
229
- val pure = props map (p => eqFreePropToSolvable( rewriteEqualsToProp(p) ))
323
+ val pure = props map (p => rewriteEqualsToProp(p))
230
324
231
- val eqAxioms = formulaBuilder
232
- @ inline def addAxiom (p : Prop ) = addFormula( eqAxioms, eqFreePropToSolvable(p))
325
+ val eqAxioms = mutable. ArrayBuffer [ Prop ]()
326
+ @ inline def addAxiom (p : Prop ) = eqAxioms += p
233
327
234
328
debug.patmat(" removeVarEq vars: " + vars)
235
329
vars.foreach { v =>
@@ -255,49 +349,30 @@ trait Logic extends Debugging {
255
349
}
256
350
}
257
351
258
- debug.patmat(" eqAxioms:\n " + cnfString(toFormula( eqAxioms)) )
259
- debug.patmat(" pure:" + pure.map(p => cnfString(p)). mkString(" \n " ))
352
+ debug.patmat(s " eqAxioms: \n ${ eqAxioms.mkString( " \n " )} " )
353
+ debug.patmat(s " pure: ${ pure.mkString(" \n " )} " )
260
354
261
355
if (Statistics .canEnable) Statistics .stopTimer(patmatAnaVarEq, start)
262
356
263
- (toFormula (eqAxioms), pure)
357
+ (And (eqAxioms : _* ), pure)
264
358
}
265
359
360
+ type Solvable
266
361
267
- // an interface that should be suitable for feeding a SAT solver when the time comes
268
- type Formula
269
- type FormulaBuilder
270
-
271
- // creates an empty formula builder to which more formulae can be added
272
- def formulaBuilder : FormulaBuilder
273
-
274
- // val f = formulaBuilder; addFormula(f, f1); ... addFormula(f, fN)
275
- // toFormula(f) == andFormula(f1, andFormula(..., fN))
276
- def addFormula (buff : FormulaBuilder , f : Formula ): Unit
277
- def toFormula (buff : FormulaBuilder ): Formula
278
-
279
- // the conjunction of formulae `a` and `b`
280
- def andFormula (a : Formula , b : Formula ): Formula
281
-
282
- // equivalent formula to `a`, but simplified in a lightweight way (drop duplicate clauses)
283
- def simplifyFormula (a : Formula ): Formula
284
-
285
- // may throw an AnalysisBudget.Exception
286
- def propToSolvable (p : Prop ): Formula = {
287
- val (eqAxioms, pure :: Nil ) = removeVarEq(List (p), modelNull = false )
288
- andFormula(eqAxioms, pure)
362
+ def propToSolvable (p : Prop ): Solvable = {
363
+ val (eqAxiom, pure :: Nil ) = removeVarEq(List (p), modelNull = false )
364
+ eqFreePropToSolvable(And (eqAxiom, pure))
289
365
}
290
366
291
- // may throw an AnalysisBudget.Exception
292
- def eqFreePropToSolvable (p : Prop ): Formula
293
- def cnfString (f : Formula ): String
367
+ def eqFreePropToSolvable (f : Prop ): Solvable
294
368
295
369
type Model = Map [Sym , Boolean ]
296
370
val EmptyModel : Model
297
371
val NoModel : Model
298
372
299
- def findModelFor (f : Formula ): Model
300
- def findAllModelsFor (f : Formula ): List [Model ]
373
+ def findModelFor (solvable : Solvable ): Model
374
+
375
+ def findAllModelsFor (solvable : Solvable ): List [Model ]
301
376
}
302
377
}
303
378
0 commit comments