Original English by Jim Hefferon — 34 validated sections. The original mathematics and supplied answers below are preserved. This is a partial-book reading edition, not the complete book or an Everyday-English rewrite.

Source, reuse and conversion details

Source revision df2262e089a02651c127f1dd12649c4622ee1383; CC BY-SA 2.5 option, with original component credits retained. This is not an Everyday-English rewrite. The complete active topic is included; comments and the end-of-file marker remain in the editable source.

Five exercises and their five supplied answers, four literal code/output listings, 41 mathematical expressions and the original graph are retained. No code or benchmark was executed. AI-assisted source-preserving conversion and checks; Current rebuild runtime is documented in the credit below; earlier intermediate work is not reattributed. No human review or exhaustive mathematical correctness audit is claimed.

One small difference between the original transcript and graph

For 50 rows, the printed transcript gives 1.05453586578 seconds, whose nearest three-decimal rounding is 1.055. The author’s Sage graph source uses 1.054. Both original versions are preserved; the graph has not been silently replotted.

Wide formulas and code listings scroll horizontally. Focus a listing and use the arrow keys.

Source-preserving rebuild, navigation, source packaging and current deterministic checks: OpenAI Codex — GPT-6 Astra, Ultra effort. Jim Hefferon remains the author of the mathematics. Earlier intermediate-conversion runtime identity is not established by its retained receipts and is not reassigned to this rebuild. No human review or exhaustive proof certification is claimed.

Speed of Calculating Determinants

For large matrices, finding the determinant by using row operations is typically much faster than using the permutation expansion. We make this statement precise by finding how many operations each method performs.

To compare the speed of two algorithms, we find for each one how the time taken grows as the size of its input data set grows. For instance, if we increase the size of the input by a factor of ten does the time taken grow by a factor of ten, or by a factor of a hundred, or by a factor of a thousand? That is, is the time taken proportional to the size of the data set, or to the square of that size, or to the cube of that size, etc.? An algorithm whose time is proportional to the square is faster than one that takes time proportional to the cube.

First consider the permutation expansion formula.

| t 1 , 1 t 1 , 2 … t 1 , n t 2 , 1 t 2 , 2 … t 2 , n ⋮ t n , 1 t n , 2 … t n , n | = ∑ permutations  ϕ t 1 , ϕ ( 1 ) t 2 , ϕ ( 2 ) ⋯ t n , ϕ ( n ) | P ϕ |

There are n ! = n ⋅ ( n − 1 ) ⋯ 2 ⋅ 1 different n -permutations so for a matrix with n  rows this sum has n ! terms (and inside each term is n -many multiplications). The factorial function grows quickly: when n is only 10 the expansion already has 10 ! = 3 , 628 , 800 terms. Observe that growth proportional to the factorial is bigger than growth proportional to the square n ! > n 2 because multiplying the first two factors in n ! gives n ⋅ ( n − 1 ) , which for large n is approximately n 2 and then multiplying in more factors will make the factorial even larger. Similarly, the factorial function grows faster than  n 3 , etc. So an algorithm that uses the permutation expansion formula, and thus performs a number of operations at least as large as the factorial of the number of rows, would be very slow.

In contrast, the time taken by the row reduction method does not grow so fast. Below is a script for row reduction in the computer language Python. (Note: The code here is naive; for example it does not handle the case that the m(p_row, p_row) entry is zero. Analysis of a finished version that includes all of the tests and subcases is messier but would gives us roughly the same speed results.)

import random

def random_matrix(num_rows, num_cols):
    m = []
    for col in range(num_cols):
        new_row = []
        for row in range(num_rows):
            new_row.append(random.uniform(0,100))
        m.append(new_row)
    return m

def gauss_method(m):
    """Perform Gauss's Method on m.  This code is for illustration only
    and should not be used in practice.
      m  list of lists of numbers; each included list is a row
    """
    num_rows, num_cols = len(m), len(m[0])
    for p_row in range(num_rows):
        for row in range(p_row+1, num_rows):
            factor = -m[row][p_row] / float(m[p_row][p_row])
            new_row = []
            for col_num in range(num_cols): 
                p_entry, entry = m[p_row][col_num], m[row][col_num]
                new_row.append(entry+factor*p_entry)
            m[row] = new_row
    return m

response = raw_input('number of rows? ')
num_rows = int(response)
m = random_matrix(num_rows, num_rows)
for row in m:
    print row
M = gauss_method(m)
print "-----"
for row in M:
    print row

Besides a routine to do Gauss’s Method, this program also has a routine to generate a matrix filled with random numbers (the numbers are between 0 and  100 , to make them readable below). This program prompts a user for the number of rows, generates a random square matrix of that size, and does row reduction on it.

$ python gauss_method.py
number of rows? 4
[69.48033741746909, 32.393754742132586, 91.35245787350696, 87.04557918402462]
[98.64189032145111, 28.58228108715638, 72.32273998878178, 26.310252241189257]
[85.22896214660841, 39.93894635139987, 4.061683241757219, 70.5925099861901]
[24.06322759315518, 26.699175587284373, 37.398583921673314, 87.42617087562161]
-----
[69.48033741746909, 32.393754742132586, 91.35245787350696, 87.04557918402462]
[0.0, -17.40743803545155, -57.37120602662462, -97.2691774792963]
[0.0, 0.0, -108.66513774392809, -37.31586824349682]
[0.0, 0.0, 0.0, -13.678536859817994]

