random-distributions/discrete.ss
;;; PLT Scheme Science Collection
;;; random-distributions/discrete.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 module implements general discrete distribution.
;;;
;;; Version  Date      Description
;;; 1.0.0    09/28/04  Marked as ready for Release 1.0.  Added
;;;                    contracts for functions.  (Doug Williams)

(module discrete mzscheme
  
  ;; Data Definition
  
  ;; discrete structure
  ;;
  ;; An instance of the discrete structure, created by make-discrete,
  ;; represents a general discrete distribution.  Only the discrete?
  ;; function is exported.
  (define-values (struct:discrete
                  discrete-constructor
                  discrete?
                  discrete-field-ref
                  set-discrete-field!)
    (make-struct-type 'discrete #f 2 0))
  
  ;; COntracts
  
  (require (lib "contract.ss"))
  
  (provide/contract
   (discrete?
    (-> any/c boolean?))
   (make-discrete
    (-> (vectorof real?) discrete?))
   (random-discrete
    (case-> (-> random-source? discrete? natural-number/c)
            (-> discrete? natural-number/c)))
   (discrete-pdf
    (-> discrete? integer? (real-in 0.0 1.0)))
   (discrete-cdf
    (-> discrete? integer? (real-in 0.0 1.0))))
  
  (require "../random-source.ss")
  
  (define discrete-p
    (make-struct-field-accessor discrete-field-ref 0 'p))
  
  (define discrete-c
    (make-struct-field-accessor discrete-field-ref 1 'c))
  
  ;; make-discrete: vector -> discrete
  ;;
  ;; This function accepts a vector of weights and returns a discrete
  ;; probability structure that can be passed to random-discrete to
  ;; generate random variates.  The weights do not have to sum to 1.0.
  (define (make-discrete w)
    (let* ((n (vector-length w))
           (p (make-vector n))
           (c (make-vector (+ n 1)))
           (sum 0.0)
           (cumm 0.0))
      ;; find sum
      (do ((i 0 (+ i 1)))
          ((= i n) (void))
        (let ((wi (vector-ref w i)))
          (if (> wi 0.0)
              (set! sum (+ sum wi)))))
      ;; build pdf and cdf
      (do ((i 0 (+ i 1)))
          ((= i n) (void))
        (let* ((wi (vector-ref w i))
               (q (if (< wi 0.0) 0.0 (/ wi sum))))
          (vector-set! p i q)
          (vector-set! c i cumm)
          (set! cumm (+ cumm q))))
      (vector-set! c n 1.0)
      ;; make and return the discrete
      (discrete-constructor p c)))
  
  ;; random-discrete: random-source x discrete -> integer
  ;; random-discrete: discrete -> integer
  ;;
  ;; This function returns a random variate from the given discrete
  ;; distribution given by d.
  (define random-discrete
    (case-lambda
      ((r d)
       (let* ((c (discrete-c d))
              (n (- (vector-length c) 1))
              (u (random-uniform r)))
         (let loop ((i 0))
           (if (<= u (vector-ref c (+ i 1)))
               i
               (loop (+ i 1))))))
      ((d)
       (random-discrete (current-random-source) d))))
  
  ;; discrete-pdf: discrete x integer -> real
  ;;
  ;; This function computes the probability density p(k) at k for a
  ;; discrete distribution given by d.
  (define (discrete-pdf d k)
    (let* ((p (discrete-p d))
           (n (vector-length p)))
      (if (or (< k 0)
              (>= k n))
          0.0
          (vector-ref p k))))
  
  ;; discrete-cdf: discrete x integer -> real
  ;;
  ;; This function computes the cummulative density d(k) at k for a
  ;; discrete distribution given by d.
  (define (discrete-cdf d k)
    (let* ((c (discrete-c d))
           (n (- (vector-length c) 1)))
      (cond ((< k 0)
             0.0)
            ((>= k n)
             1.0)
            (else
             (vector-ref c (+ k 1))))))
  
)