;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;                                                          ;;;;;;;;
;;;;;;  All files in this directory or any subdirectories are   ;;;;;;;;
;;;;;;  copyright 1997, 1998, 1999, 2000, 2002.                 ;;;;;;;;
;;;;;;  by Rafael D. Sorkin.  All rights reserved.              ;;;;;;;;
;;;;;;                                                          ;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; bibliotek.float       Time-stamp:<2002-Oct-23 14:25:08 15798.59780>

; Containing functions involved with floating point computations, especially
; ones which are inexact for reasons beyond truncation error of the result
; itself.

;: Roster of functions defined herein 
;  ==================================
;
;  polynomial-value
;
;  factorial           (for now just relies on log-factorial)
;  log-factorial       (fairly accurate, useful for large arguments)
;
;  solve-monotone       (solve y=f(x) for x)
;
;  The following numerical constants are defined in `bibliotek.constants'
;
;    pi, e, Euler, (log (sqrt 2 pi)),  
;    and a bunch of fractions for elisp like 1/3 or -1/2.
;
;  Infinity NaN are defined in the file preparations.el
;
;  A bunch of matrix functions are also in a separate file

;; If package info is needed here, see { ~/lisp/bibliotek.TCL.l } for sample.

;: The functions 

(defun polynomial-value (p x) 
  "\
 The first arg should be the LIST of coefficients  (a_0 a_1...a_n), 
 with 0's for absent terms, of course.
 NB: the coefficients are in order of INCREASING DEGREE"
  (if (null p) 0
    (+
     (car p)
     (* x (polynomial-value (cdr p) x)))))
 ;
 ; This is the more accurate and efficient way to evaluate a polynomial.
 ; Seems no need to localize the symbols.


(deff log-factorial (x &optional (thresh 20.0))  
  "\
 Natural log of factorial of x, where x is a real number > -1.
 Evaluation is done using Stirling series, after first promoting x to
 be greater than the threshold specified by the optional second argument
 (which defaults to 20).
  "
  (cond
  ;-----------------------
  ; check that arg is > -1
  ;-----------------------
   ((<= x -1)
    (error "`log-factorial' requires arg > -1 for now."))
  ;------------------------------------------------------------------
  ; handle x=0 and 1 specially to avoid roundoff error in those cases
  ;------------------------------------------------------------------
   ((or 
     (= x 0)
     (= x 1))
              0.0)
  ;------------------------------------------
  ; if x < thresh then augment it and recurse
  ;------------------------------------------
   ((< x thresh)
    (- (log-factorial (1+ x) thresh) (o log 1+ x)))  
  ;------------------------------------
  ; if x >= thresh then plug into the series
  ;------------------------------------
   (t (+
       (* (+ x 1/2) (log x))
       (- x)
       log_sqrt_2pi
       (polynomial-value 
         (list 0 1/12 0 -1/360 0 1/1260 0 -1/1680)
         (reciprocal x))))))
  ;
  ; The method is to make the argument large enough that a few terms in the
  ; Stirling series will be quite accurate. 
  ;
  ; It seems to work well, but behaves rather strangely.
  ; First of all, one might have expected the optimum threshold to be
  ; around 33 (because there the ratio of the smallest to biggest term is
  ; about 10^16) but empirically 20 appears to work best (see the tests).
  ; Moreover, many choices of threshshold in the range 15 to 80 (if not
  ; beyond) give precisely the same answer (see the tests).
  ;
  ; Improvement
  ;  Let it handle args < -1 as well.  One nuisance though, is that log is
  ;  complex in parts of this range, so maybe should give just its real part
  ;  (i.e. (log abs fact x))
  ;
  ; The coefficients in the series come from Dwight 851.5 (page 210)
  ;  Here is how they were computed
  ;
  ; (* 6  1 2) ; => 12
  ; (* 30 3 4) ; => 360
  ; (* 42 5 6) ; => 1260
  ; (* 30 8 7) ; => 1680
  ;
  ; (- (log (sqrt (atl 2 * pi))) log_sqrt_2pi) ; => 0.0