Inside of the gauss_method routine, for each row prow, the routine performs 𝚏𝚊𝚌𝚝𝚘𝚛 ⋅ ρ 𝚙𝚛𝚘𝚠 + ρ 𝚛𝚘𝚠 on the rows below. For each of these rows below, this involves operating on every entry in that row. That is a triply-nested loop. So this program has a running time that is something like the cube of the number of rows in the matrix. (Comment. We are glossing over many issues. For example, we may worry that the time taken by the program is dominated by the time to store and retrieve entries from memory, rather than by the row operations. However, development of a computation model is outside of our scope.)

If we add this code at the bottom,

def do_matrix(num_rows):
    gauss_method(random_matrix(num_rows, num_rows))

import timeit
for num_rows in [10,20,30,40,50,60,70,80,90,100]:
    s = "do_matrix("+str(num_rows)+")"
    t = timeit.timeit(stmt=s, setup="from __main__ import do_matrix",
                      number=100)
    print "num_rows=", num_rows, " seconds=", t  

then Python will time the program. Here is the output from a timed test run.

num_rows= 10  seconds= 0.0162539482117
num_rows= 20  seconds= 0.0808238983154
num_rows= 30  seconds= 0.248152971268
num_rows= 40  seconds= 0.555531978607
num_rows= 50  seconds= 1.05453586578
num_rows= 60  seconds= 1.77881097794
num_rows= 70  seconds= 2.75969099998
num_rows= 80  seconds= 4.10647988319
num_rows= 90  seconds= 5.81125879288
num_rows= 100  seconds= 7.86893582344  

Graphing that data gives part of the curve of a cubic.

Original timing scatter plot: ten hollow circles for square matrices with 10 to 100 rows, rising from about 0.016 to 7.869 seconds. The horizontal tick labels are 20, 40, 60, 80 and 100; the vertical scale runs from 0 to 8. These historical totals time 100 calls, including random-matrix generation; they are not new measurements or a fitted curve. The original numeric transcript is immediately above the graph.

Finding the fastest algorithm to compute the determinant is a topic of current research. So far, researchers have found algorithms that run in time between the square and cube of the number of rows.

The contrast between the times taken by the two determinant computation methods of permutation expansion and row operations makes the point that although in principle they give the same answer, in practice we want the one with the best performance.

Exercises

  1. Exercise 1 Supplied answer

    To get an idea of what happens for typical matrices we can use the ability of computer systems to generate random numbers (of course, these are only pseudo-random in that they come from an algorithm but they pass a number of reasonable statistical tests for randomness).

    1. Fill a 5 × 5 array with random numbers say, in the range [ 0 … 1 ) ). See if it is singular. Repeat that experiment a few times. Are singular matrices frequent or rare in this sense?

    2. Time your computer algebra system at finding the determinant of ten 10 × 10 arrays of random numbers. Find the average time per array. Repeat the prior item for 20 × 20 arrays, 30 × 30 arrays, … 100 × 100 arrays, and compare to the numbers given above. (Notice that, when an array is singular, we can sometimes decide that quickly, for instance if the first row equals the second. In the light of your answer to the first part, do you expect that singular systems play a large role in your average?)

    3. Graph the input size versus the average time.

    Back to Exercise 1

    Answer. Your timing will depend in part on the computer algebra system that you use, and in part on the power of the computer on which you do the calculation. But you should get a curve that is similar to the one shown.

  2. Exercise 2 Supplied answer

    Compute the determinant of each of these by hand using the two methods discussed above.

    1. | 2 1 5 − 3 |

    2. | 3 1 1 − 1 0 5 − 1 2 − 2 |

    3. | 2 1 0 0 1 3 2 0 0 − 1 − 2 1 0 0 − 2 1 |

    Count the number of multiplications and divisions used in each case, for each of the methods.

    Back to Exercise 2

    Answer. The number of operations depends on exactly how we do the operations.

    1. The determinant is − 11 . To row reduce takes a single row combination with two multiplications ( − 5 / 2 times 2 plus 5 , and − 5 / 2 times 1 plus − 3 ) and the product down the diagonal takes one more multiplication. The permutation expansion takes two multiplications ( 2 times − 3 and 5 times 1 ).

    2. The determinant is − 39 . Counting the operations is routine.

    3. The determinant is 4 .

  3. Exercise 3 Supplied answer

    The use by the timing routine of do_matrix has a bug. That routine does two things, generate a random matrix and then do gauss_method on it, and the timing number returned is for the combination. Produce code that times only the gauss_method routine.

    Back to Exercise 3

    Answer. You should build the matrix once and then time it being reduced many times. Note that gauss_method, as written, changes the input matrix, so be careful not to loop using the output of that routine or all the loops but the first will be on an echelon form matrix.

  4. Exercise 4 Supplied answer

    What 10 × 10 array can you invent that takes your computer the longest time to reduce? The shortest?

    Back to Exercise 4

    Answer. The identity matrix typically does not take long to reduce. For matrices that are slow, in Python you can try ones with numbers that are quite large. (Some computer algebra system also have the ability to generate special matrices; try some of those.)

  5. Exercise 5 Supplied answer

    Some computer language specifications requires that arrays be stored “by column,” that is, the entire first column is stored contiguously, then the second column, etc. Does the code fragment given take advantage of this, or can it be rewritten to make it faster, by taking advantage of the fact that computer fetches are faster from contiguous locations?

    Back to Exercise 5

    Answer. No, the above code handles the numbers by row.