;; Author Barton Willis
;; University of Nebraska at Kearney
;; Copyright (C) 2004, Barton Willis

;; Brief Description: Maxima code for linear homogeneous second order
;; differential equations.

;; Maxima odelin is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License,
;; http://www.gnu.org/copyleft/gpl.html.

;; Maxima odelin has NO WARRANTY, not even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

($put '$odelin 19 '$version)

;; [DB 2007-04-29] I have added some comments to generic-de-solver and 
;; subsidiary routines.  They are just my interpretation of the code.

;; The functions mtimesp and mexptp are either missing from 
;; commerical macsyma, or they have different names. For commerical
;; macsyma, here are the definitions.

#+kcl
(defun mtimesp (x)
  (and (consp x) (consp (car x)) (eq (caar x) 'mtimes)))

#+kcl
(defun mexptp (x)
  (and (consp x) (consp (car x)) (equal (caar x) 'mexpt)))

(eval-when
    #+gcl (load compile eval)
    #-gcl (:load-toplevel :compile-toplevel :execute)
    ($load "nset")
    ($load "polynomialp")
    ($load "sqfr")
    ($load "spherodialwave")
    ($load "kummer")
    ($load "extrabessel")
    ($load "lazysolver")
    ($load "gauss")
    ($load "odeutils"))

(defmvar $de_solver_is_loquacious t)

(defun ode-polynomialp (p x)
  (setq p ($ratdisrep p))
  (or
   ($freeof x p)
   (like p x)
   (and (or (mtimesp p) (mplusp p)) 
	(every #'(lambda (s) (ode-polynomialp s x)) (margs p)))
   (and (mexptp p) (ode-polynomialp (car (margs p)) x) 
	(integerp (cadr (margs p)))
	(> (cadr (margs p)) 0))))
   
(defun $okay (de y x)
  (let ((okay t))
  (setq de (require-linear-homogeneous-de de y x))
  (dolist (p de)
    (setq okay (and okay (ode-polynomialp ($ratnumer p) x) 
		    (ode-polynomialp ($ratnumer p) x))))
  okay))

(defun $rerat (dirtyrat)
  (let ((varlist nil) (genvar nil))
  ($rat ($ratdisrep dirtyrat))))
   
(defun require-linear-homogeneous-de (de y x)
  (setq y (require-symbol y "$odelin"))
  (setq x (require-symbol x "$odelin"))

  (cond ((or ($listp de) ($setp de))
	 (if (= 1 ($cardinality de)) 
	     (setq de (nth 1 de))
	   (merror "'odelin' doesn't handle systems of DEs.~%"))))
  
  (setq de ($ratdisrep de))
  (setq de (meqhk de))
  (let ((cf) (acc) (f de) (n ($derivdegree de y x)))
    (if (not (and (integerp n) (> n 0)))
	(merror "'odelin' doesn't handle order ~:M DEs.~%" n))
    (setq de ($rat de))
    (while (> n -1)
      (if (= n 0)
	  (setq cf ($ratcoef de y 1))
	(setq cf ($ratcoef de (list '(%derivative) y x n))))
      (if  ($freeof y cf)
	  (push cf acc)
	(merror "DE must be linear"))
      (setq f (sub f (mul cf (list '(%derivative) y x n))))
      (decf n))
    (setq f ($ratsimp f))
    (if (not (like 0 f))
	(merror "DE must be linear and homogeneous.~%"))
    acc))
  	           
(defun $odelin (de y x)
  (let ((cfs (require-linear-homogeneous-de de y x)) (n))
    (setq n (length cfs))
    (cond ((= n 2) (odelin-order-one cfs x))
	  ((= n 3) (odelin-order-two cfs x))
	  (t (merror "'odelin' doesn't handle DEs with order ~:M" (- n 1))))))

(defun odelin-order-one (cfs x)
  (let ((p1 (nth 1 cfs))
	(p0 (nth 0 cfs)))
    (fss-cleanup `(($set) ,(power '$%e ($integrate (div p0 p1) x))) x)))

(defun expunge-const-factors (e x)
  (let ((acc 1))
    (cond ((like 0 e) 0)
	  (t
	   (setq e ($factor e))
	   (setq e (if (mtimesp e) (margs e) (list e)))
	   (dolist (ei e acc)
	     (if (not ($freeof x ei)) (setq acc (mul ei acc))))))))

;; Cleanup a fundamental solution set (FSS). 
  
(defun fss-cleanup (fss x)
  (let ((nfss) (k 0) ($radexpand nil) ($ratsimpexpons t))
    (while (and (< k 5) (not (like fss (setq nfss (mbag-map #'$radcan fss)))))
      (incf k)
      (setq fss nfss))
    (mbag-map #'(lambda (s) (expunge-const-factors s x)) fss)))

(defun post-check-cleanup (fss)
  (setq fss ($substitute '%bessel_j '$fbessel_j fss))
  ($substitute '%bessel_y '$fbessel_y fss))
        
(defun odelin-order-two (cfs x)
  (let ((p0 (nth 0 cfs))
	(p1 (nth 1 cfs))
	(p2 (nth 2 cfs))
	(p) (m) (sol)
	(ode-methods (list 
		      'ode-solve-by-factoring
		      'bessel-de-solver
		      'hypergeo01-de-solver
		      'spherodialwave-de-solver
		      'bessel-sqrt-de-solver
		      'hypergeo21-de-solver))
	($radexpand nil))
    
    (setq p1 (div p1 p2))
    (setq p0 (div p0 p2))

    (if (not (and (ode-polynomialp ($ratnumer p1) x)
		  (ode-polynomialp ($ratdenom p1) x)
		  (ode-polynomialp ($ratnumer p0) x)
		  (ode-polynomialp ($ratdenom p0) x)))
	(setq ode-methods nil))
      
    (setq p (add p0 (div ($diff p1 x) -2) (mul p1 p1 (div -1 4))))
    (setq p ($rat p))
    (setq m (power '$%e (simplify ($integrate (div p1 -2) x))))
       
    (dolist (method ode-methods)
      (setq sol (funcall method p x))
      (if ($setp sol) (setq sol (fss-cleanup 
				 (mbag-map #'(lambda (s) (mul m s)) sol) x)))
      (if (check-fss cfs sol x) (return (post-check-cleanup sol))))))
   
(defprop unk simp-unk operators)

(defun simp-unk (x y z)
  (declare (ignore y z))
  (merror "Maxima doesn't know the derivative of ~:M with respect the ~:M argument" (nth 2 x) (nth 1 x)))
	  		 
(defun check-de-sol (cfs sol x)
  (let ((zip 0) ($gcd '$spmod) ($algebraic t) ($ratsimpexpons t))
    (dolist (cf cfs)
      (setq zip (add zip (mul cf sol)))
      (setq sol ($diff sol x)))
    (setq zip ($ratsimp zip))
    (or
     (like 0 zip) (like 0 ($radcan zip)) (like 0 ($radcan ($expand zip)))
     (mtell "should vanish, but it does not ~:M~%" zip))))
    
(defun check-fss (cfs fss x)
  (let (($gcd '$spmod) ($algebraic t))
    (and 
     ($setp fss)
     (= (length cfs) (length fss))
     (every #'(lambda (s) (check-de-sol cfs s x)) (cdr fss))
     (not (like 0 ($xwronskian fss x))))))

(defun check-number-of-args (n arg f)
  (if (not (= n (length arg)))
      (merror "Function ~:M requires ~:M arguments; found ~:M arguments"
	      f n (+ n (length arg)))))
  
;; Return the wronskian of the list or set of expressions s. 

(defun $xwronskian (&rest arg)
  (check-number-of-args 2 arg "$xwronskian")
  (let ((mat) (dim)
	(s (require-list-or-set (nth 0 arg) "$xwronskian"))
	(x (require-symbol (nth 1 arg) "$xwronskian")))
    (setq s `((mlist) ,@s))
    (setq x (require-symbol (nth 1 arg) "$xwronskian"))
    (setq dim ($length s))
    (dotimes (i dim)
      (push s mat)
      (setq s ($diff s x)))
    ($radcan ($determinant `(($matrix) ,@mat)))))

;; Return a polynomial in x with degree deg and a list of its
;; coefficients. Each coefficient of the polynomial is a gensym.
   	    
(defun make-unk-poly (x deg)
  (setq x ($ratdisrep x))
  (let ((p 0) (z 1) (cf) (cfs))
    (incf deg)
    (dotimes (i deg (values p (reverse cfs)))
      (setq cf (gensym))
      (push cf cfs)
      (setq p (add p (mul cf z)))
      (setq z (mul z x)))))

(defun polycfs-to-eqs (p x)
  (let ((n) (eqs) ($gcd '$spmod) ($algebraic t) ($ratfac nil) ($ratprint nil))
    (setq p ($rat p x))
    (setq n (+ 1 ($hipow p x)))
    (dotimes (i n eqs)
      (push ($ratcoef p x i) eqs))))
    
;; Solve y'' - r y by factoring the differential operator (D^2 - r). Specifically,
;; we factor D^2 - r as (D+a)(D-a), where r and a are rational functions.
;; Expanding, we have  D^2 - r = D^2 -a' - a^2.  So -r = -a' - a^2 or
;; a' + a^2 = r. Solving a' + a^2 =  r is the task for factor-differential-op. 
;; Solutions to (D+a)(D-a) f = 0 are f = mu, where mu = exp (integrate(a,x)) 
;; and f = mu * integrate(1/mu^2,x).

(defun ode-solve-by-factoring (r x)
  (if $de_solver_is_loquacious (mtell "...trying factor method~%"))
  (let ((a) (mu) ($radexpand nil))
    (cond ((setq a (factor-differential-op r x))
	   (setq a ($radcan a)) ;; x*(x+1)*'DIFF(y,x,2)+(3*x+2)*'DIFF(y,x,1)+y
	   (setq mu ($radcan ($exp ($integrate a x))))
	   `(($set) ,mu ,(mul mu ($integrate (div 1 (mul mu mu)) x))))
	  (t nil))))
	   
;; Let r = p/q be rational. We either find a rational function a such that 
;; a' + a^2 =  p/q = p q / q^2, or we return nil.  We do this by setting
;; a = w / q.  Then q w' - q' w + w^2 = pq.

(defun factor-differential-op (r x)
  (setq r ($rat r x))
  (let*
      ((p ($ratnumer r))
       (q ($ratdenom r))
       (m ($hipow q x))
       (l ($hipow p x))
       (w) (zip) (n) (vars) (sol)
       ($gcd '$spmod) ($algebraic t) ($ratfac nil))
  
    (setq m ($totaldisrep m))
    (setq l ($totaldisrep l))
    (setq m (coerce m 'fixnum))
    (setq l (coerce l 'fixnum))
    (cond ((< (+ 2 l) m)
	   (setq n (max (+ m -1) (+ l 1))))
	  ((evenp (+ m l))
	   (setq n (/ (+ m l) 2)))
	  (t
	   (setq n -1)))
    (cond ((> n -1)
	   (multiple-value-setq  (w vars) (make-unk-poly x n))
	   (setq vars (reverse vars))
	   (push '(mlist) vars)
	   (setq zip (add
		      (mul q ($diff w x)) 
		      (mul -1 w ($diff q x)) 
		      (mul w w) 
		      (mul  p q)))
	   (setq zip ($rat zip x))
	   (setq zip (polycfs-to-eqs zip x))
	   (push '(mlist) zip)
	   (setq sol ($checkedalgsys zip vars))
	
	   (cond ((not sol)
		  (setq sol nil))
		 (t
		  (setq w (div w q))
		  ($substitute sol w))))
	  (t
	   nil))))

(defun polynomial-filter (p x f)
  (let (($gcd '$spmod) ($algebraic t) ($ratfac nil) 
	($ratprint nil) ($radexpand nil))
    (setq p ($ratsimp p x)) ;; Get rid of terms like sqr(5)^2, %i^2...
    (setq p ($mysqfr p x))
    (setq p (if (mtimesp p) (margs p) (list p)))
    (let ((q 1) (n))
      (dolist (pj p q)
	(cond ((mexptp pj)
	       (setq n (nth 2 pj))
	       (setq pj (nth 1 pj)))
	      (($freeof x pj)
	       (setq n 0))
	      (t
	       (setq n 1)))
	(setq n (funcall f n))
	(setq q (mult q (power pj n)))))))
  
(defun ratfun-degree (q x)
  (let (($gcd '$spmod) ($algebraic t) ($ratfac nil))
    (setq q ($rat q x))
    (- ($hipow ($ratnumer q) x) ($hipow ($ratdenom q) x))))

(defun xeasy-eqs (p s x)
  (let ((acc `(($set))))
    (setq s (polynomial-filter s x #'(lambda (n) (min 1 n))))
    (setq s (cdr ($solve s x)))
    (dolist (si s (require-set acc "easy-eqs"))
      (setq acc ($adjoin ($substitute si p) acc)))))

;; Find conditions that make the polynomial 'cnd' vanish at each zero of
;; the polynomial 's.'  When the zero of 's' isn't messy, we simply 
;; evaluate 'cnd' at the zero.  When the zero of 's' is messy, we 
;; use a different method. In the best of all possible worlds, I'd
;; treat all zeros as messy---when I do this, some DEs in my testing
;; routine don't get solved. 

(defun easy-eqs (cnd s x)
  (let ((acc) (n) ($gcd '$spmod) ($algebraic t) 
	($solve_inconsistent_error nil) ($programmode t) 
	($globalsolve nil) ($solveexplicit t) ($solveradcan nil))
    
    (setq s (polynomial-filter s x #'(lambda (n) (min 1 n))))
    (setq s ($factor s))
    (setq s (if (mtimesp s) (margs s) (list s)))
    (dolist (si s acc)
      (setq si ($expand si))
      (setq n ($hipow si x))
      ;; Check for a non-messy zero.
      (cond ((< n 3)
	     (setq si (margs ($solve si x)))
	     (dolist (sij si)
	       (push ($substitute sij cnd) acc)))
	    (t
	     (setq acc (append acc 
			       (polycfs-to-eqs 
				($second ($divide cnd si x)) x))))))))

      
;; Expunge multiple zeros.

(defun clean-equation (p)
  (let ((acc 1))
    (setq p ($sqfr p))
    (setq p (cond ((mtimesp p) (margs p))
		  ((mexptp p) (list (car (margs p))))
		  (t (list p))))
    (dolist (pj p acc)
      (setq acc (mul acc (if (mexptp pj) (car (margs pj)) pj))))))   

;; generic-de-solver uses a method described by Bronstein and Lafaille, 
;; to compute special function solutions of non-Liouvillian 2nd order 
;; linear homogeneous ODES.
;;
;; M Bronstein, S Lafaille, Solutions of linear ordinary differential 
;; equations in terms of special functions, Proceedings of ISSAC 2002, Lille, 
;; ACM Press, 23-28. 
;; (http://www-sop.inria.fr/cafe/Manuel.Bronstein/publications/issac2002.pdf)
;;
;; Given 
;; - an ode y'' = v y, where v is a function of x, 
;; - a target 2nd order linear ODE L y = 0, with
;;    - L = D^2 + a1 D + a0 a 2nd order linear differential operator
;;    - {F1,F2} a known fundamental solution set of L y = 0
;; we seek functions m(x) and xi(x) st { m(x) F1(xi(x)), m(x) F2{xi(x)) }
;; is a fundamental solution set of y'' = v y.
;;
;; xi(x) satisfies a non-linear third-order ODE [Eq 7 in paper] (see 
;; get-de-cnd below).  The solution of this DE is a rational function 
;; xi(x)=P(x)/Q(x) where P(x) and Q(x) are polynomials.  It is shown 
;; that the denominator Q(x) is given by certain terms in the square-free
;; factorization of the denominator of v(x), and an upper bound to
;; the degree of the numerator P(x) can be found.  This allows P(x) to be 
;; found, if it exists, using the method of undetermined coefficients.  
;; These quantities depend on the order at infinity of v(x) and 
;; Delta=(a1)^2+2*a1'-4*a0
;; 
;;
;; For each target equation L = D^2 + a1 D + a0 we specialize the solver
;; by providing
;; o  params:       a list of additional parameters in the operator
;; o  denom-filter: a rule to select the terms in the square-free 
;;                  factorization of the denominator of v(x) that form Q(x).
;; o  degree-bound: the upper bound for the degree of the numerator P(x)
;; o  de-cnd: -Delta/4, where Delta = (a1)^2  + 2 a1' - 4 a0
;;
;;
;;                           i
;; Theorem 1:  Let Product( Q  ) be the squarefree decomposition of 
;;                           i
;; the denominator of v(x)and d = order at infinity of Delta, with d<2.
;;                                                 i
;; Then the denominator Q(x) of xi(x) is Product( Q  ) with n=(2-d)*i+2
;;                                                 n
;;
;; The numerator P(x) is a polynomial in x.  Either deg(P) <= deg(Q)+1
;; or deg(P) = deg(Q) + (2-order_at_infinity(v))/(2-d).  When d<0 the
;; first bound becomes deg(P) <= deg(Q).
;;
;; The function denom-filter(n) returns the exponent i for the term Q_n,
;; with i=0 for factors Q_n that are not used.
;;
;; CASE   |  n      | 1  2  3  4  5  6  7  8 |  Example
;; -------|---------|------------------------------------------------------
;; d = -1 | i=3*n+2 | 0  0  0  0  1  0  0  2 | (not used)
;; d =  0 | i=2*n+2 | 0  0  0  1  0  2  0  3 | bessel-xi-denom-filter
;; d =  1 | i=n+2   | 0  0  1  2  3  4  5  6 | bessel-sqrt-xi-denom-filter
;; d =  2 | i=0     | 0  0  0  0  0  0  0  0 | hypergeo21-xi-denom-filter
;;
;; The function degree-bound returns the upper bound of the numerator
;; P(x) of xi(x).  
;; 
;; The proof in the paper only applies for the case d<2. It does not apply
;; for the 2F1 hypergeometric function where d=2, although the method clearly 
;; works for this case too.
;;
;;
(defun generic-de-solver (v x params denom-filter degree-bound de-cnd)
  (setq v ($rat v x))
  (let ((s) (q) (p) (n) (unks) (nz) (eqs) (cnd) (sol) (xeqs)
	($gcd '$spmod) ($algebraic t) ($ratfac nil))

    (setq s ($ratdenom v))
    (setq q (polynomial-filter s x denom-filter))
    (setq n (funcall degree-bound q v x))
    (multiple-value-setq (p unks) (make-unk-poly x n))
    (setq nz `((mlist) ,p ,(sub (mul p ($diff q x)) (mul q ($diff p x)))))
    (setq unks (append params unks))
    (push `(mlist) unks)
    (setq cnd (get-de-cnd (div p q) v de-cnd x))
    (setq xeqs (easy-eqs cnd s x))
    (setq eqs (polycfs-to-eqs cnd x))
    (setq unks ($listify unks))
    (setq eqs (mapcar #'clean-equation eqs))
    (setq xeqs (mapcar #'clean-equation xeqs))
    (push `(mlist) eqs)
    (push `(mlist) xeqs)
    (setq sol ($aalgsys xeqs eqs unks nz))
    ;;(setq sol ($checkedalgsys ($append xeqs eqs) unks nz))
    
    (cond (($listp sol)
	   (setq p ($substitute sol p))
	   (setq params (mapcar #'(lambda (s) ($substitute sol s)) params))
	   `(,(div p q) ,params))
	  (t nil))))

;; Called by generic-de-solver implementing method of Bronstein and Lafaille.
;; Returns the differential equation satisfied by xi(x) [Eq 7 in paper] 
;;
;;       2                       2                           4         2
;; 3*xi'' - 2*xi'*xi''' + (a1(xi) + 2*a1'(xi) - 4*a0(xi))*xi' - 4*v*xi' = 0
;;
;; In this routine
;;    vh is the expression v(x) in DE to be solved (D^2-v)y=0
;;    v  is the expression -(a1(x)^2 + 2*a1'(x) - 4*a0(x))/4
;;    We return (-1) times equation above
;;
(defun get-de-cnd (xi vh v x)
  (let ((dxi) (ddxi) (dddxi) (dxi^2) ($gcd '$spmod) ($algebraic t) 
	($ratfac nil) ($ratprint nil))
    
    (setq xi ($ratdisrep xi))
    (setq dxi ($diff xi x))
    (setq dxi^2 (mul dxi dxi))
    (setq ddxi ($diff dxi x))
    (setq dddxi ($diff ddxi x))
    
    ;; 4*dxi^4*v(xi)-4*dxi^2*vh(x)+2*dddxi*dxi-3*ddxi^2
   
    ($ratdisrep ($ratnumer
		 (add
		  (mul 4 ($substitute xi x v) dxi^2 dxi^2)
		  (mul -4 vh dxi^2)
		  (mul 2 dxi dddxi)
		  (mul -3 ddxi ddxi))))))

;; The differential operator with the FSS {bessel_j(mu,x),bessel_y(mu,x)} 
;; is L = D^2 + a1*D + a0, with a1 = 1/x and a0 = (1-mu^2/x^2).
;; 
;; Delta = (a1)^2+2*a1'-4*a0  = (4*mu^2-1)/x^2-4
;;       -> constant as x-> infinity.  (order at infinity is 0)
;;
;; de-cnd = -Delta/4 = (4*x^2+1-4*mu^2)/(4*x^2)
;;
;; Additional parameter [mu]
;;
(defun bessel-xi-denom-filter (n)
  (if (and (evenp n) (> n 3)) (/ (- n 2) 2) 0))

(defun bessel-xi-degree-bound (q v x)
  (ceiling (+ 1 ($hipow q x) (/ (max (ratfun-degree v x) -2) 2))))

(defun bessel-de-solver (v x)
  (if $de_solver_is_loquacious
      (mtell "...trying the Bessel solver~%"))

  (let* 
      ((mu (gensym)) (z) (m) ($radexpand nil)
       (xi (generic-de-solver v x 
			      (list mu)
			      'bessel-xi-denom-filter
			      'bessel-xi-degree-bound
			      (div (add (mul 4 x x) 1 (mul -4 mu mu))
				   (mul 4 x x)))))
    (cond (xi
	   (setq z (nth 0 xi))
	   (setq mu (car (nth 1 xi)))
	   (setq z ($ratsimp z))
	   (setq mu ($ratsimp mu))
	   (setq m (mul 
		    (power z (div 1 2)) 
		    (power ($diff z x) (div -1 2))))
	   
	   (mbag-map #'(lambda (s) (mul m s))
		     `(($set) 
		       (($fbessel_j) ,mu ,z)
		       (($fbessel_y) ,mu ,z)))))))

;; The differential operator with the FSS 
;;   {bessel_j(mu,sqrt(x)),bessel_y(mu,sqrt(x))} 
;; is L = D^2 + a1 D + a0, with a1 = 1/x and a0 = (1/x-mu^2/x^2)/4.
;; 
;; Delta = (a1)^2 +2*a1'-4*a0 = (mu^2-1)/x^2-1/x
;;       -> 1/x as x-> infinity.  (order at infinity is 1)
;;
;; de-cnd = -Delta/4 = (1 + x - mu^2)/(4*x^2)
;;
;; Additional parameter [mu]
;;       
(defun bessel-sqrt-xi-denom-filter (n)
  (if (> n 2) (- n 2) 0))

(defun bessel-sqrt-xi-degree-bound (q v x)
  (+ 2 ($hipow q x) (max -2 (ratfun-degree v x))))

(defun bessel-sqrt-de-solver (v x)
  (if $de_solver_is_loquacious
      (mtell "...trying the square root Bessel solver~%"))

  (let*  ((mu (gensym)) (z) (m) ($radexpand nil)
	  (xi (generic-de-solver v x 
				 (list mu)
				 'bessel-sqrt-xi-denom-filter
				 'bessel-sqrt-xi-degree-bound
				 (div (add 1 x (mul -1 mu mu)) (mul 4 x x)))))
    (cond (xi
	   (setq z (nth 0 xi))
	   (setq mu (car (nth 1 xi)))
	   (setq z ($ratsimp z))
	   (setq mu ($ratsimp mu))
	   (setq m (mul 
		    (power z (div 1 2)) 
		    (power ($diff z x) (div -1 2))))
	   
	   (setq z (power z (div 1 2)))
	   (mbag-map #'(lambda (s) (mul m s))
		     `(($set) 
		       (($fbessel_j) ,mu ,z)
		       (($fbessel_y) ,mu ,z)))))))

;; The differential operator with the FSS {kummer_m(a,b,x),kummer_u(a,b,x)} 
;; is L = D^2 + a1*D + a0, with a1 = b/x - 1 and a0 = -a/x.
;;  
;; Delta = (a1)^2+2*a1'-4*a0 = 1+(4*a-2*b)/x+(b^2-2*b)/x^2
;;       = (x^2 + (4 a - 2 b) x + b^2 -2 b)/x^2
;;       -> 1 as x-> infinity.  (order at infinity is 0)
;;
;; de-cnd = -Delta/4 = (-x^2+(2*b-4*a)*x-b^2+2*b)/(4*x^2)
;;
;; Additional parameters to be determined are [a,b]
;;
(defun hypergeo01-xi-denom-filter (n)
  (if (and (evenp n) (> n 3)) (/ (- n 2) 2) 0))

(defun hypergeo01-xi-degree-bound (q v x)
  (ceiling (+ 1 ($hipow q x) (/ (max (ratfun-degree v x) -2) 2))))

(defun hypergeo01-de-solver (v x)
  (if $de_solver_is_loquacious (mtell "...trying the F01 solver~%"))
  (let* ((a (gensym)) (b (gensym)) (m) (z) ($radexpand nil)
	 (xi (generic-de-solver v x 
				(list a b)
				'hypergeo01-xi-denom-filter
				'hypergeo01-xi-degree-bound
				;; (-x^2+(2*b-4*a)*x-b^2+2*b)/(4*x^2)
				(div
				 (add 
				  (mul x x)
				  (mul (sub (mul 4 a) (mul 2 b)) x)
				  (mul b b)
				  (mul -2 b))
				 (mul -4 x x)))))
    (cond (xi
	   (setq z (car xi))
	   (setq a (caadr xi))
	   (setq b (cadadr xi))
	   (setq z ($ratsimp z))
	   (setq m (mul 
		    (power '$%e (div z -2)) 
		    (power z (div b 2))
		    (power ($diff z x) (div -1 2))))

	   (mbag-map #'(lambda (s) (mul s m))
		     `(($set) 
		       (($kummer_m) ,a ,b ,z)
		       (($kummer_u) ,a ,b ,z)))))))

;; The differential operator with the FSS 
;; {spherodialwave_a(b,c,q,x),spherodialwave_b(b,c,q,x)} 
;; is L = D^2 + a1*D + a0, with a1 = -2*(b+1)*x/(1-x^2) 
;; and a0 = (c-4*q*x^2)/(1-x^2)
;;  
;; Delta = (a1)^2  + 2 a1' - 4 a0
;;       = 4*(-q*x^4 + (4q+b^2+b+c)*x^2 - (b+c+1)) / (1-x^2)^2
;;       -> constant as x-> infinity.  (order at infinity is 0)
;;
;; de-cnd = -Delta/4 = (q*x^4 - (4q+b^2+b+c)*x^2 + (b+c+1)) / (1-x^2)^2
;;
;; Additional parameters to be determined are [b,c,q]
;;
;; FIXME: [DB 2007-04-29] Need to clarify definition of the spherodial
;; wave functions as I can't find a reference that exactly matches.
;; The values of a1 and a0 above:
;; - give an ODE that is satisfied by spherodialwave_a and spherodialwave_b, 
;; - value of de-cnd derived from them matches the one in the code below.
;;
(defun spherodialwave-xi-denom-filter (n)
  (if (and (evenp n) (> n 3)) (/ (- n 2) 2) 0))

(defun spherodialwave-xi-degree-bound (q v x)
  (ceiling (+ 1 ($hipow q x) (/ (max (ratfun-degree v x) -2) 2))))

(defun spherodialwave-de-solver (v x)
  (if $de_solver_is_loquacious (mtell "...trying the spherodial wave solver~%"))
  (let* ((b (gensym)) (c (gensym)) (q (gensym)) (m) (z) (x2 (mul x x))
	 ($radexpand nil)
	 (xi (generic-de-solver v x 
				(list b c q)
				'spherodialwave-xi-denom-filter
				'spherodialwave-xi-degree-bound
				(mul
				 (power (add 1 (mul (add -2 x2) x2)) -1)
				 (add 1 b c
				      (mul x2 (add (mul 
						     (add -1 (mul -1 b)) b)
						   (mul -1 c) (mul -4 q)
						   (mul 4 q x2))))))))

    ;; (x2*(4*q*x2-4*q-c+(-b-1)*b)+c+b+1)/((x2-2)*x2+1), x2 = x^2.
    
    (cond (xi
	   (setq z (nth 0 xi))
	   (setq b (nth 0 (nth 1 xi)))
	   (setq c (nth 1 (nth 1 xi)))
	   (setq q (nth 2 (nth 1 xi)))
	   (setq z ($ratsimp z))
	   (setq m (mul 
		    (power (sub (mul z z) 1) (div (add b 1) 2))
		    (power ($diff z x) (div -1 2))))

	   (mbag-map #'(lambda (s) (mul m s))
		     `(($set) 
		       (($spherodialwave_a) ,b ,c ,q ,z)
		       (($spherodialwave_b) ,b ,c ,q ,z)))))))

;; The differential operator for the hypergeometric differential equation 
;; (A&S 15.5.1) with the FSS {gauss_a(a,b,c,x),gauss_b(a,b,c,x)} is
;; L = D^2 + a1 D + a0, with a1=(c-(a+b+1)*x)/(x*(1-x)) and a0=-a*b/(x*(1-x)).
;;  
;; Delta = (a1)^2  + 2*a1' - 4*a0
;;           2            2       2                                      2
;;         (b  - 2 a b + a  - 1) x  + ((- 2 b - 2 a + 2) c + 4 a b) x + c  - 2 c
;;       = ---------------------------------------------------------------------
;;                                           2  2
;;                                    (x - 1)  x
;;
;;       -> constant/x^2 as x-> infinity.  (order at infinity is 2)
;; 
;; de-cnd = -Delta/4 
;;        = -((b^2-2*a*b+a^2-1)*x^2+((-2*b-2*a+2)*c+4*a*b)*x+c^2-2*c)/(4*x^2*(x-1)^2)
;;
;; Additional parameters to be determined are [a,b,c]
;;
(defun hypergeo21-xi-denom-filter (n)
  (declare (ignore n))
  0)

(defun hypergeo21-xi-degree-bound (q v x)
  (declare (ignore q v x))
  1)


(defun hypergeo21-de-solver (v x)
  (if $de_solver_is_loquacious (mtell "...trying the 2F1 solver~%"))
  (let* ((a (gensym)) (b (gensym)) (c (gensym)) (m) (z) ($radexpand nil)
	 (xi (generic-de-solver v x 
				(list a b c)
				'hypergeo21-xi-denom-filter
				'hypergeo21-xi-degree-bound

				(div
				 (add
				  (mul x
					 (add
					  (mul
					   (add (mul (add (mul 2 a) 
							  (*mminus b)) b)
						(*mminus (power a 2))
						1)
					   x)
					  (mul (add (mul 2 b) (mul 2 a) -2) c)
					  (mul -4 a b)))
				  (mul (add 2 (*mminus c)) c))
				 (mul (power x 2)
				      (add (mul x (add (mul 4 x) -8)) 4))))))
			
    
    (cond (xi
	   (setq z (nth 0 xi))
	   (setq a (nth 0 (nth 1 xi)))
	   (setq b (nth 1 (nth 1 xi)))
	   (setq c (nth 2 (nth 1 xi)))
	   (setq z ($ratsimp z))
;; (xi(x))^(c/2) * (xi(x)-1)^((-c+b+a+1)/2) * f(xi(x)) / sqrt(diff(xi(x),x))
	   (setq m (mul 
		    (power z (div c 2)) (power (sub z 1) 
					       (div (add 1 a b (mul -1 c)) 2))
		    (power ($diff z x) (div -1 2))))

	   (mbag-map #'(lambda (s) (mul m s))
		     `(($set) 
		       (($gauss_a) ,a ,b ,c ,z)
		       (($gauss_b) ,a ,b ,c ,z)))))))

