Использование Scala (без Z3), чтобы показать, что на самом деле есть больше решений для ограничения:
val tf = Seq(true, false)
val allValid =
for(a <- tf; b <- tf; c <- tf; d <- tf;
if((a && (b ^ c)) ^ d)) yield (
(if(a) "a" else "") + (if(b) "b" else "") +
(if(c) "c" else "") + (if(d) "d" else ""))
allValid.mkString("{ ", ", ", " }")
Печать:
{ abcd, ab, ac, ad, bcd, bd, cd, d }
Итак, если я что-то упускаю, вопрос в том, почему он не находит все решения? Теперь вот ответ на этот вопрос. (Оповещение о спойлере: "getAllModels" на самом деле не получает все модели.) Сначала давайте воспроизведем то, что вы наблюдали:
import z3.scala._
val ctx = new Z3Context("MODEL" -> true)
val a = ctx.mkFreshConst("a", ctx.mkBoolSort)
val b = ctx.mkFreshConst("b", ctx.mkBoolSort)
val c = ctx.mkFreshConst("c", ctx.mkBoolSort)
val d = ctx.mkFreshConst("d", ctx.mkBoolSort)
val cstr0 = ctx.mkXor(b, c)
val cstr1 = ctx.mkAnd(a, cstr0)
val cstr2 = ctx.mkXor(cstr1, d)
ctx.assertCnstr(cstr2)
Теперь, если я бегу: ctx.checkAndGetAllModels.foreach(println(_))
, я получаю:
d!3 -> false
a!0 -> true
c!2 -> false
b!1 -> true
d!3 -> true // this model is problematic
a!0 -> false
d!3 -> false
a!0 -> true
c!2 -> true
b!1 -> false
d!3 -> true
a!0 -> true
c!2 -> false
b!1 -> false
d!3 -> true
a!0 -> true
c!2 -> true
b!1 -> true
Теперь проблема в том, что вторая модель является неполной моделью. Z3 может вернуть его, потому что, какими бы ни были значения для b
и c
, ограничение выполнено (b
и c
являются не заботящимися переменными). Текущая реализация checkAndGetAllModels
просто отрицает модель, чтобы предотвратить повторение; в этом случае он запросит другую модель, такую, что (not (and d (not a)))
. Это предотвратит возвращение всех других моделей с этими двумя значениями. В некотором смысле неполная модель фактически представляет четыре действительные завершенные модели.
Кстати, если вы используете DSL ScalaZ3 с функцией findAll
, то все модели будут завершены со значениями по умолчанию, если они неполные (и до того, как они будут использованы для вычисления следующий). В контексте DSL мы можем сделать это, потому что мы знаем множество переменных, которые появляются в формуле. В этом случае сложнее угадать, как модель должна быть завершена. Один из вариантов - ScalaZ3 запомнить, какие переменные использовались. Лучшим решением было бы для Z3 иметь возможность всегда возвращать значения для переменных, не имеющих значения, или, возможно, просто перечислять все значения, не заботящиеся в модели.