@@ -11,7 +11,6 @@ import scala.language.postfixOps
11
11
import scala .collection .mutable
12
12
import scala .reflect .internal .util .Statistics
13
13
import scala .reflect .internal .util .HashSet
14
- import scala .annotation .tailrec
15
14
16
15
trait Logic extends Debugging {
17
16
import PatternMatchingStats ._
@@ -104,21 +103,16 @@ trait Logic extends Debugging {
104
103
def implications : List [(Sym , List [Sym ], List [Sym ])]
105
104
}
106
105
107
- private def propToLinkedHashSet (ops : Seq [Prop ]) = {
108
- val set = new mutable.LinkedHashSet [Prop ]()
109
- ops.foreach(set += _)
110
- set
111
- }
112
-
113
106
// would be nice to statically check whether a prop is equational or pure,
114
107
// but that requires typing relations like And(x: Tx, y: Ty) : (if(Tx == PureProp && Ty == PureProp) PureProp else Prop)
115
- final case class And (ops : mutable. LinkedHashSet [Prop ]) extends Prop
108
+ final case class And (ops : Set [Prop ]) extends Prop
116
109
object And {
117
- def apply (ops : Prop * ) = new And (propToLinkedHashSet( ops) )
110
+ def apply (ops : Prop * ) = new And (ops.toSet )
118
111
}
119
- final case class Or (ops : mutable.LinkedHashSet [Prop ]) extends Prop
112
+
113
+ final case class Or (ops : Set [Prop ]) extends Prop
120
114
object Or {
121
- def apply (ops : Prop * ) = new Or (propToLinkedHashSet( ops) )
115
+ def apply (ops : Prop * ) = new Or (ops.toSet )
122
116
}
123
117
124
118
final case class Not (a : Prop ) extends Prop
@@ -146,17 +140,111 @@ trait Logic extends Debugging {
146
140
def / \ (props : Iterable [Prop ]) = if (props.isEmpty) True else And (props.toSeq: _* )
147
141
def \/ (props : Iterable [Prop ]) = if (props.isEmpty) False else Or (props.toSeq: _* )
148
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
+ }
149
235
150
236
trait PropTraverser {
151
237
def apply (x : Prop ): Unit = x match {
152
238
case And (ops) => ops foreach apply
153
239
case Or (ops) => ops foreach apply
154
240
case Not (a) => apply(a)
155
241
case Eq (a, b) => applyVar(a); applyConst(b)
242
+ case s : Sym => applySymbol(s)
156
243
case _ =>
157
244
}
158
245
def applyVar (x : Var ): Unit = {}
159
246
def applyConst (x : Const ): Unit = {}
247
+ def applySymbol (x : Sym ): Unit = {}
160
248
}
161
249
162
250
def gatherVariables (p : Prop ): Set [Var ] = {
@@ -167,6 +255,14 @@ trait Logic extends Debugging {
167
255
vars.toSet
168
256
}
169
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
+
170
266
trait PropMap {
171
267
def apply (x : Prop ): Prop = x match { // TODO: mapConserve
172
268
case And (ops) => And (ops map apply)
@@ -176,27 +272,10 @@ trait Logic extends Debugging {
176
272
}
177
273
}
178
274
179
- // to govern how much time we spend analyzing matches for unreachability/exhaustivity
180
- object AnalysisBudget {
181
- private val budgetProp = scala.sys.Prop [String ](" scalac.patmat.analysisBudget" )
182
- private val budgetOff = " off"
183
- val max : Int = {
184
- val DefaultBudget = 256
185
- budgetProp.option match {
186
- case Some (`budgetOff`) =>
187
- Integer .MAX_VALUE
188
- case Some (x) =>
189
- x.toInt
190
- case None =>
191
- DefaultBudget
192
- }
193
- }
194
-
195
- abstract class Exception (val advice : String ) extends RuntimeException (" CNF budget exceeded" )
196
-
197
- object exceeded extends Exception (
198
- s " (The analysis required more space than allowed. Please try with scalac -D ${budgetProp.key}= ${AnalysisBudget .max* 2 } or -D ${budgetProp.key}= ${budgetOff}.) " )
199
-
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. " )
200
279
}
201
280
202
281
// convert finite domain propositional logic with subtyping to pure boolean propositional logic
@@ -217,7 +296,7 @@ trait Logic extends Debugging {
217
296
// TODO: for V1 representing x1 and V2 standing for x1.head, encode that
218
297
// V1 = Nil implies -(V2 = Ci) for all Ci in V2's domain (i.e., it is unassignable)
219
298
// may throw an AnalysisBudget.Exception
220
- def removeVarEq (props : List [Prop ], modelNull : Boolean = false ): (Formula , List [Formula ]) = {
299
+ def removeVarEq (props : List [Prop ], modelNull : Boolean = false ): (Prop , List [Prop ]) = {
221
300
val start = if (Statistics .canEnable) Statistics .startTimer(patmatAnaVarEq) else null
222
301
223
302
val vars = new mutable.HashSet [Var ]
@@ -241,10 +320,10 @@ trait Logic extends Debugging {
241
320
props foreach gatherEqualities.apply
242
321
if (modelNull) vars foreach (_.registerNull())
243
322
244
- val pure = props map (p => eqFreePropToSolvable( rewriteEqualsToProp(p) ))
323
+ val pure = props map (p => rewriteEqualsToProp(p))
245
324
246
- val eqAxioms = formulaBuilder
247
- @ inline def addAxiom (p : Prop ) = addFormula( eqAxioms, eqFreePropToSolvable(p))
325
+ val eqAxioms = mutable. ArrayBuffer [ Prop ]()
326
+ @ inline def addAxiom (p : Prop ) = eqAxioms += p
248
327
249
328
debug.patmat(" removeVarEq vars: " + vars)
250
329
vars.foreach { v =>
@@ -270,49 +349,30 @@ trait Logic extends Debugging {
270
349
}
271
350
}
272
351
273
- debug.patmat(s " eqAxioms: \n $eqAxioms" )
352
+ debug.patmat(s " eqAxioms: \n ${ eqAxioms.mkString( " \n " )} " )
274
353
debug.patmat(s " pure: ${pure.mkString(" \n " )}" )
275
354
276
355
if (Statistics .canEnable) Statistics .stopTimer(patmatAnaVarEq, start)
277
356
278
- (toFormula (eqAxioms), pure)
357
+ (And (eqAxioms : _* ), pure)
279
358
}
280
359
360
+ type Solvable
281
361
282
- // an interface that should be suitable for feeding a SAT solver when the time comes
283
- type Formula
284
- type FormulaBuilder
285
-
286
- // creates an empty formula builder to which more formulae can be added
287
- def formulaBuilder : FormulaBuilder
288
-
289
- // val f = formulaBuilder; addFormula(f, f1); ... addFormula(f, fN)
290
- // toFormula(f) == andFormula(f1, andFormula(..., fN))
291
- def addFormula (buff : FormulaBuilder , f : Formula ): Unit
292
- def toFormula (buff : FormulaBuilder ): Formula
293
-
294
- // the conjunction of formulae `a` and `b`
295
- def andFormula (a : Formula , b : Formula ): Formula
296
-
297
- // equivalent formula to `a`, but simplified in a lightweight way (drop duplicate clauses)
298
- def simplifyFormula (a : Formula ): Formula
299
-
300
- // may throw an AnalysisBudget.Exception
301
- def propToSolvable (p : Prop ): Formula = {
302
- val (eqAxioms, pure :: Nil ) = removeVarEq(List (p), modelNull = false )
303
- andFormula(eqAxioms, pure)
362
+ def propToSolvable (p : Prop ): Solvable = {
363
+ val (eqAxiom, pure :: Nil ) = removeVarEq(List (p), modelNull = false )
364
+ eqFreePropToSolvable(And (eqAxiom, pure))
304
365
}
305
366
306
- // may throw an AnalysisBudget.Exception
307
- def eqFreePropToSolvable (p : Prop ): Formula
308
- def cnfString (f : Formula ): String
367
+ def eqFreePropToSolvable (f : Prop ): Solvable
309
368
310
369
type Model = Map [Sym , Boolean ]
311
370
val EmptyModel : Model
312
371
val NoModel : Model
313
372
314
- def findModelFor (f : Formula ): Model
315
- def findAllModelsFor (f : Formula ): List [Model ]
373
+ def findModelFor (solvable : Solvable ): Model
374
+
375
+ def findAllModelsFor (solvable : Solvable ): List [Model ]
316
376
}
317
377
}
318
378
0 commit comments