Ошибка в примере документации для поиска максимально удовлетворяющих подмножеств в z3

Я пытаюсь использовать код из примера документации z3 , чтобы найти максимально удовлетворяющие подмножества в z3. Вот код, который я скопировал:

from z3 import *

def main():
    x, y = Reals('x y')
    soft_constraints = [x > 2, x < 1, x < 0, Or(x + y > 0, y < 0), Or(y >= 0, x >= 0), Or(y < 0, x < 0), Or(y > 0, x < 0)]
    hard_constraints = BoolVal(True)
    solver = MSSSolver(hard_constraints, soft_constraints)
    for lits in enumerate_sets(solver):
        print("%s" % lits)


def enumerate_sets(solver):
    while True:
        if sat == solver.s.check():
           MSS = solver.grow()
           yield MSS
        else:
           break

class MSSSolver:
   s = Solver()
   varcache = {}
   idcache = {}

   def __init__(self, hard, soft):
       self.n = len(soft)
       self.soft = soft
       self.s.add(hard)       
       self.soft_vars = set([self.c_var(i) for i in range(self.n)])
       self.orig_soft_vars = set([self.c_var(i) for i in range(self.n)])
       self.s.add([(self.c_var(i) == soft[i]) for i in range(self.n)])

   def c_var(self, i):
       if i not in self.varcache:
          v = Bool(str(self.soft[abs(i)]))
          self.idcache[v] = abs(i)
          if i >= 0:
             self.varcache[i] = v
          else:
             self.varcache[i] = Not(v)
       return self.varcache[i]

   # Retrieve the latest model
   # Add formulas that are true in the model to 
   # the current mss

   def update_unknown(self):
       self.model = self.s.model()
       new_unknown = set([])
       for x in self.unknown:
           if is_true(self.model[x]):
              self.mss.append(x)
           else:
              new_unknown.add(x)
       self.unknown = new_unknown
      
   # Create a name, propositional atom,
   #  for formula 'fml' and return the name.

   def add_def(self, fml):
       name = Bool("%s" % fml)
       self.s.add(name == fml)
       return name

   def relax_core(self, Fs):
       assert(Fs <= self.soft_vars)
       prefix = BoolVal(True)
       self.soft_vars -= Fs
       Fs = [ f for f in Fs ]
       for i in range(len(Fs)-1):
           prefix = self.add_def(And(Fs[i], prefix))
           self.soft_vars.add(self.add_def(Or(prefix, Fs[i+1])))

   # Resolve literals from the core that 
   # are 'explained', e.g., implied by 
   # other literals.

   def resolve_core(self, core):
       new_core = set([])
       for x in core:
           if x in self.mcs_explain:
              new_core |= self.mcs_explain[x]
           else:
              new_core.add(x)
       return new_core


   # Given a current satisfiable state
   # Extract an MSS, and ensure that currently 
   # encountered cores are avoided in next iterations
   # by weakening the set of literals that are
   # examined in next iterations.
   # Strengthen the solver state by enforcing that
   # an element from the MCS is encountered.

   def grow(self):
       self.mss = []
       self.mcs = []
       self.nmcs = []
       self.mcs_explain = {}
       self.unknown = self.soft_vars
       self.update_unknown()
       cores = []
       while len(self.unknown) > 0:
           x = self.unknown.pop()
           is_sat = self.s.check(self.mss + [x] + self.nmcs)
           if is_sat == sat:
              self.mss.append(x)
              self.update_unknown()
           elif is_sat == unsat:
              core = self.s.unsat_core()
              core = self.resolve_core(core)
              self.mcs_explain[Not(x)] = {y for y in core if not eq(x,y)}
              self.mcs.append(x)
              self.nmcs.append(Not(x)) 
              cores += [core]              
           else:
              print("solver returned %s" % is_sat)
              exit()
       mss = [x for x in self.orig_soft_vars if is_true(self.model[x])]
       mcs = [x for x in self.orig_soft_vars if not is_true(self.model[x])]
       self.s.add(Or(mcs))
       core_literals = set([])
       cores.sort(key=lambda element: len(element))
       for core in cores:
           if len(core & core_literals) == 0:
              self.relax_core(core)
              core_literals |= core
       return mss

и вот еще один код:

def all_smt(s, initial_terms):
    """
    s: a solver (with maybe some constraints
    t: a list of z3 terms

    From: https://stackoverflow.com/questions/11867611/z3py-checking-all-
    solutions-for-equation/70656700#70656700
    """
    def block_term(s, m, t):
        s.add(t != m.eval(t, model_completion=True))
    def fix_term(s, m, t):
        s.add(t == m.eval(t, model_completion=True))
    def all_smt_rec(terms):
        if sat == s.check():
           m = s.model()
           yield m
           for i in range(len(terms)):
               s.push()
               block_term(s, m, terms[i])
               for j in range(i):
                   fix_term(s, m, terms[j])
               yield from all_smt_rec(terms[i:])
               s.pop()
    yield from all_smt_rec(list(initial_terms))

Вывод функции main выглядит нормально, однако меня интересуют следующие логические задачи:

p, q = z3.Bools('p q')

hard = z3.Or(q, p)
soft = [z3.Or(p, q), z3.Not(z3.And(q, p)), z3.Not(z3.And(p, q))]

solver = MSSSolver(hard, soft)     
mms = tuple(enumerate_sets(solver))
intersection = reduce(lambda x, y: x & set(y), mms, set(mms[0]))

print(intersection, '\n')
s = z3.Solver()
s.add(intersection)
for i in all_smt(s, [p, q]):
    print(i)

