;;; matrix.scm version 0.5 -- Scheme procedures for matrix manipulation ;;; Copyright (C) 1993 Todd R. Eigenschink ;;; ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation. ;;; ;;; This program 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 General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;;; ;;; Author's address: ;;; Todd R. Eigenschink (eigenstr@CS.Rose-Hulman.Edu) ;;; 1561 River Highlands Dr. ;;; Oconomowoc, WI 53066 ;;; ;;; Bug reports are appreciated, as well as mention of anything ;;; that's incompatible across implementations. (I know there ;;; are some things...) ;;; Routines included -- primitives: ;;; ;;; (matrix? a) ;;; (matrix?? a) ;;; (matrix:col a n) ;;; (matrix:cols a) ;;; (matrix:create row-size col-size fn) ;;; (matrix:ref a i j) ;;; (matrix:row a n) ;;; (matrix:rows a) ;;; (matrix:set! a i j value) ;;; (matrix:set-row! a i value) ;;; (vector->matrix v . v-list) ;;; ;;; Other routines: ;;; ;;; (matrix:* a b) ;;; (matrix:+ a b) ;;; (matrix:- a b) ;;; (matrix:/ a b) ;;; (matrix:apply-op sym proc a b) ;;; (matrix:augment a b) ;;; (matrix:back-substitute a rhs) ;;; (matrix:choleski-factor a) ;;; (matrix:condition-number a) ;;; (matrix:copy a) ;;; (matrix:create-diagonal v) ;;; (matrix:diagonal a) ;;; (matrix:diagonally-dominant? a) ;;; (matrix:eigenvalues a . tol) ;;; (matrix:forward-substitute a rhs) ;;; (matrix:gauss-inverse a) ;;; (matrix:gauss-seidel a b . tol) ;;; (matrix:gauss-solve a rhs) ;;; (matrix:generate-latex a) ;;; (matrix:hessenberg a) ;;; (matrix:hilbert n) ;;; (matrix:identity n) ;;; (matrix:infinity-norm obj) ;;; (matrix:maximal-element a . av) ;;; (matrix:newtons-method flist guess . tol) ;;; (matrix:sorta-partial-pivot a) ;;; (matrix:permute-rows a perm-vec) ;;; (matrix:plu-factor m) ;;; (matrix:pretty-print a) ;;; (matrix:qr a . tol) ;;; (matrix:reduce a n) ;;; (matrix:square? a) ;;; (matrix:trace a) ;;; (matrix:transpose a) ;;; (matrix:tridiagonal-solve a rhs) ;;; ;;; (vector:dot-product a b) ;;; (vector:pretty-print v) ;;; ;;; (fn:partial f r . v-alist) ;;; Principles: ;;; 1) a always refers to a matrix ;;; 2) i always refers to a row subscript ;;; 3) j always refers to a column subscript ;;; 4) Subscripts are (to the user) 1-based, just ;;; like matrices in mathematics. ;;; ;;; You will notice that error checking is rather thorough. ;;; I chose to pay the speed penalty for evaluation so I was ;;; assured that programs crashed at the right time; I would ;;; rather get an error in matrix-* because the matrices are ;;; the wrong size than get an error from vector-ref because ;;; an index is out of range. It's a lot easier to find ;;; errors when they come from the highest-level possible. ;;; ;;; The one error check that I consistently ignore is making ;;; sure that numeric operations (*, /, etc.) are performed ;;; on numbers; it's perfectly fine to create an arrays of ;;; strings, but just don't try to multiply them! ;;; ;;; After some complaints about speed, I added a matrix?? ;;; routine which does the thorough error checks. These ;;; routines just use matrix?, which doesn't check nearly ;;; as thoroughly, and thus, is quite a bit faster. ;;; ;;; I have written the routines so that any procedure that ;;; would normally operate on a matrix and a vector and ;;; return a vector can operate on two matrices and return ;;; a matrix. When a second matrix is used, it is treated ;;; like a group of column vectors, and the return-value ;;; should be treated like a group of column vectors. This ;;; allows procedures such as gauss-solve to solve for ;;; multiple right-hand side values at the same time. ;;; Matrix primitives. These are the only routines that ;;; have anything to do with how the matrices are ;;; defined (i.e., vectors of vectors, lists of lists, etc.). ;;; ;;; Matrices are stored as a pair. The car of the pair is the ;;; symbol 'matrix, and the cdr is a vectors of vectors. The ;;; outer vector has length equal to the number of rows in the ;;; matrix. Its elements are again vectors with length equal ;;; to the number of columns in the matrix. ;;; ;;; There are no restrictions on what can be placed in a ;;; matrix. In pretty-print-matrix, for example, a matrix ;;; of strings is used. ;; Shorthand (define (square x) (* x x)) ;;; Configuration ;;; ;;; Since nobody seems to agree on standards for some important ;;; stuff (error output), I allow some simple tweaking. Below are ;;; two functions used that can be changed simply--look at the ;;; pair of definitions I supply for each of them, and I bet you ;;; can guess which system I used to develop this! :) ;;; (format <...>) ;;; ;;; Generalized error messages. Caveat implementor! ;; Use this one for Chez Scheme ;(define matrix:error error) ;; Use this one for scm with SLIB (require 'format) (define (matrix:error proc-name format-string . stuff) (display "error in ") (display proc-name) (display ": ") (display (apply format `(#f ,format-string ,@stuff))) (display ".") (newline) (abort)) ;;; matrix? ;;; matrix?? ;;; ;;; Return #t if the object is a matrix; #f otherwise. ;;; ;;; matrix? does not test very thoroughly. This is ;;; because the full comparison is a little bit too slow. ;;; All this one does is make sure that the object is a ;;; pair whose car is 'matrix and whose cdr is a vector. ;;; ;;; matrix?? is the thorough one. First, we make sure ;;; that the outer part is a vector, then make sure that ;;; the first element of that vector is again a vector. ;;; Once both of those are true, make sure that every other ;;; element is a vector with the same length as the the ;;; first one. (define (matrix? a) (and (pair? a) (eq? (car a) 'matrix) (vector? (cdr a)))) (define (matrix?? a) (and (matrix? a) (vector? (vector-ref (cdr a) 0)) (let ((l (vector-length (vector-ref (cdr a) 0)))) (= l (apply + (map (lambda (x) (and (vector? x) (if (= (vector-length x) l) 1 0))) (vector->list (cdr a)))))))) ;;; matrix:create ;;; ;;; Creates a new matrix with the given dimensions. ;;; The element for each position is the result of ;;; the application of fn to the row and column ;;; values. If fn is not a procedure, the value of ;;; fn is placed directly into the matrix. (define (matrix:create row-size col-size fn) (cond ((or (not (integer? row-size)) (< row-size 1)) (matrix:error 'matrix:create "row-size must be a positive integer")) ((or (not (integer? col-size)) (< col-size 1)) (matrix:error 'matrix:create "col-size must be a positive integer")) (else (do ((a (make-vector row-size)) (r (1- row-size) (1- r))) ((= r -1) (cons 'matrix a)) (vector-set! a r (do ((v (make-vector col-size)) (c (1- col-size) (1- c))) ((= c -1) v) (vector-set! v c (if (procedure? fn) (fn (1+ r) (1+ c)) fn)))))))) ;;; matrix:ref ;;; ;;; Returns the element a_ij. (define (matrix:ref a i j) (cond ((not (matrix? a)) (matrix:error 'matrix:ref "~s is not a matrix" a)) ((or (< i 1) (> i (matrix:rows a))) (matrix:error 'matrix:ref "row subscript ~s is out of range for matrix ~s" i a)) ((or (< j 1) (> j (matrix:cols a))) (matrix:error 'matrix:ref "column subscript ~s is out of range for matrix ~s" j a)) (else (vector-ref (vector-ref (cdr a) (1- i)) (1- j))))) ;;; matrix:set! ;;; ;;; Set!'s the a_ij element of the matrix to value. (define (matrix:set! a i j value) (cond ((not (matrix? a)) (matrix:error 'matrix:set! "~s is not a matrix" a)) ((or (< i 1) (> i (matrix:rows a))) (matrix:error 'matrix:ref "row subscript ~s is out of range for array ~s" i a)) ((or (< j 1) (> j (matrix:cols a))) (matrix:error 'matrix:ref "column subscript ~s is out of range for array ~s" j a)) (else (vector-set! (vector-ref (cdr a) (1- i)) (1- j) value)))) ;;; matrix:rows ;;; ;;; Returns the number of rows in the matrix. (define (matrix:rows a) (cond ((not (matrix? a)) (matrix:error 'matrix:rows "~s is not a matrix" a)) (else (vector-length (cdr a))))) ;;; matrix:cols ;;; ;;; Returns the number of columns in the matrix. (define (matrix:cols a) (cond ((not (matrix? a)) (matrix:error 'matrix:cols "~s is not a matrix" a)) (else (vector-length (vector-ref (cdr a) 0))))) ;;; matrix:row ;;; ;;; Returns the ith row of matrix a as a vector. ;;; ;;; Note: The return vector is 0-based! (define (matrix:row a i) (cond ((not (matrix? a)) (matrix:error 'matrix:row "~s is not a matrix" a)) ((or (< i 1) (> i (matrix:rows a))) (matrix:error 'matrix:row "row subscript is out of range for array ~s" a)) (else (vector-ref (cdr a) (1- i))))) ;;; matrix:col ;;; ;;; Returns the jth column of matrix a as a vector. ;;; ;;; Note: The vector is 0-based! (define (matrix:col a j) (cond ((not (matrix? a)) (matrix:error 'matrix:col "~s is not a matrix" a)) ((or (< j 1) (> j (matrix:cols a))) (matrix:error 'matrix:col "column subscript is out of range for array ~s" a)) (else (list->vector (map (lambda (v) (vector-ref v (1- j))) (vector->list (cdr a))))))) ;;; matrix:set-row! ;;; ;;; Sets the given row of the given matrix ;;; to the given value. (define (matrix:set-row! a i value) (cond ((not (matrix? a)) (matrix:error 'matrix:set-row! "~s is not a matrix" a)) ((or (< i 1) (> i (matrix:rows a))) (matrix:error 'matrix:set-row! "row subscript ~s is out of range" i)) ((not (= (matrix:cols a) (vector-length value))) (matrix:error 'matrix:set-row! "vector ~s is not of length ~s" value (matrix:cols a))) (else (vector-set! (cdr a) (1- i) value)))) ;;; vector->matrix ;;; ;;; Converts vector(s) to a matrix. The vectors ;;; given (at least 1) are treated as column can ;;; be used with these procedures. Any number ;;; of vectors can be included; these are treated ;;; as column vectors and turned into the columns ;;; of the matrix. (define (vector->matrix v . v-list) (cond ((not (vector? v)) (matrix:error 'vector->matrix "~s is not a vector" v)) (else (let ((andmap (lambda (fn l) (letrec ((ck (lambda (x) (cond ((null? x) #t) ((not (car x)) #f) (else (ck (cdr x))))))) (ck (map fn l)))))) (cond ((not (andmap (lambda (x) (and (vector? x) (= (vector-length x) (vector-length v)))) v-list)) (matrix:error 'vector->matrix "vectors are not of the correct lengths")) (else (matrix:transpose (cons 'matrix (list->vector (cons v v-list)))))))))) ;;; Other matrix routines ;;; ;;; These are not dependent on the structure of the matrices. ;;; ;;; All the routines below should use only the routines above ;;; for information about the matrix itself. Nothing should ;;; be added that depends on the implementation of the above ;;; procedures. ;;; matrix:* ;;; ;;; Multiplies a matrix by something. If by a matrix, ;;; the number of columns in the left-hand matrix must ;;; be the same as the number of rows in the right-side ;;; matrix. This operation is defined in terms of the ;;; dot product, which makes the algorithm trivial. ;;; ;;; You can also multiply a matrix by a constant, in which ;;; case, the matrix is just multiplied term-wise by the ;;; constant. ;;; ;;; If you multiply by a vector, it is assumed that it is ;;; a column-vector, and is interpreted as a 1-column ;;; matrix. (define (matrix:* a b) (cond ((not (matrix? a)) (matrix:error 'matrix:* "~s is not a matrix" a)) ((vector? b) (cond ((not (= (vector-length b) (matrix:rows a))) (matrix:error 'matrix:* "vector is the wrong length")) (else (matrix:* a (vector->matrix b))))) ((number? b) (matrix:create (matrix:rows a) (matrix:cols a) (lambda (i j) (* b (matrix:ref a i j))))) ((not (matrix? b)) (matrix:error 'matrix:* "~s is not a matrix" b)) ((not (= (matrix:cols a) (matrix:rows b))) (matrix:error 'matrix:* "# colums in A do not match # rows in B")) (else (matrix:create (matrix:rows a) (matrix:cols b) (lambda (i j) (vector:dot-product (matrix:col b j) (matrix:row a i))))))) ;;; matrix:+ ;;; ;;; Adds two matrices. They must be of the same ;;; size, of course. This trivially uses matrix:apply-op. (define (matrix:+ a b) (matrix:apply-op 'matrix:+ + a b)) ;;; matrix:- ;;; ;;; Subtracts two matrices. They must be of the same ;;; size, of course. This trivially uses matrix:apply-op. (define (matrix:- a b) (matrix:apply-op 'matrix:- - a b)) ;;; matrix:/ ;;; ;;; Divides a matrix by a constant. Uses matrix:* with ;;; the inverse of the number. (define (matrix:/ a b) (cond ((not (matrix? a)) (matrix:error 'matrix:/ "~s is not a matrix" a)) ((not (number? b)) (matrix:error 'matrix:/ "~s is not a number" b)) ((zero? b) (matrix:error 'matrix:/ "division by zero will occur")) (else (matrix:* a (/ 1 b))))) ;;; matrix:apply-op ;;; ;;; Performs some operation on corresponding elements in ;;; two same-sized matrices, and returns a new matrix ;;; composed of the results of the operation. (define (matrix:apply-op sym proc a b) (cond ((not (matrix? a)) (error sym "~s is not a matrix" a)) ((not (matrix? b)) (error sym "~s is not a matrix" b)) ((not (= (matrix:cols a) (matrix:cols b))) (error sym "matrices have differing numbers of columns")) ((not (= (matrix:rows a) (matrix:rows b))) (error sym "matrices have differing numbers of rows")) (else (matrix:create (matrix:rows a) (matrix:cols a) (lambda (i j) (proc (matrix:ref a i j) (matrix:ref b i j))))))) ;;; matrix:augment ;;; ;;; Augments matrix a by adding the columns of matrix ;;; b to its right side. The two matrices must obviously ;;; have the same number of rows. This is intended for ;;; use in Gaussian elimination, where multiple right-hand ;;; side vectors can be solved for at once. (define (matrix:augment a b) (cond ((not (matrix? a)) (matrix:error 'matrix:augment "~s is not a matrix" a)) ((not (matrix? b)) (matrix:error 'matrix:augment "~s is not a matrix" b)) ((not (= (matrix:rows a) (matrix:rows b))) (matrix:error 'matrix:augment "matrices have differing numbers of rows")) (else (matrix:create (matrix:rows a) (+ (matrix:cols a) (matrix:cols b)) (lambda (i j) (if (<= j (matrix:cols a)) (matrix:ref a i j) (matrix:ref b i (- j (matrix:cols a))))))))) ;;; matrix:back-substitute ;;; ;;; Performs back substitution on the (assumed ;;; upper-triangular) matrix using the right-hand side ;;; vectors (in matrix form). (define (matrix:back-substitute a rhs) (cond ((not (matrix? a)) (matrix:error 'matrix:back-substitute "~s is not a matrix" a)) ((not (matrix? rhs)) (matrix:error 'matrix:back-substitute "~s is not a matrix" rhs)) (else (let* ((rows (matrix:rows rhs)) (cols (matrix:cols rhs)) (m (matrix:create rows cols (lambda (i j) 0))) (term (lambda (i vec-n) (if (= i rows) (/ (matrix:ref rhs rows vec-n) (matrix:ref a rows rows)) (/ (- (matrix:ref rhs i vec-n) (let loop ((sum 0) (j (1+ i))) (cond ((> j (matrix:cols a)) sum) (else (loop (+ sum (* (matrix:ref a i j) (matrix:ref m j vec-n))) (1+ j)))))) (matrix:ref a i i)))))) (do ((i rows (1- i))) ((= i 0) m) (do ((j 1 (1+ j))) ((> j cols)) (matrix:set! m i j (term i j)))))))) ;;; matrix:choleski-factor ;;; ;;; Factors the matrix into LU by using Choleski's ;;; method. The matrix, of course, must be square. ;;; Returns the lower-triangular matrix (L) consed ;;; onto the upper-triangular (U). U is just the ;;; matrix:transpose of L. (define (matrix:choleski-factor a) (cond ((not (matrix? a)) (matrix:error 'matrix:choleski-factor "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:choleski-factor "matrix is not square")) (else (let* ((n (matrix:rows a)) (L (matrix:create n n (lambda (i j) 0)))) (matrix:set! L 1 1 (sqrt (matrix:ref a 1 1))) (do ((j 2 (1+ j))) ((> j n)) (matrix:set! L j 1 (/ (matrix:ref a j 1) (matrix:ref L 1 1)))) (do ((i 2 (1+ i))) ((= i n)) (matrix:set! L i i (sqrt (- (matrix:ref a i i) (let loop ((k 1) (sum 0)) (cond ((= k i) sum) (else (loop (1+ k) (+ sum (square (matrix:ref L i k))))))))))) (do ((j (1+ i) (1+ j))) ((> j n)) (matrix:set! L j i (/ (- (matrix:ref a j i) (let loop ((k 1) (sum 0)) (cond ((= k i) sum) (else (loop (1+ k) (+ sum (* (matrix:ref L j k) (matrix:ref L i k)))))))) (matrix:ref L i i))))) (matrix:set! L n n (sqrt (- (matrix:ref a n n) (let loop ((k 1) (sum 0)) (cond ((= k n) sum) (else (loop (1+ k) (+ sum (square (matrix:ref L n k)))))))))) (cons L (matrix:transpose L))))) ;;; matrix:condition-number ;;; ;;; Computes the condition number of a matrix. (define (matrix:condition-number a) (cond ((not (matrix? a)) (matrix:error 'matrix:condition-number "~s is not a matrix" a)) (else (* (matrix:infinity-norm a) (matrix:infinity-norm (matrix:gauss-inverse a)))))) ;;; matrix:copy ;;; ;;; Returns an identical copy of the given matrix. (define (matrix:copy a) (cond ((not (matrix? a)) (matrix:error 'matrix:copy "~s is not a matrix" a)) (else (matrix:create (matrix:rows a) (matrix:cols a) (lambda (i j) (matrix:ref a i j)))))) ;;; matrix:create-diagonal ;;; ;;; Takes a vector of length n and returns an ;;; n x n diagonal matrix with the element of ;;; the vector on the diagonal. (define (matrix:create-diagonal v) (cond ((not (vector? v)) (matrix:error 'matrix:create-diagonal "~s is not a vector" v)) (else (matrix:create (vector-length v) (vector-length v) (lambda (i j) (if (= i j) (vector-ref v (1- i)) 0)))))) ;;; matrix:diagonal ;;; ;;; Returns a vector of the elements along the diagonal ;;; of the matrix. Note: the vector is zero-based! (define (matrix:diagonal a) (cond ((not (matrix? a)) (matrix:error 'matrix:diagonal "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:diagonal "matrix is not square")) (else (let* ((n (matrix:rows a)) (v (make-vector n))) (do ((i 1 (1+ i))) ((> i n) v) (vector-set! v (1- i) (matrix:ref a i i))))))) ;;; matrix:diagonally-dominant? ;;; ;;; Returns #t if the given matrix is diagonally dominant, ;;; and #f if not. (define (matrix:diagonally-dominant? a) (cond ((not (matrix? a)) (matrix:error 'matrix:diagonally-dominant? "~s is not a matrix" a)) (else (let loop ((n 1)) (cond ((> n (matrix:rows a)) #t) (else (let* ((r (matrix:row a n)) (el (abs (vector-ref r (1- n))))) (if (>= el (- (apply + (map abs (vector->list r))) el)) (loop (1+ n)) #f)))))))) ;;; matrix:doolittle-factor ;;; ;;; Factors the matrix into LU by using Doolittle's ;;; method. Returns L consed onto U. (define (matrix:doolittle-factor a) (cond ((not (matrix? a)) (matrix:error 'matrix:doolittle-factor "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:doolittle-factor "matrix is not square")) (else (let* ((n (matrix:rows a)) (L (matrix:create n n (lambda (i j) (if (= j 1) (matrix:ref a i 1) 0)))) (U (matrix:create n n (lambda (i j) (if (= i 1) (/ (matrix:ref a 1 j) (matrix:ref L 1 1)) 0))))) (do ((j 2 (1+ j))) ((> j n)) (do ((i j (1+ i))) ((> i n)) (matrix:set! L i j (- (matrix:ref a i j) (do ((k 1 (1+ k)) (sum 0)) ((= j k) sum) (set! sum (+ sum (* (matrix:ref L i k) (matrix:ref U k j)))))))) (matrix:set! U j j 1) (do ((i (1+ j) (1+ i))) ((> i n)) (matrix:set! U j i (/ (- (matrix:ref a j i) (do ((k 1 (1+ k)) (sum 0)) ((= k j) sum) (set! sum (+ sum (* (matrix:ref L j k) (matrix:ref U k i)))))) (matrix:ref L j j))))) (cons L U))))) ;;; matrix:eigenvalues ;;; ;;; Computes the eigenvalues of the matrix, and ;;; returns them in a vector. This uses the QR ;;; method and a Hessenberg-similar matrix. If ;;; the optional parameter tol is passed, it in ;;; turn is passed to matrix:qr as the tolerance ;;; to be used in computing the eigenvalues. (define (matrix:eigenvalues a . tol) (cond ((not (matrix? a)) (matrix:error 'matrix:eigenvalues "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:eigenvalues "matrix is not square")) (else (matrix:diagonal (let ((hess (matrix:hessenberg a))) (if (null? tol) (matrix:qr hess) (matrix:qr hess (car tol)))))))) ;;; matrix:forward-substitute ;;; ;;; Performs forward substitution on the (assumed ;;; upper-triangular) matrix and the righthand side vectors ;;; (in matrix form). The method used is simple; just use ;;; backward substitution after mirroring the matrices. The ;;; matrix itself has to be sort of flipped along the diagonal; ;;; the matrix of right-hand side vectors just has to be ;;; flipped upside-down. Of course, once the set of solution ;;; vectors is found, it must be flipped back over. (define (matrix:forward-substitute a rhs) (cond ((not (matrix? a)) (matrix:error 'matrix:forward-substitute "~s is not a matrix" a)) ((not (matrix? rhs)) (matrix:error 'matrix:forward-substitute "~s is not a matrix" rhs)) ((not (= (matrix:rows a) (matrix:rows rhs))) (matrix:error 'matrix:forward-substitute "matrices have differing numbers of rows")) (else (let* ((rows (matrix:rows a)) (cols (matrix:cols a)) ;; mirror-matrix (mm (lambda (a) (matrix:create rows cols (lambda (i j) (matrix:ref a (- rows i -1) (- cols j -1)))))) ;; mirror-vector-matrix (mvm (lambda (a) (matrix:create rows (matrix:cols rhs) (lambda (i j) (matrix:ref a (- rows i -1) j)))))) (mvm (matrix:back-substitute (mm a) (mvm rhs))))))) ;;; matrix:gauss-inverse ;;; ;;; Computes the inverse of matrix a by using Gaussian ;;; elimination. Of course, the matrix must be square. ;;; The solution is solved by setting Ax = I, where I ;;; is the n x n identity matrix. (define (matrix:gauss-inverse a) (cond ((not (matrix? a)) (matrix:error 'matrix:gauss-inverse "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:gauss-inverse "cannot invert non-square matrix")) (else (matrix:gauss-solve a (matrix:identity (matrix:rows a)))))) ;;; matrix:gauss-solve ;;; ;;; Solves a matrix equation using Gaussian elimination. ;;; There can be multiple right-hand side vectors; they ;;; must be in the form of a matrix with the same number ;;; of rows as the left-hand side matrix. ;;; ;;; The method used is simple; LUx = b, so set Ux = v, and ;;; then Lv = b. Solve for v first using forward ;;; substitution, and then use backward substitution to get x. (define (matrix:gauss-solve a rhs) (cond ((not (matrix? a)) (matrix:error 'matrix:gauss-solve "~s is not a matrix" a)) ((not (matrix? rhs)) (matrix:error 'matrix:gauss-solve "~s is not a matrix" rhs)) ((not (= (matrix:rows a) (matrix:rows rhs))) (matrix:error 'matrix:gauss-solve "matrices have differing numbers of rows")) (else (let* ((PLU (matrix:plu-factor a)) (P (car PLU)) (L (cadr PLU)) (U (cddr PLU))) (matrix:back-substitute U (matrix:forward-substitute L (matrix:permute-rows rhs P))))))) ;;; matrix:gauss-seidel ;;; ;;; Attempts to find a solution vector for the matrix ;;; using Gauss-Seidel iteration. The matrix must be ;;; diagonally dominant. That is, the element on the ;;; diagonal must be at least equal to the sum of the ;;; other elements in the same row. ;;; ;;; The optional extra parameter specified a tolerance ;;; to be used; iteration will cease when all values ;;; are within this tolerance of the last value. If no ;;; tolerance is specified, a value of 1e-12 will be ;;; used. (define (matrix:gauss-seidel a b . tol) (cond ((not (matrix? a)) (matrix:error 'matrix:gauss-seidel "~s is not a matrix" a)) ((not (matrix? b)) (matrix:error 'matrix:gauss-seidel "~s is not a matrix" b)) ((not (= (matrix:rows a) (matrix:rows b))) (matrix:error 'matrix:gauss-seidel "matrices have differing numbers of rows")) ((not (matrix:diagonally-dominant? a)) (matrix:error 'matrix:gauss-seidel "matrix is not diagonally dominant")) (else (let* ((epsilon (if (null? tol) 1e-12 (car tol))) (n (matrix:rows a)) (guess (matrix:create n (matrix:cols b) 0)) (last (matrix:create n (matrix:cols b) 1))) (do ((iter 1 (1+ iter))) ((< (matrix:maximal-element (matrix:- last guess) #t) epsilon) guess) (set! last (matrix:copy guess)) (do ((k 1 (1+ k))) ((> k (matrix:cols b)) guess) (do ((i 1 (1+ i))) ((> i n)) (matrix:set! guess i k (- (/ (matrix:ref b i k) (matrix:ref a i i)) (let loop ((j 1) (sum 0)) (cond ((> j n) sum) ((= j i) (loop (1+ j) sum)) (else (loop (1+ j) (+ sum (* (/ (matrix:ref a i j) (matrix:ref a i i)) (matrix:ref guess j k)))))))))))))))) ;;; matrix:generate-latex ;;; ;;; Generates LaTeX code for the matrix passed in ;;; which can be pasted into a LaTeX document. This ;;; does not pay attention to how large the matrix is; ;;; it might be necessary to add LaTeX code to change ;;; the font size if the matrix spills off the right ;;; side of the page. (define (matrix:generate-latex a) (cond ((vector? a) (matrix:generate-latex (vector->matrix a))) ((not (matrix? a)) (matrix:error 'matrix:generate-latex "~s is not a matrix" a)) (else (let ((cols (matrix:cols a)) (rows (matrix:rows a))) ;; Turn on displayed math (display "\\[ \\left[ \\begin{array}{") (display (make-string cols #\c)) (display "}") (newline) (do ((i 1 (1+ i))) ((> i rows)) (do ((j 1 (1+ j))) ((> j cols)) (let ((el (matrix:ref a i j))) (cond ;; Handles integers or inexact (floating-point) numbers ((or (inexact? el) (and (exact? el) (integer? el))) (display el)) ;; Handles fractions (else (display "\\frac{") (display (numerator el)) (display "}{") (display (denominator el)))) ;; Inter-column spacing (if (< j cols) (display " & ")) ;; End-of row command to go to new line, ;; but not after the last row (if (and (< i rows) (= j cols)) (display " \\\\")) ;; Pop in a newline after the last column in each row (if (= j cols) (newline))))) ;; Wrap it up (display "\\end{array} \\right] \\]") (newline))))) ;;; matrix:hessenberg ;;; ;;; Returns a matrix similar to the given matrix, but ;;; in Hessenberg form. Note: This code was translated ;;; straight from FORTRAN with no attempts made to clean ;;; it up. It shows. (I can't clean it up at all because ;;; I don't understand it!) (define (matrix:hessenberg a) (cond ((not (matrix? a)) (matrix:error 'matrix:hessenberg "~s is not a matrix" a)) (else (let* ((n (matrix:rows a)) (a (matrix:create (+ n 2) (matrix:cols a) (lambda (i j) (if (<= i n) (matrix:ref a i j) 0)))) (v (make-vector 20)) (sigma 0) (pi 0) (rho 0) (sum 0)) (do ((k 1 (1+ k))) ((> k (- n 2)) a) (let ((eta (abs (matrix:ref a (1+ k) k)))) (do ((i (1+ k) (1+ i))) ((> i n)) (let ((absval (abs (matrix:ref a i k)))) (if (< eta absval) (set! eta absval)))) (if (not (zero? eta)) (begin (set! sum 0) (do ((i (1+ k) (1+ i))) ((> i n)) (vector-set! v i (/ (matrix:ref a i k) eta)) (matrix:set! a i k (vector-ref v i)) (set! sum (+ sum (square (vector-ref v i))))) (if (not (zero? (vector-ref v (1+ k)))) (set! sigma (/ (* (sqrt sum) (vector-ref v (1+ k))) (abs (vector-ref v (1+ k)))))) (vector-set! v (1+ k) (+ (vector-ref v (1+ k)) sigma)) (set! pi (* sigma (vector-ref v (1+ k)))) (matrix:set! a (1+ n) k pi) (do ((j (1+ k) (1+ j))) ((> j n)) (set! sum 0) (do ((i (1+ k) (1+ i))) ((> i n)) (set! sum (+ sum (* (vector-ref v i) (matrix:ref a i j))))) (set! rho (/ sum pi)) (do ((i (1+ k) (1+ i))) ((> i n)) (matrix:set! a i j (- (matrix:ref a i j) (* rho (vector-ref v i)))))) (do ((i 1 (1+ i))) ((> i n)) (set! sum 0) (do ((j (1+ k) (1+ j))) ((> j n)) (set! sum (+ sum (* (matrix:ref a i j) (vector-ref v j))))) (set! rho (/ sum pi)) (do ((j (1+ k) (1+ j))) ((> j n)) (matrix:set! a i j (- (matrix:ref a i j) (* rho (vector-ref v j)))))) (matrix:set! a (+ n 2) k (vector-ref v (1+ k))) (matrix:set! a (1+ k) k (* (- eta) sigma))) (matrix:set! a (1+ n) k 0)))) (do ((j 1 (1+ j))) ((> j (- n 2))) (do ((i (+ j 2) (1+ i))) ((> i n)) (matrix:set! a i j 0))) (matrix:create n (matrix:cols a) (lambda (i j) (matrix:ref a i j))))))) ;;; matrix:hilbert ;;; ;;; Returns an n x n Hilbert matrix. (define (matrix:hilbert n) (cond ((< n 0) (matrix:error 'matrix:hilbert "matrix must have positive size")) (else (matrix:create n n (lambda (i j) (/ 1 (+ i j -1))))))) ;;; matrix:identity ;;; ;;; Returns an n x n identity matrix. (define (matrix:identity n) (cond ((< n 0) (matrix:error 'matrix:identity "matrix must have positive size")) (else (matrix:create n n (lambda (i j) (if (= i j) 1 0)))))) ;;; matrix:infinity-norm ;;; ;;; Computes the infinity norm of either a matrix or ;;; a vector. (define (matrix:infinity-norm obj) (cond ((vector? obj) (apply max (map abs (vector->list obj)))) ((not (matrix? obj)) (matrix:error 'matrix:infinity-norm "~s is not a matrix or vector" obj)) (else (let loop ((row 1) (max-sum 0)) (if (> row (matrix:rows obj)) max-sum (loop (1+ row) (max max-sum (apply + (map abs (vector->list (matrix:row obj row))))))))))) ;;; matrix:maximal-element ;;; ;;; Returns the maximal element in the vector or matrix. ;;; If an optional extra parameter is #t, the elements ;;; are taken in absolute value. (define (matrix:maximal-element a . av) (let ((fn (if (or (null? av) (not (car av))) (lambda (x) x) abs))) (cond ((vector? a) (apply max (map fn (vector->list a)))) ((not (matrix? a)) (matrix:error 'matrix:maximal-element "~s is not a vector or matrix" a)) (else (let loop ((i 1) (j 1) (m (fn (matrix:ref a 1 1)))) (cond ((> i (matrix:rows a)) m) ((> j (matrix:cols a)) (loop (1+ i) 1 m)) (else (loop i (1+ j) (max m (fn (matrix:ref a i j))))))))))) ;;; matrix:newtons-method ;;; ;;; Solves a nonlinear system using Newton's method. ;;; An initial guess must be passed as a list of values ;;; for the variables of the function; if it is the null ;;; list ('()), a list of 1's the correct length will be ;;; used. Tol is optional; if it is passed, iteration ;;; will stop when all deltas are less than tol (in ;;; absolute value). (define (matrix:newtons-method flist guess . tol) (cond ((not (list? flist)) (matrix:error 'matrix:newtons-method "flist must be a list")) ((not (list? guess)) (matrix:error 'matrix:newtons-method "guess must be a list")) ((not (or (= (length flist) (length guess)) (null? guess))) (matrix:error 'matrix:newtons-method "guess is of incorrect length")) (else (let* ((epsilon (if (null? tol) 1e-12 (car tol))) (n (length flist)) (guess (if (null? guess) (matrix:create n 1 1.0) (vector->matrix (list->vector guess)))) (fv (list->vector flist)) (deltas (matrix:create n 1 1))) (do ((iter 1 (1+ iter))) ((< (matrix:maximal-element deltas #t) epsilon) guess) (let* ((vals (vector->list (matrix:col guess 1))) (mat (matrix:create n n (lambda (i j) (fn:partial (vector-ref fv (1- i)) j vals)))) (rhs (matrix:create n 1 (lambda (i j) (- (apply (vector-ref fv (1- i)) vals)))))) (set! deltas (matrix:gauss-solve mat rhs)) (set! guess (matrix:+ guess deltas)))))))) ;;; matrix:sorta-partial-pivot ;;; ;;; Performs something vaguely recognizable as partial pivoting; ;;; it's not "real" partial pivoting, but it's an algorithm that ;;; is said to help. Returns the permutation matrix consed onto ;;; the new matrix. (define (matrix:sorta-partial-pivot m) (let* ((a (matrix:copy m)) (rows (matrix:rows a)) (cols (matrix:cols a)) (P (make-vector (1+ rows))) (magic-d (lambda (i j) (let* ((row (vector->list (matrix:row a i)))) (/ (abs (matrix:ref a i j)) (apply max (map abs row)))))) (pivot (lambda () (do ((level 1 (1+ level))) ((= level rows)) (let loop ((row (1+ level)) (max-row level) (max-d (magic-d level level))) (cond ((> row (matrix:rows a)) (if (not (= max-row level)) (let ((temp-row (matrix:row a max-row)) (temp-n (vector-ref P max-row))) (matrix:set-row! a max-row (matrix:row a level)) (matrix:set-row! a level temp-row) (vector-set! P max-row level) (vector-set! P level temp-n)))) ((> (magic-d row level) max-d) (loop (1+ row) row (matrix:ref a row level))) (else (loop (1+ row) max-row max-d)))))))) ;; Create initial permutation vector (vector-set! P 0 "Ignore this.") (do ((i 1 (1+ i))) ((> i rows)) (vector-set! P i i)) ;; Actually perform the pivoting (pivot) ;; Create the return value (cons P a))) ;;; matrix:permute-rows ;;; ;;; Permutes the rows of matrix according to ;;; the permutation vector given. (define (matrix:permute-rows a perm-vec) (cond ((not (matrix? a)) (matrix:error 'matrix:permute-rows "~s is not a matrix" a)) ((not (vector? perm-vec)) (matrix:error 'matrix:permute-rows "~s is not a vector" perm-vec)) ((not (= (vector-length perm-vec) (1+ (matrix:rows a)))) (matrix:error 'matrix:permute-rows "permutation vector ~s is the wrong length" perm-vec)) (else (let ((new-a (matrix:copy a))) (do ((i 1 (1+ i))) ((> i (matrix:rows a))) (matrix:set-row! new-a i (matrix:row a (vector-ref perm-vec i)))) new-a)))) ;;; matrix:plu-factor ;;; ;;; Returns the PLU factorization of the matrix. ;;; The permutation vector is consed onto the results ;;; of consing L onto U. ;;; ;;; Gaussian elimination with partial pivoting (rows only) ;;; is used to performs the factorization. The results of ;;; switching rows are kept in a permutation vector which ;;; is returned as P in the PLU factorization. (define (matrix:plu-factor m) (cond ((not (matrix? m)) (matrix:error 'matrix:plu-factor "~s is not a matrix" m)) ((not (= (matrix:rows m) (matrix:cols m))) (matrix:error 'matrix:plu-factor "cannot factor a non-square matrix")) (else (let* ((PM (matrix:sorta-partial-pivot m)) (a (cdr PM)) (P (car PM)) (rows (matrix:rows a)) (cols (matrix:cols a))) ;; Main loop. Goes top to bottom and left to right, ;; creating zeros as it goes. (do ((k 1 (1+ k))) ((= k rows)) (do ((i (1+ k) (1+ i))) ((> i rows)) (let ((m_ik (/ (matrix:ref a i k) (matrix:ref a k k)))) (matrix:set! a i k m_ik) (do ((j (1+ k) (1+ j))) ((> j cols)) (matrix:set! a i j (- (matrix:ref a i j) (* m_ik (matrix:ref a k j)))))))) ;; Create the return value. This returns separate L and U ;; matrices by pulling the appropriate values out of a. (cons P (cons (matrix:create rows cols (lambda (i j) (cond ((= i j) 1) ((> i j) (matrix:ref a i j)) (else 0)))) (matrix:create rows cols (lambda (i j) (if (<= i j) (matrix:ref a i j) 0))))))))) ;;; matrix:pretty-print ;;; ;;; Prints a matrix in a decent format, with vertical ;;; bars on either side. (define (matrix:pretty-print a) (cond ((not (matrix? a)) (matrix:error 'matrix:pretty-print "~s is not a matrix" a)) (else (let* ((rows (matrix:rows a)) (cols (matrix:cols a)) (width-vec (make-vector (1+ cols) 0)) (s (matrix:create rows cols (lambda (i j) (matrix:format "~a" (matrix:ref a i j)))))) ;; Find the greatest width in each column (do ((i 1 (1+ i))) ((> i rows)) (do ((j 1 (1+ j))) ((> j cols)) (let ((len (string-length (matrix:ref s i j)))) (if (> len (vector-ref width-vec j)) (vector-set! width-vec j len))))) ;; Now that the widths are computed, display the matrix, ;; spacing as needed. (do ((i 1 (1+ i))) ((> i rows)) (display "|") (do ((j 1 (1+ j))) ((> j cols)) (let* ((obj (matrix:ref s i j)) (len (string-length obj))) (display " ") (display (make-string (- (vector-ref width-vec j) len) #\ )) (display obj) (display " "))) (display "|") (newline)))))) ;;; matrix:qr ;;; ;;; Applies the QR method to the given matrix. If ;;; the optional parameter tol is used, it is used ;;; as the epsilon value to be used when deciding ;;; when iteration should stop. If no value is ;;; passed, epsilon = 1e-10. Note: This code was ;;; translated directly from FORTRAN with no attempts ;;; made to clean it up. (define (matrix:qr a . tol) (cond ((not (matrix? a)) (matrix:error 'matrix:qr "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:qr "matrix is not square")) (else (let* ((n (matrix:rows a)) (a (matrix:copy a)) (gamma (make-vector n)) (eta 0) (alpha 0) (beta 0) (delta 0) (kappa 0) (sigma (make-vector n)) (epsilon (if (null? tol) 1e-10 (car tol))) (done #f)) (do () (done a) (set! kappa (matrix:ref a n n)) (matrix:set! a 1 1 (- (matrix:ref a 1 1) kappa)) (do ((k 1 (1+ k))) ((> k n)) (if (not (= k n)) (begin (set! eta (max (abs (matrix:ref a k k)) (abs (matrix:ref a (1+ k) k)))) (set! alpha (/ (matrix:ref a k k) eta)) (set! beta (/ (matrix:ref a (1+ k) k) eta)) (set! delta (sqrt (+ (* alpha alpha) (* beta beta)))) (vector-set! gamma k (/ alpha delta)) (vector-set! sigma k (/ beta delta)) (matrix:set! a k k (* eta delta)) (matrix:set! a (1+ k) k 0) (matrix:set! a (1+ k) (1+ k) (- (matrix:ref a (1+ k) (1+ k)) kappa)) (do ((j (1+ k) (1+ j))) ((> j n)) (let ((akj (matrix:ref a k j))) (matrix:set! a k j (+ (* (vector-ref gamma k) (matrix:ref a k j)) (* (vector-ref sigma k) (matrix:ref a (1+ k) j)))) (matrix:set! a (1+ k) j (- (* (vector-ref gamma k) (matrix:ref a (1+ k) j)) (* (vector-ref sigma k) akj))))))) (if (not (= k 1)) (begin (do ((j 1 (1+ j))) ((> j k)) (let ((aikl1 (matrix:ref a j (1- k)))) (matrix:set! a j (1- k) (+ (* (vector-ref gamma (1- k)) (matrix:ref a j (1- k))) (* (vector-ref sigma (1- k)) (matrix:ref a j k)))) (matrix:set! a j k (- (* (vector-ref gamma (1- k)) (matrix:ref a j k)) (* (vector-ref sigma (1- k)) aikl1))))) (matrix:set! a (1- k) (1- k) (+ (matrix:ref a (1- k) (1- k)) kappa))))) (matrix:set! a n n (+ (matrix:ref a n n) kappa)) (if (< (abs (matrix:ref a n (1- n))) epsilon) (set! n (1- n))) (if (= n 1) (set! done #t))))))) ;;; matrix:reduce-matrix ;;; ;;; Returns a new matrix, which is the original matrix ;;; without the given column or row. (define (matrix:reduce-matrix a n) (cond ((not (matrix? a)) (matrix:error 'matrix:reduce-matrix "~s is not a matrix" a)) ((> n (matrix:rows a)) (matrix:error 'matrix:reduce-matrix "row subscript ~s is out of range for matrix" n)) ((> n (matrix:cols a)) (matrix:error 'matrix:reduce-matrix "column subscript ~s is out of range for matrix" n)) (else (matrix:create (1- (matrix:rows a)) (1- (matrix:cols a)) (lambda (i j) (matrix:ref a (if (>= i n) (1+ i) i) (if (>= j n) (1+ j) j))))))) ;;; matrix:square? ;;; ;;; Returns #t if the given item is ;;; both a matrix and square, and #f if not. (define (matrix:square? a) (and (matrix? a) (= (matrix:rows a ) (matrix:cols a)))) ;;; matrix:trace ;;; ;;; Takes a matrix and returns its trace. The matrix ;;; must be square. (define (matrix:trace a) (cond ((not (matrix? a)) (matrix:error 'matrix:trace "~s is not a matrix" a)) ((not (matrix:square? a)) (matrix:error 'matrix:trace "matrix is not square")) (else (let* ((n (matrix:rows a)) (v (make-vector n))) (do ((i 1 (1+ i))) ((> i n) (apply + (vector->list v))) (vector-set! v (1- i) (matrix:ref a i i))))))) ;;; matrix:transpose ;;; ;;; Returns the matrix:transpose of the given matrix. (define (matrix:transpose a) (cond ((not (matrix? a)) (matrix:error 'matrix:transpose "~s is not a matrix" a)) (else (matrix:create (matrix:cols a) (matrix:rows a) (lambda (i j) (matrix:ref a j i)))))) ;;; matrix:tridiagonal-solve ;;; ;;; Solves a tridiagonal matrix. The right-hand side ;;; can be a matrix, which is interpreted as multiple ;;; column-vectors. (define (matrix:tridiagonal-solve a rhs) (cond ((not (matrix? a)) (matrix:error 'solve-tridiagonal "~s is not a matrix" a)) ((not (matrix? rhs)) (matrix:error 'solve-tridiagonal "~s is not a matrix" rhs)) ((not (matrix:square? a)) (matrix:error 'solve-tridiagonal "matrix is not square")) (else (let* ((a (matrix:copy a)) (rhs (matrix:copy rhs)) (n (matrix:rows a)) (get-a (lambda (k) (if (= k 1) 0 (matrix:ref a k (1- k))))) (get-d (lambda (k) (matrix:ref a k k))) (get-c (lambda (k) (if (= k n) 0 (matrix:ref a k (1+ k))))) (set-d (lambda (k v) (matrix:set! a k k v))) (x (matrix:create (matrix:rows rhs) (matrix:cols rhs) (lambda (i j) 0)))) (do ((k 2 (1+ k))) ((> k n)) (if (= (matrix:ref a (1- k) (1- k)) 0) (matrix:error 'solve-tridiagonal "matrix cannot be solved")) (let ((m (/ (matrix:ref a k (1- k)) (matrix:ref a (1- k) (1- k))))) (set-d k (- (get-d k) (* m (get-c (1- k))))) (do ((j 1 (1+ j))) ((> j (matrix:cols rhs))) (matrix:set! rhs k j (- (matrix:ref rhs k j) (* m (matrix:ref rhs (1- k) j))))))) (if (= (matrix:ref a n n) 0) (matrix:error 'matrix:tridiagonal-solve "matrix cannot be solved")) (do ((j 1 (1+ j))) ((> j (matrix:cols rhs))) (matrix:set! x n j (/ (matrix:ref rhs n j) (get-d n)))) (do ((k (1- n) (1- k))) ((= k 0)) (do ((j 1 (1+ j))) ((> j (matrix:cols rhs))) (matrix:set! x k j (/ (- (matrix:ref rhs k j) (* (get-c k) (matrix:ref x (1+ k) j))) (get-d k))))) x)))) ;;; vector:dot-product ;;; ;;; Returns the dot product of two vectors. (define (vector:dot-product x y) (cond ((not (vector? x)) (matrix:error 'vector:dot-product "~s is not a vector" x)) ((not (vector? y)) (matrix:error 'vector:dot-product "~s is not a vector" y)) ((not (= (vector-length x) (vector-length y))) (matrix:error 'vector:dot-product "vectors are of differing lengths")) (else (let loop ((r 0) (i 0)) (cond ((= i (vector-length x)) r) (else (loop (+ r (* (vector-ref x i) (vector-ref y i))) (1+ i)))))))) ;;; vector:pretty-print ;;; ;;; Prints a vector in a decent format (brackets on either ;;; side). (define (vector:pretty-print v) (cond ((not (vector? v)) (matrix:error 'vector:pretty-print "~s is not a vector" v)) (else (display "[ ") (do ((i 0 (1+ i))) ((= i (vector-length v))) (display (vector-ref v i)) (display " ")) (display "]") (newline)))) ;;; fn:partial ;;; ;;; Computes the partial of f with respect to the r-th ;;; variable at the point described by the value list. ;;; There must be as many variables in the list as f ;;; needs. A call might be: ;;; ;;; (fn:partial f 2 '(3 5 7)), where f = f(x, y, z) ;;; ;;; This computes the partial of f with respect to y at the ;;; point (3, 5, 7). (define (fn:partial f r vlist) (cond ((not (procedure? f)) (matrix:error 'fn:partial "first parameter must be a procedure")) ((> r (length vlist)) (matrix:error 'fn:partial "not enough values")) ((< r 1) (matrix:error 'fn:partial "variable number too small")) (else (let* ((delta 1e-7) (fx (apply f vlist)) (perturb (lambda (f r vl d) (let loop ((n 1) (vl vl) (v '())) (cond ((null? vl) (apply f (reverse v))) ((= n r) (loop (1+ n) (cdr vl) (cons (+ d (car vl)) v))) (else (loop (1+ n) (cdr vl) (cons (car vl) v))))))) (fdx1 (perturb f r vlist delta))) (/ (- fdx1 fx) delta))))) ;;; EOF