CS 227 Lecture -*- Outline -*- * Matrices (9.4) (focus on the math) A matrix is like a table, a two-dimensional vector. ** Example For example, in a phone book, have a row for each person: The code for this is in matrix-code.ss ------------------------------------------- A MATRIX "Jones, J." "2117 Plum St." "412-8421" "Jones, M." "1392 First Ave." "424-7773" "Jose, M." "3314 Valley Dr." "421-0035" "Joslin, J." "2550 Western" "412-5531" ------------------------------------------- ** values *** Terms Point out rows, columns. Show 0-based indexes of elements (2,1) and "3314 Valley Dr." Q: What is index of "2550 Western" ? of "Jose, M." ? Row comes first, then Column. Math notation: a_{ij} is element at position (i,j). m by n matrix has m rows and n columns. Q: What are m and n for our example? *** value def -------------------- MATRIX VALUES Def: a (matrix T) value is a mapping from {(0,0),(0,1),...,(0,n-1), (1,0),(1,1),...,(1,n-1), ... (m-1,0),(m-1,1),...,(m-1,n-1)} to objects of type T, where m >= 0 is the number of rows and n >= 0 is the number of columns --------------------- *** object def -------------------- MATRIX OBJECTS Def: a (matrix T) object is ((matrix-generator f) m n) where f has type (-> (natural natural) T), m >= 0 is the number of rows and n >= 0 is the number of columns. -------------------- ** Basic procedures for the ADT *** constructor -------------------------------------------- BASIC PROCEDURES FOR MATRICES matrix-generator: (-> ((-> (natural natural) T)) (-> (natural natural) (matrix T))) -------------------------------------------- ((matrix-generator gen-proc) mrows ncolumns) (matrix-view is an exercise) This is like a vector-generator, except that the generating function takes both a row and a column index. For example, the nxn identity matrix, O's on the diagonal from top left to bottom right. (define make-id-matrix (lambda (size) ((matrix-generator (lambda (i j) (if (= i j) 1 0))) size size))) Q: How would you make a matrix of size 5x4 so that each element was (list i j), for each row i, col j ? *** observers ------------------------------------- num-rows: (-> ((matrix T)) natural) num-cols: (-> ((matrix T)) natural) ------------------------------------- the equations: (num-rows ((matrix-generator f) m n)) = m (num-cols ((matrix-generator f) m n)) = n ------------------------------------- matrix-ref: (-> ((matrix T)) (-> (natural natural) T)) ; REQUIRES: the first natural is less than ; the number of columns of the matrix, and ; the second is less than number of rows. -------------------------------------- It's curried! The currying allows us to specialize this to a particular matrix. ((matrix-ref mat) row-index col-index) Try: (define A-ref (matrix-ref A)) (A-ref 1 2) (A-ref 2 1) equation: if 0 <= i < m and 0 <= j < n, ((matrix-ref ((matrix-generator f) m n)) i j) = (f i j) *** mutator -------------------------------------------- matrix-set! : (-> ((matrix T)) (-> (natural natural T) void)) ; REQUIRES: the first natural is less than ; the number of columns of the matrix, and ; the second is less than number of rows. --------------------------------------------- This is also curried, in the same way as matrix-ref ((matrix-set! mat) row-index col-index obj) ** A representation To get random access and mutation, need to use a vector We will use a vector that contains all the elements. For an (m x n) matrix, the vector will have size m*n (plus 1). *** Row major or Column major order Consider the following matrix ---------------------- REPRESENTING MATRICES Example matrix: 0 1 2 3 10 11 12 13 20 21 22 23 ---------------------- Row major order: write down row 0, row 1, etc. -------------------------------------------- ROW MAJOR ORDER 0 1 2 3 10 11 12 13 20 21 22 23 -------------------------------------------- Column major order: write down column 0 first, then column 1, etc. -------------------------------------------- COLUMN MAJOR ORDER 0 10 20 1 11 21 2 12 22 3 13 23 -------------------------------------------- Column major order is used in FORTRAN. We will use row major order (arbitrarily). (column major is a homework) Q: Given a vector of 12 numbers, can we tell if it is a 3x4, 4x3, 6x2, etc. matrix ? So need to store the size of rows too. In this rep, we make it the last element of the vector. -------------------------------------------- OUR REPRESENTATION (IDEA IS ROW MAJOR) (vector 0 1 2 3 10 11 12 13 20 21 22 23 4) -------------------------------------------- *** Address computation Consider the element number 13. Q: What is its index in the matrix ? Row size is 4, block off first 2, count over in index 1 row, starting from 0. Q: What is the index of 13 in the representing vector ? 7 Note that 7 = 4*1 + 3 We can reverse this process to implement matrix-ref. ---------------------- INDEX OF i,j IN REP (row-size)*i+j ; - Program 9.32, pg. 294 - (define matrix-ref (lambda (mat) (let ((ncols (num-cols mat))) (lambda (i j) (vector-ref mat (+ (* i ncols) j)))))) ------------------------- Note the practical advantage of currying: don't have repeated calculation of ncols ------------------------- ; - Program 9.30, pg. 293 - (define num-cols (lambda (mat) (let ((size (sub1 (vector-length mat)))) (vector-ref mat size)))) ---------------------- Note that the size of a row is the same as the number of columns. The following is for completeness. Skip if low on time. -------------------------- ; - Program 9.31, pg. 294 - (define num-rows (lambda (mat) (let ((size (sub1 (vector-length mat)))) (/ size (vector-ref mat size))))) -------------------------- *** Implementation of matrix-generator We have to make a vector to represent the matrix. Vector generator takes a function of a single index, we have a function of two indexes. Use quotient and remainder to translate as above. Store the number of columns and last element. ---------------------- ; - Program 9.33, pg. 295 - (define matrix-generator ; TYPE: (-> ((-> (natural natural) T)) ; (-> (natural natural) ; (matrix T))) (lambda (gen-proc) (lambda (nrows ncols) ; ENSURES: result is a matrix of ; nrows by ncols whose (i,j)th ; element is (gen-proc i j) (let ((size (* nrows ncols))) (let ((vec-gen-proc ; TYPE: (-> (natural) void) (lambda (k) ; REQUIRES: k <= size ; ENSURES: result is ; the kth element of the ; row vector rep, ; or ncols if k = size (if (< k size) (gen-proc (quotient k ncols) (remainder k ncols) ) ncols)))) ((vector-generator vec-gen-proc) (add1 size))))))) ---------------------- --------------------- Exercise: use this to write make-matrix like make-vector, but curried. ((make-matrix 0.0) 7 3) --------------------- (define make-matrix ; TYPE: (-> (T) ; (-> (natural natural) (matrix T))) (lambda (fill) (lambda (nrows ncols) ((matrix-generator (lambda (i j) fill) nrows ncols))))) Show how to write an imperative version of this! *** Slicing (skip if low on time) Can take *slices* of a matrix, that is extract a row or column as a vector. ---------------------- > (define mat ((matrix-generator (lambda (i j) (+ (* 10 i) j))) 3 4)) > ((row-of mat) 2) #(20 21 22 23) Return a new vector. (define row-of ; TYPE: (-> ((matrix T)) ; (-> (natural) (vector T))) (lambda (mat) ; ENSURES: (result i) is the ith ; row vector of mat, ; if 0 <= i < (num-rows mat) (let ((mat-ref (matrix-ref mat)) (number-of-columns (num-cols mat))) (lambda (i) ; REQUIRES: i < (num-rows mat) (let ((gen-proc (lambda (j) (mat-ref i j))) ) ((vector-generator gen-proc) number-of-columns)))))) ---------------------- Q: In writing this procedure, what has to be done ? Q: How would you change this into col-of ? *** Matrix-set! ------------------------- ; - Program 9.39, pg. 300 - (define matrix-set! ; TYPE: (-> ((matrix T)) ; (-> (natural natural T) void) (lambda (mat) (let ((ncols (num-cols mat))) (lambda (i j obj) ; REQUIRES: i < ncols, ; and j < (num-rows mat) ; MODIFIES: mat ; EFFECT: make the (i,j)th ; element in mat be obj (vector-set! mat (+ (* i ncols) j) obj))))) -------------------------------------------- Note: what good is the let doing? If time: Q: how to improve the error message of matrix-set! ? *** An application (omit if low on time) Matrix Multiplication. Often in math, will want to multiply matrices. (Common in Linear Algebra, lots of applications) Imagine a toy company ----------------------------------------------------------------- SILLY TOY COMPANY stl pls rbr Ames DSM widget 4 2 2 steel $8 $7 xylophn 5 2 2 plastic $4 $5 yack 4 3 1 rubber $5 $4 zebra 5 5 2 ------------------------------------------------------------------- i.e., widgets use 4 units of steel, 2 of plastic, 2 of rubber. (good quality!) The company has 2 plants. cost in Ames for steel is $8 per unit. Q: How much does it cost to make a zebra in DSM? That is the dot product of the zebra row and the DSM column. Collection of all such results is the matrix product. ------------------------------------------- Ames DSM widgets $50 $46 xylophn $58 $53 yack $49 $47 zebra $54 $54 ------------------------------------------- Q: Where should they make yacks? Write an imperative-style procedure to do this, (See 9.38 for a functional version) ; - Program 9.38 - functional version (define matrix-product ; TYPE: (-> ((matrix number) (matrix number)) ; (matrix number)) (lambda (mat-a mat-b) ; REQUIRES: (num-cols mat-a) = (num-rows mat-b) ; ENSURES: result is the product of mat-a and mat-b, ; i.e., a (num-rows mat-a) by (num-cols mat-b) matrix ; whose (i,j)th element is teh dot-product of the ith row of a ; and the jth column of b (let ((ncols-a (num-cols mat-a)) (a-ref (matrix-ref mat-a)) (b-ref (matrix-ref mat-b))) ; REQUIRES: ncols-a = (num-rows mat-b) ; ENSURES: result is the product of mat-a and mat-b, ; i.e., a (num-rows mat-a) by (num-cols mat-b) matrix ; whose (i,j)th element is teh dot-product of the ith row of a ; and the jth column of b (if (not (= ncols-a (num-rows mat-b))) (error "matrix-product:" "The matrices are not compatible.") (let ((gen-proc (lambda (i j) (letrec ((dot-prod ; TYPE: (-> (natural number) number) (lambda (r acc) ; REQUIRES: 0 <= r <= ncols-a ; and acc is the sum of all products of ; the form a[i,r']*b[r',j] for all 0 <= r' < r. ; ENSURES: result is dot product of ; the ith row of mat-a and the jth col of mat-b (if (= r ncols-a) acc (dot-prod (add1 r) (+ acc (* (a-ref i r) (b-ref r j)))))))) (dot-prod 0 0))))) ((matrix-generator gen-proc) (num-rows mat-a) (num-cols mat-b))))))) Q: can you extract dot-prod as a useful procedure at the top level? Hint: use currying ------------------------------ (define dot-prod-internal ; TYPE: (-> ((matrix number) (matrix number)) ; (-> (natural number) number)) (lambda (mat-a mat-b) ; REQUIRES: (num-cols mat-a) = (num-rows mat-b) ; ENSURES: (result i j) is the dot product of ; the ith row of mat-a and the jth col of mat-b (let ((ncols-a (num-cols mat-a)) (a-ref (matrix-ref mat-a)) (b-ref (matrix-ref mat-b))) (lambda (i j) (letrec ((dot-prod ; TYPE: (-> (natural number) number) (lambda (r acc) ; REQUIRES: 0 <= r <= ncols-a ; and acc is the sum of all products of ; the form a[i,r']*b[r',j] for all 0 <= r' < r. ; ENSURES: result is dot product of ; the ith row of mat-a and the jth col of mat-b (if (= r ncols-a) acc (dot-prod (add1 r) (+ acc (* (a-ref i r) (b-ref r j)))))))) (dot-prod 0 0)))))) (define matrix-product ; TYPE: (-> ((matrix number) (matrix number)) ; (matrix number)) (lambda (mat-a mat-b) ; REQUIRES: (num-cols mat-a) = (num-rows mat-b) ; ENSURES: result is the product of mat-a and mat-b, ; i.e., a (num-rows mat-a) by (num-cols mat-b) matrix ; whose (i,j)th element is teh dot-product of the ith row of a ; and the jth column of b (let ((ncols-a (num-cols mat-a)) (dot-prod (dot-prod-internal mat-a mat-b))) (if (not (= ncols-a (num-rows mat-b))) (error "matrix-product:" "The matrices are not compatible.") ((matrix-generator dot-prod) (num-rows mat-a) (num-cols mat-b)))))) ; imperative version, compare program 9.38 (define matrix-product-imp ; TYPE: (-> ((matrix number) (matrix number)) ; (matrix number)) (lambda (mat-a mat-b) ; REQUIRES: (num-cols mat-a) = (num-rows mat-b) ; ENSURES: result is the product of mat-a and mat-b, ; i.e., a (num-rows mat-a) by (num-cols mat-b) matrix ; whose (i,j)th element is teh dot-product of the ith row of a ; and the jth column of b (let ((nrows-a (num-rows mat-a)) ; NOTE: different than above!!! (ncols-b (num-cols mat-b))) ; ditto (if (not (= (num-cols mat-a) (num-rows mat-b))) (error "matrix-product:" "The matrices are not compatible.") (let ((mat ((make-matrix 0.0) (num-rows mat-a) (num-cols mat-b))) (dot-prod (dot-product-internal mat-a mat-b))) (let ((mat-set! (matrix-set! mat))) (letrec ((each-row! ; TYPE: (-> (natural) void) (lambda (i) ; REQUIRES: i <= nrows-a ; MODIFIES: mat ; EFFECT: set each element in the ith row of mat to ; the dot-product of the ith row of mat-a and the ; appropriate column of mat-b (letrec ((each-col! ; TYPE: (-> (natural) void) (lambda (j) ; REQUIRES: j <= ncols-b ; EFFECT: set the (i,j)th element of mat to ; the dot-product of the ith row of mat-a and the ; jth column of mat-b (if (< j ncols-b) (begin (mat-set! i j (dot-prod i j)) (each-col! (add1 j))))))) (if (< i nrows-a) (begin (each-col! 0) (each-row! (add1 i)))))))) (each-row! 0) mat))))))) -------------------------- ** exercises The following were used in discussion sections... define the following matrix using matrix-generator 2 3 4 3 4 5 4 5 6 write: add1-matrix matrix-sum matrix-transpose draw-horiz-line! matrix-update! (called matrix-set!-b) more on matrix multiplication