#+TITLE: Linear Systems in Lisp #+HTML_HEAD: #+options: num:nil # This will export a README.org file for Github, so that people that land in my repo know where to find the relevant webpage #+BEGIN_SRC org :tangle README.org :exports none :eval never see description [[http://geokon-gh.github.io/linearsystems/index.html][here]] #+END_SRC * Intro I'm going through [[matrixanalysis.com][Matrix Analysis & Applied Linear Algebra]] (Carl Meyer) and implementing the algorithms in ELisp. An excuse to learn ELisp and solidy my linear algebra. There is no emphasis on performance - just on clarity and extensability * Matices Linear system (ie. a system of equations) are for convenience represented using matrices. Once we define a matrix and all operations on it we can begin to use them for manipulating linear systems. The standard way of representing a matrix that you often see in C is using multidimensional arrays. For a 2D matrix this has 3 values: ~number of rows~ ~number of columns~ ~data~ ~data~ is a long list of size ~num-row * num-col~. When making a new matrix it's useful to be able to make a list of zeroes of the required size #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun build-list-of-zeroes (size) "Recursively builds a list of zeroes of the given size" (if (zerop size) '() ;base case (cons 0 (build-list-of-zeroes (1- size))))) ; "else"/iterative step #+END_SRC #+BEGIN_QUOTE ~zerop~ tests if the value is zero #+END_QUOTE #+BEGIN_QUOTE ~()~ with a quote is the /empty-list/ #+END_QUOTE #+BEGIN_QUOTE ~cons~ attaches the first argument to the second argument (which is normally a list) #+END_QUOTE Now we can build the actual matrix #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-zeroes (number-of-rows number-of-columns) "Builds a matrix of zeroes" (list number-of-rows number-of-columns (build-list-of-zeroes (* number-of-rows number-of-columns)))) #+END_SRC #+BEGIN_QUOTE ~list~ build a list of values equivalent to ~(cons number-of-rows (cons number-of-columns (cons (build-list-of-zeroes ...) '() )))~ *(note the last /empty-list/ element)* #+END_QUOTE Similarly we would like to be able to build matrices of random integers #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun build-list-of-random-digits (size) "Recursively builds a list of zeroes of the given size" (if (zerop size) '() ;base case (cons (random 10) (build-list-of-random-digits(1- size))))) ; iterative step (defun matrix-random-digits (number-of-rows number-of-columns) "Builds a matrix of zeroes" (list number-of-rows number-of-columns (build-list-of-random-digits (* number-of-rows number-of-columns)))) ; ex: (matrix-random-digits 2 3) #+END_SRC And matrices from manually entered data values #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-from-data-list (number-of-rows number-of-columns data-list) "Builds a matrix from a data list" (list number-of-rows number-of-columns data-list)) #+END_SRC A couple of helper function to get the 3 fields will also improve readability #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-rows (matrix) "Get the number of rows" (nth 0 matrix)) (defun matrix-columns (matrix) "Get the number of columns" (nth 1 matrix)) (defun matrix-data (matrix) "Get the data list from the matrix" (nth 2 matrix)) #+END_SRC #+BEGIN_QUOTE ~nth~ gets the nth element of the list #+END_QUOTE For debugging we need to be able to print out the matrix for inspection #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-data-get-first-n-values (data n) "Given a list of values, get the first n in a string" (if (zerop n) "" ;base case (concat (number-to-string (car data)) " " (matrix-data-get-first-n-values (cdr data) (1- n))))) ;iterative step (defun matrix-data-print (number-of-rows number-of-columns data) "Print out the data list gives the dimension of the original matrix" (if (zerop number-of-rows) "" ;base case (concat (matrix-data-get-first-n-values data number-of-columns) "\n" (matrix-data-print ;iterative step (1- number-of-rows) number-of-columns (nthcdr number-of-columns data ))))) (defun matrix-print (matrix) "Print out the matrix" (concat "\n" (matrix-data-print (matrix-rows matrix) (matrix-columns matrix) (matrix-data matrix)))) ; ex: (message (matrix-print (matrix-from-values 2 2 '(1 2 3 4)))) #+END_SRC #+BEGIN_QUOTE ~cdr~ returns the list without the first element #+END_QUOTE * Matrix Operations Next we need to define some basics operations on the martices ** Addition The simplest operation is addition. We need to check the matrices have the right size and then simple add the ~values~ lists #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-equal-size-p (matrix1 matrix2) "Check if 2 matrices are the same size" (and (equal (matrix-rows matrix1) (matrix-rows matrix2)) (equal (matrix-columns matrix1) (matrix-columns matrix2)))) (defun for-each-pair (list1 list2 operator) "Go through 2 lists applying an operator on each pair of elements" (if (null list1) '() (cons (funcall operator (car list1) (car list2)) (for-each-pair (cdr list1) (cdr list2) operator)))) (defun matrix-add (matrix1 matrix2) "Add to matrices together" (if (check-addition matrix1 matrix2) (matrix-from-data-list (matrix-rows matrix1) (matrix-columns matrix1) (for-each-pair (matrix-data matrix1) (matrix-data matrix2) '+)))) (defun matrix-subtract (matrix1 matrix2) "Subtract MATRIX2 from MATRIX1" (if (check-addition matrix1 matrix2) (matrix-from-data-list (matrix-rows matrix1) (matrix-columns matrix1) (for-each-pair (matrix-data matrix1) (matrix-data matrix2) '-)))) #+END_SRC #+BEGIN_QUOTE ~funcall~ applied the first arugment (a function) with the remaining items in the list as arguments #+END_QUOTE ** Submatrices The next fundamental step is we want to be able to extract submatrices #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-extract-subrow (matrix row start-column end-column) "Get part of a row of a matrix and generate a row matrix from it. START-COLUMN is inclusive, END-COLUMN is exclusive" (let ((number-of-columns-on-input (matrix-columns matrix)) (number-of-columns-on-output (- end-column start-column))) (matrix-from-data-list 1 number-of-columns-on-output (subseq (matrix-data matrix) (+ (* row number-of-columns-on-input) start-column) (+ (* row number-of-columns-on-input) end-column))))) (defun matrix-append (matrix1 matrix2) "Append one matrix (set of linear equations) to another" (if (null matrix2) matrix1 (matrix-from-data-list (+ (matrix-rows matrix2) (matrix-rows matrix1)) (matrix-columns matrix1) (append (matrix-data matrix1) (matrix-data matrix2))))) (defun matrix-submatrix (matrix start-row start-column end-row end-column) "Get a submatrix. start-row/column are inclusive. end-row/column are exclusive" (if (equal start-row end-row) '() (matrix-append (matrix-extract-subrow matrix start-row start-column end-column) (matrix-submatrix matrix (1+ start-row) start-column end-row end-column)))) #+END_SRC Which allows us to build these two very familiar function #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-get-row (matrix row) "Get a row from a matrix. Index starts are ZERO" (matrix-extract-subrow matrix row 0 (matrix-columns matrix))) (defun matrix-get-column (matrix column) "Get a column from a matrix. Index starts are ZERO" (matrix-submatrix matrix 0 column (nth 0 matrix) (1+ column))) #+END_SRC ** Multiplication There are fundamentally two different multiplications for matrices. One is multiplying 2 conformable matrices, and the other is multiplying a matrix by a scalar. First I will deal with the latter b/c it's the simpler case. #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-scalar-product (matrix scalar) "Multiple the matrix by a scalar. ie. multiply each value by the scalar" (matrix-from-values (matrix-rows matrix) (matrix-columns matrix) (mapcar (lambda (x) (* scalar x)) (matrix-data matrix)))) #+END_SRC To do matrix multiplcation we need to work in small steps and first define the inner product #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-inner-product (row column) "Multiply a row times a column and returns a scalar" (reduce '+ (for-each-pair (matrix-data row) (matrix-data column) '*))) #+END_SRC #+BEGIN_QUOTE ~reduce~ works down the list elements-by-element applying the operator on each cumulative result #+END_QUOTE Once we have the inner product we can calculate each value (it's just ~row * column~) and build up to calculating the whole matrix product. #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-product-one-value (matrix1 matrix2 row column) "Calculate one value in the resulting matrix of the product of two matrices" (matrix-inner-product (matrix-get-row matrix1 row ) (matrix-get-column matrix2 column))) (defun matrix-product-rec (matrix1 matrix2 row column) "A recursive helper function that builds the matrix multiplication's data vector" (if (equal (matrix-rows matrix1) row) '() (if (equal (matrix-columns matrix2) column) (matrix-product-rec matrix1 matrix2 (1+ row) 0) (cons (matrix-product-one-value matrix1 matrix2 row column) (matrix-product-rec matrix1 matrix2 row (1+ column)))))) (defun matrix-conformable? (matrix1 matrix2) "Check that two matrices can be multiplied" (equal (matrix-columns matrix1) (matrix-rows matrix2))) (defun matrix-product (matrix1 matrix2) "Multiply two matrices" (matrix-from-data-list (matrix-rows matrix1) (matrix-columns matrix2) (matrix-product-rec matrix1 matrix2 0 0))) #+END_SRC ** Transposition Transposition is quick and easy using our existing tools #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-transpose (matrix) "Get the transpose of a matrix" (if (equal (matrix-columns matrix) 1) (matrix-from-values 1 (matrix-rows matrix) (matrix-data matrix)) (matrix-append (matrix-from-values 1 (matrix-rows matrix) (matrix-data (matrix-get-column matrix 0))) (matrix-transpose (matrix-submatrix matrix 0 1 (matrix-rows matrix) (matrix-columns matrix)))))) #+END_SRC ** TESTS :noexport: #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (ert-deftest matrix-test-operations () "Testing - Matrix Operations" (let ((matrix1 '(2 2 (1 2 3 4))) (matrix2 '(2 2 (5 6 7 8)))) (should (equal (matrix-extract-subrow '(2 2 (1 2 3 4)) 1 0 2) '(1 2 (3 4)))) (should (equal (matrix-equal-size-p matrix1 matrix2) 't)) (should (equal (matrix-add matrix1 matrix2) '(2 2 (6 8 10 12)))) (should (equal (matrix-subtract matrix1 matrix2) '(2 2 (-4 -4 -4 -4)))) (should (equal (matrix-scalar-product (matrix-identity 3) 7) '(3 3 (7 0 0 0 7 0 0 0 7)))))) #+END_SRC * Elementary Matrices ** Identity Matrix The simplest matrix is the identity matrix *I*. For any matrix *A* * *I* = *A*. This is the matrix of all *1*'s on the diagonal. #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-build-identity-rec (rank row column) "Helper function that build the data vector of the identity matrix" (if (equal column rank) ; time to build next row (if (equal row (1- rank)) '() ; we're done (matrix-build-identity-rec rank (1+ row) 0)) (if (equal row column) (cons 1 (matrix-build-identity-rec rank row (1+ column))) (cons 0 (matrix-build-identity-rec rank row (1+ column)))))) (defun matrix-identity (rank) "Build an identity matrix of the given size/rank" (matrix-from-values rank rank (matrix-build-identity-rec rank 0 0 ))) #+END_SRC ** Unit Column The next simplest matrix is the unit column #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-unit-column (row size) "Build a unit column. ROW is where you want the 1 to be placed. SIZE is the overall length" (letrec ((matrix-unit-column-data-rec (lambda (row size) (if (equal size 0) '() (if (equal row 0) (cons 1 (funcall matrix-unit-column-data-rec (1- row) (1- size))) (cons 0 (funcall matrix-unit-column-data-rec (1- row) (1- size)))))))) (matrix-from-values size 1 (funcall matrix-unit-column-data-rec row size)))) #+END_SRC #+BEGIN_QUOTE Here I'm just trying out a new notation. With ~letrec~ we can hide the recursive helper function inside the function that uses it. #+END_QUOTE ** Matrix Elementary Transformations Taking the identity matrix we can change it into any matrix we want. The manipulation of matrices can be broken down into 3 fundamental elementary operation *** Type I - Row/Column Interchange Interchaning rows (or columns) /i/ and /j/ #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-elementary-interchange (rowcol1 rowcol2 rank) "Make an elementary row/column interchange matrix for ROWCOL1 and ROWCOL2 (ZERO indexed)" (let ((u (matrix-subtract (matrix-unit-column rowcol1 rank) (matrix-unit-column rowcol2 rank)))) (matrix-subtract (matrix-identity rank) (matrix-product u (matrix-transpose u))))) #+END_SRC *** Type II - Row/Column Multiple Multiplying row (or column) /i/ by /\alpha/ #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-elementary-multiply (rowcol scalar rank) "Make an elementary row/column multiple matrix for a given ROWCOL (ZERO indexed)" (let ((elementary-column (matrix-unit-column rowcol rank))) (matrix-subtract (matrix-identity rank) (matrix-product elementary-column (matrix-scalar-product (matrix-transpose elementary-column) (- 1 scalar)))))) #+END_SRC *** Type III - Row/Column Addition Adding a multiple of a row (or column) /i/ to row (or column) /j/ #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (defun matrix-elementary-addition (rowcol1 rowcol2 scalar rank) "Make an elementary row/column product addition matrix. Multiply ROWCOL1 (ZERO indexed) by SCALAR and add it to ROWCOL2 (ZERO indexed)" (matrix-add (matrix-identity rank) (matrix-scalar-product (matrix-product (matrix-unit-column rowcol2 rank) (matrix-transpose (matrix-unit-column rowcol1 rank))) scalar))) #+END_SRC ** TESTS :noexport: #+BEGIN_SRC emacs-lisp :results output silent :session :tangle matrix.el (ert-deftest matrix-test-elementary-operation () "Testing - Elementary Matrix Transformations" (let ((matrix1 '(2 2 (1 2 3 4))) (matrix2 '(2 2 (5 6 7 8)))) (should (equal (matrix-identity 3) '(3 3 (1 0 0 0 1 0 0 0 1)))) (should (equal (matrix-unit-column 3 5) '( 5 1 (0 0 0 1 0)))) (should (equal (matrix-elementary-interchange 0 1 3) '(3 3 (0 1 0 1 0 0 0 0 1)))) (should (equal (matrix-elementary-multiply 1 7 3) '(3 3 (1 0 0 0 7 0 0 0 1)))) (should (equal (matrix-elementary-addition 0 2 7 3) '(3 3 (1 0 0 0 1 0 7 0 1)))))) #+END_SRC * TODOs Remove zero matrix and random values matrix. Makes things longer and confusing and isn't necessary at all #+BEGIN_QUOTE This webpage is generated from an org-document (at ~./index.org~) that also generates all the files described. Once opened in Emacs:\\ - ~C-c C-e h h~ generates the webpage \\ - ~C-c C-v C-t~ exports the code blocks into the appropriate files\\ #+END_QUOTE