hanseitest

Fri May 16 11:53:59+0200 2025

Creative Commons LicenseCreative Commons LicenseCreative Commons License

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License

Table of contents
  1. Introduction
  2. Implementation
  3. test/procc/coin-model: pass
  4. test/procc/coin-model/when: pass
  5. test/procc/grass-model: pass
  6. test/procc/grass-model/complete: pass
  7. test/uniform/range: pass
  8. test/geometric: pass

Tests summary

scheme code
((ran 6) (failed 0))

1. Introduction

Quoting from [1]:


Broadly speaking, probabilistic programming languages are to express computations with degrees of uncertainty, which comes from the imprecision in input data, lack of the complete knowledge or is inherent in the domain. More precisely, the goal of probabilistic programming languages is to represent and automate reasoning about probabilistic models, which describe uncertain quantities -- random variables -- and relationships among them.

Here we focus on the dsl Hansei [2] and the corresponding published paper [3], by Oleg Kiselyov and Chung-chieh Shan; moreover, an application to logic can be found in [4].

2. Implementation

The first implementation of our language uses the probability monad that represents a stochastic computation as a lazy search tree. That is, our implementation uses the type constructor pV defined below.

ocaml code
type 'a vc = V of 'a | C of (unit -> 'a pV) and 'a pV = (prob * 'a vc) list
Each node in a tree is a weighted list of branches. The empty list denotes failure, and a singleton list [(p, V v)] denotes a deterministic successful outcomevwith the probability mass p. A branch of the form V v is a leaf node that describes a possible successful outcome, whereas a branch of the form C thunk is not yet explored. The intended meaning of a search tree of type 'a pV is a discrete probability distribution over values of type 'a.

scheme code
(module
  hansei
  *
  (import
    scheme
    (chicken base)
    (chicken continuation)
    (chicken pretty-print)
    (chicken fixnum)
    (chicken sort)
    srfi-69
    aux)
  (define op/times (make-parameter *))
  (define op/plus (make-parameter +))
  (define op/subtract (make-parameter -))
  (define op/divide (make-parameter (λ (m n) (exact->inexact (/ m n)))))
  (define op/greater (make-parameter >))
  (define-syntax
    probcc-τ
    (syntax-rules () ((_ p body ...) `((C ,(τ body ...)) ,p))))
  (define (probcc-value p v) `((V ,v) ,p))
  (define-syntax
    let1/probccpair
    (syntax-rules
      ()
      ((_ ((slot p) probpairexpr) body ...)
       (let* ((probpair probpairexpr)
              (slot (car probpair))
              (p (cadr probpair)))
         body
         ...))))
  (define-syntax
    cond/probccslot
    (syntax-rules
      (V C)
      ((_ slotexpr ((V v) vbody ...) ((C t) cbody ...))
       (let* ((slot slotexpr) (flag (car slot)) (payload (cadr slot)))
         (cond ((equal? flag 'V) (let1 (v payload) vbody ...))
               ((equal? flag 'C) (let1 (t payload) cbody ...))
               (else (error `(not a probability slot ,slot))))))))
  (define (probcc-explore maxdepth choices)
    (letrec ((times (op/times))
             (plus (op/plus))
             (loop (λ (p depth down choices ans susp)
                       (cond ((null? choices) susp)
                             (else
                              (let1/probccpair
                                ((slot pt) (car choices))
                                (let* ((p*pt (times p pt))
                                       (A (λ (w) (plus w p*pt)))
                                       (rest (cdr choices)))
                                  (cond/probccslot
                                    slot
                                    ((V v)
                                     (hash-table-update!/default ans v A 0)
                                     (loop p depth down rest ans susp))
                                    ((C t)
                                     (cond (down
                                            (loop p
                                                  depth
                                                  down
                                                  rest
                                                  ans
                                                  (loop p*pt
                                                        (add1 depth)
                                                        (< depth maxdepth)
                                                        (t)
                                                        ans
                                                        susp)))
                                           (else
                                            (let1 (s (cons (probcc-τ p*pt (t)) susp))
                                                  (loop p depth down rest ans s)))))))))))))
      (let* ((ans (make-hash-table))
             (susp (loop 1 0 #t choices ans '()))
             (f (λ (v p l) (cons (probcc-value p v) l)))
             (folded (hash-table-fold ans f susp))
             (greater (op/greater)))
        (sort folded (λ (a b) (greater (cadr a) (cadr b)))))))
  (define (probcc-next-value choices)
    (cond ((null? choices) '())
          (else
           (let1/probccpair
             ((slot pt) (car choices))
             (cond/probccslot
               slot
               ((V v) choices)
               ((C t)
                (let1 (times (op/times))
                      (probcc-next-value
                        (append
                          (cdr choices)
                          (letmap
                            ((pair (t)))
                            (let1/probccpair
                              ((slot p) pair)
                              `(,slot ,(times p pt)))))))))))))
  (define (probcc-normalize choices)
    (let* ((divide (op/divide))
           (plus (op/plus))
           (tot (foldr (λ (each t) (plus t (cadr each))) 0 choices))
           (N (λ (each) (list (car each) (divide (cadr each) tot)))))
      (map N choices)))
  (define (probcc-distribution distribution)
    (letcc/shift
      k
      (letmap
        ((pair distribution))
        (letcar&cdr (((v p) pair)) (probcc-τ (car p) (k v))))))
  (define (probcc-reflect choices)
    (letcc/shift
      k
      (letrec ((make-choices (λ (pv) (map f pv)))
               (f (λ (probpair)
                      (let1/probccpair
                        ((slot p) probpair)
                        (cond/probccslot
                          slot
                          ((V v) (probcc-τ p (k v)))
                          ((C t) (probcc-τ p (make-choices (t)))))))))
        (make-choices choices))))
  (define (probcc-impossible) (probcc-distribution '()))
  (define (probcc-unit v) (list (probcc-value 1 v)))
  (define (probcc-bernoulli t f p)
    (probcc-distribution `((,t ,p) (,f ,((op/subtract) 1 p)))))
  (define (probcc-coin p) (probcc-bernoulli #t #f p))
  (define (probcc-uniform n)
    (cond ((equal? n 1) 0)
          ((> n 1)
           (letrec ((p (/ 1 n))
                    (plus (op/plus))
                    (subtract (op/subtract))
                    (loop (λ (pacc acc i)
                              (if (zero? i)
                                (probcc-distribution
                                  (cons `(,i ,(subtract 1 pacc)) acc))
                                (loop (plus pacc p) (cons `(,i ,p) acc) (sub1 i))))))
             (loop 0 '() (sub1 n))))
          (else (error `(non-positive count ,n)))))
  (define (probcc-uniform/range low high)
    (+ low (probcc-uniform (add1 (- high low)))))
  (define (probcc-geometric p s f)
    (letrec ((subtract (op/subtract))
             (loop (λ (n)
                       (list (probcc-τ p (list (probcc-value 1 (cons s n))))
                             (probcc-τ (subtract 1 p) (loop (cons f n)))))))
      (probcc-reflect (loop '()))))
  (define-syntax
    probcc-when
    (syntax-rules
      ()
      ((_ test body ...) (cond (test body ...) (else (probcc-impossible))))))
  (define (probcc-reify/0 model) (resetcc (probcc-unit (model))))
  (define ((probcc-reify depth) model)
    (probcc-explore depth (probcc-reify/0 model)))
  (define probcc-reify/exact/a (probcc-reify +inf.0))
  (define-syntax
    probcc-reify/exact
    (syntax-rules () ((_ body ...) (probcc-reify/exact/a (τ body ...)))))
  (define (probcc-variable-elimination f)
    (λ args (probcc-reflect (probcc-reify/exact (apply f args)))))
  (define-syntax
    λ-probcc-bucket
    (syntax-rules
      ()
      ((_ args body ...)
       (letrec ((f (λ args body ...))
                (bucket (λ-memo bargs (probcc-reify/exact (apply f bargs)))))
         (o probcc-reflect bucket)))))
  (define (probcc-leaves choices)
    (letrec ((L (λ (choices count)
                    (let1 (F (λ (probpair acc)
                                 (let1/probccpair
                                   ((slot p) probpair)
                                   (cond/probccslot
                                     slot
                                     ((V v) (add1 acc))
                                     ((C t) (L (t) acc))))))
                          (foldr F count choices)))))
      (L choices 0)))
  (define (probcc-dfs choices)
    (letmap
      ((probpair choices))
      (let1/probccpair
        ((slot p) probpair)
        (cond/probccslot
          slot
          ((V v) (list probpair))
          ((C t)
           (apply append
                  (probcc-dfs
                    (letmap
                      ((inner (t)))
                      (let1/probccpair
                        ((slot pi) inner)
                        (list slot ((op/times) p pi))))))))))))

3. test/procc/coin-model: pass

Joint distribution of tossing two biased coins, where head has probability 0.6 to appear.

scheme code
(define (test/procc/coin-model _)
  (⊦= '(((V ((x #t) (y #t))) 0.36)
          ((V ((x #t) (y #f))) 0.24)
          ((V ((x #f) (y #t))) 0.24)
          ((V ((x #f) (y #f))) 0.16))
        (probcc-normalize
          (probcc-reify/exact
            (let* ((p 0.6) (x (probcc-coin p)) (y (probcc-coin p)))
              `((x ,x) (y ,y)))))))
scheme code
((eta 0.0) (memory #(6291456 1066528 1048576)) (stdout "") (stderr ""))

4. test/procc/coin-model/when: pass

Slightly variation of the previous test, here it has been observed that at least one head appeared.

scheme code
(define (test/procc/coin-model/when _)
  (⊦= '(((V (#t #t)) 0.428571428571429)
          ((V (#t #f)) 0.285714285714286)
          ((V (#f #t)) 0.285714285714286))
        (probcc-normalize
          (probcc-reify/exact
            (let* ((p 0.6) (x (probcc-coin p)) (y (probcc-coin p)))
              (probcc-when (or x y) (list x y)))))))
scheme code
((eta 0.0) (memory #(6291456 1067728 1048576)) (stdout "") (stderr ""))

5. test/procc/grass-model: pass


The canonical example is the grass model, with three random variables representing the events of rain, of a switched-on sprinkler and wet grass. The (a priori) probabilities of the first two events are judged to be 30% and 50% correspondingly. Probabilities are non-negative real numbers that may be regarded as weights on non-deterministic choices. Rain almost certainly (90%) wets the grass. The sprinkler also makes the grass wet, in 80% of the cases. The grass may also be wet for some other reason. The modeler gives such an unaccounted event 10% of a chance. This model is often depicted as a directed acyclic graph (DAG) -- so-called Bayesian, or belief network -- with nodes representing random variables and edges conditional dependencies. Associated with each node is a distribution (such as Bernoulli distribution: the flip of a biased coin), or a function that computes a distribution from the node's inputs (such as the noisy disjunction nor). The sort of reasoning we wish to perform on the model is finding out the probability distribution of some of its random variables. For example, we can work out from the model that the probability of the grass being wet is 60.6%. Such reasoning is called probabilistic inference. Often we are interested in the distribution conditioned on the fact that some random variables have been observed to hold a particular value. In our example, having observed that the grass is wet, we want to find out the chance it was raining on that day.

The solution to this problem shows the probability distribution of raining, provided that has been observed a wet grass:

scheme code
(((V (rain #f)) 0.53152855727963) ((V (rain #t)) 0.46847144272037))
as required. The following test defines and captures this problem.

scheme code
(define (test/procc/grass-model _)
  (define result
    (probcc-reify/exact
      (let* ((rain (probcc-coin 0.3))
             (sprinkler (probcc-coin 0.5))
             (grass-is-wet
               (or (and (probcc-coin 0.9) rain)
                   (and (probcc-coin 0.8) sprinkler)
                   (probcc-coin 0.1))))
        (probcc-when grass-is-wet `(rain ,rain)))))
  (⊦= (list (probcc-value 0.322 '(rain #f)) (probcc-value 0.2838 '(rain #t)))
        result))
scheme code
((eta 0.0) (memory #(6291456 1069512 1048576)) (stdout "") (stderr ""))

6. test/procc/grass-model/complete: pass

If we remove the assumption that has been observed a wet grass, then we have the joint probability distribution of all variables:

scheme code
(define (test/procc/grass-model/complete _)
  (define result
    (probcc-reify/exact
      (let* ((rain (probcc-coin 0.3))
             (sprinkler (probcc-coin 0.5))
             (grass-is-wet
               (or (and (probcc-coin 0.9) rain)
                   (and (probcc-coin 0.8) sprinkler)
                   (probcc-coin 0.1))))
        `((rain ,rain) (sprinkler ,sprinkler) (grass-is-wet ,grass-is-wet)))))
  (⊦= '(((V ((rain #f) (sprinkler #f) (grass-is-wet #f))) 0.315)
          ((V ((rain #f) (sprinkler #t) (grass-is-wet #t))) 0.287)
          ((V ((rain #t) (sprinkler #t) (grass-is-wet #t))) 0.1473)
          ((V ((rain #t) (sprinkler #f) (grass-is-wet #t))) 0.1365)
          ((V ((rain #f) (sprinkler #t) (grass-is-wet #f))) 0.063)
          ((V ((rain #f) (sprinkler #f) (grass-is-wet #t))) 0.035)
          ((V ((rain #t) (sprinkler #f) (grass-is-wet #f))) 0.0135)
          ((V ((rain #t) (sprinkler #t) (grass-is-wet #f))) 0.0027))
        result)
  (⊦= (probcc-normalize result) result))
scheme code
((eta 0.0) (memory #(6291456 1071072 1048576)) (stdout "") (stderr ""))

7. test/uniform/range: pass

scheme code
(define (test/uniform/range _)
  (⊦= '(((V 1) 1/8)
          ((V 2) 1/8)
          ((V 3) 1/8)
          ((V 4) 1/8)
          ((V 5) 1/8)
          ((V 6) 1/8)
          ((V 7) 1/8)
          ((V 8) 1/8))
        (sort (probcc-reify/exact (probcc-uniform/range 1 8))
              (λ (a b) (< (cadr (car a)) (cadr (car b)))))))
scheme code
((eta 0.0) (memory #(6291456 1072240 1048576)) (stdout "") (stderr ""))

8. test/geometric: pass

scheme code
(define (test/geometric _)
  (define result ((probcc-reify 5) (τ (probcc-geometric 0.85 's 'f))))
  (define t6 (cadr (car (sixth result))))
  (define t7 (cadr (car (seventh result))))
  (define t8 (cadr (car (eighth result))))
  (⊦= `(((V (s)) 0.85)
          ((V (s f)) 0.1275)
          ((V (s f f)) 0.019125)
          ((V (s f f f)) 0.00286875)
          ((V (s f f f f)) 0.0004303125)
          ((C ,t6) 6.4546875e-05)
          ((C ,t7) 9.68203125000001e-06)
          ((C ,t8) 1.70859375e-06))
        result))
scheme code
((eta 0.0) (memory #(6291456 1076400 1048576)) (stdout "") (stderr ""))

References
[1] Embedded domain-specific languages for probabilistic programming
[2] Embedded probabilistic domain-specific language HANSEI
[3] Embedded Probabilistic Programming, In proceedings of the IFIP working conference on domain-specific languages, ed. Walid Taha. LNCS 5658, Springer, 2009, pp. 360-384.
[4] HANSEI as a Declarative Logic Programming Language