如何用 z3py 解决这个 exclusion/inclusion 问题?

How to solve this exclusion/inclusion problem with z3py?

感谢社区的帮助,我想出了这个代码:

    from z3 import *
    
    Color, (Red, Green, Blue) = EnumSort('Color', ('Red', 'Green', 'Blue'))
    Size,  (Big, Medium, Small) = EnumSort('Size',  ('Big', 'Medium', 'Small'))
    
    h1c, h2c, h3c = Consts('h1c h2c h3c', Color)
    h1s, h2s, h3s = Consts('h1s h2s h3s', Size)
    
    s = Solver()
    
    myvars = [h1c, h2c, h3c, h1s, h2s, h3s]
    
    s.add(Distinct([h1c, h2c, h3c]))
    s.add(Distinct([h1s, h2s, h3s]))
    
    s.add(h3s == Medium)
    s.add(h3c == Red)
    
    res = s.check()
    
    n = 1
    while (res == sat):
      print("%d. " % n),
      m = s.model()
      block = []
      for var in myvars:
          v = m.evaluate(var, model_completion=True)
          print("%s = %-5s " % (var, v)),
          block.append(var != v)
      s.add(Or(block))
      n = n + 1
      res = s.check()

这解决了只有一个房子可以是中等大小和红色的问题。其他组合保持变化。

但是我还想要一个条件,即House why is,例如Green is Small。最初不指向特定的房子。这将排除所有未组合绿色或小型的变体(绿色不能是中型,小型不能是红色等)......但也要保持不同,例如,只有一个房子可以是绿色和小型.所以稍后如果我说 house 1 是 Green 或 Small,那么 house 1 就是这个变体,没有其他房子(变体)可以是 Green 或 Small。

Example after 1st condition (Green is Small):

h1 = Green + Small
h2 = Green + Small
h3 = Green + Small
h1 = Red + Medium
h1 = Red + Big
h2 = Red + Medium
h2 = Red + Big
h3 = Red + Medium
h3 = Red + Big
h1 = Blue + Medium
h1 = Blue + Big
h2 = Blue + Medium
h2 = Blue + Big
h3 = Blue + Medium
h3 = Blue + Big ( I might missed something)

Example after 2nd condition (House 1 is Small/Green):

h1 = Green + Small
h2 = Red + Medium
h2 = Red + Big
h3 = Red + Medium
h3 = Red + Big
h2 = Blue + Medium
h2 = Blue + Big
h3 = Blue + Medium
h3 = Blue + Big ( I might missed something)

我一直在研究 Functionschildren 变量,但看不出如何比较堆栈中的任何变量。我认为代码需要完全重组?

您需要以某种方式添加条件:

s.add(Or(And(h1c == Green, h1s == Small),
         And(h2c == Green, h2s == Small),
         And(h3c == Green, h3s == Small)))

一切都可以用数组写得更灵活一点:

from z3 import EnumSort, Consts, Solver, Distinct, Or, And, sat

Color, (Red, Green, Blue) = EnumSort('Color', ('Red', 'Green', 'Blue'))
Size, (Big, Medium, Small) = EnumSort('Size', ('Big', 'Medium', 'Small'))
hc = Consts('h1c h2c h3c', Color)
hs = Consts('h1s h2s h3s', Size)

s = Solver()
s.add(Distinct(hc))
s.add(Distinct(hs))

s.add(Or([And(hci == Green, hsi == Small) for hci, hsi in zip(hc, hs)]))

res = s.check()
n = 1
while (res == sat):
    print(f"{n:-2d}.", end=" ")
    m = s.model()
    block = []
    for i, (hci, hsi) in enumerate (zip(hc, hs), start=1):
        hci_v = m.evaluate(hci, model_completion=True)
        hsi_v = m.evaluate(hsi, model_completion=True)
        print(f'{f"h{i}:{hci_v}+{hsi_v}":<15}', end="")
        block.append(hci != hci_v)
        block.append(hsi != hsi_v)
    print()
    s.add(Or(block))
    n += 1
    res = s.check()

结果:

 1. h1:Blue+Big    h2:Green+Small h3:Red+Medium  
 2. h1:Green+Small h2:Red+Medium  h3:Blue+Big    
 3. h1:Red+Medium  h2:Blue+Big    h3:Green+Small 
 4. h1:Red+Big     h2:Blue+Medium h3:Green+Small 
 5. h1:Blue+Big    h2:Red+Medium  h3:Green+Small 
 6. h1:Blue+Medium h2:Red+Big     h3:Green+Small 
 7. h1:Blue+Medium h2:Green+Small h3:Red+Big     
 8. h1:Red+Big     h2:Green+Small h3:Blue+Medium 
 9. h1:Red+Medium  h2:Green+Small h3:Blue+Big    
10. h1:Green+Small h2:Blue+Medium h3:Red+Big     
11. h1:Green+Small h2:Blue+Big    h3:Red+Medium  
12. h1:Green+Small h2:Red+Big     h3:Blue+Medium 

PS:简化小房子是绿色的条件的一种方法是改变表示法。可以代表每种颜色和每种尺寸的门牌号,而不是表示每个房屋的颜色和尺寸。这将需要附加条件,即每种颜色应为 1,2 或 3。尺寸的条件相同:

from z3 import Ints, Solver, Distinct, Or, And, sat

Red, Green, Blue = Ints('Red Green Blue')
Big, Medium, Small = Ints('Big Medium Small')
colors = [Red, Green, Blue]
sizes = [Big, Medium, Small]

