packages feed

safe-coupling-0.1.0.0: README.md

# safe-coupling
Library for relational verification of probabilistic algorithms.

Supports two proving methods:
 - Upper bound _Kantorovich distance_ between two distributions
 - Establish a _boolean relation_ on samples from two distributions (this is stronger)

Includes two larger examples of verification:
 - Stability of stochastic gradient descent (src/SGD) using Kantorovich distance
 - Convergence of temporal difference learning (src/TD0) using boolean relations

## A smaller example (src/Bins/Bins.hs)

This function recursively counts how many times the ball hit the bin after n attempted throws:

    bins :: Double -> Nat -> PrM Nat
    bins _ 0 = ppure 0
    bins p n = liftA2 (+) (bernoulli p) (bins p (n - 1)) 

Throws succeed with probability _p_ which is simulated by `bernoulli p`. The function returns a distribution over natural numbers. When comparing results of two throwers with respective chances of success _p_ and _q > p_, we expect the second thrower to score notably better with the increase of _n_. Formally, we can show that Kantorovich distance between `bins p n` and `bins q n` is upper bounded by _(q - p)·n_.

## Proof (src/Bins/Theorem.hs)

The proof uses four definitions from the library:
 * In the first case, no throws were made. Axiom `pureDist` allows deriving Kantorovich distance between pure expressions. In our case, _0_ and _0_.
 * In the second case, axiom `liftA2Dist` derives Kantorovich distance between the inductive cases. Numeric arguments specify the expected bound in format _a·x + b·y + c_ where _x_ and _y_ are bounds for the second and third arguments of `liftA2` respectively. As the last argument, the axiom requires proof of linearity of plus. It is empty since it can be automatically constructed by an SMT-solver.
 * Axiom `bernoulliDist` upper bounds the distance between calls to `bernoulli` with _q - p_ — this is our _x_. The second upper bound _y_ is provided by a recursive call to our theorem. 
 * A function `distInt` is used to measure the distance between arguments of `liftA2`. In this case, all of them provide integer values. A pre-defined distance between _n_ and _m_ is _|n - m|_ but this allows customization.

```
{-@ binsDist :: p:Prob -> {q:Prob|p <= q} -> n:Nat 
             -> {dist (kant distInt) (bins p n) (bins q n) <= n * (q - p)} / [n] @-}
binsDist :: Prob -> Prob -> Nat -> ()
binsDist p q 0 = pureDist distInt 0 0 
binsDist p q n
= liftA2Dist d d d 1 (q - p) 1 ((n - 1) * (q - p)) 0
    (+) (bernoulli p) (bins p (n - 1)) 
    (+) (bernoulli q) (bins q (n - 1))
    (bernoulliDist d p q)
    (binsDist p q (n - 1))
    (\_ _ _ _ -> ())
where 
    d = distInt
```

This concludes the mechanized proof of the boundary _(q-p)·n_.

## Installation
1. Install stack https://docs.haskellstack.org/en/stable/install_and_upgrade/

2. Compile the library and case studies

        $ cd safe-coupling
        $ stack install --fast
        ...
        Registering library for safe-coupling-0.1.0.0..


3. Run unit tests on executable case studies

        $ stack test
        ...                          
        test/Spec.hs
        Spec
            Bins
            mockbins 1 it:     OK
            mockbins 2 it:     OK
            bins 1 it:         OK
            bins 2 it:         OK (0.02s)
            exp dist mockbins: OK (0.12s)
            SGD
            sgd:               OK
            TD0
            td0 base:          OK
            td0 simple:        OK

        All 8 tests passed (0.12s)

        safe-coupling> Test suite safe-coupling-test passed
        Completed 2 action(s).


In case of errors try

    $ stack clean