├── README.md ├── advanced-examples.htm ├── examples ├── advanced.htm ├── arith.1.py ├── arith.2.py ├── arith.3.py ├── arith.4.py ├── arith.5.py ├── array.1.py ├── array.2.py ├── array.3.py ├── array.4.py ├── array.5.py ├── array.py ├── bintree.py ├── bit.1.py ├── bit.2.py ├── bitvec.1.py ├── bitvec.2.py ├── bitvec.3.py ├── circuit.py ├── comment.py ├── datatype.1.py ├── datatype.2.py ├── datatype.3.py ├── datatype.4.py ├── datatype.5.py ├── euf1.py ├── euf2.py ├── euf3.py ├── expr.1.py ├── expr.2.py ├── expr.3.py ├── expr.4.py ├── expr.5.py ├── expr.6.py ├── expr.7.py ├── expr.8.py ├── fixedpoint.1.py ├── fixedpoint.10.py ├── fixedpoint.2.py ├── fixedpoint.3.py ├── fixedpoint.4.py ├── fixedpoint.5.py ├── fixedpoint.8.py ├── fixedpoint.9.py ├── fixedpoints.htm ├── format.py ├── guide.htm ├── install.1.py ├── install.2.py ├── k.1.py ├── k.2.py ├── list.1.py ├── list.2.py ├── list1.py ├── list2.py ├── msolver.1.py ├── nqueens.py ├── printer.py ├── probe.1.py ├── probe.1.ty ├── probe.2.py ├── probe.3.py ├── puzzle.1.py ├── puzzle.2.py ├── puzzle.3.py ├── quant.1.py ├── quant.2.py ├── quant.3.py ├── quant.4.py ├── quant.5.py ├── quant.6.py ├── quant.7.py ├── quant.8.py ├── queens.png ├── strategies.htm ├── sudoku.png ├── sudoku.py ├── tactic.1.py ├── tactic.10.py ├── tactic.2.py ├── tactic.3.py ├── tactic.4.py ├── tactic.5.py ├── tactic.6.py ├── tactic.7.py ├── tactic.8.py ├── tactic.9.py ├── tree.py ├── uninterp.1.py ├── unsatcore.1.py ├── z3py.1.py ├── z3py.10.py ├── z3py.11.py ├── z3py.12.py ├── z3py.13.py ├── z3py.14.py ├── z3py.15.py ├── z3py.16.py ├── z3py.2.py ├── z3py.3.py ├── z3py.4.py ├── z3py.5.py ├── z3py.6.py ├── z3py.6a.py ├── z3py.6aa.py ├── z3py.7.py ├── z3py.8.py └── z3py.9.py ├── fixpoint-examples.htm ├── guide-examples.htm ├── index.html ├── installation-guide.pdf ├── strategies-examples.htm └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # Z3py tutorial 2 | 3 | This project archives files in the official [Z3py website](http://rise4fun.com/z3py), which was shut down due to security issues. The official [Z3 website](http://z3.codeplex.com/) however is still online and [active](https://github.com/Z3Prover/z3). 4 | 5 | * [Introduction](http://ericpony.github.io/z3py-tutorial/guide-examples.htm) 6 | * [Strategies](http://ericpony.github.io/z3py-tutorial/strategies-examples.htm) 7 | * [Fixed-point computation](http://ericpony.github.io/z3py-tutorial/fixpoint-examples.htm) 8 | * [Advanced topics](http://ericpony.github.io/z3py-tutorial/advanced-examples.htm) 9 | * [API documentation](http://z3prover.github.io/api/html/namespacez3py.html) (also available in [pydoc format](http://z3prover.github.io/api/html/z3.html)) 10 | 11 | The source code of Z3Py is available in the Z3 distribution, where you can also find APIs in C, .NET, OCaml, and Java. Other cool front-ends for Z3 includes [ScalaZ3](https://github.com/epfl-lara/ScalaZ3) and [SBV](https://github.com/LeventErkok/sbv). 12 | 13 | ## Related projects 14 | 15 | * [A symbolic execution engine using Z3py](https://github.com/thomasjball/PyExZ3) 16 | * [Z3py with set comprehensions](https://github.com/sllam/pysetcomp) 17 | * [More Z3py examples](https://github.com/0vercl0k/z3-playground) 18 | -------------------------------------------------------------------------------- /examples/advanced.htm: -------------------------------------------------------------------------------- 1 | 2 |
3 |11 | Please send feedback, comments and/or corrections to leonardo@microsoft.com. 12 | Your comments are very valuable. 13 |
14 | 15 |In Z3, expressions, sorts and declarations are called ASTs. 18 | ASTs are directed acyclic graphs. Every expression has a sort (aka type). 19 | The method sort() retrieves the sort of an expression. 20 |
21 | 22 | 23 | 24 |The function eq(n1, n2) returns True if n1 25 | and n2 are the same AST. This is a structural test. 26 |
27 | 28 | 29 | 30 |The method hash() returns a hashcode for an AST node. 31 | If eq(n1, n2) returns True, then n1.hash() 32 | is equal to n2.hash(). 33 |
34 | 35 | 36 | 37 |Z3 expressions can be divided in three basic groups: applications, 38 | quantifiers and bounded/free variables. 39 | Applications are all you need if your problems do not contain 40 | universal/existential quantifiers. Although we say Int('x') is an 41 | integer "variable", it is technically an integer constant, and internally 42 | is represented as a function application with 0 arguments. 43 | Every application is associated with a declaration and contains 44 | 0 or more arguments. The method decl() returns 45 | the declaration associated with an application. The method num_args() 46 | returns the number of arguments of an application, and arg(i) one of the arguments. 47 | The function is_expr(n) returns True 48 | if n is an expression. Similarly is_app(n) (is_func_decl(n)) 49 | returns True if n is an application (declaration). 50 |
51 | 52 | 53 | 54 |Declarations have names, they are retrieved using the method name(). 55 | A (function) declaration has an arity, a domain and range sorts. 56 |
57 | 58 | 59 | 60 |61 | The built-in declarations are identified using their kind. The kind 62 | is retrieved using the method kind(). The complete list of built-in declarations 63 | can be found in the file z3consts.py (z3_api.h) in the Z3 distribution. 64 |
65 | 66 | 67 | 68 |69 | The following example demonstrates how to substitute sub-expressions in Z3 expressions. 70 |
71 | 72 | 73 | 74 |75 | The function Const(name, sort) declares a constant (aka variable) of the given sort. 76 | For example, the functions Int(name) and Real(name) are shorthands for 77 | Const(name, IntSort()) and Const(name, RealSort()). 78 |
79 | 80 | 81 | 82 |As part of formulating a programme of a mathematical theory of computation 85 | McCarthy proposed a basic theory of arrays as characterized by 86 | the select-store axioms. The expression Select(a, i) returns 87 | the value stored at position i of the array a; 88 | and Store(a, i, v) returns a new array identical to a, 89 | but on position i it contains the value v. 90 | In Z3Py, we can also write Select(a, i) as a[i]. 91 |
92 | 93 | 94 | 95 |96 | By default, Z3 assumes that arrays are extensional over select. 97 | In other words, Z3 also enforces that if two arrays agree on all positions, 98 | then the arrays are equal. 99 |
100 | 101 |102 | Z3 also contains various extensions 103 | for operations on arrays that remain decidable and amenable 104 | to efficient saturation procedures (here efficient means, 105 | with an NP-complete satisfiability complexity). 106 | We describe these extensions in the following using a collection of examples. 107 | Additional background on these extensions is available in the 108 | paper Generalized and Efficient Array Decision Procedures. 109 |
110 | 111 |112 | Arrays in Z3 are used to model unbounded or very large arrays. 113 | Arrays should not be used to model small finite collections of values. 114 | It is usually much more efficient to create different variables using list comprehensions. 115 |
116 | 117 | 118 | 119 |Let us first check a basic property of arrays. 123 | Suppose A is an array of integers, then the constraints 124 | A[x] == x, Store(A, x, y) == A 125 | are satisfiable for an array that contains an index x that maps to x, 126 | and when x == y. 127 | We can solve these constraints. 128 |
129 | 130 | 131 | 132 |The interpretation/solution for array variables is very similar to the one used for functions.
133 | 134 |The problem becomes unsatisfiable/infeasible if we add the constraint x != y. 135 | 136 |
137 | 138 |141 | The array that maps all indices to some fixed value can be specified in Z3Py using the 142 | K(s, v) construct where s is a sort/type and v is an expression. 143 | K(s, v) returns a array that maps any value of s into v. 144 | The following example defines a constant array containing only ones. 145 |
146 | 147 | 148 | 149 |Algebraic datatypes, known from programming languages such as ML, 152 | offer a convenient way for specifying common data structures. Records 153 | and tuples are special cases of algebraic datatypes, and so are 154 | scalars (enumeration types). But algebraic datatypes are more 155 | general. They can be used to specify finite lists, trees and other 156 | recursive structures.
157 | 158 |159 | The following example demonstrates how to declare a List in Z3Py. It is 160 | more verbose than using the SMT 2.0 front-end, but much simpler than using 161 | the Z3 C API. It consists of two phases. 162 | First, we have to declare the new datatype, its constructors and accessors. 163 | The function Datatype('List') declares a "placeholder" that will 164 | contain the constructors and accessors declarations. The method 165 | declare(cname, (aname, sort)+) declares a constructor named 166 | cname with the given accessors. Each accessor has an associated sort 167 | or a reference to the datatypes being declared. 168 | For example, declare('cons', ('car', IntSort()), ('cdr', List)) 169 | declares the constructor named cons that builds a new List 170 | using an integer and a List. It also declares the accessors car and 171 | cdr. The accessor car extracts the integer of a cons 172 | cell, and cdr the list of a cons cell. 173 | After all constructors were declared, we use the method create() to 174 | create the actual datatype in Z3. Z3Py makes the new Z3 declarations and constants 175 | available as slots of the new object. 176 |
177 | 178 | 179 | 180 |181 | The following example demonstrates how to define a Python function that 182 | given a sort creates a list of the given sort. 183 |
184 | 185 | 186 | 187 |The example above demonstrates that Z3 supports operator overloading. 188 | There are several functions named cons, but they are different since they receive and/or 189 | return values of different sorts. 190 | Note that it is not necessary to use a different sort name for each instance of the sort 191 | list. That is, the expression 'List_of_%s' % sort.name() is not necessary, we 192 | use it just to provide more meaningful names. 193 |
194 | 195 |196 | As described above enumeration types are a special case of algebraic datatypes. 197 | The following example declares an enumeration type consisting of three values: 198 | red, green and blue. 199 |
200 | 201 | 202 | 203 |204 | Z3Py also provides the following shorthand for declaring enumeration sorts. 205 |
206 | 207 | 208 | 209 |210 | Mutually recursive datatypes can also be declared. The only difference is that we use 211 | the function CreateDatatypes instead of the method create() to create 212 | the mutually recursive datatypes. 213 |
214 | 215 | 216 | 217 |220 | Function and constant symbols in pure first-order logic are uninterpreted or free, 221 | which means that no a priori interpretation is attached. 222 | This is in contrast to arithmetic operators such as + and - 223 | that have a fixed standard interpretation. 224 | Uninterpreted functions and constants are maximally flexible; 225 | they allow any interpretation that is consistent with the constraints over the function or constant. 226 |
227 | 228 |229 | To illustrate uninterpreted functions and constants let us introduce an (uninterpreted) sort A, 230 | and the constants x, y ranging over A. 231 | Finally let f be an uninterpreted function that takes one 232 | argument of sort A and results in a value of sort A. 233 | The example illustrates how one can force an interpretation where f applied twice to x results in x again, 234 | but f applied once to x is different from x. 235 |
236 | 237 | 238 | 239 |240 | The resulting model introduces abstract values for the elements in A, 241 | because the sort A is uninterpreted. The interpretation for f in the 242 | model toggles between the two values for x and y, which are different. 243 | The expression m[A] returns the interpretation (universe) for the uninterpreted sort A 244 | in the model m. 245 |
246 | 247 |250 | Z3 is can solve quantifier-free problems containing arithmetic, bit-vector, Booleans, 251 | arrays, functions and datatypes. Z3 also accepts and can work with formulas 252 | that use quantifiers. It is no longer a decision procedure for 253 | such formulas in general (and for good reasons, as there can be 254 | no decision procedure for first-order logic). 255 |
256 | 257 | 258 | 259 |260 | Nevertheless, Z3 is often able to handle formulas involving 261 | quantifiers. It uses several approaches to handle quantifiers. 262 | The most prolific approach is using pattern-based quantifier 263 | instantiation. This approach allows instantiating quantified formulas 264 | with ground terms that appear in the current search context based 265 | on pattern annotations on quantifiers. 266 | Z3 also contains a model-based quantifier instantiation 267 | component that uses a model construction to find good terms to instantiate 268 | quantifiers with; and Z3 also handles many decidable fragments. 269 |
270 | 271 |Note that in the previous example the constants x 272 | and y were used to create quantified formulas. 273 | This is a "trick" for simplifying the construction of quantified 274 | formulas in Z3Py. Internally, these constants are replaced by 275 | bounded variables. The next example demonstrates that. The method 276 | body() retrives the quantified expression. 277 | In the resultant formula the bounded variables are free. 278 | The function Var(index, sort) creates a bounded/free variable 279 | with the given index and sort. 280 |
281 | 282 | 283 | 284 |287 | Suppose we want to model an object oriented type system with single inheritance. 288 | We would need a predicate for sub-typing. Sub-typing should be a partial order, 289 | and respect single inheritance. For some built-in type constructors, 290 | such as for array_of, sub-typing should be monotone. 291 |
292 | 293 | 294 | 295 |298 | The Stanford Pascal verifier and the subsequent Simplify theorem prover pioneered 299 | the use of pattern-based quantifier instantiation. 300 | The basic idea behind pattern-based quantifier instantiation is in a sense straight-forward: 301 | Annotate a quantified formula using a pattern that contains all the bound variables. 302 | So a pattern is an expression (that does not contain binding operations, such as quantifiers) 303 | that contains variables bound by a quantifier. Then instantiate the quantifier whenever a term 304 | that matches the pattern is created during search. This is a conceptually easy starting point, 305 | but there are several subtleties that are important. 306 |
307 | 308 |309 | In the following example, the first two options make sure that Model-based quantifier instantiation engine is disabled. 310 | We also annotate the quantified formula with the pattern f(g(x)). 311 | Since there is no ground instance of this pattern, the quantifier is not instantiated, and 312 | Z3 fails to show that the formula is unsatisfiable. 313 |
314 | 315 | 316 | 317 |When the more permissive pattern g(x) is used. Z3 proves the formula 318 | to be unsatisfiable. More restrive patterns minimize the number of 319 | instantiations (and potentially improve performance), but they may 320 | also make Z3 "less complete". 321 |
322 | 323 | 324 | 325 |326 | Some patterns may also create long instantiation chains. Consider the following assertion. 327 |
328 | 329 |330 | ForAll([x, y], Implies(subtype(x, y), 331 | subtype(array_of(x), array_of(y))), 332 | patterns=[subtype(x, y)]) 333 |334 | 335 |
336 | The axiom gets instantiated whenever there is some ground term of the 337 | form subtype(s, t). The instantiation causes a fresh ground term 338 | subtype(array_of(s), array_of(t)), which enables a new 339 | instantiation. This undesirable situation is called a matching 340 | loop. Z3 uses many heuristics to break matching loops. 341 |
342 | 343 |344 | Before elaborating on the subtleties, we should address an important 345 | first question. What defines the terms that are created during search? 346 | In the context of most SMT solvers, and of the Simplify theorem 347 | prover, terms exist as part of the input formula, they are of course 348 | also created by instantiating quantifiers, but terms are also 349 | implicitly created when equalities are asserted. The last point means 350 | that terms are considered up to congruence and pattern matching takes 351 | place modulo ground equalities. We call the matching problem 352 | E-matching. For example, if we have the following equalities: 353 |
354 | 355 | 356 | 357 |358 | The terms f(a) and f(g(b)) are equal modulo the 359 | equalities. The pattern f(g(x)) can be matched and x bound to b 360 | and the equality f(g(b)) == b is deduced. 361 |
362 | 363 |364 | While E-matching is an NP-complete problem, the main sources of overhead in larger verification 365 | problems comes from matching thousands of patterns in the context of an evolving set of terms and 366 | equalities. Z3 integrates an efficient E-matching engine using term indexing techniques. 367 |
368 | 369 |372 | In some cases, there is no pattern that contains all bound variables 373 | and does not contain interpreted symbols. In these cases, we use 374 | multi-patterns. In the following example, the quantified formula 375 | states that f is injective. This quantified formula is annotated with 376 | the multi-pattern MultiPattern(f(x), f(y)). 377 |
378 | 379 | 380 | 381 |382 | The quantified formula is instantiated for every pair of occurrences 383 | of f. A simple trick allows formulating injectivity of f in such a way 384 | that only a linear number of instantiations is required. The trick is 385 | to realize that f is injective if and only if it has a partial 386 | inverse. 387 |
388 | 389 | 390 | 391 |394 | In Z3Py, the following additional attributes are supported: qid (quantifier identifier 395 | for debugging), weight (hint to the quantifier instantiation module: "more weight equals less instances"), 396 | no_patterns (expressions that should not be used as patterns, skid (identifier 397 | prefix used to create skolem constants/functions. 398 |
399 | 400 |In Z3Py and Z3 4.0 multiple solvers can be simultaneously used. 403 | It is also very easy to copy assertions/formulas from one solver to another. 404 |
405 | 406 | 407 | 408 |Z3Py also supports unsat core extraction. The basic idea is to use 411 | assumptions, that is, auxiliary propositional variables that we want to track. 412 | Assumptions are also available in the Z3 SMT 2.0 frontend, and in other Z3 front-ends. 413 | They are used to extract unsatisfiable cores. They may be also used to "retract" 414 | constraints. Note that, assumptions are not really soft constraints, but they can be used to implement them. 415 |
416 | 417 | 418 | 419 |The example above also shows that a Boolean variable (p1) can be used to track 420 | more than one constraint. Note that Z3 does not guarantee that the unsat cores are minimal. 421 |
422 | 423 |426 | Z3Py uses a formatter (aka pretty printer) for displaying formulas, expressions, solvers, and other 427 | Z3 objects. The formatter supports many configuration options. 428 | The command set_option(html_mode=False) makes all formulas and expressions to be 429 | displayed in Z3Py notation. 430 |
431 | 432 | 433 | 434 |By default, Z3Py will truncate the output if the object being displayed is too big. 435 | Z3Py uses … to denote the output is truncated. 436 | The following configuration options can be set to control the behavior of Z3Py's formatter: 437 |
438 | 439 |11 |
12 | 13 |14 | This tutorial illustrates uses of Z3's fixedpoint engine. 15 | The following papers 16 | 17 | μZ - An Efficient Engine for Fixed-Points with Constraints. 18 | (CAV 2011) and 19 | 20 | Generalized Property Directed Reachability (SAT 2012) 21 | describe some of the main features of the engine. 22 |
23 | 24 |25 | Please send feedback, comments and/or corrections to 26 | nbjorner@microsoft.com. 27 |
28 | 29 |33 | This tutorial covers some of the fixedpoint utilities available with Z3. 34 | The main features are a basic Datalog engine, an engine with relational 35 | algebra and an engine based on a generalization of the Property 36 | Directed Reachability algorithm. 37 |
38 | 39 | 40 |The default fixed-point engine is a bottom-up Datalog engine. 43 | It works with finite relations and uses finite table representations 44 | as hash tables as the default way to represent finite relations. 45 |
46 | 47 |56 | fp = Fixedpoint() 57 |58 | creates a context for fixed-point computation. 59 |
60 | fp.register_relation(a.decl(), b.decl(), c.decl()) 61 |62 | Register the relations a, b, c as recursively defined. 63 |
64 | fp.rule(a,b) 65 |66 | Create the rule that a follows from b. 67 | In general you can create a rule with multiple premises and a name using 68 | the format 69 |
70 | fp.rule(head,[body1,...,bodyN],name) 71 |72 | The name is optional. It is used for tracking the rule in derivation proofs. 73 | 74 | Continuing with the example, a is false unless b is established. 75 |
76 | fp.query(a) 77 |78 | Asks if a can be derived. The rules so far say that a 79 | follows if b is established and that b follows if c 80 | is established. But nothing establishes c and b is also not 81 | established, so a cannot be derived. 82 |
83 | fp.fact(c) 84 |85 | Add a fact (shorthand for fp.rule(c,True)). 86 | Now it is the case that a can be derived. 87 | 88 |
90 | It is also possible to get an explanation for a derived query. 91 | For the finite Datalog engine, an explanation is a trace that 92 | provides information of how a fact was derived. The explanation 93 | is an expression whose function symbols are Horn rules and facts used 94 | in the derivation. 95 |
96 | 97 | 98 |100 | Relations can take arguments. We illustrate relations with arguments 101 | using edges and paths in a graph. 102 |
103 | 104 | 105 | The example uses the declaration 106 |107 | fp.declare_var(a,b,c) 108 |109 | to instrument the fixed-point engine that a, b, c 110 | should be treated as variables 111 | when they appear in rules. Think of the convention as they way bound variables are 112 | passed to quantifiers in Z3Py. 113 | 114 |
116 | A more entertaining example of using the basic fixed point engine
117 | is to solve the Rush Hour puzzle. The puzzle is about moving
118 | a red car out of a gridlock. We have encoded a configuration
119 | and compiled a set of rules that encode the legal moves of the cars
120 | given the configuration. Other configurations can be tested by
121 | changing the parameters passed to the constructor for
149 | Below we give a simple example that illustrates a loop at location l0. 150 | The loop is incremented as long as the loop counter does not exceed an upper 151 | bound. Using the combination of bound and interval domains 152 | we can collect derived invariants from the loop and we can also establish 153 | that the state after the loop does not exceed the bound. 154 |
155 | 156 | 157 | 158 | 159 | The example uses the option 160 |161 | set_option(dl_compile_with_widening=True) 162 |163 | to instrument Z3 to apply abstract interpretation widening on loop boundaries. 164 | 165 | 166 |
172 | set_option(dl_engine=1) 173 |174 | The version in Z3 applies to Horn clauses with arithmetic and 175 | Boolean domains. When using arithmetic you should enable 176 | the main abstraction engine used in Z3 for arithmetic in PDR. 177 |
178 | set_option(dl_pdr_use_farkas=True) 179 |180 | The engine also works with domains using algebraic 181 | data-types and bit-vectors, although it is currently not 182 | overly tuned for either. 183 | The PDR engine is targeted at applications from symbolic model 184 | checking of software. The systems may be infinite state. 185 | The following examples also serve a purpose of showing 186 | how software model checking problems (of safety properties) 187 | can be embedded into Horn clauses and solved using PDR. 188 | 189 |
191 | McCarthy's 91 function illustrates a procedure that calls itself recursively 192 | twice. The Horn clauses below encode the recursive function: 193 | 194 |
195 | mc(x) = if x > 100 then x - 10 else mc(mc(x+11)) 196 |197 | 198 | The general scheme for encoding recursive procedures is by creating a predicate 199 | for each procedure and adding an additional output variable to the predicate. 200 | Nested calls to procedures within a body can be encoded as a conjunction 201 | of relations. 202 | 203 | 204 | 205 | 206 | The first two queries are unsatisfiable. The PDR engine produces the same proof of unsatisfiability. 207 | The proof is an inductive invariant for each recursive predicate. The PDR engine introduces a 208 | special query predicate for the query. 209 | 210 |
213 | We can also prove invariants of reactive systems. It is convenient to encode reactive systems 214 | as guarded transition systems. It is perhaps for some not as convenient to directly encode 215 | guarded transitions as recursive Horn clauses. But it is fairly easy to write a translator 216 | from guarded transition systems to recursive Horn clauses. We illustrate a translator 217 | and Lamport's two process Bakery algorithm in the next example. 218 |
219 | 220 | 221 | The rather verbose (and in no way minimal) inductive invariants are produced as answers. 222 | 223 |11 | High-performance solvers, such as Z3, contain many tightly integrated, handcrafted heuristic 12 | combinations of algorithmic proof methods. While these heuristic 13 | combinations tend to be highly tuned for known classes of problems, 14 | they may easily perform very badly on new classes of problems. 15 | This issue is becoming increasingly pressing 16 | as solvers begin to gain the attention of practitioners in diverse areas of science and engineering. 17 | In many cases, changes to the solver heuristics can make a 18 | tremendous difference. 19 |
20 | 21 |22 | In this tutorial we show how to create custom strategies using the basic building blocks 23 | available in Z3. Z3Py and Z3 4.0 implement the ideas proposed in this article. 24 |
25 | 26 |27 | Please send feedback, comments and/or corrections to leonardo@microsoft.com. 28 | Your comments are very valuable. 29 |
30 | 31 |34 | Z3 implements a methodology for orchestrating reasoning 35 | engines where "big" symbolic reasoning steps are represented as 36 | functions known as tactics, and tactics are composed using 37 | combinators known as tacticals. Tactics process sets of 38 | formulas called Goals. 39 |
40 | 41 |42 | When a tactic is applied to some goal G, four different outcomes 43 | are possible. The tactic succeeds in showing G to be satisfiable (i.e., feasible); 44 | succeeds in showing G to be unsatisfiable (i.e., infeasible); produces a sequence of subgoals; or fails. 45 | When reducing a goal G to a sequence of subgoals G1, ..., 46 | Gn, we face the problem of model conversion. 47 | A model converter construct a model for G 48 | using a model for some subgoal Gi. 49 |
50 | 51 |In the following example, we create a goal g consisting of three formulas, and a tactic t 52 | composed of two built-in tactics: simplify and solve-eqs. The tactic simplify 53 | apply transformations equivalent to the ones found in the command simplify. The tactic solver-eqs 54 | eliminate variables using Gaussian elimination. Actually, solve-eqs is not restricted only to linear arithmetic. 55 | It can also eliminate arbitrary variables. Then, combinator Then applies simplify to the input goal 56 | and solve-eqs to each subgoal produced by simplify. In this example, only one subgoal is produced. 57 |
58 | 59 | 60 | 61 |In the example above, variable x is eliminated, and is not present the resultant goal. 62 |
63 | 64 |In Z3, we say a clause is any constraint of the form Or(f_1, ..., f_n). 65 | The tactic split-clause will select a clause Or(f_1, ..., f_n) in the input goal, and split it 66 | n subgoals. One for each subformula f_i. 67 |
68 | 69 | 70 | 71 |Z3 comes equipped with many built-in tactics. 74 | The command describe_tactics() provides a short description of all built-in tactics. 75 |
76 | 77 | 78 | 79 |Z3Py comes equipped with the following tactic combinators (aka tacticals): 80 |
81 | 82 |The following example demonstrate how to use these combinators.
104 | 105 | 106 | 107 |In the tactic split_solver, the tactic solve-eqs discharges all but one goal. 108 | Note that, this tactic generates one goal: the empty goal which is trivially satisfiable (i.e., feasible)
109 | 110 |The list of subgoals can be easily traversed using the Python for statement.
111 | 112 | 113 | 114 |A tactic can be converted into a solver object using the method solver(). 115 | If the tactic produces the empty goal, then the associated solver returns sat. 116 | If the tactic produces a single goal containing False, then the solver returns unsat. 117 | Otherwise, it returns unknown. 118 |
119 | 120 | 121 | 122 |In the example above, the tactic bv_solver implements a basic bit-vector solver using equation solving, 123 | bit-blasting, and a propositional SAT solver. Note that, the command Tactic is suppressed. 124 | All Z3Py combinators automatically invoke Tactic command if the argument is a string. 125 | Finally, the command solve_using is a variant of the solve command where the first 126 | argument specifies the solver to be used. 127 |
128 | 129 |In the following example, we use the solver API directly instead of the command solve_using. 130 | We use the combinator With to configure our little solver. We also include the tactic aig 131 | which tries to compress Boolean formulas using And-Inverted Graphs. 132 |
133 | 134 | 135 | 136 |The tactic smt wraps the main solver in Z3 as a tactic.
137 | 138 | 139 | 140 |Now, we show how to implement a solver for integer arithmetic using SAT. The solver is complete 141 | only for problems where every variable has a lower and upper bound. 142 |
143 | 144 | 145 | 146 |147 | Tactics can be combined with solvers. For example, we can apply a tactic to a goal, produced a set of subgoals, 148 | then select one of the subgoals and solve it using a solver. The next example demonstrates how to do that, and how to 149 | use model converters to convert a model for a subgoal into a model for the original goal. 150 |
151 | 152 | 153 | 154 |157 | Probes (aka formula measures) are evaluated over goals. 158 | Boolean expressions over them can be built using relational operators and Boolean connectives. 159 | The tactic FailIf(cond) fails if the given goal does not satisfy the condition cond. 160 | Many numeric and Boolean measures are available in Z3Py. The command describe_probes() provides the list of 161 | all built-in probes. 162 |
163 | 164 | 165 | 166 |In the following example, we build a simple tactic using FailIf. It also shows that a probe can be applied directly 167 | to a goal.
168 | 169 | 170 | 171 |Z3Py also provides the combinator (tactical) If(p, t1, t2) which is a shorthand for:
172 | 173 |OrElse(Then(FailIf(Not(p)), t1), t2)174 | 175 |
The combinator When(p, t) is a shorthand for:
176 | 177 |If(p, t, 'skip')178 | 179 |
The tactic skip just returns the input goal. 180 | The following example demonstrates how to use the If combinator.
181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /examples/sudoku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericpony/z3py-tutorial/de86c5e47c7ded29853c6d69ca6ac5c8b9b6c4aa/examples/sudoku.png -------------------------------------------------------------------------------- /examples/sudoku.py: -------------------------------------------------------------------------------- 1 | 2 | """Please Fill the Input by following methods 3 | 4 | If the (x, y) cell is filled with 'n' value, 5 | then put 'pxyn' into the Input list 6 | 7 | E.g. Consider following sudoku 8 | 9 | ------------------ 10 | 4 | | 4 || | | 11 | --------||-------- 12 | 3 | | || 4 | | 13 | ================== 14 | 2 | | 2 || | | 15 | --------||-------- 16 | 1 | | || 1 | | 17 | y ------------------ 18 | x 1 2 3 4 19 | 20 | (3, 1) cell is filled with 1 21 | Hence 'p311' is in Input list 22 | """ 23 | 24 | Input = ['p222', 'p244', 'p311', 'p334'] 25 | 26 | 27 | """ Below is the flow to generate formaula """ 28 | I = ['1','2','3','4'] 29 | Is = [['1','2'], ['3','4']] 30 | 31 | """ Check input integrity """ 32 | SI = set(I) 33 | for p in Input: 34 | if not len(p) == 4 or p[0] != 'p' or not p[1] in SI or not p[2] in SI or not p[3] in SI: 35 | print('Error:' + p + 'is not a valid input.') 36 | 37 | cls = [] """cls: the list of all conjucted clause""" 38 | cls.extend(Input) 39 | for i in I: 40 | for j in I: 41 | l = [] 42 | for n in I: 43 | l.append('p'+i+j+n) 44 | cls.append( '(' + ('|'.join(l)) + ')' ) 45 | 46 | for i in I: 47 | for j in I: 48 | for n in I: 49 | for m in I: 50 | if m != n: 51 | cls.append('(p'+i+j+n+'->~'+'p'+i+j+m+')') 52 | for n in I: 53 | for i in I: 54 | l = [] 55 | for j in I: 56 | l.append('p'+i+j+n) 57 | cls.append('(' + ('|'.join(l)) +')') 58 | 59 | for n in I: 60 | for j in I: 61 | l = [] 62 | for i in I: 63 | l.append('p'+i+j+n) 64 | cls.append('(' + ('|'.join(l)) +')') 65 | 66 | for k1 in Is: 67 | for k2 in Is: 68 | for n in I: 69 | l = [] 70 | for i in k1: 71 | for j in k2: 72 | l.append('p'+i+j+n) 73 | cls.append('(' + ('|'.join(l)) +')') 74 | 75 | print('&'.join(cls)) 76 | -------------------------------------------------------------------------------- /examples/tactic.1.py: -------------------------------------------------------------------------------- 1 | x, y = Reals('x y') 2 | g = Goal() 3 | g.add(x > 0, y > 0, x == y + 2) 4 | print g 5 | 6 | t1 = Tactic('simplify') 7 | t2 = Tactic('solve-eqs') 8 | t = Then(t1, t2) 9 | print t(g) 10 | -------------------------------------------------------------------------------- /examples/tactic.10.py: -------------------------------------------------------------------------------- 1 | t = Then('simplify', 2 | 'normalize-bounds', 3 | 'solve-eqs') 4 | 5 | x, y, z = Ints('x y z') 6 | g = Goal() 7 | g.add(x > 10, y == x + 3, z > y) 8 | 9 | r = t(g) 10 | # r contains only one subgoal 11 | print r 12 | 13 | s = Solver() 14 | s.add(r[0]) 15 | print s.check() 16 | # Model for the subgoal 17 | print s.model() 18 | # Model for the original goal 19 | print r.convert_model(s.model()) 20 | -------------------------------------------------------------------------------- /examples/tactic.2.py: -------------------------------------------------------------------------------- 1 | x, y = Reals('x y') 2 | g = Goal() 3 | g.add(Or(x < 0, x > 0), x == y + 1, y < 0) 4 | 5 | t = Tactic('split-clause') 6 | r = t(g) 7 | for g in r: 8 | print g 9 | -------------------------------------------------------------------------------- /examples/tactic.3.py: -------------------------------------------------------------------------------- 1 | describe_tactics() 2 | -------------------------------------------------------------------------------- /examples/tactic.4.py: -------------------------------------------------------------------------------- 1 | x, y, z = Reals('x y z') 2 | g = Goal() 3 | g.add(Or(x == 0, x == 1), 4 | Or(y == 0, y == 1), 5 | Or(z == 0, z == 1), 6 | x + y + z > 2) 7 | 8 | # Split all clauses" 9 | split_all = Repeat(OrElse(Tactic('split-clause'), 10 | Tactic('skip'))) 11 | print split_all(g) 12 | 13 | split_at_most_2 = Repeat(OrElse(Tactic('split-clause'), 14 | Tactic('skip')), 15 | 1) 16 | print split_at_most_2(g) 17 | 18 | # Split all clauses and solve equations 19 | split_solve = Then(Repeat(OrElse(Tactic('split-clause'), 20 | Tactic('skip'))), 21 | Tactic('solve-eqs')) 22 | 23 | print split_solve(g) 24 | -------------------------------------------------------------------------------- /examples/tactic.5.py: -------------------------------------------------------------------------------- 1 | x, y, z = Reals('x y z') 2 | g = Goal() 3 | g.add(Or(x == 0, x == 1), 4 | Or(y == 0, y == 1), 5 | Or(z == 0, z == 1), 6 | x + y + z > 2) 7 | 8 | # Split all clauses" 9 | split_all = Repeat(OrElse(Tactic('split-clause'), 10 | Tactic('skip'))) 11 | for s in split_all(g): 12 | print s 13 | -------------------------------------------------------------------------------- /examples/tactic.6.py: -------------------------------------------------------------------------------- 1 | bv_solver = Then('simplify', 2 | 'solve-eqs', 3 | 'bit-blast', 4 | 'sat').solver() 5 | 6 | x, y = BitVecs('x y', 16) 7 | solve_using(bv_solver, x | y == 13, x > y) 8 | -------------------------------------------------------------------------------- /examples/tactic.7.py: -------------------------------------------------------------------------------- 1 | bv_solver = Then(With('simplify', mul2concat=True), 2 | 'solve-eqs', 3 | 'bit-blast', 4 | 'aig', 5 | 'sat').solver() 6 | x, y = BitVecs('x y', 16) 7 | bv_solver.add(x*32 + y == 13, x & y < 10, y > -100) 8 | print bv_solver.check() 9 | m = bv_solver.model() 10 | print m 11 | print x*32 + y, "==", m.evaluate(x*32 + y) 12 | print x & y, "==", m.evaluate(x & y) 13 | 14 | -------------------------------------------------------------------------------- /examples/tactic.8.py: -------------------------------------------------------------------------------- 1 | x, y = Ints('x y') 2 | s = Tactic('smt').solver() 3 | s.add(x > y + 1) 4 | print s.check() 5 | print s.model() 6 | -------------------------------------------------------------------------------- /examples/tactic.9.py: -------------------------------------------------------------------------------- 1 | s = Then(With('simplify', arith_lhs=True, som=True), 2 | 'normalize-bounds', 'lia2pb', 'pb2bv', 3 | 'bit-blast', 'sat').solver() 4 | x, y, z = Ints('x y z') 5 | solve_using(s, 6 | x > 0, x < 10, 7 | y > 0, y < 10, 8 | z > 0, z < 10, 9 | 3*y + 2*x == z) 10 | # It fails on the next example (it is unbounded) 11 | s.reset() 12 | solve_using(s, 3*y + 2*x == z) 13 | -------------------------------------------------------------------------------- /examples/tree.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | # Create the tree datatype. 4 | Tree = Datatype('Tree') 5 | TreeList = Datatype('TreeList') 6 | Tree.declare('leaf', ('value', IntSort())) 7 | Tree.declare('node', ('children', TreeList)) 8 | TreeList.declare('nil') 9 | TreeList.declare('cons', ('car', Tree), ('cdr', TreeList)) 10 | 11 | Tree, TreeList = CreateDatatypes(Tree, TreeList) 12 | 13 | print 'Tree.value(Tree.leaf(10)):' 14 | print Tree.value(Tree.leaf(10)) 15 | print '' 16 | 17 | print 'simplify(Tree.value(Tree.leaf(10))):' 18 | print simplify(Tree.value(Tree.leaf(10))) 19 | print '' 20 | 21 | n1 = Tree.node(TreeList.cons(Tree.leaf(10), TreeList.cons(Tree.leaf(20), TreeList.nil))) 22 | n2 = Tree.node(TreeList.cons(n1, TreeList.nil)) 23 | print 'n1 = Tree.node(TreeList.cons(Tree.leaf(10), TreeList.cons(Tree.leaf(20), TreeList.nil)))' 24 | print 'n2 = Tree.node(TreeList.cons(n1, TreeList.nil))' 25 | print '' 26 | 27 | print 'simplify(n2 == n1):' 28 | print simplify(n2 == n1) 29 | print '' 30 | 31 | print 'simplify(TreeList.car(Tree.children(n2)) == n1):' 32 | print simplify(TreeList.car(Tree.children(n2)) == n1) 33 | print '' 34 | -------------------------------------------------------------------------------- /examples/uninterp.1.py: -------------------------------------------------------------------------------- 1 | A = DeclareSort('A') 2 | x, y = Consts('x y', A) 3 | f = Function('f', A, A) 4 | 5 | s = Solver() 6 | s.add(f(f(x)) == x, f(x) == y, x != y) 7 | 8 | print s.check() 9 | m = s.model() 10 | print m 11 | print "interpretation assigned to A:" 12 | print m[A] 13 | -------------------------------------------------------------------------------- /examples/unsatcore.1.py: -------------------------------------------------------------------------------- 1 | p1, p2, p3 = Bools('p1 p2 p3') 2 | x, y = Ints('x y') 3 | # We assert Implies(p, C) to track constraint C using p 4 | s = Solver() 5 | s.add(Implies(p1, x > 10), 6 | Implies(p1, y > x), 7 | Implies(p2, y < 5), 8 | Implies(p3, y > 0)) 9 | print s 10 | # Check satisfiability assuming p1, p2, p3 are true 11 | print s.check(p1, p2, p3) 12 | print s.unsat_core() 13 | 14 | # Try again retracting p2 15 | print s.check(p1, p3) 16 | print s.model() 17 | -------------------------------------------------------------------------------- /examples/z3py.1.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | solve(x > 2, y < 10, x + 2*y == 7) 4 | -------------------------------------------------------------------------------- /examples/z3py.10.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | 4 | s = Solver() 5 | print s 6 | 7 | s.add(x > 10, y == x + 2) 8 | print s 9 | print "Solving constraints in the solver s ..." 10 | print s.check() 11 | 12 | print "Create a new scope..." 13 | s.push() 14 | s.add(y < 11) 15 | print s 16 | print "Solving updated set of constraints..." 17 | print s.check() 18 | 19 | print "Restoring state..." 20 | s.pop() 21 | print s 22 | print "Solving restored set of constraints..." 23 | print s.check() 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/z3py.11.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | s = Solver() 3 | s.add(2**x == 3) 4 | print s.check() 5 | -------------------------------------------------------------------------------- /examples/z3py.12.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | y = Real('y') 3 | s = Solver() 4 | s.add(x > 1, y > 1, Or(x + y > 3, x - y < 2)) 5 | print "asserted constraints..." 6 | for c in s.assertions(): 7 | print c 8 | 9 | print s.check() 10 | print "statistics for the last check method..." 11 | print s.statistics() 12 | # Traversing statistics 13 | for k, v in s.statistics(): 14 | print "%s : %s" % (k, v) 15 | 16 | -------------------------------------------------------------------------------- /examples/z3py.13.py: -------------------------------------------------------------------------------- 1 | x, y, z = Reals('x y z') 2 | s = Solver() 3 | s.add(x > 1, y > 1, x + y > 3, z - x < 10) 4 | print s.check() 5 | 6 | m = s.model() 7 | print "x = %s" % m[x] 8 | 9 | print "traversing model..." 10 | for d in m.decls(): 11 | print "%s = %s" % (d.name(), m[d]) 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/z3py.14.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | f = Function('f', IntSort(), IntSort()) 4 | solve(f(f(x)) == x, f(x) == y, x != y) 5 | -------------------------------------------------------------------------------- /examples/z3py.15.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | f = Function('f', IntSort(), IntSort()) 4 | s = Solver() 5 | s.add(f(f(x)) == x, f(x) == y, x != y) 6 | print s.check() 7 | m = s.model() 8 | print "f(f(x)) =", m.evaluate(f(f(x))) 9 | print "f(x) =", m.evaluate(f(x)) 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/z3py.16.py: -------------------------------------------------------------------------------- 1 | p, q = Bools('p q') 2 | demorgan = And(p, q) == Not(Or(Not(p), Not(q))) 3 | print demorgan 4 | 5 | def prove(f): 6 | s = Solver() 7 | s.add(Not(f)) 8 | if s.check() == unsat: 9 | print "proved" 10 | else: 11 | print "failed to prove" 12 | 13 | print "Proving demorgan..." 14 | prove(demorgan) 15 | -------------------------------------------------------------------------------- /examples/z3py.2.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | print simplify(x + y + 2*x + 3) 4 | print simplify(x < y + x + 2) 5 | print simplify(And(x + 1 >= 3, x**2 + x**2 + y**2 + 2 >= 5)) 6 | -------------------------------------------------------------------------------- /examples/z3py.3.py: -------------------------------------------------------------------------------- 1 | x = Int('x') 2 | y = Int('y') 3 | n = x + y >= 3 4 | print "num args: ", n.num_args() 5 | print "children: ", n.children() 6 | print "1st child:", n.arg(0) 7 | print "2nd child:", n.arg(1) 8 | print "operator: ", n.decl() 9 | print "op name: ", n.decl().name() 10 | -------------------------------------------------------------------------------- /examples/z3py.4.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | y = Real('y') 3 | solve(x**2 + y**2 > 3, x**3 + y < 5) 4 | -------------------------------------------------------------------------------- /examples/z3py.5.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | y = Real('y') 3 | solve(x**2 + y**2 == 3, x**3 == 2) 4 | 5 | set_option(precision=30) 6 | print "Solving, and displaying result with 30 decimal places" 7 | solve(x**2 + y**2 == 3, x**3 == 2) 8 | 9 | -------------------------------------------------------------------------------- /examples/z3py.6.py: -------------------------------------------------------------------------------- 1 | print 1/3 2 | print RealVal(1)/3 3 | print Q(1,3) 4 | 5 | x = Real('x') 6 | print x + 1/3 7 | print x + Q(1,3) 8 | print x + "1/3" 9 | print x + 0.25 10 | -------------------------------------------------------------------------------- /examples/z3py.6a.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | solve(x > 4, x < 0) 3 | -------------------------------------------------------------------------------- /examples/z3py.6aa.py: -------------------------------------------------------------------------------- 1 | x = Real('x') 2 | solve(3*x == 1) 3 | 4 | set_option(rational_to_decimal=True) 5 | solve(3*x == 1) 6 | 7 | set_option(precision=30) 8 | solve(3*x == 1) 9 | -------------------------------------------------------------------------------- /examples/z3py.7.py: -------------------------------------------------------------------------------- 1 | p = Bool('p') 2 | q = Bool('q') 3 | r = Bool('r') 4 | solve(Implies(p, q), r == Not(q), Or(Not(p), r)) 5 | 6 | -------------------------------------------------------------------------------- /examples/z3py.8.py: -------------------------------------------------------------------------------- 1 | p = Bool('p') 2 | q = Bool('q') 3 | print And(p, q, True) 4 | print simplify(And(p, q, True)) 5 | print simplify(And(p, False)) 6 | -------------------------------------------------------------------------------- /examples/z3py.9.py: -------------------------------------------------------------------------------- 1 | p = Bool('p') 2 | x = Real('x') 3 | solve(Or(x < 5, x > 10), Or(p, x**2 == 2), Not(p)) 4 | -------------------------------------------------------------------------------- /fixpoint-examples.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 |14 | This tutorial illustrates uses of Z3's fixedpoint engine. 15 | The following papers 16 | 17 | μZ - An Efficient Engine for Fixed-Points with Constraints. 18 | (CAV 2011) and 19 | 20 | Generalized Property Directed Reachability (SAT 2012) 21 | describe some of the main features of the engine. 22 |
23 | 24 |25 | Please send feedback, comments and/or corrections to 26 | nbjorner@microsoft.com. 27 |
28 | 29 |33 | This tutorial covers some of the fixedpoint utilities available with Z3. 34 | The main features are a basic Datalog engine, an engine with relational 35 | algebra and an engine based on a generalization of the Property 36 | Directed Reachability algorithm. 37 |
38 | 39 | 40 |The default fixed-point engine is a bottom-up Datalog engine. 43 | It works with finite relations and uses finite table representations 44 | as hash tables as the default way to represent finite relations. 45 |
46 | 47 |fp = Fixedpoint()
54 |
55 | a, b, c = Bools('a b c')
56 |
57 | fp.register_relation(a.decl(), b.decl(), c.decl())
58 | fp.rule(a,b)
59 | fp.rule(b,c)
60 | fp.set(engine='datalog')
61 |
62 | print ("current set of rules\n", fp)
63 | print (fp.query(a))
64 |
65 | fp.fact(c)
66 | print ("updated set of rules\n", fp)
67 | print (fp.query(a))
68 | print (fp.get_answer())
69 |
74 | fp = Fixedpoint() 75 |76 | creates a context for fixed-point computation. 77 |
78 | fp.register_relation(a.decl(), b.decl(), c.decl()) 79 |80 | Register the relations a, b, c as recursively defined. 81 |
82 | fp.rule(a,b) 83 |84 | Create the rule that a follows from b. 85 | In general you can create a rule with multiple premises and a name using 86 | the format 87 |
88 | fp.rule(head,[body1,...,bodyN],name) 89 |90 | The name is optional. It is used for tracking the rule in derivation proofs. 91 | 92 | Continuing with the example, a is false unless b is established. 93 |
94 | fp.query(a) 95 |96 | Asks if a can be derived. The rules so far say that a 97 | follows if b is established and that b follows if c 98 | is established. But nothing establishes c and b is also not 99 | established, so a cannot be derived. 100 |
101 | fp.fact(c) 102 |103 | Add a fact (shorthand for fp.rule(c,True)). 104 | Now it is the case that a can be derived. 105 | 106 |
108 | It is also possible to get an explanation for a derived query. 109 | For the finite Datalog engine, an explanation is a trace that 110 | provides information of how a fact was derived. The explanation 111 | is an expression whose function symbols are Horn rules and facts used 112 | in the derivation. 113 |
114 |fp = Fixedpoint()
116 |
117 | a, b, c = Bools('a b c')
118 |
119 | fp.register_relation(a.decl(), b.decl(), c.decl())
120 | fp.rule(a,b)
121 | fp.rule(b,c)
122 | fp.fact(c)
123 | fp.set(generate_explanations=True, engine='datalog')
124 | print (fp.query(a))
125 | print (fp.get_answer())
126 |
131 | Relations can take arguments. We illustrate relations with arguments 132 | using edges and paths in a graph. 133 |
134 |fp = Fixedpoint()
136 | fp.set(engine='datalog')
137 |
138 | s = BitVecSort(3)
139 | edge = Function('edge', s, s, BoolSort())
140 | path = Function('path', s, s, BoolSort())
141 | a = Const('a',s)
142 | b = Const('b',s)
143 | c = Const('c',s)
144 |
145 | fp.register_relation(path,edge)
146 | fp.declare_var(a,b,c)
147 | fp.rule(path(a,b), edge(a,b))
148 | fp.rule(path(a,c), [edge(a,b),path(b,c)])
149 |
150 | v1 = BitVecVal(1,s)
151 | v2 = BitVecVal(2,s)
152 | v3 = BitVecVal(3,s)
153 | v4 = BitVecVal(4,s)
154 |
155 | fp.fact(edge(v1,v2))
156 | fp.fact(edge(v1,v3))
157 | fp.fact(edge(v2,v4))
158 |
159 | print ("current set of rules", fp)
160 |
161 |
162 | print (fp.query(path(v1,v4)), "yes we can reach v4 from v1")
163 |
164 | print (fp.query(path(v3,v4)), "no we cannot reach v4 from v3")
165 |
170 | fp.declare_var(a,b,c) 171 |172 | to instrument the fixed-point engine that a, b, c 173 | should be treated as variables 174 | when they appear in rules. Think of the convention as they way bound variables are 175 | passed to quantifiers in Z3Py. 176 | 177 |
179 | A more entertaining example of using the basic fixed point engine
180 | is to solve the Rush Hour puzzle. The puzzle is about moving
181 | a red car out of a gridlock. We have encoded a configuration
182 | and compiled a set of rules that encode the legal moves of the cars
183 | given the configuration. Other configurations can be tested by
184 | changing the parameters passed to the constructor for
class Car():
191 | def __init__(self, is_vertical, base_pos, length, start, color):
192 | self.is_vertical = is_vertical
193 | self.base = base_pos
194 | self.length = length
195 | self.start = start
196 | self.color = color
197 |
198 | def __eq__(self, other):
199 | return self.color == other.color
200 |
201 | def __ne__(self, other):
202 | return self.color != other.color
203 |
204 | dimension = 6
205 |
206 | red_car = Car(False, 2, 2, 3, "red")
207 | cars = [
208 | Car(True, 0, 3, 0, 'yellow'),
209 | Car(False, 3, 3, 0, 'blue'),
210 | Car(False, 5, 2, 0, "brown"),
211 | Car(False, 0, 2, 1, "lgreen"),
212 | Car(True, 1, 2, 1, "light blue"),
213 | Car(True, 2, 2, 1, "pink"),
214 | Car(True, 2, 2, 4, "dark green"),
215 | red_car,
216 | Car(True, 3, 2, 3, "purple"),
217 | Car(False, 5, 2, 3, "light yellow"),
218 | Car(True, 4, 2, 0, "orange"),
219 | Car(False, 4, 2, 4, "black"),
220 | Car(True, 5, 3, 1, "light purple")
221 | ]
222 |
223 | num_cars = len(cars)
224 | B = BoolSort()
225 | bv3 = BitVecSort(3)
226 |
227 |
228 | state = Function('state', [ bv3 for c in cars] + [B])
229 |
230 | def num(i):
231 | return BitVecVal(i,bv3)
232 |
233 | def bound(i):
234 | return Const(cars[i].color, bv3)
235 |
236 | fp = Fixedpoint()
237 | fp.set(generate_explanations=True)
238 | fp.declare_var([bound(i) for i in range(num_cars)])
239 | fp.register_relation(state)
240 |
241 | def mk_state(car, value):
242 | return state([ (num(value) if (cars[i] == car) else bound(i))
243 | for i in range(num_cars)])
244 |
245 | def mk_transition(row, col, i0, j, car0):
246 | body = [mk_state(car0, i0)]
247 | for index in range(num_cars):
248 | car = cars[index]
249 | if car0 != car:
250 | if car.is_vertical and car.base == col:
251 | for i in range(dimension):
252 | if i <= row and row < i + car.length and i + car.length <= dimension:
253 | body += [bound(index) != num(i)]
254 | if car.base == row and not car.is_vertical:
255 | for i in range(dimension):
256 | if i <= col and col < i + car.length and i + car.length <= dimension:
257 | body += [bound(index) != num(i)]
258 |
259 | s = "%s %d->%d" % (car0.color, i0, j)
260 |
261 | fp.rule(mk_state(car0, j), body, s)
262 |
263 |
264 | def move_down(i, car):
265 | free_row = i + car.length
266 | if free_row < dimension:
267 | mk_transition(free_row, car.base, i, i + 1, car)
268 |
269 |
270 | def move_up(i, car):
271 | free_row = i - 1
272 | if 0 <= free_row and i + car.length <= dimension:
273 | mk_transition(free_row, car.base, i, i - 1, car)
274 |
275 | def move_left(i, car):
276 | free_col = i - 1;
277 | if 0 <= free_col and i + car.length <= dimension:
278 | mk_transition(car.base, free_col, i, i - 1, car)
279 |
280 |
281 | def move_right(i, car):
282 | free_col = car.length + i
283 | if free_col < dimension:
284 | mk_transition(car.base, free_col, i, i + 1, car)
285 |
286 |
287 | # Initial state:
288 | fp.fact(state([num(cars[i].start) for i in range(num_cars)]))
289 |
290 | # Transitions:
291 | for car in cars:
292 | for i in range(dimension):
293 | if car.is_vertical:
294 | move_down(i, car)
295 | move_up(i, car)
296 | else:
297 | move_left(i, car)
298 | move_right(i, car)
299 |
300 |
301 | def get_instructions(ans):
302 | lastAnd = ans.arg(0).children()[-1]
303 | trace = lastAnd.children()[1]
304 | while trace.num_args() > 0:
305 | print (trace.decl())
306 | trace = trace.children()[-1]
307 |
308 | print (fp)
309 |
310 | goal = state([ (num(4) if cars[i] == red_car else bound(i))
311 | for i in range(num_cars)])
312 | fp.query(goal)
313 |
314 | get_instructions(fp.get_answer())
315 |
316 |
340 | Below we give a simple example that illustrates a loop at location l0. 341 | The loop is incremented as long as the loop counter does not exceed an upper 342 | bound. Using the combination of bound and interval domains 343 | we can collect derived invariants from the loop and we can also establish 344 | that the state after the loop does not exceed the bound. 345 |
346 | 347 | 348 |I = IntSort()
350 | B = BoolSort()
351 | l0 = Function('l0',I,I,B)
352 | l1 = Function('l1',I,I,B)
353 |
354 | s = Fixedpoint()
355 | s.set(engine='datalog',compile_with_widening=True,
356 | unbound_compressor=False)
357 |
358 | s.register_relation(l0,l1)
359 | s.set_predicate_representation(l0, 'interval_relation', 'bound_relation')
360 | s.set_predicate_representation(l1, 'interval_relation', 'bound_relation')
361 |
362 | m, x, y = Ints('m x y')
363 |
364 | s.declare_var(m, x, y)
365 | s.rule(l0(0,m), 0 < m)
366 | s.rule(l0(x+1,m), [l0(x,m), x < m])
367 | s.rule(l1(x,m), [l0(x,m), m <= x])
368 |
369 | print ("At l0 we learn that x, y are non-negative:")
370 | print (s.query(l0(x,y)))
371 | print (s.get_answer())
372 |
373 | print ("At l1 we learn that x <= y and both x and y are bigger than 0:")
374 | print (s.query(l1(x,y)))
375 | print (s.get_answer())
376 |
377 |
378 | print ("The state where x < y is not reachable")
379 | print (s.query(And(l1(x,y), x < y)))
380 |
385 | set_option(dl_compile_with_widening=True) 386 |387 | to instrument Z3 to apply abstract interpretation widening on loop boundaries. 388 | 389 | 390 |
396 | set_option(dl_engine=1) 397 |398 | The version in Z3 applies to Horn clauses with arithmetic and 399 | Boolean domains. When using arithmetic you should enable 400 | the main abstraction engine used in Z3 for arithmetic in PDR. 401 |
402 | set_option(dl_pdr_use_farkas=True) 403 |404 | The engine also works with domains using algebraic 405 | data-types and bit-vectors, although it is currently not 406 | overly tuned for either. 407 | The PDR engine is targeted at applications from symbolic model 408 | checking of software. The systems may be infinite state. 409 | The following examples also serve a purpose of showing 410 | how software model checking problems (of safety properties) 411 | can be embedded into Horn clauses and solved using PDR. 412 | 413 |
415 | McCarthy's 91 function illustrates a procedure that calls itself recursively 416 | twice. The Horn clauses below encode the recursive function: 417 | 418 |
419 |420 | mc(x) = if x > 100 then x - 10 else mc(mc(x+11)) 421 |422 | 423 | The general scheme for encoding recursive procedures is by creating a predicate 424 | for each procedure and adding an additional output variable to the predicate. 425 | Nested calls to procedures within a body can be encoded as a conjunction 426 | of relations. 427 | 428 | 429 |
mc = Function('mc', IntSort(), IntSort(), BoolSort())
431 | n, m, p = Ints('n m p')
432 |
433 | fp = Fixedpoint()
434 |
435 | fp.declare_var(n,m)
436 | fp.register_relation(mc)
437 |
438 | fp.rule(mc(m, m-10), m > 100)
439 | fp.rule(mc(m, n), [m <= 100, mc(m+11,p),mc(p,n)])
440 |
441 | print (fp.query(And(mc(m,n),n < 90)))
442 | print (fp.get_answer())
443 |
444 | print (fp.query(And(mc(m,n),n < 91)))
445 | print (fp.get_answer())
446 |
447 | print (fp.query(And(mc(m,n),n < 92)))
448 | print (fp.get_answer())
449 |
459 | We can also prove invariants of reactive systems. It is convenient to encode reactive systems 460 | as guarded transition systems. It is perhaps for some not as convenient to directly encode 461 | guarded transitions as recursive Horn clauses. But it is fairly easy to write a translator 462 | from guarded transition systems to recursive Horn clauses. We illustrate a translator 463 | and Lamport's two process Bakery algorithm in the next example. 464 |
465 | 466 |set_option(relevancy=0,verbose=1)
468 |
469 | def flatten(l):
470 | return [s for t in l for s in t]
471 |
472 |
473 | class TransitionSystem():
474 | def __init__(self, initial, transitions, vars1):
475 | self.fp = Fixedpoint()
476 | self.initial = initial
477 | self.transitions = transitions
478 | self.vars1 = vars1
479 |
480 | def declare_rels(self):
481 | B = BoolSort()
482 | var_sorts = [ v.sort() for v in self.vars1 ]
483 | state_sorts = var_sorts
484 | self.state_vals = [ v for v in self.vars1 ]
485 | self.state_sorts = state_sorts
486 | self.var_sorts = var_sorts
487 | self.state = Function('state', state_sorts + [ B ])
488 | self.step = Function('step', state_sorts + state_sorts + [ B ])
489 | self.fp.register_relation(self.state)
490 | self.fp.register_relation(self.step)
491 |
492 | # Set of reachable states are transitive closure of step.
493 |
494 | def state0(self):
495 | idx = range(len(self.state_sorts))
496 | return self.state([Var(i,self.state_sorts[i]) for i in idx])
497 |
498 | def state1(self):
499 | n = len(self.state_sorts)
500 | return self.state([Var(i+n, self.state_sorts[i]) for i in range(n)])
501 |
502 | def rho(self):
503 | n = len(self.state_sorts)
504 | args1 = [ Var(i,self.state_sorts[i]) for i in range(n) ]
505 | args2 = [ Var(i+n,self.state_sorts[i]) for i in range(n) ]
506 | args = args1 + args2
507 | return self.step(args)
508 |
509 | def declare_reachability(self):
510 | self.fp.rule(self.state1(), [self.state0(), self.rho()])
511 |
512 |
513 | # Define transition relation
514 |
515 | def abstract(self, e):
516 | n = len(self.state_sorts)
517 | sub = [(self.state_vals[i], Var(i,self.state_sorts[i])) for i in range(n)]
518 | return substitute(e, sub)
519 |
520 | def declare_transition(self, tr):
521 | len_s = len(self.state_sorts)
522 | effect = tr["effect"]
523 | vars1 = [Var(i,self.state_sorts[i]) for i in range(len_s)] + effect
524 | rho1 = self.abstract(self.step(vars1))
525 | guard = self.abstract(tr["guard"])
526 | self.fp.rule(rho1, guard)
527 |
528 | def declare_transitions(self):
529 | for t in self.transitions:
530 | self.declare_transition(t)
531 |
532 | def declare_initial(self):
533 | self.fp.rule(self.state0(),[self.abstract(self.initial)])
534 |
535 | def query(self, query):
536 | self.declare_rels()
537 | self.declare_initial()
538 | self.declare_reachability()
539 | self.declare_transitions()
540 | query = And(self.state0(), self.abstract(query))
541 | print (self.fp)
542 | print (query)
543 | print (self.fp.query(query))
544 | print (self.fp.get_answer())
545 | # print self.fp.statistics()
546 |
547 |
548 | L = Datatype('L')
549 | L.declare('L0')
550 | L.declare('L1')
551 | L.declare('L2')
552 | L = L.create()
553 | L0 = L.L0
554 | L1 = L.L1
555 | L2 = L.L2
556 |
557 |
558 | y0 = Int('y0')
559 | y1 = Int('y1')
560 | l = Const('l', L)
561 | m = Const('m', L)
562 |
563 |
564 | t1 = { "guard" : l == L0,
565 | "effect" : [ L1, y1 + 1, m, y1 ] }
566 | t2 = { "guard" : And(l == L1, Or([y0 <= y1, y1 == 0])),
567 | "effect" : [ L2, y0, m, y1 ] }
568 | t3 = { "guard" : l == L2,
569 | "effect" : [ L0, IntVal(0), m, y1 ]}
570 | s1 = { "guard" : m == L0,
571 | "effect" : [ l, y0, L1, y0 + 1 ] }
572 | s2 = { "guard" : And(m == L1, Or([y1 <= y0, y0 == 0])),
573 | "effect" : [ l, y0, L2, y1 ] }
574 | s3 = { "guard" : m == L2,
575 | "effect" : [ l, y0, L0, IntVal(0) ]}
576 |
577 |
578 | ptr = TransitionSystem( And(l == L0, y0 == 0, m == L0, y1 == 0),
579 | [t1, t2, t3, s1, s2, s3],
580 | [l, y0, m, y1])
581 |
582 | ptr.query(And([l == L2, m == L2 ]))
583 |
# let max max2 x y z = max2 (max2 x y) z
602 | # let f x y = if x > y then x else y
603 | # assert (f (max f x y z) x) = (max f x y z)
604 |
605 |
606 | Expr = Datatype('Expr')
607 | Expr.declare('Max')
608 | Expr.declare('f')
609 | Expr.declare('I', ('i', IntSort()))
610 | Expr.declare('App', ('fn',Expr),('arg',Expr))
611 | Expr = Expr.create()
612 | Max = Expr.Max
613 | I = Expr.I
614 | App = Expr.App
615 | f = Expr.f
616 | Eval = Function('Eval',Expr,Expr,Expr,BoolSort())
617 |
618 | x = Const('x',Expr)
619 | y = Const('y',Expr)
620 | z = Const('z',Expr)
621 | r1 = Const('r1',Expr)
622 | r2 = Const('r2',Expr)
623 | max = Const('max',Expr)
624 | xi = Const('xi',IntSort())
625 | yi = Const('yi',IntSort())
626 |
627 | fp = Fixedpoint()
628 | fp.register_relation(Eval)
629 | fp.declare_var(x,y,z,r1,r2,max,xi,yi)
630 |
631 | # Max max x y z = max (max x y) z
632 | fp.rule(Eval(App(App(App(Max,max),x),y), z, r2),
633 | [Eval(App(max,x),y,r1),
634 | Eval(App(max,r1),z,r2)])
635 |
636 | # f x y = x if x >= y
637 | # f x y = y if x < y
638 | fp.rule(Eval(App(f,I(xi)),I(yi),I(xi)),xi >= yi)
639 | fp.rule(Eval(App(f,I(xi)),I(yi),I(yi)),xi < yi)
640 |
641 | print (fp.query(And(Eval(App(App(App(Max,f),x),y),z,r1),)
642 | Eval(App(f,x),r1,r2),
643 | r1 != r2))
644 |
645 | print (fp.get_answer())
646 |
14 | High-performance solvers, such as Z3, contain many tightly integrated, handcrafted heuristic 15 | combinations of algorithmic proof methods. While these heuristic 16 | combinations tend to be highly tuned for known classes of problems, 17 | they may easily perform very badly on new classes of problems. 18 | This issue is becoming increasingly pressing 19 | as solvers begin to gain the attention of practitioners in diverse areas of science and engineering. 20 | In many cases, changes to the solver heuristics can make a 21 | tremendous difference. 22 |
23 | 24 |25 | In this tutorial we show how to create custom strategies using the basic building blocks 26 | available in Z3. Z3Py and Z3 4.0 implement the ideas proposed in this article. 27 |
28 | 29 |30 | Please send feedback, comments and/or corrections to leonardo@microsoft.com. 31 | Your comments are very valuable. 32 |
33 | 34 |37 | Z3 implements a methodology for orchestrating reasoning 38 | engines where "big" symbolic reasoning steps are represented as 39 | functions known as tactics, and tactics are composed using 40 | combinators known as tacticals. Tactics process sets of 41 | formulas called Goals. 42 |
43 | 44 |45 | When a tactic is applied to some goal G, four different outcomes 46 | are possible. The tactic succeeds in showing G to be satisfiable (i.e., feasible); 47 | succeeds in showing G to be unsatisfiable (i.e., infeasible); produces a sequence of subgoals; or fails. 48 | When reducing a goal G to a sequence of subgoals G1, ..., 49 | Gn, we face the problem of model conversion. 50 | A model converter construct a model for G 51 | using a model for some subgoal Gi. 52 |
53 | 54 |In the following example, we create a goal g consisting of three formulas, and a tactic t 55 | composed of two built-in tactics: simplify and solve-eqs. The tactic simplify 56 | apply transformations equivalent to the ones found in the command simplify. The tactic solver-eqs 57 | eliminate variables using Gaussian elimination. Actually, solve-eqs is not restricted only to linear arithmetic. 58 | It can also eliminate arbitrary variables. Then, combinator Then applies simplify to the input goal 59 | and solve-eqs to each subgoal produced by simplify. In this example, only one subgoal is produced. 60 |
61 | 62 |x, y = Reals('x y')
64 | g = Goal()
65 | g.add(x > 0, y > 0, x == y + 2)
66 | print (g)
67 |
68 | t1 = Tactic('simplify')
69 | t2 = Tactic('solve-eqs')
70 | t = Then(t1, t2)
71 | print (t(g))
72 |
In the example above, variable x is eliminated, and is not present the resultant goal. 76 |
77 | 78 |In Z3, we say a clause is any constraint of the form Or(f_1, ..., f_n). 79 | The tactic split-clause will select a clause Or(f_1, ..., f_n) in the input goal, and split it 80 | n subgoals. One for each subformula f_i. 81 |
82 | 83 |x, y = Reals('x y')
85 | g = Goal()
86 | g.add(Or(x < 0, x > 0), x == y + 1, y < 0)
87 |
88 | t = Tactic('split-clause')
89 | r = t(g)
90 | for g in r:
91 | print (g)
92 |
Z3 comes equipped with many built-in tactics. 98 | The command describe_tactics() provides a short description of all built-in tactics. 99 |
100 | 101 |describe_tactics()
103 |
Z3Py comes equipped with the following tactic combinators (aka tacticals): 107 |
108 | 109 |The following example demonstrate how to use these combinators.
131 | 132 |x, y, z = Reals('x y z')
134 | g = Goal()
135 | g.add(Or(x == 0, x == 1),
136 | Or(y == 0, y == 1),
137 | Or(z == 0, z == 1),
138 | x + y + z > 2)
139 |
140 | # Split all clauses"
141 | split_all = Repeat(OrElse(Tactic('split-clause'),
142 | Tactic('skip')))
143 | print (split_all(g))
144 |
145 | split_at_most_2 = Repeat(OrElse(Tactic('split-clause'),
146 | Tactic('skip')),
147 | 1)
148 | print (split_at_most_2(g))
149 |
150 | # Split all clauses and solve equations
151 | split_solve = Then(Repeat(OrElse(Tactic('split-clause'),
152 | Tactic('skip'))),
153 | Tactic('solve-eqs'))
154 |
155 | print (split_solve(g))
156 |
In the tactic split_solver, the tactic solve-eqs discharges all but one goal. 160 | Note that, this tactic generates one goal: the empty goal which is trivially satisfiable (i.e., feasible)
161 | 162 |The list of subgoals can be easily traversed using the Python for statement.
163 | 164 |x, y, z = Reals('x y z')
166 | g = Goal()
167 | g.add(Or(x == 0, x == 1),
168 | Or(y == 0, y == 1),
169 | Or(z == 0, z == 1),
170 | x + y + z > 2)
171 |
172 | # Split all clauses"
173 | split_all = Repeat(OrElse(Tactic('split-clause'),
174 | Tactic('skip')))
175 | for s in split_all(g):
176 | print (s)
177 |
A tactic can be converted into a solver object using the method solver(). 181 | If the tactic produces the empty goal, then the associated solver returns sat. 182 | If the tactic produces a single goal containing False, then the solver returns unsat. 183 | Otherwise, it returns unknown. 184 |
185 | 186 |bv_solver = Then('simplify',
188 | 'solve-eqs',
189 | 'bit-blast',
190 | 'sat').solver()
191 |
192 | x, y = BitVecs('x y', 16)
193 | solve_using(bv_solver, x | y == 13, x > y)
194 |
In the example above, the tactic bv_solver implements a basic bit-vector solver using equation solving, 198 | bit-blasting, and a propositional SAT solver. Note that, the command Tactic is suppressed. 199 | All Z3Py combinators automatically invoke Tactic command if the argument is a string. 200 | Finally, the command solve_using is a variant of the solve command where the first 201 | argument specifies the solver to be used. 202 |
203 | 204 |In the following example, we use the solver API directly instead of the command solve_using. 205 | We use the combinator With to configure our little solver. We also include the tactic aig 206 | which tries to compress Boolean formulas using And-Inverted Graphs. 207 |
208 | 209 |bv_solver = Then(With('simplify', mul2concat=True),
211 | 'solve-eqs',
212 | 'bit-blast',
213 | 'aig',
214 | 'sat').solver()
215 | x, y = BitVecs('x y', 16)
216 | bv_solver.add(x*32 + y == 13, x & y < 10, y > -100)
217 | print (bv_solver.check())
218 | m = bv_solver.model()
219 | print (m)
220 | print (x*32 + y, "==", m.evaluate(x*32 + y))
221 | print (x & y, "==", m.evaluate(x & y))
222 |
The tactic smt wraps the main solver in Z3 as a tactic.
226 | 227 |x, y = Ints('x y')
229 | s = Tactic('smt').solver()
230 | s.add(x > y + 1)
231 | print (s.check())
232 | print (s.model())
233 |
Now, we show how to implement a solver for integer arithmetic using SAT. The solver is complete 237 | only for problems where every variable has a lower and upper bound. 238 |
239 | 240 |s = Then(With('simplify', arith_lhs=True, som=True),
242 | 'normalize-bounds', 'lia2pb', 'pb2bv',
243 | 'bit-blast', 'sat').solver()
244 | x, y, z = Ints('x y z')
245 | solve_using(s,
246 | x > 0, x < 10,
247 | y > 0, y < 10,
248 | z > 0, z < 10,
249 | 3*y + 2*x == z)
250 | # It fails on the next example (it is unbounded)
251 | s.reset()
252 | solve_using(s, 3*y + 2*x == z)
253 |
257 | Tactics can be combined with solvers. For example, we can apply a tactic to a goal, produced a set of subgoals, 258 | then select one of the subgoals and solve it using a solver. The next example demonstrates how to do that, and how to 259 | use model converters to convert a model for a subgoal into a model for the original goal. 260 |
261 | 262 |t = Then('simplify',
264 | 'normalize-bounds',
265 | 'solve-eqs')
266 |
267 | x, y, z = Ints('x y z')
268 | g = Goal()
269 | g.add(x > 10, y == x + 3, z > y)
270 |
271 | r = t(g)
272 | # r contains only one subgoal
273 | print (r)
274 |
275 | s = Solver()
276 | s.add(r[0])
277 | print (s.check())
278 | # Model for the subgoal
279 | print (s.model())
280 | # Model for the original goal
281 | print (r.convert_model(s.model()))
282 |
288 | Probes (aka formula measures) are evaluated over goals. 289 | Boolean expressions over them can be built using relational operators and Boolean connectives. 290 | The tactic FailIf(cond) fails if the given goal does not satisfy the condition cond. 291 | Many numeric and Boolean measures are available in Z3Py. The command describe_probes() provides the list of 292 | all built-in probes. 293 |
294 | 295 |describe_probes()
297 |
In the following example, we build a simple tactic using FailIf. It also shows that a probe can be applied directly 301 | to a goal.
302 | 303 |x, y, z = Reals('x y z')
305 | g = Goal()
306 | g.add(x + y + z > 0)
307 |
308 | p = Probe('num-consts')
309 | print ("num-consts:", p(g))
310 |
311 | t = FailIf(p > 2)
312 | try:
313 | t(g)
314 | except Z3Exception:
315 | print ("tactic failed")
316 |
317 | print ("trying again...")
318 | g = Goal()
319 | g.add(x + y > 0)
320 | print (t(g))
321 |
Z3Py also provides the combinator (tactical) If(p, t1, t2) which is a shorthand for:
325 | 326 |OrElse(Then(FailIf(Not(p)), t1), t2)327 | 328 |
The combinator When(p, t) is a shorthand for:
329 | 330 |If(p, t, 'skip')331 | 332 |
The tactic skip just returns the input goal. 333 | The following example demonstrates how to use the If combinator.
334 | 335 |x, y, z = Reals('x y z')
337 | g = Goal()
338 | g.add(x**2 - y**2 >= 0)
339 |
340 | p = Probe('num-consts')
341 | t = If(p > 2, 'simplify', 'factor')
342 |
343 | print (t(g))
344 |
345 | g = Goal()
346 | g.add(x + x + y + z >= 0, x**2 - y**2 >= 0)
347 |
348 | print (t(g))
349 |