Matrix calculator
Up to three matrices of at most 5 x 5, typed with whole numbers, fractions or decimals and kept as exact fractions: sum, difference, product, a number times a matrix, transpose, the determinant by Gaussian elimination and the inverse by Gauss-Jordan elimination - checked by multiplying back. Each result is kept as R for the next operation.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Up to three matrices, A, B and C, of at most 5 x 5. Entries are typed as whole numbers, fractions or decimals and kept as exact fractions, so no answer is rounded. One operation at a time - sum, difference, product, a number times a matrix, transpose, determinant, inverse - and each result is kept as R, which the next operation can use.
main.eml- the menu, entering a matrix row by row, reading an operation, and the matrices on screenmatrix.eml- the operationsfrac.eml- exact fractions, from P007 (calculator), with reading and writing a number
How each part works:
- A number is a fraction in lowest terms with a positive denominator. EML has no
//and/goes through a float, so the whole-number division that keeps fractions in lowest terms is written out by doubling, as in P007. - The product takes row i of the first matrix times column j of the second for each entry, as in the corpus case
matrix-multiplication; the transpose turns rows into columns as inmatrix-transpose-manual. - The determinant is found by Gaussian elimination: multiples of one row are taken from the rows below it until the matrix is upper triangular, and the determinant is the product of the diagonal, its sign flipped for each swap of two rows. The corpus case
matrix-determinantexpands by cofactors, which takes n! terms - 120 for a 5 x 5 matrix - where elimination takes about n^3 / 3 steps; with exact fractions both give the same answer. - The inverse is found by Gauss-Jordan elimination on A beside the identity matrix: when the left half has become the identity, the right half is the inverse. A matrix whose determinant is 0 has none. Every inverse is multiplied back, and the program says so when A times R is the identity.
- An operation is read as one of
A+B,A-B,A*B, a number times a matrix (2*A,-3/2*B),trans A,det Aandinv A; spaces do not matter, and R can stand in for a matrix.
What is checked: A, B or C as the matrix to enter; 1 to 5 rows and columns; each row as exactly that many numbers - whole numbers, fractions like 3/4 or decimals like 0.25, each part at most 9 digits, a denominator never 0; an operation the matrices fit: the same size for a sum or difference, as many columns in the first as rows in the second for a product, a square matrix for a determinant or an inverse. An empty answer cancels.
Sessions: sessions/basic.in enters a 3 x 3 matrix A, a 3 x 2 matrix B with fractions and decimals, and a 2 x 2 matrix C whose second row is twice its first; finds the determinant of A (-1), its inverse (checked), R times A (the identity), A times B, the transpose of B and 1/2 times C; then the determinant of C is 0 and C has no inverse. sessions/bad-input.in gives menu choices 0 and x, shows and calculates with no matrices, matrix D, sizes 0, 6 and x, rows with too few and too many numbers, x and 1/0, a matrix cancelled halfway; then a 2 x 3 matrix A and the identity B: a sum and a product of the wrong sizes, the determinant and inverse of a matrix that is not square, R before there is a result, C and Q, and three operations that are not one; and finally B times A, its transpose, 0.5 times R and -3/2 times B.
Built on the verified corpus cases matrix-multiplication (a product by row times column), matrix-determinant (a determinant by cofactor expansion, checked against known values) and matrix-transpose-manual (a transpose by nested loops).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Matrix calculator ==
Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 0
Pick a number from 1 to 4.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> x
Pick a number from 1 to 4.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 2
No matrices yet.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
A has not been entered yet.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)>
Cancelled.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> D
Type A, B or C.
which matrix (A, B or C)>
Cancelled.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 0
Type a number from 1 to 5.
rows (1 to 5)> 6
Type a number from 1 to 5.
rows (1 to 5)> x
Type a number from 1 to 5.
rows (1 to 5)>
Cancelled.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 2
columns (1 to 5)> 3
row 1> 1 2
That is 2 numbers; this row needs 3.
row 1> 1 2 3 4
That is 4 numbers; this row needs 3.
row 1> 1 x 3
Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.
row 1> 1/0 2 3
Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.
row 1> 1 2 3
row 2>
Cancelled.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 2
columns (1 to 5)> 3
row 1> 1 2 3
row 2> 4 5 6
A (2 x 3):
[ 1 2 3 ]
[ 4 5 6 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> b
rows (1 to 5)> 2
columns (1 to 5)> 2
row 1> 1 0
row 2> 0 1
B (2 x 2):
[ 1 0 ]
[ 0 1 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+B
A is 2 x 3 and B is 2 x 2: adding or subtracting needs the same size.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*A
A is 2 x 3 and A is 2 x 3: a product needs the columns of the first (3) to equal the rows of the second (2).
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
Only a square matrix has a determinant or an inverse; this one is 2 x 3.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A
Only a square matrix has a determinant or an inverse; this one is 2 x 3.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R*A
There is no result yet.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> C+A
C has not been entered yet.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> Q*A
There is no matrix called Q.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A+
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> +A
Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> B*A
R is B * A.
R (2 x 3):
[ 1 2 3 ]
[ 4 5 6 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans R
R is the transpose of R.
R (3 x 2):
[ 1 4 ]
[ 2 5 ]
[ 3 6 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 0.5*R
R is 1/2 times R.
R (3 x 2):
[ 1/2 2 ]
[ 1 5/2 ]
[ 3/2 3 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> -3/2*B
R is -3/2 times B.
R (2 x 2):
[ -3/2 0 ]
[ 0 -3/2 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 2
A (2 x 3):
[ 1 2 3 ]
[ 4 5 6 ]
B (2 x 2):
[ 1 0 ]
[ 0 1 ]
R (2 x 2):
[ -3/2 0 ]
[ 0 -3/2 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 4
Bye.
What was typed (68 lines)
0
x
2
3
det A
3
1
D
1
A
0
6
x
1
A
2
3
1 2
1 2 3 4
1 x 3
1/0 2 3
1 2 3
1
A
2
3
1 2 3
4 5 6
1
b
2
2
1 0
0 1
3
A+B
3
A*A
3
det A
3
inv A
3
R*A
3
C+A
3
Q*A
3
A
3
A+
3
+A
3
B*A
3
trans R
3
0.5*R
3
-3/2*B
2
4
basic
interpreter: byte-equal== Matrix calculator ==
Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> A
rows (1 to 5)> 3
columns (1 to 5)> 3
row 1> 2 1 -1
row 2> -3 -1 2
row 3> -2 1 2
A (3 x 3):
[ 2 1 -1 ]
[ -3 -1 2 ]
[ -2 1 2 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> B
rows (1 to 5)> 3
columns (1 to 5)> 2
row 1> 1/2 0.25
row 2> -1 2
row 3> 3 -3/4
B (3 x 2):
[ 1/2 1/4 ]
[ -1 2 ]
[ 3 -3/4 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 1
which matrix (A, B or C)> c
rows (1 to 5)> 2
columns (1 to 5)> 2
row 1> 1 2
row 2> 2, 4
C (2 x 2):
[ 1 2 ]
[ 2 4 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 2
A (3 x 3):
[ 2 1 -1 ]
[ -3 -1 2 ]
[ -2 1 2 ]
B (3 x 2):
[ 1/2 1/4 ]
[ -1 2 ]
[ 3 -3/4 ]
C (2 x 2):
[ 1 2 ]
[ 2 4 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det A
The answer is the determinant of A: -1.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv A
R is the inverse of A.
R (3 x 3):
[ 4 3 -1 ]
[ -2 -2 1 ]
[ 5 4 -1 ]
Checked: A * R is the identity matrix.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> R * A
R is R * A.
R (3 x 3):
[ 1 0 0 ]
[ 0 1 0 ]
[ 0 0 1 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> A*B
R is A * B.
R (3 x 2):
[ -3 13/4 ]
[ 11/2 -17/4 ]
[ 4 0 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> trans B
R is the transpose of B.
R (2 x 3):
[ 1/2 -1 3 ]
[ 1/4 2 -3/4 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> 1/2 * C
R is 1/2 times C.
R (2 x 2):
[ 1/2 1 ]
[ 1 2 ]
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> det C
The answer is the determinant of C: 0.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 3
operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> inv C
C has no inverse: its determinant is 0.
1) enter a matrix 2) show the matrices 3) calculate 4) quit
choice> 4
Bye.
What was typed (38 lines)
1
A
3
3
2 1 -1
-3 -1 2
-2 1 2
1
B
3
2
1/2 0.25
-1 2
3 -3/4
1
c
2
2
1 2
2, 4
2
3
det A
3
inv A
3
R * A
3
A*B
3
trans B
3
1/2 * C
3
det C
3
inv C
4
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P038 matrix calculator: up to three matrices A, B and C of at most 5 x 5,
# with entries typed as whole numbers, fractions or decimals and kept as
# exact fractions. One operation at a time - sum, difference, product, a
# number times a matrix, transpose, determinant, inverse - and its result is
# kept as R, which the next operation can use.
import frac
import matrix
5 => largest
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def upper(s):
"abcdefghijklmnopqrstuvwxyz" => small
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => big
"" => out
for c in s:
0 => k
while k < 26 and small[k] != c:
k + 1 => k
if k < 26:
out + big[k] => out
else:
out + c => out
return out
def words(s):
# s cut at spaces and commas.
[] => out
"" => word
for c in s + " ":
if c == " " or c == ",":
if word != "":
out + [word] => out
"" => word
else:
word + c => word
return out
def without_spaces(s):
"" => out
for c in s:
if c != " ":
out + c => out
return out
def dims(m):
return str(len(m)) + " x " + str(len(m[0]))
def show(name, m):
# Columns right-aligned to their widest entry.
(name + " (" + dims(m) + "):") ^0
[] => widths
for j in [0:len(m[0]) - 1]:
0 => w
for row in m:
if len(frac.text(row[j])) > w:
len(frac.text(row[j])) => w
widths + [w] => widths
for row in m:
" [" => line
for j in [0:len(row) - 1]:
frac.text(row[j]) => t
while len(t) < widths[j]:
" " + t => t
line + " " + t => line
(line + " ]") ^0
def ask_size(prompt):
while True:
trim(input(prompt)) => answer
if answer == "":
return -1
frac.digits_value(answer) => n
if n >= 1 and n <= largest:
return n
("Type a number from 1 to " + str(largest) + ".") ^0
def ask_row(i, cols):
# One row of cols numbers, or [] when the answer is empty.
while True:
trim(input("row " + str(i) + "> ")) => answer
if answer == "":
return []
words(answer) => ws
[] => row
True => good
for w in ws:
frac.parse(w) => x
if len(x) == 0:
False => good
else:
row + [x] => row
if not good:
"Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces." ^0
elif len(row) != cols:
("That is " + str(len(row)) + " numbers; this row needs " + str(cols) + ".") ^0
else:
return row
def enter(store):
while True:
upper(trim(input("which matrix (A, B or C)> "))) => name
if name == "":
"Cancelled." ^0
return store
if name == "A" or name == "B" or name == "C":
ask_size("rows (1 to " + str(largest) + ")> ") => rows
if rows == -1:
"Cancelled." ^0
return store
ask_size("columns (1 to " + str(largest) + ")> ") => cols
if cols == -1:
"Cancelled." ^0
return store
[] => m
for i in [1:rows]:
ask_row(i, cols) => row
if len(row) == 0:
"Cancelled." ^0
return store
m + [row] => m
m => store[name]
show(name, m)
return store
"Type A, B or C." ^0
def operand(store, s):
# The matrix called s, [False, why] if there is none.
upper(s) => name
if not (name in store):
if name == "A" or name == "B" or name == "C":
return [False, name + " has not been entered yet."]
if name == "R":
return [False, "There is no result yet."]
return [False, "There is no matrix called " + s + "."]
return [True, store[name]]
def calculate(store, s):
# One operation. Returns [True, "matrix" or "number", the result, what
# was done, a check to show or ""], or [False, why not].
without_spaces(s) => s
upper(s) => u
if u[0:3] == "DET" or u[0:3] == "INV" or u[0:5] == "TRANS":
3 => k
if u[0:5] == "TRANS":
5 => k
operand(store, s[k:len(s)]) => x
if not x[0]:
return x
x[1] => a
upper(s[k:len(s)]) => name
if u[0:5] == "TRANS":
return [True, "matrix", matrix.transposed(a), "the transpose of " + name, ""]
if len(a) != len(a[0]):
return [False, "Only a square matrix has a determinant or an inverse; this one is " + dims(a) + "."]
if u[0:3] == "DET":
return [True, "number", matrix.determinant(a), "the determinant of " + name, ""]
matrix.inverse(a) => inv
if len(inv) == 0:
return [False, name + " has no inverse: its determinant is 0."]
"" => check
if matrix.product(a, inv) == matrix.identity(len(a)):
"Checked: " + name + " * R is the identity matrix." => check
return [True, "matrix", inv, "the inverse of " + name, check]
# a binary operation: * first, so that "-2*A" keeps its minus sign
-1 => at
for i in [0:len(s) - 1]:
if at == -1 and s[i] == "*":
i => at
if at == -1:
for i in [1:len(s) - 1]:
if at == -1 and (s[i] == "+" or s[i] == "-"):
i => at
if at <= 0 or at == len(s) - 1:
return [False, "Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A."]
s[at] => op
s[0:at] => left
s[at + 1:len(s)] => right
operand(store, right) => y
if not y[0]:
return y
y[1] => b
if op == "*":
frac.parse(left) => k
if len(k) > 0:
return [True, "matrix", matrix.scaled(k, b), frac.text(k) + " times " + upper(right), ""]
operand(store, left) => x
if not x[0]:
return x
x[1] => a
upper(left) + " " + op + " " + upper(right) => what
if op == "*":
if len(a[0]) != len(b):
return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": a product needs the columns of the first (" + str(len(a[0])) + ") to equal the rows of the second (" + str(len(b)) + ")."]
return [True, "matrix", matrix.product(a, b), what, ""]
if len(a) != len(b) or len(a[0]) != len(b[0]):
return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": adding or subtracting needs the same size."]
return [True, "matrix", matrix.combined(a, b, op == "-"), what, ""]
"== Matrix calculator ==" ^0
"Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions." ^0
{} => store
True => running
while running:
"" ^0
"1) enter a matrix 2) show the matrices 3) calculate 4) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
enter(store) => store
elif choice == "2":
if len(store) == 0:
"No matrices yet." ^0
for name in ["A", "B", "C", "R"]:
if name in store:
show(name, store[name])
elif choice == "3":
trim(input("operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> ")) => op
if op == "":
"Cancelled." ^0
else:
calculate(store, op) => r
if not r[0]:
r[1] ^0
elif r[1] == "number":
("The answer is " + r[3] + ": " + frac.text(r[2]) + ".") ^0
else:
r[2] => store["R"]
("R is " + r[3] + ".") ^0
show("R", r[2])
if r[4] != "":
r[4] ^0
elif choice == "4":
False => running
else:
"Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import frac
import matrix
largest = 5
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def upper(s):
small = "abcdefghijklmnopqrstuvwxyz"
big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
out = ""
for c in s:
k = 0
while k < 26 and small[k] != c:
k = k + 1
if k < 26:
out = out + big[k]
else:
out = out + c
return out
def words(s):
out = []
word = ""
for c in s + " ":
if c == " " or c == ",":
if word != "":
out = out + [word]
word = ""
else:
word = word + c
return out
def without_spaces(s):
out = ""
for c in s:
if c != " ":
out = out + c
return out
def dims(m):
return str(len(m)) + " x " + str(len(m[0]))
def show(name, m):
print(name + " (" + dims(m) + "):")
widths = []
for j in range(0, len(m[0])):
w = 0
for row in m:
if len(frac.text(row[j])) > w:
w = len(frac.text(row[j]))
widths = widths + [w]
for row in m:
line = " ["
for j in range(0, len(row)):
t = frac.text(row[j])
while len(t) < widths[j]:
t = " " + t
line = line + " " + t
print(line + " ]")
def ask_size(prompt):
while True:
answer = trim(input(prompt))
if answer == "":
return -1
n = frac.digits_value(answer)
if n >= 1 and n <= largest:
return n
print("Type a number from 1 to " + str(largest) + ".")
def ask_row(i, cols):
while True:
answer = trim(input("row " + str(i) + "> "))
if answer == "":
return []
ws = words(answer)
row = []
good = True
for w in ws:
x = frac.parse(w)
if len(x) == 0:
good = False
else:
row = row + [x]
if not good:
print("Type whole numbers, fractions like 3/4 or decimals like 0.25, separated by spaces.")
elif len(row) != cols:
print("That is " + str(len(row)) + " numbers; this row needs " + str(cols) + ".")
else:
return row
def enter(store):
while True:
name = upper(trim(input("which matrix (A, B or C)> ")))
if name == "":
print("Cancelled.")
return store
if name == "A" or name == "B" or name == "C":
rows = ask_size("rows (1 to " + str(largest) + ")> ")
if rows == -1:
print("Cancelled.")
return store
cols = ask_size("columns (1 to " + str(largest) + ")> ")
if cols == -1:
print("Cancelled.")
return store
m = []
for i in range(1, rows+1):
row = ask_row(i, cols)
if len(row) == 0:
print("Cancelled.")
return store
m = m + [row]
store[name] = m
show(name, m)
return store
print("Type A, B or C.")
def operand(store, s):
name = upper(s)
if not name in store:
if name == "A" or name == "B" or name == "C":
return [False, name + " has not been entered yet."]
if name == "R":
return [False, "There is no result yet."]
return [False, "There is no matrix called " + s + "."]
return [True, store[name]]
def calculate(store, s):
s = without_spaces(s)
u = upper(s)
if u[0:3] == "DET" or u[0:3] == "INV" or u[0:5] == "TRANS":
k = 3
if u[0:5] == "TRANS":
k = 5
x = operand(store, s[k:len(s)])
if not x[0]:
return x
a = x[1]
name = upper(s[k:len(s)])
if u[0:5] == "TRANS":
return [True, "matrix", matrix.transposed(a), "the transpose of " + name, ""]
if len(a) != len(a[0]):
return [False, "Only a square matrix has a determinant or an inverse; this one is " + dims(a) + "."]
if u[0:3] == "DET":
return [True, "number", matrix.determinant(a), "the determinant of " + name, ""]
inv = matrix.inverse(a)
if len(inv) == 0:
return [False, name + " has no inverse: its determinant is 0."]
check = ""
if matrix.product(a, inv) == matrix.identity(len(a)):
check = "Checked: " + name + " * R is the identity matrix."
return [True, "matrix", inv, "the inverse of " + name, check]
at = -1
for i in range(0, len(s)):
if at == -1 and s[i] == "*":
at = i
if at == -1:
for i in range(1, len(s)):
if at == -1 and (s[i] == "+" or s[i] == "-"):
at = i
if at <= 0 or at == len(s) - 1:
return [False, "Type one operation, like A+B, A-B, A*B, 2*A, trans A, det A or inv A."]
op = s[at]
left = s[0:at]
right = s[at + 1:len(s)]
y = operand(store, right)
if not y[0]:
return y
b = y[1]
if op == "*":
k = frac.parse(left)
if len(k) > 0:
return [True, "matrix", matrix.scaled(k, b), frac.text(k) + " times " + upper(right), ""]
x = operand(store, left)
if not x[0]:
return x
a = x[1]
what = upper(left) + " " + op + " " + upper(right)
if op == "*":
if len(a[0]) != len(b):
return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": a product needs the columns of the first (" + str(len(a[0])) + ") to equal the rows of the second (" + str(len(b)) + ")."]
return [True, "matrix", matrix.product(a, b), what, ""]
if len(a) != len(b) or len(a[0]) != len(b[0]):
return [False, upper(left) + " is " + dims(a) + " and " + upper(right) + " is " + dims(b) + ": adding or subtracting needs the same size."]
return [True, "matrix", matrix.combined(a, b, op == "-"), what, ""]
print("== Matrix calculator ==")
print("Matrices A, B and C of up to 5 x 5; entries are kept as exact fractions.")
store = {}
running = True
while running:
print("")
print("1) enter a matrix 2) show the matrices 3) calculate 4) quit")
choice = trim(input("choice> "))
if choice == "1":
store = enter(store)
elif choice == "2":
if len(store) == 0:
print("No matrices yet.")
for name in ["A", "B", "C", "R"]:
if name in store:
show(name, store[name])
elif choice == "3":
op = trim(input("operation (A+B, A-B, A*B, 2*A, trans A, det A, inv A)> "))
if op == "":
print("Cancelled.")
else:
r = calculate(store, op)
if not r[0]:
print(r[1])
elif r[1] == "number":
print("The answer is " + r[3] + ": " + frac.text(r[2]) + ".")
else:
store["R"] = r[2]
print("R is " + r[3] + ".")
show("R", r[2])
if r[4] != "":
print(r[4])
elif choice == "4":
running = False
else:
print("Pick a number from 1 to 4.")
print("Bye.")
matrix.eml
eml# P038 matrix calculator - the operations. A matrix is a list of rows of
# fractions (see frac.eml), all rows the same length.
import frac
def size(m):
return [len(m), len(m[0])]
def zero(rows, cols):
[] => m
for i in [1:rows]:
[] => row
for j in [1:cols]:
row + [[0, 1]] => row
m + [row] => m
return m
def identity(n):
zero(n, n) => m
for i in [0:n - 1]:
[1, 1] => m[i][i]
return m
def combined(a, b, subtract):
# a + b, or a - b when subtract is True; the sizes are equal.
[] => out
for i in [0:len(a) - 1]:
[] => row
for j in [0:len(a[0]) - 1]:
if subtract:
row + [frac.minus(a[i][j], b[i][j])] => row
else:
row + [frac.plus(a[i][j], b[i][j])] => row
out + [row] => out
return out
def product(a, b):
# a * b: entry (i, j) is row i of a times column j of b, as in the corpus
# case matrix-multiplication; columns of a = rows of b.
[] => out
for i in [0:len(a) - 1]:
[] => row
for j in [0:len(b[0]) - 1]:
[0, 1] => total
for k in [0:len(b) - 1]:
frac.plus(total, frac.times(a[i][k], b[k][j])) => total
row + [total] => row
out + [row] => out
return out
def scaled(k, a):
[] => out
for row in a:
[] => r
for x in row:
r + [frac.times(k, x)] => r
out + [r] => out
return out
def transposed(a):
[] => out
for j in [0:len(a[0]) - 1]:
[] => row
for i in [0:len(a) - 1]:
row + [a[i][j]] => row
out + [row] => out
return out
def copied(a):
[] => out
for row in a:
out + [row[0:len(row)]] => out
return out
def determinant(a):
# Gaussian elimination with exact fractions: bring the matrix to upper
# triangular form by taking multiples of one row from the rows below it;
# the determinant is the product of the diagonal, with its sign flipped
# for each swap of two rows. About n^3 / 3 steps, where expanding by
# cofactors (the corpus case matrix-determinant) takes n! terms.
copied(a) => m
len(m) => n
[1, 1] => det
for col in [0:n - 1]:
col => p
while p < n and frac.is_zero(m[p][col]):
p + 1 => p
if p == n:
return [0, 1]
if p != col:
m[p] => keep
m[col] => m[p]
keep => m[col]
frac.minus([0, 1], det) => det
frac.times(det, m[col][col]) => det
for r in [col + 1:n - 1]:
if not frac.is_zero(m[r][col]):
frac.over(m[r][col], m[col][col]) => f
for j in [col:n - 1]:
frac.minus(m[r][j], frac.times(f, m[col][j])) => m[r][j]
return det
def inverse(a):
# Gauss-Jordan elimination on [a | I]: when the left half has become I,
# the right half is the inverse. [] if a is singular.
len(a) => n
[] => m
identity(n) => e
for i in [0:n - 1]:
m + [a[i] + e[i]] => m
for col in [0:n - 1]:
col => p
while p < n and frac.is_zero(m[p][col]):
p + 1 => p
if p == n:
return []
if p != col:
m[p] => keep
m[col] => m[p]
keep => m[col]
m[col][col] => pivot
[] => row
for x in m[col]:
row + [frac.over(x, pivot)] => row
row => m[col]
for r in [0:n - 1]:
if r != col and not frac.is_zero(m[r][col]):
m[r][col] => f
[] => changed
for j in [0:2 * n - 1]:
changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))] => changed
changed => m[r]
[] => out
for i in [0:n - 1]:
out + [m[i][n:2 * n]] => out
return out
Python projection (matrix.py)
import frac
def size(m):
return [len(m), len(m[0])]
def zero(rows, cols):
m = []
for i in range(1, rows+1):
row = []
for j in range(1, cols+1):
row = row + [[0, 1]]
m = m + [row]
return m
def identity(n):
m = zero(n, n)
for i in range(0, n):
m[i][i] = [1, 1]
return m
def combined(a, b, subtract):
out = []
for i in range(0, len(a)):
row = []
for j in range(0, len(a[0])):
if subtract:
row = row + [frac.minus(a[i][j], b[i][j])]
else:
row = row + [frac.plus(a[i][j], b[i][j])]
out = out + [row]
return out
def product(a, b):
out = []
for i in range(0, len(a)):
row = []
for j in range(0, len(b[0])):
total = [0, 1]
for k in range(0, len(b)):
total = frac.plus(total, frac.times(a[i][k], b[k][j]))
row = row + [total]
out = out + [row]
return out
def scaled(k, a):
out = []
for row in a:
r = []
for x in row:
r = r + [frac.times(k, x)]
out = out + [r]
return out
def transposed(a):
out = []
for j in range(0, len(a[0])):
row = []
for i in range(0, len(a)):
row = row + [a[i][j]]
out = out + [row]
return out
def copied(a):
out = []
for row in a:
out = out + [row[0:len(row)]]
return out
def determinant(a):
m = copied(a)
n = len(m)
det = [1, 1]
for col in range(0, n):
p = col
while p < n and frac.is_zero(m[p][col]):
p = p + 1
if p == n:
return [0, 1]
if p != col:
keep = m[p]
m[p] = m[col]
m[col] = keep
det = frac.minus([0, 1], det)
det = frac.times(det, m[col][col])
for r in range(col + 1, n):
if not frac.is_zero(m[r][col]):
f = frac.over(m[r][col], m[col][col])
for j in range(col, n):
m[r][j] = frac.minus(m[r][j], frac.times(f, m[col][j]))
return det
def inverse(a):
n = len(a)
m = []
e = identity(n)
for i in range(0, n):
m = m + [a[i] + e[i]]
for col in range(0, n):
p = col
while p < n and frac.is_zero(m[p][col]):
p = p + 1
if p == n:
return []
if p != col:
keep = m[p]
m[p] = m[col]
m[col] = keep
pivot = m[col][col]
row = []
for x in m[col]:
row = row + [frac.over(x, pivot)]
m[col] = row
for r in range(0, n):
if r != col and not frac.is_zero(m[r][col]):
f = m[r][col]
changed = []
for j in range(0, 2 * n):
changed = changed + [frac.minus(m[r][j], frac.times(f, m[col][j]))]
m[r] = changed
out = []
for i in range(0, n):
out = out + [m[i][n:2 * n]]
return out
frac.eml
eml# P038 matrix calculator - exact fractions, from P007 (calculator). A number
# is a list [n, d]: n / d in lowest terms with d > 0. Integers have no size
# limit, but EML has no //, and a / b goes through a float that cannot hold a
# large quotient exactly, so whole-number division is written out below.
def quotient(a, b):
# a // b for whole numbers a >= 0 and b > 0, exact at any size. Long
# division by doubling: take away the largest b * 2^k that still fits.
0 => q
while a >= b:
b => m
1 => k
while m + m <= a:
m + m => m
k + k => k
a - m => a
q + k => q
return q
def gcd(a, b):
while b != 0:
a % b => r
b => a
r => b
return a
def make(n, d):
# n / d in lowest terms with a positive denominator (d != 0).
if d < 0:
0 - n => n
0 - d => d
abs(n) => a
gcd(a, d) => g
if n < 0:
return [0 - quotient(a, g), quotient(d, g)]
return [quotient(a, g), quotient(d, g)]
def plus(x, y):
return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])
def minus(x, y):
return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])
def times(x, y):
return make(x[0] * y[0], x[1] * y[1])
def over(x, y):
# x / y; the caller has checked that y is not zero.
return make(x[0] * y[1], x[1] * y[0])
def is_zero(x):
return x[0] == 0
def digits_value(s):
# The value of a string of 1 to 9 digits, otherwise -1.
if s == "" or len(s) > 9:
return -1
0 => n
for c in s:
if not (c in "0123456789"):
return -1
n * 10 + int(c) => n
return n
def parse(s):
# "3", "-2", "3/4", "-0.25" as a fraction; [] if it is not a number.
1 => sign
if len(s) > 0 and s[0] == "-":
-1 => sign
s[1:len(s)] => s
0 => k
while k < len(s) and s[k] != "/" and s[k] != ".":
k + 1 => k
if k == len(s):
digits_value(s) => n
if n < 0:
return []
return make(sign * n, 1)
digits_value(s[0:k]) => whole
s[k + 1:len(s)] => rest
digits_value(rest) => part
if whole < 0 or part < 0:
return []
if s[k] == "/":
if part == 0:
return []
return make(sign * whole, part)
# a decimal point: 0.25 is 25 / 100
1 => scale
for c in rest:
scale * 10 => scale
return make(sign * (whole * scale + part), scale)
def text(x):
# "3", "-3", "3/4", "-3/4".
if x[1] == 1:
return str(x[0])
return str(x[0]) + "/" + str(x[1])
Python projection (frac.py)
def quotient(a, b):
q = 0
while a >= b:
m = b
k = 1
while m + m <= a:
m = m + m
k = k + k
a = a - m
q = q + k
return q
def gcd(a, b):
while b != 0:
r = a % b
a = b
b = r
return a
def make(n, d):
if d < 0:
n = 0 - n
d = 0 - d
a = abs(n)
g = gcd(a, d)
if n < 0:
return [0 - quotient(a, g), quotient(d, g)]
return [quotient(a, g), quotient(d, g)]
def plus(x, y):
return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])
def minus(x, y):
return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])
def times(x, y):
return make(x[0] * y[0], x[1] * y[1])
def over(x, y):
return make(x[0] * y[1], x[1] * y[0])
def is_zero(x):
return x[0] == 0
def digits_value(s):
if s == "" or len(s) > 9:
return -1
n = 0
for c in s:
if not c in "0123456789":
return -1
n = n * 10 + int(c)
return n
def parse(s):
sign = 1
if len(s) > 0 and s[0] == "-":
sign = -1
s = s[1:len(s)]
k = 0
while k < len(s) and s[k] != "/" and s[k] != ".":
k = k + 1
if k == len(s):
n = digits_value(s)
if n < 0:
return []
return make(sign * n, 1)
whole = digits_value(s[0:k])
rest = s[k + 1:len(s)]
part = digits_value(rest)
if whole < 0 or part < 0:
return []
if s[k] == "/":
if part == 0:
return []
return make(sign * whole, part)
scale = 1
for c in rest:
scale = scale * 10
return make(sign * (whole * scale + part), scale)
def text(x):
if x[1] == 1:
return str(x[0])
return str(x[0]) + "/" + str(x[1])