который распечатывает

{Or(p, q), Not(And(q, p)), Not(And(p, q))} 

[Not(And(p, q)) = True]
[p = True, Not(And(p, q)) = True]
[p = True, q = True, Not(And(p, q)) = True]
[q = True, p = False, Not(And(p, q)) = True]

С другой стороны, следующее:

s = z3.Solver()
s.add({z3.Or(p, q), z3.Not(z3.And(q, p)), z3.Not(z3.And(p, q))})
for i in all_smt(s, [p, q]):
    print(i)

распечатывает

[q = True, p = False]
[p = True, q = False]

В принципе, они должны быть эквивалентны, однако у них разные выходные данные. В частности, последнее верно, тогда как первое производит больше моделей, чем имеется фактически удовлетворяющих моделей. Я не совсем понимаю внутренности этого кода, поэтому мне сложно сказать, в чем разница.

Просто чтобы подтвердить, что происходит что-то странное, следующее:

p, q = z3.Bools('p q')
s = z3.Solver()
s.add(list(intersection)[1])
s.add(p)
s.add(q)
if s.check():
    print(s.model())

распечатывает:

[Not(And(q, p)) = True, p = True, q = True]

---- добавление -----

Следующее сравнение может оказаться полезным:

print([x.sexpr() for x in intersection])
print([x.sexpr() for x in {z3.Or(p, q), z3.Not(z3.And(q, p)), z3.Not(z3.And(p, q))}])

который печатает:

['|Or(p, q)|', '|Not(And(q, p))|', '|Not(And(p, q))|']
['(not (and p q))', '(not (and q p))', '(or p q)']
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В SMTLib имя переменной, напечатанное между вертикальными полосами, представляет собой одну атомарную единицу. (Это позволяет именам переменных содержать произвольные символы.) А именно, обратите внимание на то, что мы получаем в решателе:

mms = tuple(enumerate_sets(solver))
intersection = reduce(lambda x, y: x & set(y), mms, set(mms[0]))
s = z3.Solver()
s.add(intersection)
print(s.sexpr())

Это печатает:

(declare-fun |Or(p, q)| () Bool)
(declare-fun |Not(And(q, p))| () Bool)
(declare-fun |Not(And(p, q))| () Bool)
(assert |Or(p, q)|)
(assert |Not(And(q, p))|)
(assert |Not(And(p, q))|)

И вы можете видеть, что имена |Or(p, q)| создаются; это не то, что вы намеревались.

Я думаю, что путаница здесь в том, что вы пытаетесь трактовать результат вызова enumerate_sets как список z3-выражений. Но это не так: это просто логические литералы, соответствующие основным терминам. Они просто использовали сам термин как имя этого термина и дали ему логическое имя. (Это объясняет наличие вертикальных полос на распечатке.)

Признаюсь, это очень сбивает с толку. Я предполагаю, что первоначальные авторы не планировали использовать результаты в качестве выражений. Чтобы делать то, что вы хотите, вам придется отслеживать эти термины по мере построения mss или вам придется «анализировать» полученные представления. Быстрое и грязное решение — просто eval вернуть их обратно:

solver = MSSSolver(hard, soft)
mms_terms = reduce(lambda x, y: x + y, list(enumerate_sets(solver)), [])
mms = [eval(t.sexpr().replace("|", "")) for t in mms_terms]
s = z3.Solver()
s.add(mms)
print(s.sexpr())

for i in all_smt(s, [p, q]):
    print(i)

Это печатает:

(declare-fun q () Bool)
(declare-fun p () Bool)
(assert (or p q))
(assert (not (and q p)))
(assert (not (and p q)))

[p = True, q = False]
[p = False, q = True]

Я думаю, это то, чего вы ожидали. (Обратите внимание, что нам пришлось отфильтровать | из представлений, чтобы убедиться, что они являются допустимыми конструкциями z3 Python; как обсуждалось выше.)

Разъяснилось, почему я думаю, что с функцией что-то не так.

whatamess 30.07.2024 18:04

Вы используете all_smt неправильно. Сначала вам нужно добавить все ограничения в s. Второй аргумент (initial_terms) — это термины, которые вы хотите «оценивать» по-другому. В типичном приложении это будет пустой список. (Итак, попробуйте s.add(hard), s.add(soft), затем all_smt(s, []).

alias 30.07.2024 18:50

Спасибо! Делая то, что вы описываете, с заданным вручную списком, я получаю: [p = True, q = False], а с выводом MSsolver я получаю [Or(p, q) = True, Not(And(q, p)) = True, Not(And(p, q)) = True]. Если я чего-то не упускаю, они разные.

whatamess 30.07.2024 22:42

Добавлен еще один источник данных, объясняющий, почему я считаю, что выходные данные MSsolver неверны.

whatamess 31.07.2024 10:00

Путаница возникает потому, что MMS не представляет собой список терминов. Это список логических значений. Смотрите мой обновленный ответ.

alias 31.07.2024 15:34

Спасибо, это очень помогло - теперь я понимаю проблему. К сожалению, стратегию «eval» сложно реализовать в моем приложении, поскольку константы определены где-то еще и могут измениться. Есть какие-нибудь подсказки, как отслеживать сроки в ммс?

whatamess 31.07.2024 17:00

Вам следует изменить add_def (github.com/Z3Prover/z3/blob/…) и отслеживать формулу и соответствующую переменную. (Возможно, в глобальной таблице, или вы можете передать таблицу себе и поддерживать ее.) Затем найдите ее в этой таблице после того, как получите весь набор.

alias 31.07.2024 17:40

Другие вопросы по теме