我正在玩一个小的多目标整数规划问题:
在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]
由于我的真实问题有目标的浮点系数,我将目标除以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.0 2.0)
似乎暗示仍有浮点数。这真的是(/ 1 2)
吗? (我认为这些实际上是重要的)。 答案 0 :(得分:1)
我认为你基本上回答了自己的问题。底线是Python是一种无类型语言,因此当您将不同类型的操作数混合并匹配到算术运算符时,您将受到库的支配,因为它将为您“匹配”这些类型,并且这并不奇怪它在这里做错了。在SMT-Lib2或任何其他更强类型的绑定中,您将获得类型错误。
永远不要在算术中混合类型,并且始终是显式的。或者,更好的是,使用一个在其类型系统中强制执行此操作的接口,而不是隐式强制执行常量。所以,简短的回答是,是的;这是设计上的,但不是因为任何深层原因,而是Python绑定的行为方式。
这是一个更简单的演示:
>>> from z3 import *
>>> x = Int('x')
>>> y = Real('y')
>>> x*2.5
x*2
>>> y*2.5
y*5/2
因此,看起来一旦你有一个声明的变量,那么与它们交互的常量会自动强制转换为该变量的类型。但我根本不会指望这一点:当你在一个无类型的环境中工作时,最好总是明确的。