0
votes

Scenario

In the world of mathematical optimization, the need to model inequality g_k(...) constraints arises. g_k(...) can sometimes be a function call to an external program that is, for all intents and purposes, a blackbox. Simply finding satisfactory answers can be beneficial for certain engineering analysis.

Example

An example application of the above scenario for Reals, but could also be Ints or Booleans:

min f(x,y)
g1(x,y) <= 25
g2(x,y) >= 7.7
x,y ε Real
x >= 0
x <= 50.0
y >= 0
y <= 5.0

g1 and g2 are Python functions that call an external program. The functions return a real number. Following this Z3 format to find a model that simply satisfies the constraints would be represented as:

from z3 import *
from ExternalCodes import Code1, Code2 #For example, these could be Python wrappers to C++ codes

def g_1(x, y):
    return Code1(x, y) #isinstance(Code1(x,y), float) == True

def g_2(x, y):
    return Code2(x, y) #isinstance(Code2(x,y), float) == True

s = Solver()

x, y = Reals('x y')
s.add(g_1(x, y) <= 25.0)
s.add(g_2(x, y) >= 7.7)
s.add(x <= 0)
s.add(50.0 <= x)
s.add(y <= 0)
s.add(5.0 <= y)

m = s.model()
print(m)

Questions

  1. I understand that the type returned by Code1 and Code2 need to be Z3 datatypes. How do I convert Python types to Z3 types like mentioned in the 1st comment
  2. How is Z3 used to find a sat model when constraints may need to be evaluated in external code, i.e. declare functions? I understand it may be inefficient, I would loose heuristics, etc., because it is undecidable, but for certain engineering applications, enumerating a sat solution, if it exists, is more advantageous than employing an optimizer from the get-go.

Relevant answers

  • z3python: using math library

    -Not quite the same application. I'm curious if, 4 years later, is this answer still the case, but this is not my question.

  • Can Z3 call an externally defined function?

    -In essence, the same question. No Z3Py answer, and unfortunately, the Rise4fun link is broken. I also cannot find the mentioned F#/.NET example in the Github repo

1

1 Answers

1
votes

You're looking for uninterpreted functions.

Search for the term "uninterpreted functions" in http://ericpony.github.io/z3py-tutorial/advanced-examples.htm

Your question seems to make some assumptions about how SMT solvers can be used; which don't quite reflect the current state-of-the-art. A great resource to read about the use of SMT solvers is: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/nbjorner-smt-application-chapter.pdf It would be well worth your time to go over it to see how it applies in practice.

Some general comments:

Z3py

Z3py is not a system for reasoning about Python programs. Z3py is a collection of libraries so you can script Z3 in a much more clearer and easier way. There are many such bindings from many languages: C/C++/Java/Haskell/Scala, you name it. The advantage of Z3py is that it is easier to learn and use. But you shouldn't think of it as a system to reason about Python itself. It's merely a way of scripting Z3 in a lightweight form.

Solver logics

SMT solvers essentially work on decidable fragments of (mostly quantifier-free) many-sorted logics of various theories. You can find these in detail at: http://smtlib.cs.uiowa.edu/logics.shtml

SMTLib

Most solvers accept input in the so-called SMT-Lib format, detailed here: http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf

Note that any "binding" at the C/Python/Java etc. level is merely a programmers convenience. While many solvers also provide extended features, the SMTLib language is what you should think of. In your particular case, uninterpreted functions are well described in the above document.

Types

SMTLib understands a set of types: Integers, Reals, Bit-vectors (machine integers), Floating-point, etc. It also allows composite types such as algebraic data types, which can even be recursive. (Though solver support varies.) You have to "map" your external function types to these types: Hopefully, there's something close enough. If not, feel free to ask specific questions about types you are interested in.

"Importing" functions

It is impossible to import functions written in other languages (Python/C/C++ etc.) into SMTLib and reason about them. There's no mechanism for doing so, neither there ever will be. This isn't the goal of an SMT solver. If you want to reason about programs written in a particular language, then you should look for tools that are specifically designed to work on those languages. (For instance Dafny for general imperative programs, Klee/CBMC for C programs, LiquidHaskell for Haskell programs, etc.) These tools vary in their capabilities, and what they allow you to specify and prove. Note that these tools themselves can use SMT-solvers underneath to accomplish their tasks---and they often do, not the other way around.

Sticking to SMTLib

If there are no other tools available (and unfortunately this is likely the case for most languages out there, especially legacy ones), you're essentially stuck with whatever the SMTLib language provides. In your case, the best method for modeling such "external" functions using SMTLib is to use uninterpreted functions, together with axioms. In general, you need axioms to restrict the behavior of the uninterpreted functions themselves, to model your external functions. On the flip side, if the axioms are quantified (which in general they will be), the solver might return unknown.

Summary

While SMT solvers are great tools, you should always keep in mind that the language they operate on is SMTLib, not Python/C or anything else. Those bindings are simply means of accessing the solver as you might incorporate it in a bigger project. I hope that clears the expected use case.

Asking specific questions about what you've tried and what you're trying to achieve (code samples) is the best way to get mileage out of this forum!