CS195Y Lecture 31

4/21/2017


Foundations of Prescriptive Analytics (CSCI2951-)

Serdar Kadioglu came in and went over the Foundations of Prescriptive Analytics
course, which builds off of this one in a lot of ways. Serdar also leads a research group at Oracle studying constraint solvers and how to scale them and use them for optimization problems. Prescriptive analytics is the step beyond data analysis -
taking predictions from machine learning and the like, and figuring out what to do with them. Students implement constraint
solvers of their own, and all the projects have realtime leaderboards to anonymously compare results.

SMT Solvers

Picking up from last time, SMT solvers are a whole area of analytics we haven't touched.
We can finally put this SMT stuff in context! And learn why we're suffering through Racket!

#lang rosette/safe

(define-symbolic anint integer?) ; We've seen this kind of thing before

(define anint2 10) ; But this is Racket! we can define real variables

But what's the difference between the two?
The symbolic variable represents some kind of goal - it'll be populated by the solver. The Racket variable
is concrete - it's some value that the program decides.

(define (plusone x)
    (+ x 1))

We now have this function, and we can call it on any value:

> (plusone anint2)
11

Why does 11 happen? Because there's an implicit value associated with anint2 and we can fully evaluate the expression.

But what in the world does (plusone anint) mean?

You can think of Rosette as a "lifted" version of Racket. There's the Racket language, and then there's this notion
of symbolic values on top. That means that Rosette knows what to do when we apply a function to a symbolic value.
In particular, we get (+ 1 anint). It's not quite a lambda because the solver's understanding it, not Racket. But it
is a formula we can put into a SMT solver.

Let's write a predicate that will evaluate to true or false:

(define mypredicate (<= (plusone anint) 0))

Is this true? Be the solver and come up with an instance. If anint is 1, (plusone anint) is 2, which isn't less than
0.

Now that I've got something that the SMT solver can understand, I can ask it for an instance:

(solve (assert mypredicate))

It returns an instance (model [anint -1]) where the predicate succeeds.

The solver can also tell us if this always holds:

(verify (assert mypredicate))

Now we get (model [anint 0]), which is a counterexample.

Do these constructs remind you of anything in Alloy? It's like run and check, but with a richer language on top.

This is something that might come up in your homework:

(verify (assert (forall (list anint) mypredicate)))

This will do universal quantification over the variables in the list (list aninit) like you'd expect. However, it'll
give you an empty (model) as a counterexample. The solver is only giving you bindings for free variables, so when
we do this quantification, aninit isn't unbound anymore.

Now I want to talk about something a bit more interesting than verification. All semester, we've been talking about
properties and verification, verification, verification. But now we have a programming language!

Let's write a function:

(require rosette/lib/synthax)

(define (f x)
    ((choose + - *) x (??)))

(??) will tell the solver to "find me an integer"

Q: What if I used (??) with something that didn't operate on integers?
A: It would probably fail silently. This is a shorthand for the full syntax, which requires a type. The default is
an integer, so I can use it here.

(choose + - *) tells the solver to pick one of these three operations that works.

So, now we have this function with two holes in it. This is exciting! The idea of synthesis, where we have the solver
generate parts of our program, is going to start making its way into programming languages.

Now we can get the solver to give us our program!

(define goal (implies (>= anint 0) (< (f aninit) 2)))

; Unfortunately, this part is just boilerplate to get the solver to give us a function
(generate-forms
    (synthesize #:forall (list anint)
                #:guarantee (assert goal)))

We can be the solver for a minute and come up with some functions that work:

Q: What are those #'s for?
A: That's Racket syntax for optional arguments. We're telling it to quantify over all anint and ensure that goal
holds.

The solver gives us (f x) (- x -16). How do we feel about this solution? It doesn't work! We can use 8 as a
counterexample - it gives us 24. But right now, the solver doesn't understand what 24 is! We're using the default
bitwidth of 5, so it doesn't understand what numbers greater than 15 are.

We can turn off the bitwidth with (current-bitwidth #f), but then the solver doesn't finish.

Q: Is there a way to tell Rosette that f should hold for any number but have it only check some?
A: SMT solvers can do that, but I don't know if Rosette exposes it.

Q: Why can we sometimes quantify over all integers but not always?
A: It has to do with the transformations Rosette makes, which is what I'll get to next.

Why are we going through all these horrible worlds where sometimes we get a wrong answer and sometimes it doesn't
finish? Synthesis is hard, and you sometimes have to massage the solver to get it to work. That's what I'm trying to show
here.

So, how does this work? Rosette translates this into more things to solve for. (??) is asking to find us an integer,
so we can use an integer variable. We can use two boolean variables for choose - one for +, one for -, and then
implicitly * if both are false.

We can see what variables Rosette generates for us and what their bindings are:

(synthesize #:forall (list anint)
            #:guarantee (assert goal))

This gives us two boolean variables and an integer variable, like we expected. We can also see what (f anint) looks
like - it's this big expression in terms of all these generated variables.