(module gamma mzscheme
(require (lib "contract.ss"))
(provide/contract
(random-gamma
(case-> (-> random-source? (>/c 0.0) real? (>=/c 0.0))
(-> (>/c 0.0) real? (>=/c 0.0))))
(random-gamma-int
(-> random-source? natural-number/c (>=/c 0.0)))
(gamma-pdf
(-> real? (>/c 0.0) real? (>=/c 0.0)))
(gamma-cdf
(-> real? (>/c 0.0) real? (real-in 0.0 1.0))))
(require "../machine.ss")
(require "../math.ss")
(require "../random-source.ss")
(require "../special-functions/gamma.ss")
(define random-gamma
(case-lambda
((r a b)
(let ((na (inexact->exact (floor a))))
(cond ((= a na)
(* b (random-gamma-int r na)))
((= na 0.0)
(* b (random-gamma-frac r a)))
(else
(* b (+ (random-gamma-int r na)
(random-gamma-frac r (- a na))))))))
((a b)
(random-gamma (current-random-source) a b))))
(define (random-gamma-int r na)
(if (< na 12)
(let ((prod 1.0))
(do ((i 0 (+ i 1)))
((= i na) (- (log prod)))
(set! prod (* prod (random-uniform r)))))
(random-gamma-large r na)))
(define (random-gamma-large r a)
(let ((sqa (sqrt (- (* 2.0 a) 1.0)))
(x 0.0)
(y 0.0)
(v 0.0))
(let loop1 ()
(let loop2 ()
(set! y (tan (* pi (random-uniform r))))
(set! x (+ (* sqa y) a -1))
(if (<= x 0.0) (loop2)))
(set! v (random-uniform r))
(if (> v (- (* (+ 1.0 (* y y))
(exp (- (* (- a 1.0) (log (/ x (- a 1.0))))
(* sqa y))))))
(loop1)))
x))
(define (random-gamma-frac r a)
(let ((p (/ e (+ a e)))
(q 0.0)
(x 0.0)
(u 0.0)
(v 0.0))
(let loop ()
(set! u (random-uniform r))
(set! v (random-uniform r))
(if (< u p)
(begin
(set! x (exp (* (/ 1.0 a) (log v))))
(set! q (exp (- x))))
(begin
(set! x (- 1.0 (log v)))
(set! q (exp (* (- a 1.0) (log x))))))
(if (>= (random-uniform r) q)
(loop)))
x))
(define (gamma-pdf x a b)
(cond ((< x 0)
0.0)
((= x 0.0)
(if (= a 1.0)
(/ 1.0 b)
0.0))
((= a 1.0)
(exp (/ (/ (- x) b) b)))
(else
(/ (exp (+ (* (- a 1.0) (log (/ x b)))
(- (/ x b))
(- (lngamma a))))
b))))
(define LARGE-A 85)
(define (norm-arg x a)
(let ((t 0.0)
(arg (+ (x (/ 1.0 3.0) (- a) (- (/ 0.02 a)))))
(u (/ (- a 0.5) x)))
(cond ((< (abs (- u 1.0)) double-epsilon)
(set! t 0.0))
((< (abs u) double-epsilon)
(set! t 1.0))
((> u 0.0)
(let ((v (- 1.0 u)))
(set! t (/ (+ 1.0 (- (* u u)) (* 2.0 u (log u))) (* v v)))))
(else
(set! t +nan.0)))
(* arg (sqrt (/ (+ 1.0 t) x)))))
(define (gamma-cdf x a b)
(cond ((< x 0.0)
0.0)
(else
(let ((y (/ x b)))
(gamma-inc-P a y)))))
)