Computer Science 15-112, Fall 2011
Optional Lecture Notes:  Matrices

 


 

These notes were prepared by a student attending the optional lecture on matrices.  As such, they may contain a small error here or there (standard disclaimer).  They are provided here as a service to those who may have missed the lecture.

 


 

First, here is the code that we wrote:  matrices.py

Second, here is the header for that file, which explains the overall goal and approach:

# Partly written in class on Thu 6-Oct with many additions in an
# optional lecture on Sat 8-Oct, and then one more addition (changing
# floats to fractions) by motivated students after the lecture.  :-)
# As usual, with code written in real-time in class, this may have
# some stylistic shortcomings (such as lacking comments) and may
# even have a bug lurking here or there.

# This optional case study solves each of the following problems.  Each
# solution uses the solutions to the preceding problems.

# 1) Multiply two matrices
# 2) Invert a matrix
# 3) Solve a system of linear equations
# 4) Fit an exact polynomial to a list of points
# 5) Find the coefficients of the polynomial for a power sum

# This last problem needs a little explaining.  Consider this power sum:
# 1 + 2 + ... + n.  We know this equals n(n+1)/2.  We can represent this
# quadratic function by its coefficients alone, as [1/2, 1/2, 0] (ignoring
# integer division in this explanation).

# Now, the sum of the squares is: 1**2 + 2**2 + ... + n**2.  This is a cubic
# equation.  It happens to be [1/3, 1/2, 1/6, 0].  That is:
# 1**2 + ... + n**2 = (1/3)n**3 + (1/2)n**2 + (1/6)n

# In fact, for positive integer k, 1**k + ... + n**k is a (k+1)-degree
# polynomial.  The problem we are solving is to write a function which takes
# this number k, and returns the coefficients of that polynomial.

# For some test cases, you can see the expected polynomials for k=1 to k=10
# here: http://mathworld.wolfram.com/PowerSum.html

# Also, our approach for matrix multiplication needs some slight explaining.
# We basically use Gauss-Jordan Elimination to get reduced row echelon form
# while applying the same elementary row operations to an identity matrix -- in
# so doing, the identity matrix is transformed into the inverse of the original
# matrix.  Amazing!
# You can see this in action, interactively entering your own NxN matrix and
# watch the inverse matrix being derived step-by-step, here:
# http://www.math.odu.edu/~bogacki/cgi-bin/lat.cgi?c=inv

# Finally, since our solutions are all fractions, we opted to the fractions
# module in Python.  This hardly changed our original floating-point solution,
# so the code should be readable even if you are unfamiliar with the
# fractions module.

 

Ultimately we’re going to be able to solve for the coefficients of a power sum, but let’s first go over Matrix Multiplication:

 

The general pattern: K* cth row into rth row

·   “K*cth row”: if K is in column c in the 1st matrix, it will grab the cth row of the 2nd matrix when multiplying

·   “into rth row”: whichever row of the 1st matrix K appears in will be the row K will be added onto in the answer

 

Screen shot 2011-10-08 at 5.02.30 PM.JPG 

 

We can use the principle of matrix multiplication to invert a matrix…

 

def invertMatrix(m):

    n = len(m)

    assert(len(m) == len(m[0]))

    inverse = makeIdentity(n)

    for col in xrange(n): #proceeds 1 column at a time

        # 1. make the diagonal contain a 1

        diagonalRow = col

        k = 1.0 / m[diagonalRow][col] #multiply the row by a constant (k)

        m = multiplyRowOfSquareMatrix(m, diagonalRow, k) #make sure to assign this to a variable!

                                                                                             #otherwise returns the original matrix

        inverse = multiplyRowOfSquareMatrix(inverse, diagonalRow, k) #we could swap rows here instead

        assert(m[diagonalRow][col] != 0) #make sure it’s a singular, non-invertible matrix

        # 2. use the 1 on the diagonal to make everything else in this column a 0

        sourceRow = diagonalRow

        for targetRow in xrange(n): #targetRow is the variable marching through other rows, NOT sourceRow

            if (sourceRow != targetRow):

                k = -m[targetRow][col] #this negation makes the value go away!

                m = addMultipleOfRowOfSquareMatrix(m, sourceRow, k, targetRow)

                inverse = addMultipleOfRowOfSquareMatrix(inverse, sourceRow, k, targetRow)

    return inverse

Now that we know how to use matrix multiplication to invert a matrix, we can use inverse matrices to solve systems of linear equations!

 

How? Well, let’s look at the example    Ax = b

                                                      A-1Ax = A-1

         A-1A is the identity                          x = A-1b

 

