z3 and interpretation of floating point coefficients

六眼飞鱼酱① 提交于 2020-01-05 05:44:12

问题


I was playing with a small multi-objective integer programming problem:

In Z3 (using the Python bindings) we can state this very elegantly:

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())

This solves correctly and gives:

[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]

As my real problem has floating point coefficients for the objectives, I divided the objectives by 2:

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

This model should give the same five solutions for the x variables. However, when we run it, we see some wrong results:

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

When I print opt I can see where things go wrong:

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

The coefficients are silently truncated and converted to integers: 0.5 arrived as 0 and 1.5 became 1.

A workaround seems to be:

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

This converts the floating point coefficient to their rational equivalents:

(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)))))

Now 0.5 becomes (/ 1.0 2.0) and 1.5 is represented by (/ 3.0 2.0).

My questions are:

  1. Is this truncation "as designed"?
  2. Is my workaround the correct way to go about this? Or should I avoid floating point coefficients altogether?
  3. The printed rational number (/ 1.0 2.0) seems to hint there are still floating point numbers involved. Is this really (/ 1 2)? (I assume these are actually bigints).

回答1:


I think you essentially answered your own question. Bottom line is that Python is an untyped language, so when you mix-and-match different typed operands to arithmetic operators, you are at the mercy of the library as it will "match" these types for you, and it is not surprising that it does the wrong thing here. In SMT-Lib2, or any other more strongly-typed binding, you'd instead get a type error.

Never mix types in arithmetic, and always be explicit. Or, better yet, use an interface that enforces this in its type system, instead of implicitly coercing constants. So, short answer is, yes; this is by design, but not because of any deep reason, but rather how the Python bindings behave.

Here's a simpler demo:

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

So, it appears that once you have a declared variable, then the constants that interact with them automatically coerce to the type of that variable. But I wouldn't count on that at all: It's best to be always explicit when you are working in an untyped setting.



来源:https://stackoverflow.com/questions/46721639/z3-and-interpretation-of-floating-point-coefficients

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!