random-distributions/gaussian.ss
;;; PLT Scheme Science Collection
;;; random-distributions/gaussian.ss
;;; Copyright (c) 2004 M. Douglas Williams
;;;
;;; This library is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU Lesser General Public
;;; License as published by the Free Software Foundation; either
;;; version 2.1 of the License, or (at your option) any later version.
;;;
;;; This library is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;;; Lesser General Public License for more details.
;;;
;;; You should have received a copy of the GNU Lesser General Public
;;; License along with this library; if not, write to the Free
;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
;;; 02111-1307 USA.
;;;
;;; -------------------------------------------------------------------
;;;
;;; This code in based on the Random Number Distributions in the GNU
;;; Scientific Library (GSL).
;;;
;;; Version  Date      Description
;;; 0.9.0    08/05/04  This is the initial release of the guassion
;;;                    distribution routines ported from GSL. (Doug
;;;                    Williams)
;;; 0.9.1    08/07/04  Added cummulative density functions. (Doug
;;;                    Williams)
;;; 1.0.0    09/28/04  Marked as ready for Release 1.0.  Added
;;;                    contracts for functions.  (Doug Williams)

(module gaussian mzscheme
  
  (require (lib "contract.ss"))
  
  (provide/contract
   (random-gaussian
    (case-> (-> random-source? real? (>=/c 0.0) real?)
            (-> real? (>=/c 0.0) real?)))
   (random-unit-gaussian
    (case-> (-> random-source? real?)
            (-> real?)))
   (random-gaussian-ratio-method
    (case-> (-> random-source? real? (>=/c 0.0) real?)
            (-> real? (>=/c 0.0) real?)))
   (random-unit-gaussian-ratio-method
    (case-> (-> random-source? real?)
            (-> real?)))
   (gaussian-pdf
    (-> real? real? (>=/c 0.0) (>=/c 0.0)))
   (unit-gaussian-pdf
    (-> real? (>=/c 0.0)))
   (gaussian-cdf
    (-> real? real? (>=/c 0.0) (real-in 0.0 1.0)))
   (unit-gaussian-cdf
    (-> real? (real-in 0.0 1.0))))

  (require "../math.ss")
  (require "../random-source.ss")
  (require "../special-functions/error.ss")
    
  ;; Gaussian (normal) distribution
  
  ;; random-gaussian: random-source x real x real -> real
  ;; random-gaussian: real x real -> real
  ;;
  ;; Polar (Box-Muller) method; see Knuth v2, 3rd ed. p122
  (define random-gaussian
    (case-lambda
      ((r mu sigma)
       (if (not (random-source? r))
           (raise-type-error 'random-gaussian
                             "random-source" r))
       (if (not (real? mu))
           (raise-type-error 'random-gaussian
                             "real" mu))
       (if (not (real? sigma))
           (raise-type-error 'random-gaussian
                             "real" sigma))
       (let ((x 0.0)
             (y 0.0)
             (r2 0.0))
         (let loop ()
           ;; Choose x,y in uniform square (-1,-1) to (+1, +1)
           (set! x (+ -1.0 (* 2.0 (random-uniform r))))
           (set! y (+ -1.0 (* 2.0 (random-uniform r))))
           ;; See if it is in the unit circle
           (set! r2 (+ (* x x) (* y y)))
           ;; Note: since neither x not y can = 0.0, r2 > 0.0
           (if (> r2 1.0)
               (loop)))
         ;; Box-Muller transform
         (+ mu (* sigma y (sqrt (/ (* -2.0 (log r2)) r2))))))
      ((mu sigma)
       (random-gaussian (current-random-source) mu sigma))))
  
  ;; random-unit-gaussian: random-source -> real
  ;; random-unit-gaussian: -> real
  (define random-unit-gaussian
    (case-lambda
      ((r)
       (if (not (random-source? r))
           (raise-type-error 'random-unit-gaussian
                             "random-source" r))
       (random-gaussian r 0.0 1.0))
      (()
       (random-unit-gaussian (current-random-source)))))
  
  ;; random-gaussian-ratio-method: ransom-source x real x real -> real
  ;; random-gaussian-ratio-method: real x real -> real
  ;; Ratio method (Kinderman-Monahan)
  (define random-gaussian-ratio-method
    (case-lambda
      ((r mu sigma)
       (if (not (random-source? r))
           (raise-type-error 'random-gaussian-ratio-method
                             "random-source" r))
       (if (not (real? mu))
           (raise-type-error 'random-gaussian-ratio-method
                             "real" mu))
       (if (not (real? sigma))
           (raise-type-error 'random-gaussian-ratio-method
                             "real" sigma))
       (let ((u 0.0)
             (v 0.0)
             (x 0.0))
         (let loop ()
           (set! v (random-uniform r))
           (set! u (random-uniform r))
           ;; Note: u > 0.0
           ;; Const 1.715... = sqrt(8/e)
           (set! x (/ (* 1.71552776992141359295 (- v 0.5)) u))
           (if (> (* x x)
                  (* -4.0 (log u)))
               (loop)))
         (+ mu (* sigma x))))
      ((mu sigma)
       (random-gaussian-ratio-method (current-random-source) mu sigma))))
  
  ;; random-unit-gaussian-ratio-method: random-source -> real
  ;; random-unit-gaussian-ratio-method: -> real
  (define random-unit-gaussian-ratio-method
    (case-lambda
      ((r)
       (if (not (random-source? r))
           (raise-type-error 'random-unit-gaussian-ratio-method
                             "random-source" r))
       (random-gaussian-ratio-method r 0.0 1.0))
      (()
       (random-unit-gaussian-ratio-method (current-random-source)))))
  
  ;; gaussian-pdf: real x real x real -> real
  ;;
  ;; This function computes the probability density p(x) at x for a
  ;; gaussian distribution with mean mu and standard deviation sigma.
  (define (gaussian-pdf x mu sigma)
    (if (not (real? x))
        (raise-type-error 'gaussion-pdf
                          "real" x))
    (if (not (real? mu))
        (raise-type-error 'gaussian-pdf
                          "real" mu))
    (if (not (real? sigma))
        (raise-type-error 'gaussian-pdf
                          "real" sigma))
    (* (/ 1.0 (* sigma (sqrt (* 2.0 pi))))
       (exp (/ (- (* (- x mu) (- x mu)))
               (* 2.0 sigma sigma)))))
  
  ;; unit-gaussian-pdf: real -> real
  (define (unit-gaussian-pdf x)
    (if (not (real? x))
        (raise-type-error 'unit-gaussian-pdf
                          "real" x))
    (gaussian-pdf x 0.0 1.0))
  
  ;; gaussian-cdf: real x real -> real
  ;;
  ;; This function computes the cummulative density d(x) at x for a
  ;; gaussian distribution with mean mu and standard deviation sigma.
  (define (gaussian-cdf x mu sigma)
    (if (not (real? x))
        (raise-type-error 'gaussion-cdf
                          "real" x))
    (if (not (real? mu))
        (raise-type-error 'gaussian-cdf
                          "real" mu))
    (if (not (real? sigma))
        (raise-type-error 'gaussian-cdf
                          "real" sigma))
    (/ (+ 1.0 (erf (/ (- x mu) (* sigma (sqrt 2.0))))) 2.0))
  
  ;; unit-gaussian-cdf: real -> real
  
  (define (unit-gaussian-cdf x)
    (if (not (real? x))
        (raise-type-error 'unit-gaussian-cdf
                          "real" x))
    (gaussian-cdf x 0.0 1.0))
  
)