def testSolveSystemOfEquations():

    print "Testing solveSystemOfEquations...",

    #Here, we have this system of linear equations:

    # 3x + 2y - 2z = 10

    # 2x - 4y + 8z =  0

    # 4x + 4y - 7z = 13

    # x = 2, y = 3, z = 1

    #Keep in mind that variables must be in the same order across all lines and equations must be linear

    A = [ [3 2, -2],  #A consists of the constant coefficients of the left hand side of the system of equations

             [2, -4 8],

             [4 4, -7] ]

    b = [ [ 10 ],   #And b is just the 2d matrix of the right hand side of the above system of equations!

             [  0 ],

             [ 13 ] ]

    observedX = solveSystemOfEquations(A,b)

    expectedX = [ [ 2 ],

                  [ 3 ],

                  [ 1 ] ]

    assert(almostEqualMatrices(observedX, expectedX))

    print "Passed!"

def solveSystemOfEquations(A, b):

    return multiplyMatrices(invertMatrix(A), b)

Next, we realize that given a set of points, we can find a polynomial that fits them.

 

A basic example:

:Screen shot 2011-10-09 at 11.48.53 AM.png

If we plug these points into a1x1 + a0x0 = y, we have…

 

a1(-1) + a0 = -7

a1(3) + a0 = 5

 

Where the variables x & y are now the constants in the equations and the coefficients are now the variables.

 

If we have three points, there must be a unique parabola that goes through them.

(To prove this we’d have to show the unique coefficients, and since the coefficients are unique, so is the parabola.)

 

 

:Screen shot 2011-10-09 at 12.14.18 PM.png

 

Also remember to keep in mind that we need three points to describe a polynomial of size 2!

 

From the above graph,

 

         a2x2 + a1x1 + a0 = y

x2 a2 + x1 a1 + a0 = y

 

Putting this system of equations into matrices…

 

:Screen shot 2011-10-09 at 12.16.25 PM.png

 

Notice here that the 2nd matrix holds all the coefficients! This is great because we know from earlier that we can solve for the values in that matrix by multiplying both sides of the equation by the inverse of the 1st matrix. Thus, we’d be solving for the coefficients.

 

So, let’s turn this into code…

 

def fitExactPolynomial(pointList):

    n = len(pointList) #n=number of points

    degree = n - 1

    # 1. make A

    A = make2dList(n,n) #allocating space

    for row in xrange(n): #row determines rth point of x&y we're dealing with

        for col in xrange(n): #col determines exponent

            x = pointList[row][0] #this step adds clarity

            exponent = degree - col

            #when in col 0, degree

            #        col 1, degree - 1

            #      col col, degree - col

            A[row][col] = x**exponent

    # 2. make b

    b = make2dList(n,1)

    for row in xrange(n):

        y = pointList[row][1]

        b[row][0] = y

    # use system solver to find solution

    return solveSystemOfEquations(A, b)

 

def testFitExactPolynomial():

    print "Testing fitPolynomialExactly...",

    def f(x): return 3*x**3 + 2*x**2 + 4*x + 1 #cubic

    expected = [[3], [2], [4], [1]]

    pointList = [(1,f(1)), (2,f(2)), (5,f(5)), (-3,f(-3))]

    observed = fitExactPolynomial(pointList)

    assert(almostEqualMatrices(observed, expected))   

    print "Passed!"

 

 

Say we have f(n) = 1k + 2k + … + nk

 

:Screen shot 2011-10-09 at 1.12.00 PM.png

 

When x is 1, y is 1k

         x is 2, y is 1k + 2k

            x is 3, y is 1k + 2k + 3k

 

If we follow this pattern, we know that we can generate as many points as needed. We can then pass these points to our curve fitting function, which solves for the coefficients by solving a system of equations.

 

def findCoefficientsOfPowerSum(k): #k is the power

    # If it’s a power of 1, the coefficients are ½ ½ 0!

    # Assume f(n) = 1**k + ... + n**k

    # We argued by handwaving-ish-calculusy-stuff that f(n) is a polynomial of degree (k+1)

    # We need (k+2) points to fit such a polynomial

    pointList = []

    y = 0   

    for x in xrange(1,k+3): #we removed 0 as the first element to avoid a row swap, so this becomes k+3 instead of k+2

        # y = 1**k + ... + n**k

        y += x**k

        pointList += [(x,y)]

    return fitExactPolynomial(pointList)

def testFindCoefficientsOfPowerSum():

    print "Testing findCoefficientsOfPowerSum...",

    for k in xrange(10):

        print k, findCoefficientsOfPowerSum(k)

    print "Passed!"

Now if we want, we can format our answers to actually look like power sums.

Search ‘python fractions’ and then seek out all the constants!

·         In makeIdentity…

o   result[i][i] = Fraction(1,1)

·         In multiplyMatrices…

o   dotProduct = Fraction(0,1)

·         In findCoefficientsOfPowerSums…

o   y = Fraction(0,1)

o   for n in xrange (1,+3) #make sure you use n, not x!

o        x=Fraction(n,1)

So we originally set out to find the power sum coefficients and now have the ability to take a set of points and fit them to a curve. We can also take a polynomial with n equations and n unknowns and use matrix multiplication and matrix inverses to solve the system of linear equations. Pretty cool!