Section B.3 SymPy for linear algebra
SymPy is a Python library for symbolic algebra. On its own, itβs not as powerful as programs like Maple, but it handles a lot of basic manipulations in a fairly simple fashion, and when we need more power, it can interface with other Python libraries.
Another advantage of SymPy is sophisticated βpretty-printingβ. In fact, we can enable MathJax within SymPy, so that output is rendered in the same way as when LaTeX is entered in a markdown cell.
Subsection B.3.1 SymPy basics
Running the following Sage cell will load the SymPy library and turn on MathJax.
xxxxxxxxxx
from sympy import *
init_printing()
Note: the command
from sympy import *
given above is not best practice. It can be convenient when you want to do a quick calculation (for example, on a test), but can have unintended consequences. It is better to only load those parts of a library you are going to use; for example, from sympy import Matrix, init_printing
.If you are going to be working with multiple libraries, and more than one of them defines a certain command, you can use prefixes to indicate what library you want to use. For example, if you enter matrix.
import sympy as sy
, each SymPy command will need to be appended with sy
; for example, you might write sy.Matrix
instead of simply Matrix
. Letβs use SymPy to create a xxxxxxxxxx
from sympy import Matrix, init_printing
init_printing()
A = Matrix(2,3,[1,2,3,4,5,6])
A
The
A
on the second line asks Python to print the matrix using SymPyβs printing support. If we use Pythonβs print
command, we get something different; note that the next Sage cell remembers the values from the previous one, if you are using the HTML version of the book.xxxxxxxxxx
print(A)
Weβll have more on matrices in Subsection B.3.2. For now, letβs look at some more basic constructions. One basic thing to be mindful of is the type of numbers weβre working with. For example, if we enter
2/7
in a code cell, Python will interpret this as a floating point number (essentially, a division).(If you are using Sage cells in HTML rather than Jupyter, this will automatically be interpreted as a fraction.)
xxxxxxxxxx
2/7
But we often do linear algebra over the rational numbers, and so SymPy will let you specify this. First, youβll need to load the
Rational
function.xxxxxxxxxx
from sympy import Rational
Rational(2,7)
You might not think to add the comma above, because youβre used to writing fractions like Fortunately, the SymPy authors thought of that:
xxxxxxxxxx
Rational(2/7)
Hmm... You might have got the output you expected in the cell above, but maybe not. If you got a much worse looking fraction, read on.
Another cool command is the
sympify
command, which can be called with the shortcut S
. The input 2
is interpreted as an int
by Python, but S(2)
is a βSymPy Integer
β:xxxxxxxxxx
from sympy import S
S(2)/7
Of course, sometimes you do want to use floating point, and you can specify this, too:
xxxxxxxxxx
2.5
xxxxxxxxxx
from sympy import Float
Float(2.5)
xxxxxxxxxx
Float(2.5e10)
One note of caution:
Float
is part of SymPy, and not the same as the core Python float
command. You can also put decimals into the Rational command and get the corresponding fraction:xxxxxxxxxx
Rational(0.75)
The only thing to beware of is that computers convert from decimal to binary and then back again, and sometimes weird things can happen:
xxxxxxxxxx
Rational(0.2)
Of course, there are workarounds. One way is to enter as a string:
xxxxxxxxxx
Rational('0.2')
Another is to limit the size of the denominator:
xxxxxxxxxx
Rational(0.2).limit_denominator(10**12)
We can also deal with repeating decimals. These are entered as strings, with square brackets around the repeating part. Then we can βsympifyβ:
xxxxxxxxxx
S('0.[1]')
Finally, SymPy knows about mathematical constants like which youβll need from time to time in linear algebra. If you ever need this is entered as
oo
.xxxxxxxxxx
I*I
xxxxxxxxxx
I-sqrt(-1)
xxxxxxxxxx
from sympy import pi
pi.is_irrational
Finally, from time to time you may need to include parameters (variables) in an expression. Typical code for this is of the form
a, b, c = symbols('a b c', real = True, constant = True)
. Here, we introduce the symbols a,b,c
with the specification that they represent real-valued constants.Subsection B.3.2 Matrices in SymPy
Here we collect some of the SymPy commands used throughout this text, for ease of reference. For further details, please consult the online documentationβ1β.
To create a matrix, we can write either
A=Matrix(2,3,[1,2,3,4,5,6])
or A=Matrix([[1,2,3],[4,5,6]])
, where of course the size and entries can be changed to whatever you want. The former method is a bit faster, but once your matrices get a bit bigger, the latter method is less prone to typos.xxxxxxxxxx
A=Matrix(2,3,[1,2,3,4,5,6])
B=Matrix([[1,2,3],[4,5,6]])
A,B
Also of note: a column vector can be entered using identity matrix, use zero matrix, use
Matrix([1,2,3])
. There are also certain built in special matrices. To get an eye(n)
. To get an zeros(m,n)
, or zeros(n)
for a square matrix. There is also syntax for diagonal matrices, such as diag(1,2,3)
. Whatβs cool is that you can even use this for block diagonal matrices:xxxxxxxxxx
A=Matrix(2,2,[1,2,3,4])
B=Matrix(2,2,[5,6,7,8])
D=diag(A,B)
D
To get the reduced row-echelon form of the matrix simply use
A.rref()
. Addition, subtraction, and multiplication use the obvious syntax: A+B
, A*B
, etc.. The determinant of a square matrix is given by A.det()
. Inverses can be computed using A.inv()
or A**-1
. The latter is rather natural, since powers in general are entered as A**n
for In most cases where you want to reduce a matrix, youβre going to want to simply use the
rref
function. But there are times where this can be overzealous; for example, if you have a matrix with one or more symbols. One option is to replace A.rref()
with A.echelon_form()
. The echelon_form
function creates zeros in the pivot columns, but does not create leading on ones.For example, letβs take the matrix Note the difference in output between
rref
and echelon_form
.xxxxxxxxxx
from sympy import Symbol
a = Symbol('a')
b = Symbol('b')
A = Matrix(3,3,[a,2,b,2,1,a,2*a,b,3])
A, A.rref(), A.echelon_form()
It is possible to manually perform row operations when you need additional control. This is achieved using the function
A.elementary_row_op(<arguments>)
, with arguments op,row,k,row1,row2
.We have the following general syntax:
- To swap two rows:
op='n<->m'
row1=i
, wherei
is the index of the first row being swapped (remembering that rows are indexed starting with for the first row).row2=j
, wherej
is the index of the second row being swapped.
- To rescale a row:
op='n->kn'
row=i
, wherei
is the index of the row being rescaled.k=c
, wherec
is the value of the scalar you want to multiply by.
- To add a multiple of one row to another:
op='n->n+km'
row=i
, wherei
is the index of the row you want to change.k=c
, wherec
is the multiple of the other row.row2=j
, wherej
is the index of the other row.
When studying matrix transformations, we are often interested in the null space and column space, since these correspond to the kernel and image of a linear transformation. This is achieved, simply enough, using
A.nullspace()
and A.colspace()
. The output will be a basis of column vectors for these spaces, and these are exactly the ones youβd find doing Gaussian elimination by hand.Once you get to orthogonality, youβll want to be able to compute things like dot products, and transpose. These are simple enough. The dot product of vectors
X,Y
is simply X.dot(Y)
. The transpose of a matrix A
is A.T
. As we should expect, X\dotp Y = X^TY
.xxxxxxxxxx
X=Matrix(3,1,[1,2,3])
Y=Matrix(3,1,[4,5,6])
X.dot(Y),(X.T)*Y
Of course, nobody wants to do things like the Gram Schmidt algorithm by hand. Fortunately, thereβs a function for that. If we have vectors
X,Y,Z
, we can make a list L=[X,Y,Z]
, and perform Gram Schmidt with GramSchmidt(L)
. If you want your output to be an orthonormal basis (and not merely orthogonal), then you can use GramSchmidt(L,true)
.Itβs useful to note that the output from functions like
nullspace()
are automatically treated as lists. So one can use simple code like the following:xxxxxxxxxx
from sympy import GramSchmidt
A=Matrix(2,3,[1,0,3,2,-1,4])
L=A.nullspace()
GramSchmidt(L)
If for some reason you need to reference particular vectors in a list, this can be done by specifying the index. If
L=[X,Y,Z]
, then L[0]==X
, L[1]==Y
, and L[2]==Z
.- For the characteristic polynomial, use
A.charpoly()
. However, the result will give you something SymPy calls a βPurePolyβ, and thefactor
command will have no effect. Instead, useA.charpoly().as_expr()
. -
If we know that
is an eigenvalue of a matrix one way to get a basis for the eigenspace is to do:B=A-3*eye(4) B.nullspace()
If you just want all the eigenvalues and eigenvectors without going through the steps, then you can simply executeA.eigenvects()
. The result is a list of lists β each list in the list is of the form: eigenvalue, multiplicity, basis for the eigenspace.For diagonalization, one can doA.diagonalize()
. But this will not necessarily produce orthogonal diagonalization for a symmetric matrix.
For complex vectors and matrices, the main additional operation we need is the hermitian conjugate. The hermitian conjugate of a matrix we can execute the inner product in SymPy using matrix rather than a number.
A
is called using A.H
, which is simple enough. Unfortunately, there is no built-in complex inner product, perhaps because mathematicians and physicists cannot agree on which of the two vectors in the inner product should have the complex conjugate applied to it. Since we define the complex inner product by Z.dot(W.H)
, or (W.H)*Z
, although the latter gives the output as a Donβt forget that when entering complex matrices, the complex unit is entered as hermitian matrix with distinct eigenvalues. When doing a problem like this in a Sage cell, itβs a good idea to execute each line of code (and display output) before moving on to the next. In this case, printing the output for the list
I
. Also, complex expressions are not simplified by default, so you will often need to wrap your output line in simplify()
. The Sage Cell below contains complete code for the unitary diagonalization of a L
given by A.eigenvects()
helps explain the complicated-looking definitions of the vectors v,w
. Of course, if we had a matrix with repeated eigenvalues, weβd need to add steps involving Gram Schmidt.xxxxxxxxxx
from sympy import Matrix,init_printing,simplify
init_printing()
A = Matrix(2,2,[4,3-I,3+I,1])
L = A.eigenvects()
v = ((L[0])[2])[0]
w = ((L[1])[2])[0]
u1 = (1/v.norm())*v
u2 = (1/w.norm())*w
U = u1.row_join(u2)
u1, u2, U, simplify(U.H*A*U)
There are a few other commands that might come in handy as you work through this material:
- Two matrices can be glued together. If matrices
A,B
have the same number of rows, the commandA.row_join(B)
will glue the matrices together, left-to-right. If they have the same number of columns,A.col_join(B)
will glue them together top-to-bottom. - To insert a column
C
into a matrixM
(of appropriate size) as the th column, you can doM.col_insert(j,C)
. Just remember that columns are indexed starting at zero, so you might wantj-1
instead ofj
. This can be useful for things like solving a system where you want to append the column to the matrix - A
-factorization can be performed usingQ,R=A.QRdecomposition()
- The Jordan canonical form
of a matrix can be obtained (along with the matrix whose columns are a Jordan basis) usingP,M=A.jordan_form()
.
You have attempted 1 of 1 activities on this page.