z3 和浮点系数的解释

z3 and interpretation of floating point coefficients

我在玩一个小的多objective整数规划问题:

在 Z3 中(使用 Python 绑定)我们可以非常优雅地说明这一点:

from z3 import *

x1,x2 = Ints('x1 x2')
z1,z2 = Reals('z1 z2')
opt = Optimize()
opt.set(priority='pareto')
opt.add(x1 >= 0, x2 >=0, x1 <= 2, x2 <= 2)
opt.add(x1 <= 2*x2)
# this version is ok: 
#    opt.add(z1 == x1 - 2*x2, z2 == -x1 + 3*x2)
# this truncates coefficients (round down to integer): 
#    opt.add(z1 == 0.5*x1 - 1.0*x2, z2 == -0.5*x1 + 1.5*x2)
# this one seems to work: 
#    opt.add(z1 == 0.5*ToReal(x1) - 1.0*ToReal(x2), z2 == -0.5*ToReal(x1) + 1.5*ToReal(x2))
opt.add(z1 == x1 - 2*x2, z2 == -x1 + 3*x2)
f1 = opt.maximize(z1)
f2 = opt.maximize(z2)
while opt.check() == sat:
    print(opt.model())

这正确解决并给出:

[x1 = 2, x2 = 1, z2 = 1, z1 = 0]
[x1 = 0, x2 = 2, z2 = 6, z1 = -4]
[x1 = 2, x2 = 2, z2 = 4, z1 = -2]
[x1 = 1, x2 = 1, z2 = 2, z1 = -1]
[x1 = 1, x2 = 2, z2 = 5, z1 = -3]

因为我的实际问题有 objectives 的浮点系数,所以我将 objectives 除以 2:

opt.add(z1 == 0.5*x1 - 1.0*x2, z2 == -0.5*x1 + 1.5*x2)

此模型应该为 x 变量给出相同的五个解。然而,当我们 运行 它时,我们看到一些错误的结果:

[x1 = 0, x2 = 0, z2 = 0, z1 = 0]
[x1 = 0, x2 = 2, z2 = 2, z1 = -2]
[x1 = 0, x2 = 1, z2 = 1, z1 = -1]

当我打印 opt 时,我可以看到哪里出了问题:

(assert (= z1 (to_real (- (* 0 x1) (* 1 x2)))))
(assert (= z2 (to_real (+ (* 0 x1) (* 1 x2)))))

系数被静默处理 运行 并转换为整数:0.5 到达 0,1.5 变为 1。

解决方法似乎是:

opt.add(z1 == 0.5*ToReal(x1) - 1.0*ToReal(x2), z2 == -0.5*ToReal(x1) + 1.5*ToReal(x2))

这会将浮点系数转换为其有理数:

(assert (= z1 (- (* (/ 1.0 2.0) (to_real x1)) (* 1.0 (to_real x2)))))
(assert (= z2 (+ (* (- (/ 1.0 2.0)) (to_real x1)) (* (/ 3.0 2.0) (to_real x2)))))

现在 0.5 变成 (/ 1.0 2.0) 而 1.5 表示为 (/ 3.0 2.0)

我的问题是:

  1. 这是运行阳离子"as designed"吗?
  2. 我的变通办法是解决这个问题的正确方法吗?或者我应该完全避免浮点系数吗?
  3. 打印的有理数 (/ 1.0 2.0) 似乎暗示仍然涉及浮点数。这真的是(/ 1 2)吗? (我假设这些实际上是 bigints)。

我认为你基本上回答了你自己的问题。底线是 Python 是一种无类型语言,因此当您将不同类型的操作数与算术运算符混合搭配时,您将受到库的支配,因为它会为您 "match" 这些类型,它在这里做错事也就不足为奇了。在 SMT-Lib2 或任何其他更强类型的绑定中,您会收到类型错误。

切勿在算术中混用类型,并且始终是显式的。或者,更好的是,使用在其类型系统中强制执行此操作的接口,而不是隐式强制常量。所以,简短的回答是,是的;这是设计使然,但不是因为任何深层原因,而是 Python 绑定的行为方式。

这是一个更简单的演示:

>>> from z3 import *
>>> x = Int('x')
>>> y = Real('y')
>>> x*2.5
x*2
>>> y*2.5
y*5/2

因此,看起来一旦您声明了变量,与它们交互的常量就会自动强制转换为该变量的类型。但我根本不会指望这一点:当你在非类型设置中工作时,最好始终明确。