;;; Above (log-factorial) is ridiculously slow in cmucl, WHY??
;;; See ~/lisp/developing/factorial.el for attempts to improve it! 

(defun factorial (x) 
  "\
 A poor excuse for this function scrabbled together from wherever.
 Among other problems, it rejects arguments < -1"
  (if (and (integerp x) (< x 171)) (! x)
    (o exp log-factorial x)))


;;; modifiying following to make gcl accept it, LOGIC MIGHT HAVE BEEN CHANGED!!

(deff solve-monotone (f y &key 
			   ((:ii ii) (list -1.0 1.0))
			   ((:tol-x tol_x) (* 5 float-epsilon)) 
			   ((:tol-y tol_y) (* 50 float-epsilon))
			   ((:maxit maxit) 512)
			   ((:lbound lbound))
			   ((:ubound ubound)))
  "\
 Solves the equation y=f(x) for x, where f is a monotonically 
 increasing or decreasing function.  Arguments are:
   1. the function f (or a symbol for it)
   2. the desired value y
   3. :ii = (a b) is the `initial interval' in which to begin the search
            (defaults to [-1 1]) 
   4. :tol-x = (absolute) tolerance for x (defaults to (* 5 float-epsilon))
   5. :tol-y = (absolute) tolerance for y (defaults to (* 50 float-epsilon))
   6. :maxit = maximum number of iterations before quitting (defaults to 512)
   7. :lbound and :ubound = limits of search "
 ;-------------------------------
 ;/localizations and declarations
 ;-------------------------------
  (&localize 
     f y ii tol_x tol_y maxit lbound ubound
     a b c sgn@a sgn@b sgn@c 
     g x fx u v w basta j plan)

  (declare 
   (optimize (speed 3))
   (type fixnum maxit)
   (type realfloat y tol_x tol_y))

  (varbind 
   c 0.0
   u 0.0
   v 0.0
   a 0.0
   b 0.0
   w 0.0
   sgn@a 5
   sgn@b 5
   sgn@c 5
   plan nil)
  (declare 
   (type real-float a b c u v w)
   (type fixnum sgn@a sgn@b sgn@c))

 ;---------------------------------------------------------------------------
 ;/make sure arguments have the types they're declared to have or should have
 ;---------------------------------------------------------------------------
  (setf
    tol_x (coerce tol_x *float*)  ; type info doesnt seem to propagate in cmucl
    tol_y (coerce tol_y *float*))
  (assert (>= tol_x 0))
 ;-------------------------------------------------
 ;/get bounds for initial interval (and cast types)
 ;-------------------------------------------------
  (setq 
    a (coerce (car  ii) *float*)
    b (coerce (cadr ii) *float*))
  (assert (< tol_x (- b a)) 
     nil "solve-monotone: wrongly specified initial interval or bad tol-x")
  ;;(princ (list a b))			;;;
 ;-----------------------------------------------------
 ;/define g(x) := f(x) - y   (We will seek a zero of g)
 ;-----------------------------------------------------
  (fbind g(x) 
    (declare (type realfloat x))
    ;; (princ (list a b))					;;;
    (vbind fx (funcall f x))
    (if *tcl* (assert (realp fx)))
    (- fx y))
 ;---------------
 ;/define `basta'
 ;---------------
  (fbind basta ()
    (setq c (/ (+ a b) 2.0))
    (if (> (- b a) tol_x)
      (error 
        (tcl-or-elisp
	  "solve-monotone: ~s might be a solution if :tol-x were ~0,1g"
	  "solve-monotone: %s might be a solution if :tol-x were %0.1g")
	c (- b a)))
    (if (> (o abs g c) tol_y)
      (error 
        (tcl-or-elisp
	  "solve-monotone: ~s might be a solution if :tol-y were ~0,1g"
	  "solve-monotone: %s might be a solution if :tol-y were %0.1g")
	c (o abs g a)))
    (return-from solve-monotone c))
 ;-----------------------
 ;/begin iterative search
 ;-----------------------
  (psetq u (g a) v (g b))
  ;; (princ (list u v))			;;;
  (loop 
   for j from 0
  ;--------------------------------------
  ;/time to give up (por too many tries)?
  ;--------------------------------------
   if (> j maxit) 
   do (error 
       (tcl-or-elisp
	 "more than ~d tries in `solve-monotone'" 
	 "more than %d tries in `solve-monotone'")
       maxit)
  ;----------------------------------
  ;/find the signs of f at a and at b
  ;----------------------------------
   do (psetq sgn@a (sgn u) sgn@b (sgn v))
  ;----------------------------------------------
  ;/return if either a or b is already a solution
  ;----------------------------------------------
   if (= 0 sgn@a) return a
   if (= 0 sgn@b) return b
  ;-----------------------------
  ;/call `basta' if b-a <= tol_x
  ;-----------------------------
   if (<= (- b a) tol_x) do (basta)
  ;-----------------------------------------------------
  ;/determine whether root is within (a b) or outside it
  ;-----------------------------------------------------
   do (if (= sgn@a sgn@b) (setq plan 'caminar) (setq plan 'narrow))
  ;-------------------------
  ;/execute plan accordingly
  ;-------------------------
   do
   (case plan
    ;---------------------------------
    ;/case where root is outside (a b)
    ;---------------------------------
     ((caminar)
      (cond
	((< u v) (setq plan (if (> sgn@a 0) 'left  'right)))
	((> u v) (setq plan (if (< sgn@a 0) 'left  'right)))
	(t (basta)))
      (case plan
	((left)
	 (psetq 
	   a (- (* 3 a) (* 2 b))
	   b a)
	 (if (and lbound (< a lbound))(error "out of bounds, can't continue"))
	 (psetq 
	   u (g a)
	   v u))
	((right)
	 (psetq 
	   a b
	   b (- (* 3 b) (* 2 a)))
	 (if (and ubound (> b ubound))(error "out of bounds, can't continue")) 
	 (psetq 
	   u v
	   v (g b)))))
    ;--------------------------------
    ;/case where root is inside  (a b)
    ;--------------------------------
     ((narrow)
      (setq c (/ (+ a b) 2.0))
      (if (or (= a c) (= c b)) (basta))
      (setq 
       w (g c)
       sgn@c (sgn w))
      (if (= 0 sgn@c) (return c))
      (cond
       ((/= sgn@a sgn@c) (setq b c v (g b)))
       (t          (setq a c u (g a)))))))) 
 ;
 ; NOTES
 ;
 ; Some variables
 ;  c = midpoint of interval [a b] 
 ;  u = f(a), v = f(b), w = g(c)
 ;  sgn@a = sgn(u)  etc
 ;
 ; The search strategy 
 ;   if root is within interval, buscalo recursvely in the half where it is;
 ;   if it seems outside, look in the adjacent interval of twice the size.
 ;
 ; The function `basta' takes over when iteration stops. 
 ; It decides whether either issues an error mesage or returns the root found.
 ;
 ; We will have trouble if f has a big flat stretch, but there is probably no
 ; point in trying to deal with this for now (we could do it by giving extra
 ; information to know whether it is monotone increasing or decreasing, or a
 ; memory of this) 
 ;
 ; A problem that was cured was that for b-a very small c can equal one of
 ; them, then you just keep repeating the same interval (a b), now we catch 
 ; that and call basta.
 ;
 ; Localization of variables can be important. In an earlier version,
 ; `x' was left unlocalized, causing trouble. 
 ;
 ; Possible Improvements (see developing/solve-monotone.l)


;: End 
