;;; Matrix Routines ;;; ;;; The main advantage of this file is that is can work with matrices ;;; with elements of arbitrary types (bignums, ratios). ;;; The main disadvantages are performance and limited functionality. ;;; ;;; If you need fast floating point matrix operations, ;;; you should useMatLisp (http://matlisp.sourceforge.net/), ;;; which relies on BLAS (http://www.netlib.org/blas) and ;;; LAPACK (http://www.netlib.org/lapack) for heavy-duty computations. ;;; ;;; Copyright (C) 2000 by Sam Steingold ;;; This is Free Software, covered by the GNU GPL (v2) ;;; See http://www.gnu.org/copyleft/gpl.html ;;; ;;; $Id: matrix.lisp,v 2.5 2001/11/02 22:31:15 sds Exp $ ;;; $Source: /cvsroot/clocc/clocc/src/cllib/matrix.lisp,v $ (in-package :cllib) (eval-when (compile load eval) (require :cllib-base (translate-logical-pathname "clocc:src;cllib;base")) ;; `with-type', `index-t' (require :cllib-withtype (translate-logical-pathname "cllib:withtype")) ;; `divf' (require :cllib-math (translate-logical-pathname "cllib:math"))) (export '(matrix-print matrix-multiply array-copy array-lin-comb dimension matrix-id matrix-id-p matrix-symmetric-p bilinear matrix-solve-lower matrix-solve-upper matrix-solve-lu matrix-solve matrix-inverse)) (import '(matrix-print) :cl-user) ; format ~// ;;; ;;; printing ;;; (defun matrix-print (out aa colp atp &optional (fmt (formatter "~5,1,10$ "))) "Print a matrix. Suitable for `format' ~//. By default prints the contents. @ means print the rank and dimensions too. : means print just the rank and the dimensions." (declare (stream out) (type array aa)) (let ((rank (array-rank aa))) (declare (type index-t rank)) (when atp (format out ";; Matrix: " aa)) (if colp (format out "rank: ~d; dimension~:p: ~{~d~^x~}~%" rank (array-dimensions aa)) (case rank (1 (loop :for elt :across aa :do (format out fmt elt) (terpri out))) (2 (loop :with dim1 :of-type index-t = (1- (array-dimension aa 0)) :and dim2 :of-type index-t = (1- (array-dimension aa 1)) :for ii :of-type index-t :from 0 :to dim1 :do (loop :for jj :of-type index-t :from 0 :to dim2 :do (format out fmt (aref aa ii jj))) (terpri out))) (t (error 'case-error :proc 'matrix-print :args (list 'matrix aa 1 2))))) aa)) ;;; ;;; unity &c ;;; (define-condition dimension (code) ((mesg :type simple-string :reader code-mesg :initform "dimension mismatch:~@{ ~{~d~^x~}~}"))) (defun matrix-id (nn) "Generate an NxN identity matrix" (declare (type index-t nn)) (loop :with matrix = (make-array (list nn nn) :initial-element 0) :for ii :from 0 :to (1- nn) :do (setf (aref matrix ii ii) 1) :finally (return matrix))) (defun array-copy (aa &optional bb) "Copy an arbitrary array." (declare (type array aa)) (case (array-rank aa) (1 (if bb (replace bb aa) (copy-seq aa))) (t (let* ((res (or bb (make-array (array-dimensions aa) :element-type (array-element-type aa)))) (tot (array-total-size aa)) (v0 (make-array tot :displaced-to aa)) (v1 (make-array tot :displaced-to res))) (declare (dynamic-extent v0 v1)) (replace v1 v0) res)))) (defun matrix-symmetric-p (mx) "Check for the matrix being symmetric." (declare (type array mx)) (let ((dim (array-dimension mx 0))) (and (= dim (array-dimension mx 1)) (loop :for ii :of-type index-t :from 1 :to (1- dim) :unless (loop :for jj :of-type index-t :from 0 :to (1- ii) :unless (= (aref mx ii jj) (aref mx jj ii)) :return nil :finally (return t)) :return nil :finally (return t))))) (defun matrix-id-p (mx) "Check the matrix for being identity." (declare (type array mx)) (let ((dim (array-dimension mx 0))) (and (= dim (array-dimension mx 1)) (loop :for ii :of-type index-t :from 1 :to (1- dim) :unless (and (loop :for jj :of-type index-t :from 0 :to (1- ii) :unless (and (zerop (aref mx ii jj)) (zerop (aref mx jj ii))) :return nil :finally (return t)) (= 1 (aref mx ii ii))) :return nil :finally (return t))))) ;;; ;;; linear combinations ;;; (defun array-check-return (arr dims) "Make the value to be returned." (if arr (if (equal (array-dimensions arr) dims) arr (error 'dimension :proc 'array-check-return :args (list (array-dimensions arr) dims))) (make-array dims :initial-element 0))) (defun array-lin-comb (l0 a0 l1 a1 &optional res) "Linear combination of arbitrary arrays." (declare (type array a0 a1) (number l0 l1)) (let ((dims (array-dimensions a0))) (unless (equal dims (array-dimensions a1)) (error 'dimension :proc 'array-check-return :args (list dims (array-dimensions a1)))) (let* ((ret (array-check-return res dims)) (tot (array-total-size ret)) (v0 (make-array tot :displaced-to a0)) (v1 (make-array tot :displaced-to a1)) (v2 (make-array tot :displaced-to ret))) (declare (dynamic-extent v0 v1 v2)) (dotimes (ii tot ret) (setf (aref v2 ii) (+ (* l0 (aref v0 ii)) (* l1 (aref v1 ii)))))))) ;;; ;;; Bilinear form ;;; (defun bilinear (mx v0 v1) "Compute the bilinear form (Ax,y)." (declare (type (array * (* *)) mx) (type (array * (*)) v0 v1)) (unless (and (= (array-dimension v0 0) (array-dimension mx 0)) (= (array-dimension v0 0) (array-dimension mx 0))) (error 'dimension :proc 'bilinear :args (list (array-dimensions mx) (array-dimensions v0) (array-dimensions v1)))) (loop :for ii :of-type index-t :from 0 :to (1- (array-dimension mx 0)) :sum (* (aref v0 ii) (loop :for jj :of-type index-t :from 0 :to (1- (array-dimension mx 1)) :sum (* (aref mx ii jj) (aref v1 jj)))))) ;;; ;;; Matrix Multiplication ;;; (defun matrix-multiply (aa bb &optional re) "Multiply two matrixes. The optional third argument is the place where to put the return value." (flet ((num-arr (num input dim) ; scalar X any array (loop :with res :of-type array = (array-check-return re dim) :with tot :of-type index-t = (array-total-size res) :with to = (make-array tot :displaced-to res) :with fr = (make-array tot :displaced-to input) :for ii :of-type index-t :from 0 :to (1- tot) :do (setf (aref to ii) (* num (aref fr ii))) :finally (return res)))) (cond ((numberp aa) (num-arr aa bb (array-dimensions bb))) ((numberp bb) (num-arr bb aa (array-dimensions aa))) ((and (= 2 (array-rank aa)) ; matrix X matrix (= 2 (array-rank bb))) (if (= (array-dimension aa 1) (array-dimension bb 0)) (loop :with res :of-type array = (array-check-return re (list (array-dimension aa 0) (array-dimension bb 1))) :for ii :from 0 :to (1- (array-dimension aa 0)) :do (loop :for jj :from 0 :to (1- (array-dimension bb 1)) :do (loop :for kk :from 0 :to (1- (array-dimension aa 1)) :do (incf (aref res ii jj) (* (aref aa ii kk) (aref bb kk jj))))) :finally (return res)) (error 'dimension :proc 'matrix-multiply :args (list (array-dimensions aa) (array-dimensions bb))))) ((and (= 1 (array-rank aa)) ; row X matrix (= 2 (array-rank bb))) (if (= (array-dimension aa 0) (array-dimension bb 0)) (loop :with res :of-type array = (array-check-return re (list (array-dimension bb 1))) :for ii :from 0 :to (1- (array-dimension bb 1)) :do (loop :for jj :from 0 :to (1- (array-dimension aa 0)) :do (incf (aref res ii) (* (aref aa jj) (aref bb jj ii)))) :finally (return res)) (error 'dimension :proc 'matrix-multiply :args (list (array-dimensions aa) (array-dimensions bb))))) ((and (= 2 (array-rank aa)) ; matrix X column (= 1 (array-rank bb))) (if (= (array-dimension aa 1) (array-dimension bb 0)) (loop :with res :of-type array = (array-check-return re (list (array-dimension aa 0))) :for ii :from 0 :to (1- (array-dimension aa 0)) :do (loop :for jj :from 0 :to (1- (array-dimension bb 0)) :do (incf (aref res ii) (* (aref aa ii jj) (aref bb jj)))) :finally (return res)) (error 'dimension :proc 'matrix-multiply :args (list (array-dimensions aa) (array-dimensions bb))))) ((and (= 1 (array-rank aa)) ; column X row (row X col is dot) (= 1 (array-rank bb))) (loop :with res :of-type array = (array-check-return re (list (array-dimension aa 0) (array-dimension bb 0))) :for ii :from 0 :to (1- (array-dimension aa 0)) :do (loop :for jj :from 0 :to (1- (array-dimension bb 0)) :do (setf (aref res ii jj) (* (aref aa ii) (aref bb jj)))) :finally (return res))) (t (error 'code :proc 'matrix-multiply :args (list aa bb) :mesg "cannot multiply a matrix <~:/matrix-print/> ~ by a matrix <~:/matrix-print/>"))))) ;;; ;;; solving linear systems ;;; (defun mx-solve-check (aa bb) (unless (and (= 2 (array-rank aa)) (= 1 (array-rank bb)) (= (array-dimension aa 0) (array-dimension aa 1) (array-dimension bb 0))) (error 'dimension :proc 'matrix-solve :args (list (array-dimensions aa) (array-dimensions bb))))) (defun matrix-solve-lower (aa bb &optional diag1) "Solve Ax=b, put the result in b. A is assumed to be a lower-triangular matrix." (declare (type (array * (* *)) aa) (type (array * (*)) bb)) (mx-solve-check aa bb) (loop :with nn :of-type index-t = (1- (array-dimension aa 0)) :for ii :of-type index-t :from 0 :to nn :do (unless diag1 (divf (aref bb ii) (aref aa ii ii))) (loop :for jj :of-type index-t :from (1+ ii) :to nn :do (decf (aref bb jj) (* (aref aa jj ii) (aref bb ii)))) :finally (return bb))) (defun matrix-solve-upper (aa bb &optional diag1) "Solve Ax=b, put the result in b. A is assumed to be an upper-triangular matrix." (declare (type (array * (* *)) aa) (type (array * (*)) bb)) (mx-solve-check aa bb) (loop :with nn :of-type index-t = (1- (array-dimension aa 0)) :for ii :of-type index-t :downfrom nn :to 1 :do (unless diag1 (divf (aref bb ii) (aref aa ii ii))) (loop :for jj :of-type index-t :downfrom (1- ii) :to 1 :do (decf (aref bb jj) (* (aref aa jj ii) (aref bb ii)))) :finally (return bb))) (defun matrix-lu (mx lu) "Decompose the matrix MX into Lower*Upper. The diagonal elements of the upper-triangular part are 1. MX and LU can be the same matrix. See Horn/Johnson 'Matrix Analysis' 3.5.2." (declare (type (array * (* *)) mx lu)) (unless (and (= 2 (array-rank mx) (array-rank lu)) (equal (array-dimensions mx) (array-dimensions lu)) (= (array-dimension mx 0) (array-dimension mx 1))) (error 'dimension :proc 'matrix-lu :args (list (array-dimensions mx) (array-dimensions lu)))) (let ((nn (1- (array-dimension mx 0)))) (flet ((lu-sum (ii jj) (loop :for kk :of-type index-t :downfrom (1- (min ii jj)) :to 0 :sum (* (aref lu ii kk) (aref lu kk jj))))) (loop :for ii :of-type index-t :from 0 :to nn :do (loop :for jj :of-type index-t :from ii :to nn :do (setf (aref lu jj ii) (- (aref mx jj ii) (lu-sum jj ii)))) (loop :for jj :of-type index-t :from (1+ ii) :to nn :do (setf (aref lu ii jj) (/ (- (aref mx ii jj) (lu-sum ii jj)) (aref lu ii ii)))) :finally (return lu))))) (defun matrix-solve-lu (mx bb &optional (lu mx) (xx bb)) "Solve the linear system Ax=b. The result is placed in the 4th (optional, default - b) arg. The 3rd (optional, default - A) arg, is destructively modified, it now contains the Lower*Upper decomposition on A. If one of the principal minors of A is 0, `matrix-solve-lu' will signal the `division-by-zero' error." (declare (type (array * (* *)) mx) (type (array * (*)) bb)) (mx-solve-check mx bb) (setq lu (array-check-return lu (array-dimensions mx)) xx (array-check-return xx (array-dimensions bb))) (unless (eq xx bb) (replace xx bb)) (matrix-lu mx lu) (matrix-solve-lower lu xx nil) (matrix-solve-upper lu xx t)) ;;; ;;; matrix inversion ;;; (defsubst mx-swap-rows (mx nn ii jj) (declare (type simple-array mx) (type index-t nn ii jj)) (dotimes (kk nn) (declare (type index-t kk)) (rotatef (aref mx ii kk) (aref mx jj kk)))) (defsubst mx-swap-cols (mx nn ii jj) (declare (type simple-array mx) (type index-t nn ii jj)) (dotimes (kk nn) (declare (type index-t kk)) (rotatef (aref mx kk ii) (aref mx kk jj)))) (defun matrix-inverse (mx) "Invert the matrix in-place. Return the determinant of the matrix." (unless (and (= 2 (array-rank mx)) (= (array-dimension mx 0) (array-dimension mx 1))) (error 'dimension :proc 'matrix-inverse :args (list (array-dimensions mx)))) (labels ((pivot (aa nn kk pivot) (declare (type (array * (* *)) aa) (type index-t nn kk) (type number pivot)) (dotimes (ii nn) ; divide column by minus pivot (declare (type index-t ii)) (divf (aref aa kk ii) (- pivot))) (setf (aref aa kk kk) pivot) (dotimes (ii nn) ; reduce matrix (declare (type index-t ii)) (unless (= ii kk) (loop :with ki = (aref aa kk ii) :for jj :of-type index-t :from 0 :to (1- nn) :unless (= jj kk) :do (setf (aref aa jj ii) (+ (* ki (aref aa jj kk)) (aref aa jj ii)))))) (dotimes (jj nn) ; divide row by pivot (declare (type index-t jj)) (divf (aref aa jj kk) pivot)) ;; replace pivot by reciprocal (setf (aref aa kk kk) (/ pivot)) (values)) (inv (mx nn kk) (declare (type (array * (* *)) mx) (type index-t nn kk)) (if (>= kk nn) 1 (let ((ii kk) (jj kk) (max (abs (aref mx kk kk))) pivot) (declare (type index-t ii jj)) (loop :for i0 :of-type index-t :from kk :to (1- nn) :do (loop :for j0 :of-type index-t :from kk :to (1- nn) :when (> (abs (aref mx i0 j0)) max) :do (setq ii i0 jj j0 max (abs (aref mx i0 j0))))) (setq pivot (aref mx ii jj)) (when (= pivot 0) (return-from matrix-inverse 0)) (mx-swap-rows mx nn ii kk) (mx-swap-cols mx nn jj kk) (pivot mx nn kk pivot) ;; determinant is the recursive product of pivots (prog1 (* pivot (inv mx nn (1+ kk))) (mx-swap-rows mx nn jj kk) (mx-swap-cols mx nn ii kk)))))) (inv mx (array-dimension mx 0) 0))) ;;; ;;; `matrix-solve' - all in one ;;; (defun matrix-solve (mx bb &optional (tm mx) (xx bb)) "Solve the linear system Ax=b. The optional 3rd and 4th arguments have the same meaning as in `matrix-solve-lu'. If `matrix-solve-lu' fails, the matrix is inverted. In this case, the 3rd argument contains the inverse, and the determinant is returned as the second value." (declare (type (array * (* *)) mx) (type (array * (*)) bb)) (mx-solve-check mx bb) (setq tm (array-check-return tm (array-dimensions mx)) xx (array-check-return xx (array-dimensions bb))) (handler-case (matrix-solve-lu mx bb tm xx) (division-by-zero (co) (declare (ignore co)) (let ((det (matrix-inverse (array-copy mx tm)))) (when (zerop det) (error "the matrix is degenerate (det = 0):~%~/matrix-print/" mx)) (values (matrix-multiply tm bb xx) det))))) (provide :cllib-matrix) ;;; matrix.lisp ends here