;; A Maxima ring stucture
;; Copyright (C) 2005, 2007, Barton Willis

;; Barton Willis
;; Department of Mathematics
;; University of Nebraska at Kearney
;; Kearney NE 68847
;; willisb@unk.edu

;; This source code is licensed under the terms of the Lisp Lesser 
;; GNU Public License (LLGPL). The LLGPL consists of a preamble, published
;; by Franz Inc. (http://opensource.franz.com/preamble.html), and the GNU 
;; Library General Public License (LGPL), version 2, or (at your option)
;; any later version.  When the preamble conflicts with the LGPL, 
;; the preamble takes precedence. 

;; 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
;; Library General Public License for details.

;; You should have received a copy of the GNU Library General Public
;; License along with this library; if not, write to the
;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
;; Boston, MA  02110-1301, USA.

;; Let's have version numbers 1,2,3,...

(eval-when (:compile-toplevel :load-toplevel :execute)
  ($put '$mring 1 '$version))

;; (1) In maxima-grobner.lisp, there is a structure 'ring.'  

;; (2) Some functions in this structure, for example 'great' might 
;;     not be defined for a ring; when this is the case, a function 
;;     can signal an error.

;; (3) Floating point addition isn't associative; so a mring needn't
;;     be a ring.  But a mring is 'close' to being a ring.

;; Description of the mring fields:

(defstruct mring
  name
  coerce-to-lisp-float
  abs
  great
  add
  div
  rdiv
  reciprocal
  mult
  sub
  negate
  psqrt
  add-id
  mult-id
  fzerop
  adjoint
  maxima-to-mring
  mring-to-maxima)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defmvar $%mrings `((mlist) $floatfield $complexfield $rationalfield $crering $generalring $bigfloatfield
		      $runningerror $noncommutingring)))
		
(defun $require_ring (ringname pos fun)
  (if ($member ringname $%mrings) (get ringname 'ring)
    (merror "The ~:M argument of the function '~:M' must be the name of a ring" pos fun)))

(defparameter *floatfield*
  (make-mring
   :name '$floatfield
   :coerce-to-lisp-float #'cl:identity
   :abs #'abs
   :great #'>
   :add #'+
   :div #'/
   :rdiv #'/
   :reciprocal #'/
   :mult #'*
   :sub #'-
   :negate #'-
   :psqrt #'(lambda (s) (if (>= s 0) (cl:sqrt s) nil))
   :add-id #'(lambda () 0.0)
   :mult-id #'(lambda () 1.0)
   :fzerop #'(lambda (s)  (< (abs s) (* 4 double-float-epsilon))) 
   :adjoint #'cl:identity
   :mring-to-maxima #'cl:identity
   :maxima-to-mring #'(lambda (s) 
			(setq s ($float s))
			(if (floatp s) s (merror "Unable to convert ~:M to a double float" s)))))

(setf (get '$floatfield 'ring) *floatfield*)

(defparameter *complexfield*
  (make-mring
   :name '$complexfield
   :coerce-to-lisp-float #'cl:identity
   :abs #'abs
   :great #'>
   :add #'+
   :div #'/
   :rdiv #'/
   :reciprocal #'/
   :mult #'*
   :sub #'-
   :negate #'-
   :psqrt #'(lambda (s) (if (and (= 0 (imagpart s)) (>= (realpart s) 0)) (cl:sqrt s) nil))
   :add-id #'(lambda () 0.0)
   :mult-id #'(lambda () 1.0)
   :fzerop #'(lambda (s) (< (abs s) (* 4 double-float-epsilon)))
   :adjoint #'cl:conjugate
   :mring-to-maxima #'(lambda (s) (add (realpart s) (mult '$%i (imagpart s)))) ;; was complexify
   :maxima-to-mring #'(lambda (s) 
			(progn 
			  (setq s ($rectform (meval s)))
			  (if (complex-number-p s 'float-or-rational-p)
			      (complex ($float ($realpart s)) ($float ($imagpart s)))
			    (merror "Unable to convert ~:M to a complex double float" s))))))

(setf (get '$complexfield 'ring) *complexfield*)

(defparameter *rationalfield*
  (make-mring
   :name '$rationalfield 
   :coerce-to-lisp-float #'(lambda (s) ($float s))
   :abs #'abs
   :great #'>
   :add #'+
   :div #'/
   :rdiv #'/
   :reciprocal #'/
   :mult #'*
   :sub #'-
   :negate #'-
   :psqrt #'(lambda (s) (let ((x))
			  (cond ((>= s 0)
				 (setq x (isqrt (numerator s)))
				 (setq x (/ x (isqrt (denominator s))))
				 (if (= s (* x x)) x nil))
				(t nil))))
   :add-id #'(lambda () 0)
   :mult-id #'(lambda () 1)
   :fzerop #'(lambda (s) (= s 0))
   :adjoint #'cl:identity
   :mring-to-maxima #'(lambda (s) (simplify `((rat) ,(numerator s) ,(denominator s))))
   :maxima-to-mring 
   #'(lambda (s) 
       (if (or (floatp s) ($bfloatp s)) (setq s ($rationalize s)))
       (if ($ratnump s) (if (integerp s) s (/ ($num s) ($denom s)))
	 (merror "Unable to convert ~:M to a rational number" s)))))

