;;; matrix.ss (0.1) ;;; ;;; Matrix manipulation routines. ;;; ;;; Winter Quarter 1992 ;;; Rose-Hulman Institute of Technology ;;; Todd R. Eigenschink ;;; ;;; My original ideas for which routines to include were borrowed ;;; from a matrix package written by Joe Maruszewski. I started ;;; from scratch on almost all the routines, writing them, and ;;; then adding every error check I could think of. I have added ;;; a large number of routines that I thought were missing (or that ;;; I needed at the time) or could be improved. ;;; ;;; The author from whom I swiped some ideas: ;;; J. Maruszewski (jpm%fluent@dartmouth.edu) ;;; Fluent Inc. ;;; 10 Cavendish Court ;;; Lebanon, NH 03766 ;;; ;;; ;;; Copyright (C) 1993 Todd R. Eigenschink ;;; ;;; As far as I'm concerned, this is free software. I don't promise ;;; that any of this is correct or useful, although I think it's at ;;; least correct. Do what you like with it, but at least keep this ;;; comment block attached. ;;; ;;; Author's address: ;;; Todd R. Eigenschink ;;; 1561 River Highlands Dr. ;;; Oconomowoc, WI 53066 ;;; eigenstr@CS.Rose-Hulman.Edu ;;; ;;; Bug reports are appreciated, as well as anything that's not ;;; R4RS-compatible. (I already know there are some things...) ;;; Routines included: ;;; ;;; (augment-matrix a b) ;;; (back-substitute a rhs) ;;; (choleski-factor a) ;;; (condition-number a) ;;; (copy-matrix a) ;;; (diagonally-dominant? a) ;;; (dot-product a b) ;;; (forward-substitute a rhs) ;;; (gauss-inverse a) ;;; (gauss-seidel a b . tol) ;;; (gauss-solve a rhs) ;;; (generate-latex-matrix a) ;;; (hilbert-matrix n) ;;; (identity-matrix n) ;;; (infinity-norm obj) ;;; (make-matrix row-size col-size fn) ;;; (matrix-* a b) ;;; (matrix-/ a b) ;;; (matrix-+ a b) ;;; (matrix-- a b) ;;; (matrix-col a) ;;; (matrix-cols a) ;;; (matrix-op sym proc a b) ;;; (matrix-ref a i j) ;;; (matrix-row a) ;;; (matrix-rows a) ;;; (matrix-set! a i j value) ;;; (matrix-set-row! a i value) ;;; (matrix? a) ;;; (maximal-element a . av) ;;; (partial-pivot a) ;;; (permute-rows a perm-vec) ;;; (plu-factor m) ;;; (pretty-print-matrix a) ;;; (pretty-print-vector v) ;;; (reduce-matrix a n) ;;; (square? a) ;;; (transpose a) ;;; (tridiagonal-solve a rhs) ;;; (vector->matrix v . v-list) ;;; 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. ;;; 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. ;;; 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)))))))) ;;; make-matrix ;;; ;;; 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 (make-matrix row-size col-size fn) (cond ((or (not (integer? row-size)) (< row-size 1)) (error 'make-matrix "row-size must be a positive integer")) ((or (not (integer? col-size)) (< col-size 1)) (error 'make-matrix "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)) (error 'matrix-ref "~s is not a matrix" a)) ((or (< i 1) (> i (matrix-rows a))) (error 'matrix-ref "row subscript ~s is out of range for matrix ~s" i a)) ((or (< j 1) (> j (matrix-cols a))) (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)) (error 'matrix-set! "~s is not a matrix" a)) ((or (< i 1) (> i (matrix-rows a))) (error 'matrix-ref "row subscript ~s is out of range for array ~s" i a)) ((or (< j 1) (> j (matrix-cols a))) (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)) (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)) (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)) (error 'matrix-row "~s is not a matrix" a)) ((or (< i 1) (> i (matrix-rows a))) (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)) (error 'matrix-col "~s is not a matrix" a)) ((or (< j 1) (> j (matrix-cols a))) (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)) (error 'matrix-set-row! "~s is not a matrix" a)) ((or (< i 1) (> i (matrix-rows a))) (error 'matrix-set-row! "row subscript ~s is out of range" i)) ((not (= (matrix-cols a) (vector-length value))) (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)) (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)) (error 'vector->matrix "vectors are not of the correct lengths")) (else (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. ;;; square? ;;; ;;; Returns #t if the given item is ;;; both a matrix and square, and #f if not. (define (square? a) (and (matrix? a) (= (matrix-rows a ) (matrix-cols a)))) ;;; copy-matrix ;;; ;;; Returns an identical copy of the given matrix. (define (copy-matrix a) (cond ((not (matrix? a)) (error 'copy-matrix "~s is not a matrix" a)) (else (make-matrix (matrix-rows a) (matrix-cols a) (lambda (i j) (matrix-ref a i j)))))) ;;; transpose ;;; ;;; Returns the transpose of the given matrix. (define (transpose a) (cond ((not (matrix? a)) (error 'transpose "~s is not a matrix" a)) (else (make-matrix (matrix-cols a) (matrix-rows a) (lambda (i j) (matrix-ref a j i)))))) ;;; permute-rows ;;; ;;; Permutes the rows of matrix according to ;;; the permutation vector given. (define (permute-rows a perm-vec) (cond ((not (matrix? a)) (error 'permute-rows "~s is not a matrix" a)) ((not (vector? perm-vec)) (error 'permute-rows "~s is not a vector" perm-vec)) ((not (= (vector-length perm-vec) (1+ (matrix-rows a)))) (error 'permute-rows "permutation vector ~s is the wrong length" perm-vec)) (else (let ((new-a (copy-matrix 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)))) ;;; hilbert-matrix ;;; ;;; Returns an n x n Hilbert matrix. (define (hilbert-matrix n) (cond ((< n 0) (error 'hilbert-matrix "matrix must have positive size")) (else (make-matrix n n (lambda (i j) (/ 1 (+ i j -1))))))) ;;; indentity-matrix ;;; ;;; Returns an n x n identity matrix. (define (identity-matrix n) (cond ((< n 0) (error 'identity-matrix "matrix must have positive size")) (else (make-matrix n n (lambda (i j) (if (= i j) 1 0)))))) ;;; augment-matrix ;;; ;;; 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. ;;; ;;; This was 'roxed almost completely from jpm's original ;;; code. I just basically just added error-checking. (define (augment-matrix a b) (cond ((not (matrix? a)) (error 'augment-matrix "~s is not a matrix" a)) ((not (matrix? b)) (error 'augment-matrix "~s is not a matrix" b)) ((not (= (matrix-rows a) (matrix-rows b))) (error 'augment-matrix "matrices have differing numbers of rows")) (else (make-matrix (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))))))))) ;;; 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 (gauss-inverse a) (cond ((not (matrix? a)) (error 'gauss-inverse "~s is not a matrix" a)) ((not (square? a)) (error 'gauss-inverse "cannot invert non-square matrix ~s" a)) (else (gauss-solve a (identity-matrix (matrix-rows a)))))) ;;; 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 (gauss-solve a rhs) (cond ((not (matrix? a)) (error 'gauss-solve "~s is not a matrix" a)) ((not (matrix? rhs)) (error 'gauss-solve "~s is not a matrix" rhs)) ((not (= (matrix-rows a) (matrix-rows rhs))) (error 'gauss-solve "matrices have differing numbers of rows")) (else (let* ((PLU (PLU-factor a)) (P (car PLU)) (L (cadr PLU)) (U (cddr PLU))) (back-substitute U (forward-substitute L (permute-rows rhs P))))))) ;;; back-substitute ;;; ;;; Performs back substitution on the (assumed ;;; upper-triangular) matrix using the right-hand side ;;; vectors (in matrix form). (define (back-substitute a rhs) (cond ((not (matrix? a)) (error 'back-substitute "~s is not a matrix" a)) ((not (matrix? rhs)) (error 'back-substitute "~s is not a matrix" rhs)) (else (let* ((rows (matrix-rows rhs)) (cols (matrix-cols rhs)) (m (make-matrix 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)) (do ((j 1 (1+ j))) ((> j cols)) (matrix-set! m i j (term i j)))) m)))) ;;; 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 (forward-substitute a rhs) (cond ((not (matrix? a)) (error 'forward-substitute "~s is not a matrix" a)) ((not (matrix? rhs)) (error 'forward-substitute "~s is not a matrix" rhs)) ((not (= (matrix-rows a) (matrix-rows rhs))) (error 'forward-substitute "matrices have differing numbers of rows")) (else (let* ((rows (matrix-rows a)) (cols (matrix-cols a)) ;; mirror-matrix (mm (lambda (a) (make-matrix rows cols (lambda (i j) (matrix-ref a (- rows i -1) (- cols j -1)))))) ;; mirror-vector-matrix (mvm (lambda (a) (make-matrix rows (matrix-cols rhs) (lambda (i j) (matrix-ref a (- rows i -1) j)))))) (mvm (back-substitute (mm a) (mvm rhs))))))) ;;; partial-pivot ;;; ;;; Performs partial pivoting, and returns the permutation ;;; matrix consed onto the new matrix. (define (partial-pivot m) (let* ((a (copy-matrix 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 (do ((i 1 (1+ i))) ((> i rows)) (vector-set! P i i)) ;; Actually perform the pivoting (pivot) ;; Create the return value (cons P a))) ;;; 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 (plu-factor m) (cond ((not (matrix? m)) (error 'plu-factor "~s is not a matrix" m)) ((not (= (matrix-rows m) (matrix-cols m))) (error 'plu-factor "cannot factor a non-square matrix")) (else (let* ((PM (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 (make-matrix rows cols (lambda (i j) (cond ((= i j) 1) ((> i j) (matrix-ref a i j)) (else 0)))) (make-matrix rows cols (lambda (i j) (if (<= i j) (matrix-ref a i j) 0))))))))) ;;; doolittle-factor ;;; ;;; Factors the matrix into LU by using Doolittle's ;;; method. Returns L consed onto U. (define (doolittle-factor a) (cond ((not (matrix? a)) (error 'doolittle-factor "~s is not a matrix" a)) ((not (square? a)) (error 'doolittle-factor "matrix is not square")) (else (let* ((n (matrix-rows a)) (L (make-matrix n n (lambda (i j) (if (= j 1) (matrix-ref a i 1) 0)))) (U (make-matrix 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))))) ;;; 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 ;;; transpose of L. (define (choleski-factor a) (cond ((not (matrix? a)) (error 'choleski-factor "~s is not a matrix" a)) ((not (square? a)) (error 'choleski-factor "matrix is not square")) (else (let* ((n (matrix-rows a)) (L (make-matrix 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 (expt (matrix-ref L i k) 2))))))))) (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 (expt (matrix-ref L n k) 2))))))))) (cons L (transpose L)))))) ;;; 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)) (error 'matrix-* "~s is not a matrix" a)) ((vector? b) (cond ((not (= (vector-length b) (matrix-rows a))) (error 'matrix-* "vector is the wrong length")) (else (matrix-* a (vector->matrix b))))) ((number? b) (make-matrix (matrix-rows a) (matrix-cols a) (lambda (i j) (* b (matrix-ref a i j))))) ((not (matrix? b)) (error 'matrix-* "~s is not a matrix" b)) ((not (= (matrix-cols a) (matrix-rows b))) (error 'matrix-* "# colums in A do not match # rows in B")) (else (make-matrix (matrix-rows a) (matrix-cols b) (lambda (i j) (dot-product (matrix-col b j) (matrix-row a i))))))) ;;; matrix-/ ;;; ;;; Divides a matrix by a constant. Uses matrix-* with ;;; the inverse of the number. (define (matrix-/ a b) (cond ((not (matrix? a)) (error 'matrix-/ "~s is not a matrix" a)) ((not (number? b)) (error 'matrix-/ "~s is not a number" b)) (else (matrix-* a (/ 1 b))))) ;;; matrix-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-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 (make-matrix (matrix-rows a) (matrix-cols a) (lambda (i j) (proc (matrix-ref a i j) (matrix-ref b i j))))))) ;;; matrix-- ;;; ;;; Subtracts two matrices. They must be of the same ;;; size, of course. This trivially uses matrix-op. (define (matrix-- a b) (matrix-op 'matrix-- - a b)) ;;; matrix-+ ;;; ;;; Adds two matrices. They must be of the same ;;; size, of course. This trivially uses matrix-op. (define (matrix-+ a b) (matrix-op 'matrix-+ + a b)) ;;; tridiagonal-solve ;;; ;;; Solves a tridiagonal matrix. The right-hand side ;;; can be a matrix, which is interpreted as multiple ;;; column-vectors. (define (tridiagonal-solve a rhs) (cond ((not (matrix? a)) (error 'solve-tridiagonal "~s is not a matrix" a)) ((not (matrix? rhs)) (error 'solve-tridiagonal "~s is not a matrix" rhs)) ((not (square? a)) (error 'solve-tridiagonal "matrix is not square")) (else (let* ((a (copy-matrix a)) (rhs (copy-matrix 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 (make-matrix (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) (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) (error 'solve-tridiagonal "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)))) ;;; pretty-print-vector ;;; ;;; Prints a vector in a decent format (brackets on either ;;; side). (define (pretty-print-vector v) (cond ((not (vector? v)) (error 'pretty-print-vector "~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)))) ;;; pretty-print-matrix ;;; ;;; Prints a matrix in a decent format, with vertical ;;; bars on either side. (define (pretty-print-matrix a) (cond ((not (matrix? a)) (error 'pretty-print-matrix "~s is not a matrix" a)) (else (let* ((rows (matrix-rows a)) (cols (matrix-cols a)) (width-vec (make-vector (1+ cols))) (s (make-matrix rows cols (lambda (i j) (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)))))) ;;; generate-latex-matrix ;;; ;;; 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 (generate-latex-matrix a) (cond ((vector? a) (generate-latex-matrix (vector->matrix a))) ((not (matrix? a)) (error 'generate-latex-matrix "~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))))) ;;; dot-product ;;; ;;; Returns the dot product of two vectors. (define (dot-product x y) (cond ((not (vector? x)) (error 'dot-product "~s is not a vector" x)) ((not (vector? y)) (error 'dot-product "~s is not a vector" y)) ((not (= (vector-length x) (vector-length y))) (error '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)))))))) ;;; infinity-norm ;;; ;;; Computes the infinity-norm of either a matrix or ;;; a vector. (define (infinity-norm obj) (cond ((vector? obj) (apply max (map abs (vector->list obj)))) ((not (matrix? obj)) (error '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))))))))))) ;;; condition-number ;;; ;;; Computes the condition number of a matrix. (define (condition-number a) (cond ((not (matrix? a)) (error 'condition-number "~s is not a matrix" a)) (else (* (infinity-norm a) (infinity-norm (gauss-inverse a)))))) ;;; diagonally-dominant? ;;; ;;; Returns #t if the given matrix is diagonally dominant, ;;; and #f if not. (define (diagonally-dominant? a) (cond ((not (matrix? a)) (error '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)))))))) ;;; 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 (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)) (error '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))))))))))) ;;; reduce-matrix ;;; ;;; Returns a new matrix, which is the original matrix ;;; without the given column or row. (define (reduce-matrix a n) (cond ((not (matrix? a)) (error 'reduce-matrix "~s is not a matrix" a)) ((> n (matrix-rows a)) (error 'reduce-matrix "row subscript ~s is out of range for matrix" n)) ((> n (matrix-cols a)) (error 'reduce-matrix "column subscript ~s is out of range for matrix" n)) (else (make-matrix (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))))))) ;;; 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-8 will be ;;; used. (define (gauss-seidel a b . tol) (cond ((not (matrix? a)) (error 'gauss-seidel "~s is not a matrix" a)) ((not (matrix? b)) (error 'gauss-seidel "~s is not a matrix" b)) ((not (= (matrix-rows a) (matrix-rows b))) (error 'gauss-seidel "matrices have differing numbers of rows")) ((not (diagonally-dominant? a)) (error 'gauss-seidel "matrix is not diagonally dominant")) (else (let* ((epsilon (if (not (null? tol)) (car tol) 1e-8)) (n (matrix-rows a)) (guess (make-matrix n (matrix-cols b) 0)) (last (make-matrix n (matrix-cols b) 1))) (do ((iter 1 (1+ iter))) ((< (maximal-element (matrix-- last guess) #t) epsilon) guess) (set! last (copy-matrix 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))))))))))))))))