如何让 z3 返回多个 unsat 核心,多个令人满意的分配

spg*_*pga 2 smt z3 sat

我正在研究研究工具的一个组成部分;我有兴趣检索(对于 QF_LRA)

- 多个(最小或其他)UNSAT 核心和
- 多个 SAT 作业

我已经查看了论坛上有关此主题的早期讨论,例如, 如何在逻辑 QF_LRA 上使用 z3 时获得不同的未饱和内核

他们指的是 z3 Python 教程,例如http://rise4fun.com/Z3Py/tutorial/musmss

现在似乎处于离线状态。我已经尝试了 github 等的其他建议来找到提到的教程,但没有运气。

我正在使用 z3 Java API;但很高兴转向替代品。

Nik*_*ner 5

这是教程。您可以在 Mark Liffiton 的网页上找到有关 MARCO 的更多信息。

最小不可满足核心和最大满足子集的枚举

本教程说明了如何使用 Z3 提取所有最小不可满足核心以及所有最大满足子集。

起源

我们接下来描述的算法代表了 Liffiton 和 Malik 以及 Previti 和 Marques-Silva 独立提出的核心提取程序的本质:

枚举不可行性:快速查找多个 MUS 在Proc. 中
标记 H. Liffiton 和 Ammar Malik 10约束编程中人工智能 (AI) 和运筹学 (OR) 技术集成国际会议(CPAIOR-2013),160-175,2013 年 5 月。

部分 MUS 枚举
Alessandro Previti、Joao Marques-Silva in Proc。AAAI-2013 2013 年7 月

Z3py 特点

此实现不包含任何调整。它由 Mark Liffiton 贡献,它是他的 Marco Polo 网站上可用版本之一的简化​​版。eMUS 的代码也可用。该示例说明了 Z3 基于 Python 的 API 的以下功能:

  1. 使用假设来跟踪不可满足的核心。
  2. 使用多个求解器并在它们之间传递约束。
  3. 从 Python 调用基于 C 的 API。并非所有 API 函数都受 Python 包装器支持。此示例显示如何获取 AST 的唯一整数标识符,该标识符可用作哈希表中的键。

算法思想

该算法的主要思想是维护两个逻辑上下文并在它们之间交换信息:

  1. MapSolver用于枚举套从句的已经不是现有的不可满足核心的集,并且还不是满足最大分配的一个子集。该MapSolver使用每一个独特的原子谓词条款,所以它列举套原子谓词。对于每个最小不可满足核心,例如,由谓词 p 1 , p 2 , p 5 表示MapSolver包含子句¬ p 1 ∨ ¬ p 2 ∨ ¬ p 5。对于每个最大可满足子集,例如,由谓词 p 2 , p 3 表示, p 5MapSolver包含一个子句,对应于不在最大可满足子集p 1 ∨ p 4 ∨ p 6 中的所有文字的析取
  2. 所述SubsetSolver含有(与唯一指示器原子存在的否定条款)的一组软条款。所述MapSolver其馈送给一组子句(指示灯原子)。回想一下,这些还不是现有最小不可满足核心的超集,或者最大满足分配的子集。如果断言这些原子使SubsetSolver上下文不可行,那么它会找到对应于这些原子的最小不可满足子集。如果断言原子与SubsetSolver一致,那么它最大程度地扩展这组原子到一个令人满意的集合。
from Z3 import *

def main():
    x, y = Reals('x y')
    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)]
    csolver = SubsetSolver(constraints)
    msolver = MapSolver(n=csolver.n)
    for orig, lits in enumerate_sets(csolver, msolver):
        output = "%s %s" % (orig, lits)
        print(output)



def get_id(x):
    return Z3_get_ast_id(x.ctx.ref(),x.as_ast())

def MkOr(clause):
    if clause == []:
       return False
    else:
       return Or(clause)
Run Code Online (Sandbox Code Playgroud)

子集求解器:

class SubsetSolver:
   constraints = []
   n = 0
   s = Solver()
   varcache = {}
   idcache = {}

   def __init__(self, constraints):
       self.constraints = constraints
       self.n = len(constraints)
       for i in range(self.n):
           self.s.add(Implies(self.c_var(i), constraints[i]))

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

   def check_subset(self, seed):
       assumptions = self.to_c_lits(seed)
       return (self.s.check(assumptions) == sat)

   def to_c_lits(self, seed):
       return [self.c_var(i) for i in seed]

   def complement(self, aset):
       return set(range(self.n)).difference(aset)

   def seed_from_core(self):
       core = self.s.unsat_core()
       return [self.idcache[get_id(x)] for x in core]

   def shrink(self, seed):       
       current = set(seed)
       for i in seed:
          if i not in current:
             continue
          current.remove(i)
          if not self.check_subset(current):
             current = set(self.seed_from_core())
          else:
             current.add(i)
       return current

   def grow(self, seed):
       current = seed
       for i in self.complement(current):
           current.append(i)
           if not self.check_subset(current):
              current.pop()
       return current
Run Code Online (Sandbox Code Playgroud)

地图解算器:

class MapSolver:
    def __init__(self, n):
        """Initialization.
           Args:
            n: The number of constraints to map.
        """
       self.solver = Solver()
       self.n = n
       self.all_n = set(range(n))  # used in complement fairly frequently

   def next_seed(self):
       """Get the seed from the current model, if there is one. 
            Returns:
            A seed as an array of 0-based constraint indexes.
       """
       if self.solver.check() == unsat:
            return None
       seed = self.all_n.copy()  # default to all True for "high bias"
       model = self.solver.model()
       for x in model:
           if is_false(model[x]):
              seed.remove(int(x.name()))
       return list(seed)

   def complement(self, aset):
       """Return the complement of a given set w.r.t. the set of mapped constraints."""
       return self.all_n.difference(aset)

   def block_down(self, frompoint):
       """Block down from a given set."""
       comp = self.complement(frompoint)
       self.solver.add( MkOr( [Bool(str(i)) for i in comp] ) )

   def block_up(self, frompoint):
       """Block up from a given set."""
       self.solver.add( MkOr( [Not(Bool(str(i))) for i in frompoint] ) )



  def enumerate_sets(csolver, map):
      """Basic MUS/MCS enumeration, as a simple example."""
      while True:
        seed = map.next_seed()
        if seed is None:
           return
        if csolver.check_subset(seed):
           MSS = csolver.grow(seed)
           yield ("MSS", csolver.to_c_lits(MSS))
           map.block_down(MSS)
        else:
           MUS = csolver.shrink(seed)
           yield ("MUS", csolver.to_c_lits(MUS))
           map.block_up(MUS)

   main()
Run Code Online (Sandbox Code Playgroud)