(setf (get '$rationalfield 'ring) *rationalfield*)

(defparameter *crering*
  (make-mring
   :name '$crering
   :coerce-to-lisp-float nil
   :abs #'(lambda (s) (simplify (mfuncall '$cabs s)))
   :great #'(lambda (a b) (declare (ignore a)) (eq t (meqp b 0)))
   :add #'add
   :div #'div
   :rdiv #'div
   :reciprocal #'(lambda (s) (div 1 s))
   :mult #'mult
   :sub #'sub
   :negate #'(lambda (s) (mult -1 s))
   :psqrt #'(lambda (s) (if (member (csign ($ratdisrep s)) `($pos $pz $zero)) (take '(%sqrt) s) nil))
   :add-id #'(lambda () 0)
   :mult-id #'(lambda () 1)
   :fzerop #'(lambda (s) (eq t (meqp s 0)))
   :adjoint #'(lambda (s) (take '($conjugate) s))
   :mring-to-maxima #'(lambda (s) s)
   :maxima-to-mring #'(lambda (s) ($rat s))))
		
(setf (get '$crering 'ring) *crering*)

(defparameter *generalring*
  (make-mring
   :name '$generalring
   :coerce-to-lisp-float nil
   :abs #'(lambda (s) (simplify (mfuncall '$cabs s)))
   :great #'(lambda (a b) (declare (ignore a)) (eq t (meqp b 0)))
   :add #'(lambda (a b) ($rectform (add a b)))
   :div #'(lambda (a b) ($rectform (div a b)))
   :rdiv #'(lambda (a b) ($rectform (div a b)))
   :reciprocal #'(lambda (s) (div 1 s))
   :mult #'(lambda (a b) ($rectform (mult a b)))
   :sub #'(lambda (a b) ($rectform (sub a b)))
   :negate #'(lambda (a) (mult -1 a))
   :psqrt #'(lambda (s) (if (member (csign s) `($pos $pz $zero)) (take '(%sqrt) s) nil))
   :add-id #'(lambda () 0)
   :mult-id #'(lambda () 1)
   :fzerop #'(lambda (s) (eq t (meqp s 0)))
   :adjoint #'(lambda (s) (take '($conjugate) s))
   :mring-to-maxima #'(lambda (s) s)
   :maxima-to-mring #'(lambda (s) s)))

(setf (get '$generalring 'ring) *generalring*)

(defparameter *bigfloatfield*
  (make-mring
   :name '$bigfloatfield
   :coerce-to-lisp-float #'(lambda (s) 
			     (setq s ($rectform ($float s)))
			     (complex ($realpart s) ($imagpart s)))
   
   :abs #'(lambda (s) (simplify (mfuncall '$cabs s)))
   :great #'mgrp
   :add #'(lambda (a b) ($rectform (add a b)))
   :div #'(lambda (a b) ($rectform (div a b)))
   :rdiv #'(lambda (a b) ($rectform (div a b)))
   :reciprocal #'(lambda (s) (div 1 s))
   :mult #'(lambda (a b) ($rectform (mult a b)))
   :sub #'(lambda (a b) ($rectform (sub a b)))
   :negate #'(lambda (a) (mult -1 a))
   :psqrt #'(lambda (s) (if (mlsp s 0) nil (take '(%sqrt) s)))
   :add-id #'(lambda () bigfloatzero)
   :mult-id #'(lambda () bigfloatone)
   :fzerop #'(lambda (s) (like s bigfloatzero))
   :adjoint #'cl:identity
   :mring-to-maxima #'(lambda (s) s)
   :maxima-to-mring #'(lambda (s) 
			(setq s ($rectform ($bfloat s)))
			(if (or (eq s '$%i) (complex-number-p s 'bigfloat-or-number-p)) s
				(merror "Unable to convert matrix entry to a big float")))))

(setf (get '$bigfloatfield 'ring) *bigfloatfield*)

(defun fp-abs (a)
  (list (abs (first a)) (second a)))

(defun fp+ (a b)
  (cond ((= (first a) 0.0) b)
	((= (first b) 0.0) a)
	(t
	 (let ((s (+ (first a) (first b))))
	   (if (= 0.0 s) (merror "floating point divide by zero"))
	   (list s (ceiling (+ 1
			       (abs (/ (* (first a) (second a)) s))
			       (abs (/ (* (first b) (second b)) s)))))))))
				
(defun fp- (a b)
  (cond ((= (first a) 0.0) (list (- (first b)) (second b)))
	((= (first b) 0.0) a)
	(t
	 (let ((s (- (first a) (first b))))
	   (if (= 0.0 s) (merror "floating point divide by zero"))
	   (list s (ceiling (+ 1
			       (abs (/ (* (first a) (second a)) s))
			       (abs (/ (* (first b) (second b)) s)))))))))

(defun fp* (a b)
  (if (or (= (first a) 0.0) (= (first b) 0.0)) (list 0.0 0)
    (list (* (first a) (first b)) (+ 1 (second a) (second b)))))

(defun fp/ (a b)
  (if (= (first a) 0) (list 0.0 0)
    (list (/ (first a) (first b)) (+ 1 (second a) (second b)))))

(defun $addmatrices(fn &rest m)
  (mfuncall '$apply '$matrixmap `((mlist) ,fn ,@m)))

(defparameter *runningerror*
  (make-mring 
   :name '$runningerror
   :coerce-to-lisp-float #'(lambda (s) (if (consp s) (first s) s))
   :abs #'fp-abs
   :great #'(lambda (a b) (> (first a) (first b)))
   :add #'fp+
   :div #'fp/
   :rdiv #'fp/
   :reciprocal #'(lambda (s) (fp/ (list 1 0) s))
   :mult #'fp*
   :sub #'fp-
   :negate #'(lambda (s) (list (- (first s)) (second s)))
   :psqrt #'(lambda (s) (if (> (first s) 0) (list (cl:sqrt (first s)) (+ 1 (second s))) nil))
   :add-id #'(lambda () (list 0 0))
   :mult-id #'(lambda () (list 1 0))
   :fzerop #'(lambda (s) (like (first s) 0))
   :adjoint #'cl:identity
   :mring-to-maxima #'(lambda (s) `((mlist) ,@s))
   :maxima-to-mring #'(lambda (s) (if ($listp s) (cdr s) (list ($float s) 1)))))

(setf (get '$runningerror 'ring) *runningerror*)

(defparameter *noncommutingring* 
  (make-mring
   :name '$noncommutingring
   :coerce-to-lisp-float nil
   :abs #'(lambda (s) (simplify (mfuncall '$cabs s)))
   :great #'(lambda (a b) (declare (ignore a)) (eq t (meqp b 0)))
   :add #'(lambda (a b) (add a b))
   :div #'(lambda (a b) (progn
			  (let (($matrix_element_mult '&.)
				($matrix_element_transpose '$transpose))
			    (setq b (if ($matrixp b) ($invert_by_lu b '$noncommutingring)
				      (take '(mncexpt) b -1)))
			    (take '(mnctimes) a b))))
   
   :rdiv #'(lambda (a b) (progn
			   (let (($matrix_element_mult '&.)
				 ($matrix_element_transpose '$transpose))
			     (setq b (if ($matrixp b) ($invert_by_lu b '$noncommutingring)
				       (take '(mncexpt) b -1)))
			     (take  '(mnctimes) b a))))
				

   :reciprocal #'(lambda (s) (progn
			       (let (($matrix_element_mult '&.)
				     ($matrix_element_transpose '$transpose))
				 (if ($matrixp s) ($invert_by_lu s '$noncommutingring) 
				   (take '(mncexpt) s -1)))))

   :mult #'(lambda (a b) (progn 
			   (let (($matrix_element_mult '&.)
				 ($matrix_element_transpose '$transpose))
			     (take  '(mnctimes) a b))))


   :sub #'(lambda (a b) (sub a b))
   :negate #'(lambda (a) (mult -1 a))
   :add-id #'(lambda () 0)
   :psqrt #'(lambda (s) (take '(%sqrt) s))
   :mult-id #'(lambda () 1)
   :fzerop #'(lambda (s) (eq t (meqp s 0)))
   :adjoint #'(lambda (s) ($transpose (take '($conjugate) s)))
   :mring-to-maxima #'cl:identity
   :maxima-to-mring #'cl:identity))

(setf (get '$noncommutingring 'ring) *noncommutingring*)

(defmspec $ringeval (e)
  (let ((fld (get (or (car (member (nth 2 e) $%mrings)) '$generalring) 'ring)))
    (funcall (mring-mring-to-maxima fld) (ring-eval (nth 1 e) fld))))
 
(defun ring-eval (e fld)
  (let ((fadd (mring-add fld))
	(fnegate (mring-negate fld))
	(fmult (mring-mult fld))
	(fdiv (mring-div fld))
	(fabs (mring-abs fld))
	(fconvert (mring-maxima-to-mring fld)))
    
    (cond ((or ($numberp e) (symbolp e)) 
	   (funcall fconvert (meval e)))
	  
	  ;; I don't think an empty sum or product is possible here. If it is, append
	  ;; the appropriate initial-value to reduce. Using the :inital-value isn't
	  ;; a problem, but (fp* (a b) (1 0)) --> (a (+ b 1)).  A better value is
	  ;; (fp* (a b) (1 0)) --> (a b).

	  ((op-equalp e 'mplus) 
	   (reduce fadd (mapcar #'(lambda (s) (ring-eval s fld)) (margs e)) :from-end t))
	  
	  ((op-equalp e 'mminus)
	   (funcall fnegate (ring-eval (first (margs e)) fld)))
	  
	  ((op-equalp e 'mtimes) 
	   (reduce fmult (mapcar #'(lambda (s) (ring-eval s fld)) (margs e)) :from-end t))
		 
	  ((op-equalp e 'mquotient)
	   (funcall fdiv (ring-eval (first (margs e)) fld)(ring-eval (second (margs e)) fld)))
	   
	  ((op-equalp e 'mabs) (funcall fabs (ring-eval (first (margs e)) fld)))
	
	  ((and (or (eq (mring-name fld) '$floatfield) (eq (mring-name fld) '$complexfield))
		(consp e) (consp (car e)) (gethash (mop e) *double-float-op*))
	   (apply (gethash (mop e) *double-float-op*) (mapcar #'(lambda (s) (ring-eval s fld)) (margs e))))
	  
	  (t (merror "Unable to evaluate ~:M in the ring '~:M'" e (mring-name fld))))))
  
