1 Examples
Roulette enables programmers to build languages that require the use of inference engines. This section gives examples of languages built on top of Roulette to show concretely how this works.
1.1 Disrupt
| #lang roulette/example/disrupt | package: roulette |
| #lang roulette/example/disrupt/safe | |
> (define first-coin (flip 0.5)) > first-coin
┌─────┬───────────┐
│Value│Probability│
├─────┼───────────┤
│#t │0.5 │
│#f │0.5 │
└─────┴───────────┘
> (define second-coin (flip 0.5)) > (define both-heads (and first-coin second-coin)) > both-heads
┌─────┬───────────┐
│Value│Probability│
├─────┼───────────┤
│#t │0.25 │
│#f │0.75 │
└─────┴───────────┘
> (observe! (not both-heads)) > first-coin
┌─────┬──────────────────┐
│Value│Probability │
├─────┼──────────────────┤
│#t │0.3333333333333333│
│#f │0.6666666666666666│
└─────┴──────────────────┘
> (if (flip 1/2) 'a 'b)
┌─────┬───────────┐
│Value│Probability│
├─────┼───────────┤
│'b │0.5 │
│'a │0.5 │
└─────┴───────────┘
> (make-categorical `((a . 1/3) (b . 1/3) (c . 1/3)))
┌─────┬──────────────────┐
│Value│Probability │
├─────┼──────────────────┤
│'c │0.3333333333333333│
│'b │0.3333333333333333│
│'a │0.3333333333333333│
└─────┴──────────────────┘
syntax
(query maybe-option ... body ...+)
maybe-option =
| #:samples iter | #:region reg-id
> (query (flip 1/2)) (pmf [#t 0.5] [#f 0.5])
> (let ([x (flip 1/2)]) (query x))
┌──────────────┬───────────┐
│Value │Probability│
├──────────────┼───────────┤
│(pmf [#t 1.0])│0.5 │
│(pmf [#f 1.0])│0.5 │
└──────────────┴───────────┘
> (query (query (flip 1/2))) (pmf [(pmf [#t 0.5] [#f 0.5]) 1.0])
> (query (let ([x (flip 1/2)]) (query x))) (pmf [(pmf [#t 1.0]) 0.5] [(pmf [#f 1.0]) 0.5])
> (query #:region r (query (flip 1/2 #:region r))) (pmf [(pmf [#t 1.0]) 0.5] [(pmf [#f 1.0]) 0.5])
> (define x (flip 1/2)) > (define y (flip 1/2)) > (and x y)
┌─────┬───────────┐
│Value│Probability│
├─────┼───────────┤
│#t │0.25 │
│#f │0.75 │
└─────┴───────────┘
> (observe! x) > (and x y)
┌─────┬───────────┐
│Value│Probability│
├─────┼───────────┤
│#t │0.5 │
│#f │0.5 │
└─────┴───────────┘
> (define-syntax-rule (expectation e) (for/all ([pmf (query e)]) (for/sum ([(val prob) (in-pmf pmf)]) (* val prob)))) > (expectation (if (flip 1/2) 5 10)) 7.5
1.2 Bayesian Network
| #lang roulette/example/bn | package: roulette |
A Bayesian network is a common kind of probabilistic graphical model that expresses conditional dependencies between variables. These networks are often described in the Bayesian Interchange Format (BIF). There are many such networks freely available online, including in the BNLearn repository. The BN language interprets Bayesian networks written in the BIF.
"cancer.rkt"
#lang roulette/example/bn
variable Pollution { type discrete [ 3 ] { low, medium, high }; }
variable Smoker { type discrete [ 2 ] { True, False }; }
variable Cancer { type discrete [ 2 ] { True, False }; }
variable Xray { type discrete [ 2 ] { positive, negative }; }
variable Dyspnoea { type discrete [ 2 ] { True, False }; }
probability ( Pollution ) {
table 0.5, 0.4, 0.1;
}
probability ( Smoker ) {
table 0.3, 0.7;
}
probability ( Cancer | Pollution, Smoker ) {
(low, True) 0.03, 0.97;
(medium, True) 0.03, 0.97;
(high, True) 0.05, 0.95;
(low, False) 0.001, 0.999;
(medium, False) 0.001, 0.999;
(high, False) 0.02, 0.98;
}
probability ( Xray | Cancer ) {
(True) 0.9, 0.1;
(False) 0.2, 0.8;
}
probability ( Dyspnoea | Cancer ) {
(True) 0.65, 0.35;
(False) 0.3, 0.7;
}
This Bayesian network is the famous "cancer" example that describes the relationship between several variables and whether a patient has lung cancer. Running this file in DrRacket allows one to explore the network with all the constructs from Disrupt in the interactions area. Additionally, the module exports all the variables, making them available to Disrupt programs via require.
> Cancer
┌──────┬────────────────────┐
│Value │Probability │
├──────┼────────────────────┤
│'True │0.011629999999999998│
│'False│0.98837 │
└──────┴────────────────────┘
> (observe! (equal? Xray 'positive)) > Cancer
┌──────┬───────────────────┐
│Value │Probability │
├──────┼───────────────────┤
│'True │0.05028802590551598│
│'False│0.949711974094484 │
└──────┴───────────────────┘
This interaction shows the posterior probability that a patient has lung cancer given that they have a positive X-ray result. Notice that the posterior probability of Cancer only marginally increases with a positive test result. Many people are surprised by this phenomenon, which is known as the base rate fallacy.
1.3 Dice
| #lang roulette/example/dice | package: roulette |
Dice is a small discrete probabilistic programming language. It was the first functional language to use knowledge-compilation-based probabilistic inference.
"noisy-or.rkt"
#lang roulette/example/dice
let n0 = flip 0.5 in
let n4 = flip 0.5 in
let n1 = if n0 then flip 0.8 else flip 0.1 in
let n21 = if n0 then flip 0.8 else flip 0.1 in
let n22 = if n4 then flip 0.8 else flip 0.1 in
let n33 = if n4 then flip 0.8 else flip 0.1 in
let n2 = (n21 || n22) in
let n31 = if n1 then flip 0.8 else flip 0.1 in
let n32 = if n2 then flip 0.8 else flip 0.1 in
let n3 = (n31 || (n32 || n33)) in
n3
Most programs should run out of the box, but there are a few limitations. A small number of operations are not supported. The implementation does not perform typechecking, so evaluating an ill-typed program is undefined behavior. Functions are not compiled modularly, and evaluation follows the "eager" strategy.
1.4 The BLOG Language
| #lang roulette/example/blog | package: roulette |
BLOG is a small probabilistic programming language. This implementation supports a subset of BLOG, built on roulette/example/disrupt. Simple BLOG programs should run without modification.
"blog-example.rkt"
#lang roulette/example/blog
type Person;
distinct Person Alice, Bob;
fixed Boolean AlwaysTrue = true;
fixed Boolean IsAlice(Person p) = p == Alice;
random Real prob ~ Categorical({0.1 -> 0.5, 0.9 -> 0.5});
random Boolean Smokes(Person p) ~ BooleanDistrib(prob);
random Boolean Healthy(Person p) ~
if Smokes(p) then BooleanDistrib(0.4)
else BooleanDistrib(0.9);
random Boolean Infected(Person p) ~ BooleanDistrib(prob);
random Boolean ExtraHealthy(Person p) ~
if (exists Person q Infected(q)) then BooleanDistrib(0.2)
else BooleanDistrib(0.8);
obs Smokes(Alice) = true;
query (ExtraHealthy(Alice));
Here are the supported features:
Declare types with type and objects with distinct. Indexed declarations such as distinct Person P[3]; create three distinct objects, accessible as P[0], P[1], and P[2]. Indices may be arithmetic expressions or finite random values.
The fixed declarations define constants or functions with parameters. The random declarations use ~ for a distribution or = for a deterministic dependency. Repeated calls to a random function with the same arguments reuse the same random value.
BooleanDistrib(p) returns a Boolean. Bernoulli(p) returns 1 with probability p and 0 otherwise. UniformInt(lo, hi) gives equal probability to every integer between the bounds, including both endpoints. Categorical accepts a map such as Categorical({0 -> 0.25, 1 -> 0.75}) where the probabilities must sum to one. Distribution parameters and the choice of distribution may depend on finite random values.
The arithmetic operators is +, -, *, /, ^, %, and unary negation. The comparisons operators are ==, !=, <, <=, >, and >=. The boolean operators are !, &, |, and =>. The forall and exists quantifiers range over the objects of a declared type.
Conditionals are written as if condition then expression else expression. Case expressions use case value in {key -> result, ...}. Branches may return values or distributions. An omitted else or an unmatched case returns null.
Supply evidence with obs expression = expression; and query an expression with query expression;.
Comments use // or non-nesting /* ... */.
Here are the current limitations:
Object populations are fixed and finite. Number statements for unknown populations and origin functions are not supported. Random-function arguments must range over declared object types. Built-in domains such as Integer are not supported as random-function argument domains.
Random functions are eagerly instantiated over the objects declared so far. Objects and parent random functions must precede their dependents. Objects declared later are not added to an existing random function’s domain. Type annotations are not fully checked.
Sets and set comprehensions, general arrays and matrices, strings, characters, and timestep literals are not supported. Indexed distinct objects are supported, but are not general arrays. Distributions other than the four listed above are not provided.
1.5 Bentham
| #lang roulette/example/bentham | package: roulette |
> (define x (flip 1/2)) > (define y (flip 1/2)) > (when x (reward! 99)) > (expected-utility (not (and x y))) 33
procedure
(expected-utility e) → number?
e : any/c