Problem 7.2.6

We are supposed to compute the determinant of the matrix $$ M=\left[ \begin{array}{rrrr} 1&2&-1&1\\ 0&1&2&1\\ 0&2&1&3\\ 1&4&0&2 \end{array} \right] $$

By expanding first along the second row, and then along the first column. What this expansion thing means, is described in Definition 7.9 of our textbook, and entails computing cofactors for our matrix. SymPy knows how to do this (it also knows how to compute minors, incidentally).

In [1]:
from sympy import *
init_printing(use_latex='mathjax')
In [2]:
M=Matrix([[1,2,-1,1],[0,1,2,1],[0,2,1,3],[1,4,0,2]]); M
Out[2]:
$\displaystyle \left[\begin{matrix}1 & 2 & -1 & 1\\0 & 1 & 2 & 1\\0 & 2 & 1 & 3\\1 & 4 & 0 & 2\end{matrix}\right]$

Before we start asking for our cofactors to plug into the determinant, there's one caveat: Python (so also SymPy) numbers things starting with $0$ (rather than $1$). So what we would call the first column (or row), Python knows as the $0^{th}$ column (or row respectively). You need to take this into account when programming.

Concretely, in this specific case, we'll want to ask SymPy for cofactors with first entry $1$ (because those correspond to the second matrix row in common parlance) and also for cofactors with second entry $0$ (because those correspond to the first column in plain English rather than Python).

With this in mind, here's, say, what we (and our textbook) would call $C_{11}$ (for the matrix $M$):

In [3]:
M.cofactor(0,0)
Out[3]:
$\displaystyle 14$

See what we did? The '$11$' subscript from our common notation became $0,0$. Similarly, the cofactor we would call $C_{23}$ is

In [4]:
M.cofactor(1,2)
Out[4]:
$\displaystyle 4$

In short, you take the indices you are used to and decrement each by 1. Now on to that expansion along the second row. Since $M$ is a small enough matrix, only $4\times 4$, we could simply write out the four summands; but this being a programming environment, the natural (and, generally, more efficient) thing to do is to use a for loop to iterate over an index $i$ counting my columns.

I don't even need to plug in the number of rows/columns by hand: SymPy can give it back to you from the matrix as a function of the matrix. Let's put all of this together into asking for the expansion along the second row then:

In [14]:
sum=0
for j in range(0,M.cols):
    sum=sum+M[1,j]*M.cofactor(1,j)
sum
Out[14]:
$\displaystyle 6$

That tells me the number I am after (i.e. the determinant of $M$) is $6$. Let's also try the expansion along the first column (we'd better get the same number; there's only one determinant, regardless of how you expand!):

In [15]:
sum=0
for i in range(0,M.rows):
    sum=sum+M[i,0]*M.cofactor(i,0)
sum
Out[15]:
$\displaystyle 6$

Phew! It did work out. Let's also check, for good measure, by just plain asking for the determinant directly:

In [16]:
M.det()
Out[16]:
$\displaystyle 6$

It does indeed check out. Before we finish, let me also do what it would have been most natural to do to begin with, given, again, that we are in a programming environment: instead of "manually" asking for the expansion along, say, the second row, I should have defined a function that takes in an index $i$ and spits out the expansion along the $i^{th}$ row. That way we could have asked for any of the expansions in single, short commands.

So here we go: I'll define two functions, one expanding along row $i$ and one expanding along column $j$ (in our, human language, so that you can ask for row 1 and actually mean row 1 rather than Python's shifted numbering).

In [17]:
def rowExp(M,i):
    sum=0
    for j in range(0,M.cols):
        sum=sum+M[i-1,j]*M.cofactor(i-1,j)
    return sum

That's for rows. Note the shift from $i$ to $i-1$, to translate from our numbering (starting at 1) to Python's numbering (starting at 0). Also one for columns:

In [21]:
def colExp(M,j):
    sum=0
    for i in range(0,M.rows):
        sum=sum+M[i,j-1]*M.cofactor(i,j-1)
    return sum

Let's now simply apply these functions to our matrix with various entries for our rows and columns, and check that we always get $6$ for the determinant:

In [19]:
rowExp(M,3)
Out[19]:
$\displaystyle 6$
In [22]:
colExp(M,2)
Out[22]:
$\displaystyle 6$
In [25]:
colExp(M,1)
Out[25]:
$\displaystyle 6$
In [26]:
rowExp(M,4)
Out[26]:
$\displaystyle 6$

Finally, note that because our $M$ only has four rows and columns, asking for expansions outside that range will return an error:

In [27]:
rowExp(M,5)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/.local/lib/python3.11/site-packages/sympy/polys/matrices/sdm.py:82, in SDM.getitem(self, i, j)
     81 try:
---> 82     return self[i][j]
     83 except KeyError:

KeyError: 4

During handling of the above exception, another exception occurred:

IndexError                                Traceback (most recent call last)
File ~/.local/lib/python3.11/site-packages/sympy/matrices/repmatrix.py:712, in _getitem_RepMatrix(self, key)
    711 try:
--> 712     return self._rep.getitem_sympy(index_(i), index_(j))
    713 except (TypeError, IndexError):

File ~/.local/lib/python3.11/site-packages/sympy/polys/matrices/domainmatrix.py:173, in DomainMatrix.getitem_sympy(self, i, j)
    172 def getitem_sympy(self, i, j):
--> 173     return self.domain.to_sympy(self.rep.getitem(i, j))

File ~/.local/lib/python3.11/site-packages/sympy/polys/matrices/sdm.py:91, in SDM.getitem(self, i, j)
     90 else:
---> 91     raise IndexError("index out of range")

IndexError: index out of range

During handling of the above exception, another exception occurred:

IndexError                                Traceback (most recent call last)
Cell In[27], line 1
----> 1 rowExp(M,5)

Cell In[17], line 4, in rowExp(M, i)
      2 sum=0
      3 for j in range(0,M.cols):
----> 4     sum=sum+M[i-1,j]*M.cofactor(i-1,j)
      5 return sum

File ~/.local/lib/python3.11/site-packages/sympy/matrices/repmatrix.py:233, in RepMatrix.__getitem__(self, key)
    232 def __getitem__(self, key):
--> 233     return _getitem_RepMatrix(self, key)

File ~/.local/lib/python3.11/site-packages/sympy/matrices/repmatrix.py:733, in _getitem_RepMatrix(self, key)
    731         else:
    732             j = [j]
--> 733         return self.extract(i, j)
    735 else:
    736     # Index/slice like a flattened list
    737     rows, cols = self.shape

File ~/.local/lib/python3.11/site-packages/sympy/matrices/common.py:370, in MatrixShaping.extract(self, rowsList, colsList)
    367     colsList = [index for index, item in enumerate(colsList) if item]
    369 # ensure everything is in range
--> 370 rowsList = [a2idx(k, self.rows) for k in rowsList]
    371 colsList = [a2idx(k, self.cols) for k in colsList]
    373 return self._eval_extract(rowsList, colsList)

File ~/.local/lib/python3.11/site-packages/sympy/matrices/common.py:370, in <listcomp>(.0)
    367     colsList = [index for index, item in enumerate(colsList) if item]
    369 # ensure everything is in range
--> 370 rowsList = [a2idx(k, self.rows) for k in rowsList]
    371 colsList = [a2idx(k, self.cols) for k in colsList]
    373 return self._eval_extract(rowsList, colsList)

File ~/.local/lib/python3.11/site-packages/sympy/matrices/common.py:3189, in a2idx(j, n)
   3187         j += n
   3188     if not (j >= 0 and j < n):
-> 3189         raise IndexError("Index out of range: a[%s]" % (j,))
   3190 return int(j)

IndexError: Index out of range: a[4]
In [ ]: