;;;;
;;;; phd-model.lsp XLISP-STAT sir model proto and methods
;;;; sir 1.0 Copyright (c) 1990 by Ker-Chau Li
;;;; XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney
;;;; Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz
;;;; You may give out copies of this software; for conditions see the file
;;;; COPYING included with this distribution.
;;;;

;;;;
;;;;
;;;; pHd Model Prototype
;;;;
;;;;

(defproto phd-model-proto
  '(x y eigen-vectors eigen-values total-evalue)
  ()
  *object*
  "Principal Hessian Directions Model")

(defun phd-model (x y &key (print T))
"Args: (x y  &key(print T))
X           - list of independent variables (list of lists)
Y           - dependent variable.
PRINT       - if not NIL print summary information and plot sir1-y-sir2
Returns a sir model object. To examine the model further assign the
result to a variable and send it messages.
Messages include :
                  x         -  original independent variables
                  matrix-x  -  matrix form of x  
                  y         -  original dependent variable
                  phd1      -  the variable for the first direction of sir
                  phd2      -  the variable for the second direction of sir
                  phd3      -  the variable for the third direction of sir
                  newx      -  the list of variables for all sir directions
                  nobs      -  # of observations in the data set
                  dim       -  # of independent variables
                  eigen-values   -  list of all absolute
                               values of eigenvalues
                  eigen-vectors  -  list of eigenvectors in list form
                  total-evalue   -  sum of all eigenvalues
                  p-values  -  p-values for checking if directions found are
                               real. if only the first k p-values are needed,
                               use the format :
                               :p-values :only k
                  plot1     -  spin-plot of phd1-y-phd2
                  plot2     -  spin-plot of phd1-phd2-phd3"

  (let (
        (m (send phd-model-proto :new))
        )
    (send m :x x)
    (send m :y y)
    (send m :compute)
    (if print (send m :display))
    m))

(defmeth phd-model-proto :isnew () (send self :needs-computing t))

