You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
markle1999 b72c72f890 v1.0 2 months ago
ex1 v1.0 2 months ago
ex2 added indiv assignment descriptions 3 months ago
ex3 added indiv assignment descriptions 3 months ago
ex4 added indiv assignment descriptions 3 months ago
farm_animals Added simple farm animal example 2 months ago
password_cracking Added password cracking example 2 months ago
README.md Update fixed link to repo in README 2 months ago
practical_assignments.pdf added assignment description 3 months ago

README.md

Logic and Computability - Practical Bonus Assignments 2021

This repository will be used for submissions of Logic and Computability practical assignments in the summer semester 2021.

Programming Exercises

This year you will have the chance to solve up to four different programming exercises to achieve some bonus points. Our main goal is to get you acquainted with the tools and how to use them to solve interesting problems. To make things easier, we have prepared a skeleton for each programming exercise. We have already implemented most of the scaffolding code so that you only need to do the encoding into SMT. We have marked all of these locations in the code with short # todo descriptions, you should not need to modify anything else. There are also several hints throughout the skeleton where we suggest the best approach of doing the task. We marked them with # hint. Your code should not include any additional packages, and we will test them in an environment where only the SMT solver z3 is available (apart for some colorizing packages, which will be listed in requirements.txt).

We will provide you with the skeleton for the programming exercises so that you only have to do the SMT encoding and testing. We will push them to our repository. You can then pull changes from that repository by setting it as your upstream. Below is a short example of how you can do this.

  1. First, clone your submission repository and enter its root directory.
  2. Register our repository as the upstream:
# git remote add upstream https://extgit.iaik.tugraz.at/scos/scos.teaching/lub2021-practicals.git
git remote add upstream https://git.pranger.xyz/sp/lub2021-practical.git
  1. Then, when we release a new exercise, pull it into your repository:
git pull upstream main
  1. Solve the exercise and push it into your main branch:
git push origin main

Your exercises will be tested! Make sure to write a few test cases for yourself. In general, we also provide you with at least one test case, and if it works correctly, you know that you did an excellent job and will probably get all points.

Basics of Z3

The exercises we will discuss in this lecture are only going to cover a small subset of the possibilities of solving problems with Z3. If you are interested in more background or need to look into some details we suggest you to take a look here.

Types, Sorts and Internal Representation

First of all we have to cover the different syntax styles Z3 covers. Z3 facilitates the SMT-LIB standard (you can read more here, but this is definitely not needed for this course) and we therefor need to use a prefix notation in our statements. This means that a statement like

p or q

needs to be written like such:

Or(p,q)

If you search the internet for examples you will probably stumble upon a syntax like this:

(or p q)
(implies r s)
etc...

which is just a different way of expressing the statement in prefix notation.

The first example that we saw above can be used with the python library z3-solver (make sure that you pip install this and not the z3 package!).

A very small example program could like like this:

# coding: utf-8
import os, sys
from z3 import *


        # v-- internal Z3 representation
x = Bool('x')
#^-- python variable

        # v-- internal Z3 representation
gamma = Bool('g') # possible, but not advisable
#^-- python variable


# Declare a solver with which we can do some work
solver = Solver()

p = Bool('p')
qu = Bool('q')
r = Bool('r')

# p -> q, r = ~q, ~p or r
# Add constraints
solver.add(Implies(p,qu))
solver.add(r == Not(qu))
solver.add(Or(Not(p), r))
# solver.add(r == q)

res = solver.check()
if res != sat:
    print("unsat")
    sys.exit(1)

m = solver.model()
for d in m.decls():
    print("%s -> %s" % (d, m[d]))

Workflow

In a python program we usually follow this workflow:

  • import z3,
  • declare needed variables of specific Sort (this is the word we use for types in z3),
  • declare a solver: solver = Solver() and
  • add constraints for the declared variables to the solver.
  • After adding all the constraints we tell the solver to try to check() for satisfiability and if the solver tells us that the model is satisfiable we may
  • print the model.

We can now dissect the example program from above and take a look at which steps we have taken:

At first we import z3 from z3 import *.

We then need to declare variables:

        # v-- internal Z3 representation
x = Bool('x')
#^-- python variable

        # v-- internal Z3 representation
gamma = Bool('g') # possible, but not advisable
#^-- python variable

Lets have a closer look: In z3 we declare variable of some kind of Sort just like you would declare variable of some type in other language. z3 has a BoolSort, IntSort, RealSort and some more. Our example from above only covers propositional logic so far so we have declared our variables of type Bool.

We have to distinct between z3 variables and python variables. The code block above gives you the answer to this distinction and as the second examples tells you, you may give these two different names, but this is not advisable since it will probably only confuse you and others which need to read your code.

In order to check for satisfiability we are going to need a solver: solver = Solver().

In the next step we will add some constraints to the solver:

p = Bool('p')
qu = Bool('q')
r = Bool('r')

# p -> q, r = ~q, ~p or r
# Add constraints
solver.add(Implies(p,qu))
solver.add(r == Not(qu))
solver.add(Or(Not(p), r))

Adding constraints is done with the solvers add() method. Remember that the constraints have to be expressed in prefix notation.

At the very end we have to tell the solver to check whether our constraints are satisfiable, is they are not we simply exit the program:

res = solver.check()
if res != sat:
    print("unsat")
    sys.exit(1)

Our example is satisfiable so in the end we print the model by asking the solver for the created model and print all of the variables which have an associated value in the created model:

m = solver.model()
for d in m.decls():
    print("%s -> %s" % (d, m[d]))

> q -> True
> p -> False
> r -> False

This can be done with solver.model().decls() which resembles a python dictionary. If you know your model is going to be small you may just simply evaluate each variable individually:

m = solver.model()
print("qu: " + str(m.eval(qu)))
print("p: " + str(m.eval(p)))
print("r: " + str(m.eval(r)))

> qu: True
> p: False
> r: False

Note the difference between the naming of the internal representation of variables and the python variables.

First Order Logic Types and Constraints

So far we have only touched propositional logic, but z3 is an SMT-solver so lets expand our knowledge to use these funtionalities.

from z3 import Solver, Int
from z3 import sat as SAT

x, y = Int('x'), Int("%s" % "y")  # create integer variables

solver = Solver()                 # create a solver
solver.add(x < 6 * y)             #   assert  x < 6y
solver.add(x % 2 == 1)            #   assert  x == 1 mod 2
solver.add(sum([x,y]) == 42)      #   assert  x + y = 42

if solver.check() == SAT:         # check if satisfiable
    m = solver.model()            # retrieve the solution
    print(m[x] + m[y])            # print symbolic sum
    print(m.eval(x) + m.eval(y))  # use eval to print
    # hint: use m[x].as_long() to get python integers
    for d in m.decls():
        print("%s -> %d" % (d, m[d].as_long()))

> 35 + 7
> 35 + 7
> x -> 35
> y -> 7

We can tell from the example above that creating z3 integer variables follows the same principle as with booleans. Additionally we can tell that the strings that we pass allow typical python manipulations, so for example pythons %-formatting.

Python expressions are valid in constraints too, for example using a built-in functio: solver.add(sum([x,y]) == 42).

With this we can tell that using z3 and python interleaved is providing us with a powerful tool. We are going to discuss two simple examples together which will be linked in this README.

Examples Discussion

We have prepared two videos discussing the topics from above where we also discuss some additional example programs.

Custom Datatypes and Sorts

Coming soon...

Uninterpreted Functions

Coming soon...