Sage is a more generic math package, made to replace Mathematica, Maple, MatLab, etc. It is built on top of Python.
You can use it online with Cocalc. (You can use a free account.)
For simple computations, you can use the Sage Cell Server.
Notebook: If you have how to run Jupyter Notebooks with Sage, you can download the notebook here.
Let's create the matrix $$ A = \begin{bmatrix} 2 & 8 & 4 \\ 2 & 5 & 1 \\ 4 & 10 & -1 \end{bmatrix} $$
A = matrix([[2, 8, 4], [2, 5, 1], [4, 10, -1]]) # enter a list of rows (each row also a list)
A
[ 2 8 4] [ 2 5 1] [ 4 10 -1]
We can also do:
A = matrix(3, 3, [2, 8, 4, 2, 5, 1, 4, 10, -1]) # enter the dimensions and list
A
[ 2 8 4] [ 2 5 1] [ 4 10 -1]
Or even
A = matrix(3, [2, 8, 4, 2, 5, 1, 4, 10, -1]) # for square matrices we can give only one dimension
A
[ 2 8 4] [ 2 5 1] [ 4 10 -1]
We can also specify where the coefficients lie. For instance, to have the same matrix over $\mathbb{F}_5 = \mathbb{Z}/5\mathbb{Z}$ (i.e., the field with $5$ elements), we can do:
A = matrix(FiniteField(5), 3, [2, 8, 4, 2, 5, 1, 4, 10, -1]) # for square matrices we can give only one dimension
A
[2 3 4] [2 0 1] [4 0 4]
(Note how the entries are reduced modulo $5$!)
We can call the function latex
to produce a $\LaTeX$ representation of our matrix:
latex(A)
\left(\begin{array}{rrr} 2 & 3 & 4 \\ 2 & 0 & 1 \\ 4 & 0 & 4 \end{array}\right)
We can create the identity matrix of any dimension:
identity_matrix(4)
[1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1]
We can also specify the type of coefficients. For instance, for coefficients in $\mathbb{F}_3 = \mathbb{Z} / 3 \mathbb{Z}$:
identity_matrix(FiniteField(3), 2)
[1 0] [0 1]
Similarly for zero matrices:
zero_matrix(3, 4)
[0 0 0 0] [0 0 0 0] [0 0 0 0]
zero_matrix(CC, 3, 2) # CC = complex numbers (using floats)
[0.000000000000000 0.000000000000000] [0.000000000000000 0.000000000000000] [0.000000000000000 0.000000000000000]
We can create random matrices. For finite entry ring (or field), it is as you'd expect:
random_matrix(FiniteField(11), 3, 4)
[1 7 0 9] [6 6 1 9] [1 0 2 8]
(Run the cell above a few times to see different random results.)
For $\mathbb{Q}$ we can specifiy bounds for the numerator and denominator:
random_matrix(QQ, 4, 5, num_bound=10, den_bound=10)
[ 3/4 -7/2 -5/2 -10 -3] [-7/5 5 5/8 0 -2] [-1/3 3/7 -2/3 3/8 -1/9] [-9/7 -2/5 -3/2 -8/5 -5/7]
For $\mathbb{R}$ (RR
in Sage), we can specify the maximum and minimum:
random_matrix(RR, 2, 2, min=-10, max=10)
[0.891317069574020 8.43768596822291] [-1.60966517026439 -9.00857316074933]
There a few implementations of complex matrices in Sage: some "numeric" (using floats/decimals) and one "symbolic" (with exact rational numbers and $i$). The latter uses I
for the imaginary number $i$:
I^2
-1
So, we can make matrices such as:
A = matrix(2, 2, [I/2, 0, 1/4 + 3*I, -3])
A
[ 1/2*I 0] [3*I + 1/4 -3]
We can use CC
to use floats:
B = matrix(CC, 2, 2, [I/2, 0, 1/4 + 3*I, -3])
B
[ 0.500000000000000*I 0.000000000000000] [0.250000000000000 + 3.00000000000000*I -3.00000000000000]
If one wants to specify the "symbolic" complex numbers, we can save if in a variable and use:
CCs = I.parent() # save the "symbolic complex field" in CCs
CCs
Number Field in I with defining polynomial x^2 + 1 with I = 1*I
C = matrix(CCs, 2, 2, [1, 0, 1/3, I])
C
[ 1 0] [1/3 I]
If we want to use symbolic computations, we need to introduce the variables:
var("a11, a12, a21, a22")
A = matrix(2, 2, [a11, a12, a21, a22])
A
[a11 a12] [a21 a22]
A.det()
-a12*a21 + a11*a22
A^2
[ a11^2 + a12*a21 a11*a12 + a12*a22] [a11*a21 + a21*a22 a12*a21 + a22^2]
A^(-1)
[1/a11 - a12*a21/(a11^2*(a12*a21/a11 - a22)) a12/(a11*(a12*a21/a11 - a22))] [ a21/(a11*(a12*a21/a11 - a22)) -1/(a12*a21/a11 - a22)]
We can add matrices:
A = random_matrix(QQ, 3, 2, num_bound=5, den_bound=5)
B = random_matrix(QQ, 3, 2, num_bound=5, den_bound=5)
A
[ 0 -1] [-3 -1] [ 2 1]
B
[-3/5 1/2] [ 1/4 -1/4] [ 0 1]
A + B
[ -3/5 -1/2] [-11/4 -5/4] [ 2 2]
We can also do scalar multiplication:
1 / 3 * A
[ 0 -1/3] [ -1 -1/3] [ 2/3 1/3]
We can multiply matrices as expected:
A = matrix(3, [2, 8, 4, 2, 5, 1, 4, 10, -1]) # for square matrices we can give only one dimension
B = matrix(3, 2, [1, 2, 0, 1, 1, 1])
A
[ 2 8 4] [ 2 5 1] [ 4 10 -1]
B
[1 2] [0 1] [1 1]
A * B
[ 6 16] [ 3 10] [ 3 17]
We can transpose:
A.T
[ 2 2 4] [ 8 5 10] [ 4 1 -1]
We can take determinants:
A.det()
18
We can invert, if possible:
A^(-1)
[-5/6 8/3 -2/3] [ 1/3 -1 1/3] [ 0 2/3 -1/3]
(Note that the result is made of rational numbers, not floats (i.e., decimals).)
We can get the reduced row echelon form:
A.echelon_form()
[2 2 1] [0 3 0] [0 0 3]
This result seems unexpected, since we don't have leading ones! This is because Sage is staying with the integers for entries! If we want to allow fractions, we must declare the matrix over the rationals $\mathbb{Q}$, which in Sage is QQ
:
A = matrix(QQ, 3, [2, 8, 4, 2, 5, 1, 4, 10, -1]) # declare rational entries!!
A
[ 2 8 4] [ 2 5 1] [ 4 10 -1]
A.echelon_form()
[1 0 0] [0 1 0] [0 0 1]
We also have vectors:
v = vector([1, 2, 3])
v
(1, 2, 3)
We can then use if for matrix multiplication:
A * v
(30, 15, 21)
Note that it knows that the vector must be interpreted as a column matrix!
But we can also do:
v * A
(18, 48, 3)
Now, it is interpreted as a row matrix.
We can perform row operations.
Remember that Python starts indexing from $0$, not $1$
A = matrix(QQ, 3, range(9))
A
[0 1 2] [3 4 5] [6 7 8]
Let's add $-3$ times the first row to the second:
A.add_multiple_of_row(1, 0, -3)
A
[ 0 1 2] [ 3 1 -1] [ 6 7 8]
Note that this changes the original matrix!
Let's multiply the first row by $5$:
A.rescale_row(0, 5)
A
[ 0 5 10] [ 3 1 -1] [ 6 7 8]
Let's swap the first and third rows:
A.swap_rows(0, 2)
A
[ 6 7 8] [ 3 1 -1] [ 0 5 10]
There is no way to check directly if two matrices are row equivalent, i.e., one can be obtained from the other by row operations. But we can still do it: $A$ and $B$ are row equivalent if and only if they have the same reduced row echelon form.
A = matrix(QQ, 3, 4, range(12))
A
[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]
B = matrix(QQ, 3, 4, range(2, 14))
B
[ 2 3 4 5] [ 6 7 8 9] [10 11 12 13]
A.echelon_form() == B.echelon_form()
True
So, $A$ and $B$ are row equivalent.
Let's check another matrix:
C = matrix(QQ, 3, 4, [i^2 for i in range(12)])
C
[ 0 1 4 9] [ 16 25 36 49] [ 64 81 100 121]
A.echelon_form() == C.echelon_form()
False
So $A$ and $C$ are not row equivalent.
Let's start with a $4 \times 3$ matrix $A$:
A = matrix(QQ, 4, 3, range(12))
A
[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]
We can ask for the row space:
A.row_space()
Vector space of degree 3 and dimension 2 over Rational Field Basis matrix: [ 1 0 -1] [ 0 1 2]
We can ask for the column space:
A.column_space()
Vector space of degree 4 and dimension 2 over Rational Field Basis matrix: [ 1 0 -1 -2] [ 0 1 2 3]
We can ask for the rank:
A.rank()
2
For the nullspace, we use right_kernel
:
A.right_kernel()
Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: [ 1 -2 1]
Note that the method kernel
gives the left kernel, meaning the space of solutions of $\vec{x} \cdot A = \vec{0}$!
A.kernel()
Vector space of degree 4 and dimension 2 over Rational Field Basis matrix: [ 1 0 -3 2] [ 0 1 -2 1]
A.left_kernel()
Vector space of degree 4 and dimension 2 over Rational Field Basis matrix: [ 1 0 -3 2] [ 0 1 -2 1]
(Note how left_kernel
gives a subspace in $4$ dimensions, while right_kernel
gives a subspace in $3$ dimensions, since $A$ is $4 \times 3$!)
So, for the nullity, we need to ask for right_nullity
:
A.right_nullity()
1
(A.T).right_kernel()
Vector space of degree 4 and dimension 2 over Rational Field Basis matrix: [ 1 0 -3 2] [ 0 1 -2 1]
Suppose we want to solve:
\begin{align*} 2x + 8y + 4z &= 2 \\ 2x + 5y + z &= 5 \\ 4x + 10y - z &= 1 \end{align*}We can do:
var("x y z") # declare the symbolic variables
eqs = [2*x + 8*y + 4*z == 2, 2*x + 5*y + z == 5, 4*x + 10*y -z == 1] # make a list of equations
solve(eqs, (x, y, z)) # call solve
[[x == 11, y == -4, z == 3]]
If the system has no solution, say
\begin{align*} x + y &= 1\\ x + y & = 2 \end{align*}eqs = [x + y == 1, x + y == 2]
solve(eqs, (x, y))
[]
It may have infinitely many, as in
\begin{align*} x + y &= 1\\ x + y & = 1 \end{align*}eqs = [x + y == 1, x + y == 1]
solve(eqs, (x, y))
[[x == -r1 + 1, y == r1]]
As you can see, it gives us a free parameter r1
.
We can also solve systems using matrices:
A = matrix(QQ, 3, 3, [2, 8, 4, 2, 5, 1, 4, 10, -1])
v = vector(QQ, [2, 5, 1])
A.solve_right(v)
(11, -4, 3)
For the other examples:
B = matrix(QQ, 2, 4 * [1])
B
[1 1] [1 1]
We get an error when there is no solution:
v = vector(QQ, [1, 2])
B.solve_right(v)
--------------------------------------------------------------------------- NotFullRankError Traceback (most recent call last) File ~/src/sage-10.2/src/sage/matrix/matrix2.pyx:942, in sage.matrix.matrix2.Matrix.solve_right() 941 try: --> 942 X = self._solve_right_nonsingular_square(C, check_rank=True) 943 except NotFullRankError: File ~/src/sage-10.2/src/sage/matrix/matrix2.pyx:988, in sage.matrix.matrix2.Matrix._solve_right_nonsingular_square() 987 if check_rank and self.rank() < self.nrows(): --> 988 raise NotFullRankError 989 D = self.augment(B) NotFullRankError: During handling of the above exception, another exception occurred: ValueError Traceback (most recent call last) Cell In [63], line 2 1 v = vector(QQ, [Integer(1), Integer(2)]) ----> 2 B.solve_right(v) File ~/src/sage-10.2/src/sage/matrix/matrix2.pyx:944, in sage.matrix.matrix2.Matrix.solve_right() 942 X = self._solve_right_nonsingular_square(C, check_rank=True) 943 except NotFullRankError: --> 944 X = self._solve_right_general(C, check=check) 945 946 if b_is_vec: File ~/src/sage-10.2/src/sage/matrix/matrix2.pyx:1062, in sage.matrix.matrix2.Matrix._solve_right_general() 1060 # Have to check that we actually solved the equation. 1061 if self*X != B: -> 1062 raise ValueError("matrix equation has no solutions") 1063 return X 1064 ValueError: matrix equation has no solutions
Here is the one with infinitely many solutions:
v = vector(QQ, [1, 1])
B.solve_right(v)
(1, 0)
Note that when there are infinitely many solutions, it only gives one particular solution! To see if there are more, we need to inspect the kernel/nullspace:
B.right_kernel()
Vector space of degree 2 and dimension 1 over Rational Field Basis matrix: [ 1 -1]
So, we see that there kernel is not trivial, and thus we have infinitely many solutions.
Let's save the particular solutions in s1
:
s1 = B.solve_right(v)
Let's save the basis for the kernel in kernel_basis
:
kernel_basis = B.right_kernel().basis()
kernel_basis
[ (1, -1) ]
We can see the the size of the basis (i.e., the dimension) is $1$:
len(kernel_basis) # len = length = number of elements
1
We can now produce new solutions:
B * (s1 + 37/54 * kernel_basis[0]) == v
True
A = matrix(QQ, 3, 3, [2, 8, 4, 2, 5, 1, 4, 10, -1])
v = vector(QQ, [2, 5, 1])
B = A.augment(v)
B
[ 2 8 4 2] [ 2 5 1 5] [ 4 10 -1 1]
B.echelon_form()
[ 1 0 0 11] [ 0 1 0 -4] [ 0 0 1 3]
B.echelon_form().column(3)
(11, -4, 3)
We can solve multiple systems at once! To solve $A \cdot \vec{x} = \vec{u}$, $A \cdot \vec{x} = \vec{v}$, $A \cdot \vec{x} = \vec{w}$, we can do:
A = matrix(QQ, 3, 3, [2, 8, 4, 2, 5, 1, 4, 10, -1])
u = vector(QQ, [1, 1, 0])
v = vector(QQ, [2, 5, 1])
w = vector(QQ, [0, -1, 3])
We augment the matrix by the three vectors:
B = A.augment(matrix([u, v, w]).T) # note the .T to make the vectors into columns!
B
[ 2 8 4 1 2 0] [ 2 5 1 1 5 -1] [ 4 10 -1 0 1 3]
C = B.echelon_form()
C
[ 1 0 0 11/6 11 -14/3] [ 0 1 0 -2/3 -4 2] [ 0 0 1 2/3 3 -5/3]
The solutions:
C.column(3), C.column(4), C.column(5)
((11/6, -2/3, 2/3), (11, -4, 3), (-14/3, 2, -5/3))
We can also use this method to see when there are no solutions:
A = matrix(QQ, 2, 2, 4 * [1])
A
[1 1] [1 1]
v = vector(QQ, [1, 2])
w = vector(QQ, [1, 1])
B = A.augment(matrix([v, w]).T)
B
[1 1 1 1] [1 1 2 1]
B.echelon_form()
[1 1 0 1] [0 0 1 0]