s = Solver()
s.add(Distinct(colors))
s.add(Distinct(sizes))
s.add(And([Or([color == i for i in (1, 2, 3)]) for color in colors]))
s.add(And([Or([size == i for i in (1, 2, 3)]) for size in sizes]))

s.add(Green == Small)

res = s.check()
n = 1
while (res == sat):
    print(f"{n:-2d}.", end=" ")
    m = s.model()
    block = []
    for x in colors + sizes:
        x_v = m.evaluate(x, model_completion=True).as_long()
        print(f"{x}:h{x_v}", end=" ")
        block.append(x != x_v)
    print()
    s.add(Or(block))
    n += 1
    res = s.check()

结果:

 1. Red:h3 Green:h2 Blue:h1 Big:h3 Medium:h1 Small:h2 
 2. Red:h2 Green:h3 Blue:h1 Big:h2 Medium:h1 Small:h3 
 3. Red:h2 Green:h3 Blue:h1 Big:h1 Medium:h2 Small:h3 
 4. Red:h1 Green:h2 Blue:h3 Big:h1 Medium:h3 Small:h2 
 5. Red:h3 Green:h2 Blue:h1 Big:h1 Medium:h3 Small:h2 
 6. Red:h1 Green:h3 Blue:h2 Big:h1 Medium:h2 Small:h3 
 7. Red:h3 Green:h1 Blue:h2 Big:h3 Medium:h2 Small:h1 
 8. Red:h3 Green:h1 Blue:h2 Big:h2 Medium:h3 Small:h1 
 9. Red:h1 Green:h3 Blue:h2 Big:h2 Medium:h1 Small:h3 
10. Red:h1 Green:h2 Blue:h3 Big:h3 Medium:h1 Small:h2 
11. Red:h2 Green:h1 Blue:h3 Big:h2 Medium:h3 Small:h1 
12. Red:h2 Green:h1 Blue:h3 Big:h3 Medium:h2 Small:h1 

如有必要,可以将输出重新格式化为与第一个解决方案相同的格式。一种解决方案是“更少的解决方法”还是“更清晰”或“更易于维护”似乎是一个非常主观的问题。将问题转换为 SAT/SMT 求解器的格式总是有点棘手。

@JohanC 的回答很好,但我同意 OP 的观点,即如果您不以系统的方式处理这些限制,这些限制可能会真正失控并且无法管理。我发现创建字典和自己的抽象真的很有帮助。请注意,这并不是 z3/z3py 特定的,但通常用于编程。例如,下面是我将如何对您的问题进行编码:

from z3 import *

Color, (Red, Green, Blue)   = EnumSort('Color', ('Red', 'Green', 'Blue'))
Size,  (Big, Medium, Small) = EnumSort('Size',  ('Big', 'Medium', 'Small'))

# Create a house and store properties in a dictionary
def mkHouse(name):
    return { 'name' : name
           , 'color': Const(name + "_color", Color)
           , 'size' : Const(name + "_size",  Size)
           }

allHouses = [mkHouse(n) for n in ["house1", "house2", "house3"]]

s = Solver ()

# Assert sizes and colors are different
s.add(Distinct([h['color'] for h in allHouses]))
s.add(Distinct([h['size']  for h in allHouses]))

def forallHouses(pred):
    cond = True
    for house in allHouses:
        cond = And(cond, pred(house))
    s.add(cond)

# Assert that Green house is small. Note the implication.
forallHouses(lambda h: Implies(h['color'] == Green, h['size'] == Small))

# Assert that If a house is Red, then it cannot be Medium
forallHouses(lambda h: Implies(h['color'] == Red, h['size'] != Medium))

# Collect the solutions:
res = s.check()

n = 1
while (res == sat):
  print("Solution %d: " % n)
  m = s.model()
  block = []
  for house in allHouses:
      hcolor = m.evaluate(house['color'], model_completion=True)
      hsize  = m.evaluate(house['size'],  model_completion=True)
      print("  %-5s = %-5s %-5s" % (house['name'], hcolor, hsize))
      block.append(Or(house['color'] != hcolor, house['size'] != hsize))
  s.add(Or(block))
  n = n + 1
  res = s.check()

注意使用字典来记录房屋的名称、大小和颜色。您可以根据需要添加新属性,并且所有内容都保留在本地以便以后轻松操作和提取。特别是,函数 forallHouses 捕捉到了您想表达的内容的本质:您想对每个单独的房子说些什么,它通过 lambda-function.

捕捉到这一点

在上面的例子中,我断言 Green 房子是 SmallRed 房子是 而不是 Medium。 (这意味着 Red 房子一定很大,这是 z3 为我们发现的。)当我 运行 它时,我得到:

Solution 1:
  house1 = Blue  Medium
  house2 = Green Small
  house3 = Red   Big
Solution 2:
  house1 = Green Small
  house2 = Red   Big
  house3 = Blue  Medium
Solution 3:
  house1 = Green Small
  house2 = Blue  Medium
  house3 = Red   Big
Solution 4:
  house1 = Red   Big
  house2 = Blue  Medium
  house3 = Green Small
Solution 5:
  house1 = Red   Big
  house2 = Green Small
  house3 = Blue  Medium
Solution 6:
  house1 = Blue  Medium
  house2 = Red   Big
  house3 = Green Small

我认为这符合您要实现的目标。希望您可以从这个骨架开始,将它变成可以在建模更复杂的约束时使用的东西。