On this page:
1.1 Disrupt
flip
make-categorical
query
observe!
sample
in-pmf
pmf?
region?
1.2 Bayesian Network
1.3 Dice
1.4 The BLOG Language
1.5 Bentham
flip
observe!
reward!
expected-utility
9.3.0.10

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

Disrupt is an example discrete probabilistic programming language built on top of the RSDD inference engine.
> (define first-coin (flip 0.5))
> first-coin

┌─────┬───────────┐

│Value│Probability│

├─────┼───────────┤

│#t   │0.5        │

│#f   │0.5        │

└─────┴───────────┘

The flip construct returns a Boolean where #t has the given probability. Evaluating first-coin at the REPL prints out its probability distribution.
> (define second-coin (flip 0.5))
> (define both-heads (and first-coin second-coin))
> both-heads

┌─────┬───────────┐

│Value│Probability│

├─────┼───────────┤

│#t   │0.25       │

│#f   │0.75       │

└─────┴───────────┘

Now, both-heads is a Boolean that takes on the value #t when both coins are also #t.
> (observe! (not both-heads))
> first-coin

┌─────┬──────────────────┐

│Value│Probability       │

├─────┼──────────────────┤

│#t   │0.3333333333333333│

│#f   │0.6666666666666666│

└─────┴──────────────────┘

Observing both-heads, specifically that it is #f, changes the probability of first-coin. Conditional on both-heads being #f, the probability first-coin being #t is 1/3.

procedure

(flip p [#:region reg]) → boolean?

  p : (real-in 0 1)
  reg : (or/c region? #f) = #f
Returns a Boolean where #t has probability p and #f has probability (- 1 p). Optionally, flip can be given a region to place the newly created value. See query for details.
> (if (flip 1/2) 'a 'b)

┌─────┬───────────┐

│Value│Probability│

├─────┼───────────┤

│'b   │0.5        │

│'a   │0.5        │

└─────┴───────────┘

procedure

(make-categorical distr) → any/c

  distr : (listof (cons any/c (real-in 0 1)))
Returns each first component of pairs in distr with the probability given by the second component.
> (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
Returns the probability mass function (PMF) associated with e. In other words, query performs nested inference. See in-pmf for an example of how to use the result of this function. Observations are delimited to the dynamic extent of body. After body has finished, any observations executed during body are forgotten.
> (query (flip 1/2))

(pmf [#t 0.5] [#f 0.5])

The #:samples option runs body expression iter times, producing a probabilistic value according to the sampling distribution. The #:region option binds reg-id to the region associated with the query. A region is a first-class representation of dynamic extent. Each application of flip places its return value in a region. A query marginalizes only over variables in its region. After a region ends, the values associated with that region are considered discarded and their probability cannot be computed.
> (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])

procedure

(observe! e) → void?

  e : boolean?
Conditions the current execution on e evaluating to #t.
> (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        │

└─────┴───────────┘

procedure

(sample e) → any/c

  e : any/c
Samples a concrete value from the given probabilistic value.
> (sample (flip 1/2))

#f

procedure

(in-pmf e) → stream?

  e : pmf?
Sequence constructor for PMFs.
> (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

procedure

(pmf? e) → boolean?

  e : any/c
Predicate for PMFs.

procedure

(region? e) → boolean?

  e : any/c
Predicate for regions.

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

Bentham is a more restrictive version of Disrupt with support for calculating expected utility.
> (define x (flip 1/2))
> (define y (flip 1/2))
> (when x (reward! 99))
> (expected-utility (not (and x y)))

33

If x holds, then the program accumulates 99 in utility via the reward! procedure. The query function returns the expected utility of the program conditional on (not (and x y)). From earlier, we know that the conditional probability of x given this expression is 1/3. Therefore, the expected utility is 33.

procedure

(flip p) → boolean?

  p : (real-in 0 1)

procedure

(observe! e) → void?

  e : boolean?
These procedures have the same meaning as they do in Disrupt.

procedure

(reward! e) → void?

  e : number?
Adds e to the amount of utility accumulated by the current program execution.

procedure

(expected-utility e) → number?

  e : any/c
Returns the expected utility given e.