@@ -16,8 +16,8 @@ trait Logic extends Debugging {
1616 import PatternMatchingStats ._
1717
1818 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
2121 if (cols.isEmpty || cols.tails.isEmpty) cols map toString
2222 else {
2323 val colLens = cols map (c => toString(c).length)
@@ -32,7 +32,7 @@ trait Logic extends Debugging {
3232 }
3333 }
3434
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 = {
3636 val maxLen = max(xss map (_.length))
3737 val padded = xss map (xs => xs ++ List .fill(maxLen - xs.length)(null ))
3838 padded.transpose.map(alignedColumns).transpose map (_.mkString(sep)) mkString(lineSep)
@@ -46,7 +46,7 @@ trait Logic extends Debugging {
4646 type Tree
4747
4848 class Prop
49- case class Eq (p : Var , q : Const ) extends Prop
49+ final case class Eq (p : Var , q : Const ) extends Prop
5050
5151 type Const
5252
@@ -105,43 +105,146 @@ trait Logic extends Debugging {
105105
106106 // would be nice to statically check whether a prop is equational or pure,
107107 // 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
111119
112120 case object True extends Prop
113121 case object False extends Prop
114122
115123 // 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 {
117125 private val id : Int = Sym .nextSymId
118126
119- override def toString = variable + " =" + const + " #" + id
127+ override def toString = variable + " =" + const + " #" + id
120128 }
121- class UniqueSym ( variable : Var , const : Const ) extends Sym (variable, const)
129+
122130 object Sym {
123131 private val uniques : HashSet [Sym ] = new HashSet (" uniques" , 512 )
124132 def apply (variable : Var , const : Const ): Sym = {
125- val newSym = new UniqueSym (variable, const)
133+ val newSym = new Sym (variable, const)
126134 (uniques findEntryOrUpdate newSym)
127135 }
128- private def nextSymId = {_symId += 1 ; _symId}; private var _symId = 0
136+ def nextSymId = {_symId += 1 ; _symId}; private var _symId = 0
129137 implicit val SymOrdering : Ordering [Sym ] = Ordering .by(_.id)
130138 }
131139
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+ }
134235
135236 trait PropTraverser {
136237 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
139240 case Not (a) => apply(a)
140241 case Eq (a, b) => applyVar(a); applyConst(b)
242+ case s : Sym => applySymbol(s)
141243 case _ =>
142244 }
143245 def applyVar (x : Var ): Unit = {}
144246 def applyConst (x : Const ): Unit = {}
247+ def applySymbol (x : Sym ): Unit = {}
145248 }
146249
147250 def gatherVariables (p : Prop ): Set [Var ] = {
@@ -152,36 +255,27 @@ trait Logic extends Debugging {
152255 vars.toSet
153256 }
154257
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+
155266 trait PropMap {
156267 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)
159270 case Not (a) => Not (apply(a))
160271 case p => p
161272 }
162273 }
163274
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. " )
185279 }
186280
187281 // convert finite domain propositional logic with subtyping to pure boolean propositional logic
@@ -202,7 +296,7 @@ trait Logic extends Debugging {
202296 // TODO: for V1 representing x1 and V2 standing for x1.head, encode that
203297 // V1 = Nil implies -(V2 = Ci) for all Ci in V2's domain (i.e., it is unassignable)
204298 // 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 ]) = {
206300 val start = if (Statistics .canEnable) Statistics .startTimer(patmatAnaVarEq) else null
207301
208302 val vars = new mutable.HashSet [Var ]
@@ -226,10 +320,10 @@ trait Logic extends Debugging {
226320 props foreach gatherEqualities.apply
227321 if (modelNull) vars foreach (_.registerNull())
228322
229- val pure = props map (p => eqFreePropToSolvable( rewriteEqualsToProp(p) ))
323+ val pure = props map (p => rewriteEqualsToProp(p))
230324
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
233327
234328 debug.patmat(" removeVarEq vars: " + vars)
235329 vars.foreach { v =>
@@ -255,49 +349,30 @@ trait Logic extends Debugging {
255349 }
256350 }
257351
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 " )} " )
260354
261355 if (Statistics .canEnable) Statistics .stopTimer(patmatAnaVarEq, start)
262356
263- (toFormula (eqAxioms), pure)
357+ (And (eqAxioms : _* ), pure)
264358 }
265359
360+ type Solvable
266361
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))
289365 }
290366
291- // may throw an AnalysisBudget.Exception
292- def eqFreePropToSolvable (p : Prop ): Formula
293- def cnfString (f : Formula ): String
367+ def eqFreePropToSolvable (f : Prop ): Solvable
294368
295369 type Model = Map [Sym , Boolean ]
296370 val EmptyModel : Model
297371 val NoModel : Model
298372
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 ]
301376 }
302377}
303378
0 commit comments