(defmeth phd-model-proto :save ()
  `(phd-model',(send self :x)
                     ',(send self :y)
                     ))
(defmeth phd-model-proto :compute ()
  (let*(
        (x (send self :x))
        (y (send self :y))
        (phdout (phd-no-output x y))
        )
    (setf (slot-value 'x) x)
    (setf (slot-value 'y) y)
    (setf (slot-value 'eigen-vectors) (nth 0 phdout))
    (setf (slot-value 'eigen-values) (nth 1 phdout))
    (setf (slot-value 'total-evalue) (nth 2 phdout))
    ))

(defmeth phd-model-proto :needs-computing (&optional set)
  (if set (setf (slot-value 'eigen-values) nil))
  (null (slot-value 'eigen-values)))

(defmeth phd-model-proto :display ()
  (let (
        (evt (send self :eigen-vectors))
        )
    (format t "==============================================================")
    (format t "~2%      **** Principal Hessian Directions Model ****")
    (format t "~2%Number of observation:               ~s" (send self :nobs))
    (format t "~2%Number of independent variables:      ~s" (send self :dim))
    (format t "~2%the first direction found by phd:~%")
    (format t "~s" (nth 0 evt))
    (format t "~2%the second direction found by phd:~%")
    (format t "~S"(nth 1 evt))
    (format t "~2%the third direction found by phd:~%")
    (format t "~s"(nth 2 evt))
    (format t "~2%the companion output eigenvalues of phd:~%")
    (format t "~s"(send self :eigen-values))
    (format t "~2%the sum of all eigenvalues:~%")
    (format t "~s"(send self :total-evalue))
    (format t "~2%==============================================================~%")
    (send self :plot1)
;    (send self :plot2)
    ))

(defmeth phd-model-proto :x (&optional new-x)
  (when new-x
        (setf (slot-value 'x) new-x)
        (send self :needs-computing t))
  (slot-value 'x))

(defmeth phd-model-proto :y (&optional new-y)
  (when new-y
        (setf (slot-value 'y) new-y)
        (send self :needs-computing t))
  (slot-value 'y))

(defmeth phd-model-proto :eigen-values ()
  (slot-value 'eigen-values))

(defmeth phd-model-proto :eigen-vectors ()
  (slot-value 'eigen-vectors))

(defmeth phd-model-proto :total-evalue ()
  (slot-value 'total-evalue))

(defmeth phd-model-proto :newx ()
  (let*(
        (mx (send self :matrix-x))
        (nx (copy-list (send self :x)))
        (evector (send self :eigen-vectors))
        )
      (dotimes (i (send self :dim))
           (setf (nth i nx) (coerce (%* mx (nth i evector)) 'list)))
    nx))

(defmeth phd-model-proto :matrix-x ()
  (let*(
        (x (send self :x))
        (dim (send self :dim))
        (nobs (send self :nobs))
        (mx (transpose (make-array (list dim nobs) :initial-contents x)))
        )
    mx))

(defmeth phd-model-proto :phd1 ()
  (let*(
        (mx (send self :matrix-x))
        (ev1 (nth 0 (send self :eigen-vectors)))
        )
    (coerce (%* mx ev1) 'list)))

(defmeth phd-model-proto :phd2 ()
  (let*(
        (mx (send self :matrix-x))
        (ev2 (nth 1 (send self :eigen-vectors)))
        )
    (coerce (%* mx ev2) 'list)))

(defmeth phd-model-proto :phd3 ()
  (let*(
        (mx (send self :matrix-x))
        (ev3 (nth 2 (send self :eigen-vectors)))
        )
    (coerce (%* mx ev3) 'list)))

(defmeth phd-model-proto :nobs ()
  (length (send self :y)))

(defmeth phd-model-proto :dim ()
  (length (send self :x)))

(defmeth phd-model-proto :p-values (&key (only nil))
  (let*(
        (dim (send self :dim))
        (nobs (send self :nobs))
        (tev (send self :total-evalue))
        (dim1 (if only only dim))
        (evl (send self :eigen-values))
        (sq-ev (sum (** evl 2)))
        (var-y  (** (standard-deviation (send self :y)) 2))
        count
        pv
        chi
        )
    (dotimes (i dim1)
             (setf count (+ i 1))
             (setf chi (chisq-cdf (/ (*  nobs   sq-ev ) (* 2 var-y))  (/ (* (- dim i) (+ 1 (- dim i))) 2)))
             (setf pv (- 1 chi))
             (format t "~%P-value for checking if the ~s -th component is real:     ~s~%" count pv)
             (setf sq-ev (- sq-ev (** (nth i evl) 2)))
             )
    (format t "~%")
    ))

(defmeth phd-model-proto :plot1 ()
  (setf plot1 (spin-plot (list (send self :phd1) (send self :y) (send self :phd2))
                         :variable-labels (list "phd1" "y" "phd2")
                         :title "Phd-view: phd1-y-phd2"))
  (send plot1 :linked t)
  )
                       

(defmeth phd-model-proto :plot2 ()
  (setf plot2 (spin-plot (list (send self :phd1) (send self :phd2) (send self :phd3))
                         :variable-labels (list "phd1" "phd2" "phd3")
                         :title "Phd-view: phd1-phd2-phd3"))
  (send plot2 :linked t)
  )

;;===========================================================

(defun phd-no-output (x y)
"phd method, by li, output ( (eigenvectors-rowvectors, eigenvalues), newx) "
(let* ( (nobs(length y))
        (dim (length x))
        (mx (transpose (make-array (list dim nobs) :initial-contents x)))
        (syxx (covw x (- y (mean y))))
        (sxx (covw x (repeat 1 nobs)))
        (meat (eigen-decomp syxx sxx))
        (evalues (coerce (nth 1 meat) 'list))
        (total-evalue (sum evalues))
        (ev (mapcar #'coerce (row-list (nth 0 meat)) (repeat 'list dim)))
             
        (order-evalue (reverse (order (abs evalues))))
        (evalues (select evalues order-evalue))
        (evv (repeat 0 dim))
        (evector (repeat 0 dim))
        )

  (dotimes (i dim)
           (setf (nth i evv) (coerce (nth (nth i order-evalue) ev) 'list))
           )
  (list evv evalues total-evalue)

  ))

;;============================================================

