Skip to content

Commit

Permalink
Improved rref/trnsform. Note that trnsform doesn't work yet.
Browse files Browse the repository at this point in the history
Improved matrix tests to be 100% randomly generated but still have full code coverage.
  • Loading branch information
nrubin29 committed Oct 13, 2017
1 parent 7360f45 commit 2eb5394
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 85 deletions.
4 changes: 4 additions & 0 deletions common.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@ def key_at(self, i):
'ADD': 'add',
'MUL': 'mul',
}


class EvaluationException(Exception):
pass
95 changes: 49 additions & 46 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import random
import unittest

import sympy

from calculator import Calculator
from common import EvaluationException


def evaluate(eqtn: str, verbose=True):
Expand Down Expand Up @@ -105,52 +108,52 @@ def runTest(self):

class MatrixTests(unittest.TestCase):
def runTest(self):
self.assertEqual(evaluate('[1,2]'), [1.0, 2.0]) # TODO: I guess we can now tell the difference between matrices and vectors...is that good?
self.assertEqual(evaluate('det([1,2,3|4,5,6|7,8,8])'), 3.0)

self.assertEqual(evaluate('[1,2|4,5]'), [[1.0, 2.0], [4.0, 5.0]])
self.assertEqual(evaluate('trans([1,2|4,5])'), [[1.0, 4.0], [2.0, 5.0]])

self.assertEqual(evaluate('inv([1,4,7|3,0,5|-1,9,11])'), [[45/8, -19/8, -5/2], [19/4, -9/4, -2], [-27/8, 13/8, 3/2]])

self.assertEqual(evaluate('[1,0,0|0,1,0|0,0,1]'), [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])

self.assertEqual(evaluate('[1,0,0,0|0,1,0,0|0,0,1,0|0,0,0,1]'), [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]])
self.assertEqual(evaluate('det([1,3,5,7|2,4,6,8|9,7,5,4|8,6,5,9])'), 2.0)

self.assertEqual(evaluate('cof([1,2,3|0,4,5|1,0,6])'), [[24, 5, -4], [-12, 3, 2], [-2, -5, 4]])

# Since we have floating-point issues, we have to test each value individually.
calc = evaluate('inv([1,2,3|0,4,5|1,0,6])')
print(calc)
ans = [[12/11, -6/11, -1/11], [5/22, 3/22, -5/22], [-2/11, 1/11, 2/11]]

for row in range(len(calc)):
for col in range(len(calc)):
self.assertAlmostEqual(calc[row][col], ans[row][col])

self.assertEqual(evaluate('identity(3)'), [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
self.assertEqual(evaluate('trnsform([0,0,2|0,3,0|4,0,0])'), [[0, 0, 1], [0, 1, 0], [1, 0, 0]])

self.assertEqual(evaluate('rref([1,2,3|4,5,6|7,8,8])'), [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
self.assertEqual(evaluate('rref([1,2,4|4,7,6|7,1,8])'), [[1, 0, 0], [0, 1, 0], [0, 0, 1]])
self.assertEqual(evaluate('rref([1,2,3|4,5,6|4,5,6])'), [[1, 0, -1], [0, 1, 2], [0, 0, 0]])
self.assertEqual(evaluate('rref([1,2,3|4,5,6|7,8,9])'), [[1, 0, -1], [0, 1, 2], [0, 0, 0]])

# for r_dim in range(3, 10):
# print(r_dim)
#
# for _ in range(10):
# mat = [[random.randint(0, 100) for _ in range(r_dim)] for _ in range(r_dim)]
# mat_str = '[' + '|'.join([','.join(map(str, line)) for line in mat]) + ']'
#
# # print('*****<')
# # print(sympy.Matrix(mat))
# # print(sympy.Matrix(evaluate('rref({})'.format(mat_str), False)))
# # print(sympy.Matrix(mat).rref()[0])
# # print('>*****')
#
# self.assertTrue(sympy.Matrix(evaluate('rref({})'.format(mat_str), False)).equals(sympy.Matrix(mat).rref()[0]))
rnd = lambda e: round(e, 5)
more_zeroes = lambda n: n if n <= 75 else 0

for r_dim in range(3, 10):
print(r_dim)

self.assertTrue(sympy.Matrix(evaluate('identity({})'.format(r_dim), False)).equals(sympy.Identity(r_dim)))

for _ in range(10):
mat = [[more_zeroes(random.randint(0, 100)) for _ in range(r_dim)] for _ in range(r_dim)]
mat_str = '[' + '|'.join([','.join(map(str, line)) for line in mat]) + ']'

sym_mat = sympy.Matrix(mat)
rref = sym_mat.rref()[0]

try:
self.assertTrue(evaluate('det({})'.format(mat_str), False) == sym_mat.det())
self.assertTrue(
sympy.Matrix(evaluate('trans({})'.format(mat_str), False)).equals(sym_mat.transpose()))
self.assertTrue(sympy.Matrix(evaluate('inv({})'.format(mat_str), False)).applyfunc(rnd).equals(
sym_mat.inv().evalf().applyfunc(rnd)))
self.assertTrue(
sympy.Matrix(evaluate('cof({})'.format(mat_str), False)).equals(sym_mat.cofactor_matrix()))
self.assertTrue(sympy.Matrix(evaluate('rref({})'.format(mat_str), False)).equals(rref))
# self.assertTrue(sym_mat.multiply_elementwise(sympy.Matrix(evaluate('trnsform({})'.format(mat_str), False))).applyfunc(rnd).equals(rref.evalf().applyfunc(rnd)))
# TODO: trnsform doesn't work.
except AssertionError:
print('FAILED')
print('matrix', sym_mat)
print('det', sym_mat.det())
print('trans', sym_mat.transpose())
print('inv', sym_mat.inv().evalf())
print('cof', sym_mat.cofactor_matrix())
print('rref', rref)
print('----')
print('det', evaluate('det({})'.format(mat_str), False))
print('trans', evaluate('trans({})'.format(mat_str), False))
print('inv', evaluate('inv({})'.format(mat_str), False))
print('cof', evaluate('cof({})'.format(mat_str), False))
print('rref', evaluate('rref({})'.format(mat_str), False))
print('trnsform', evaluate('trnsform({})'.format(mat_str), False))
print('matrix * trnsform', sym_mat.multiply_elementwise(sympy.Matrix(evaluate('trnsform({})'.format(mat_str), False))))
raise
except EvaluationException as e:
print(e)
pass


class RandomTests(unittest.TestCase):
Expand Down
79 changes: 40 additions & 39 deletions vartypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import copy

from common import Value, Token
from common import Value, Token, EvaluationException


class Type(metaclass=ABCMeta):
Expand Down Expand Up @@ -192,74 +192,64 @@ def adj(matrix: Value) -> Value:

@staticmethod
def inv(matrix: Value) -> Value:
multiplier = 1 / Matrix._det(matrix.value)
det = Matrix._det(matrix.value)

if det == 0:
raise EvaluationException('Cannot invert matrix with determinant of 0.')

multiplier = 1 / det
return Value(Matrix, [[cell * multiplier for cell in row] for row in Matrix.adj(matrix).value])

@staticmethod
def trnsform(matrix) -> Value:
# Returns the transformation matrix which, when multiplied by the original matrix, will give the rref form of the original matrix.
def _rref(matrix):
# Returns the transformation matrix which, when multiplied by the original matrix, will give its rref form.
mat = copy.deepcopy(matrix.value)
ident = Number.identity(Value(Number, len(mat))).value
row = 0
col = 0

def count_leading_zeroes(row):
for i in range(len(row)):
if row[i] != 0:
return i

return len(row)

while row < len(mat) - 1:
if count_leading_zeroes(mat[row]) > count_leading_zeroes(mat[row + 1]):
mat[row], mat[row + 1] = mat[row + 1], mat[row]
ident[row], ident[row + 1] = ident[row + 1], ident[row]
row = 0

else:
row += 1
def arrange_by_leading_zeroes():
r = 0

row = 0
def count_leading_zeroes(row):
for i in range(len(row)):
if row[i] != 0:
return i

return Value(Matrix, ident)
return len(row)

@staticmethod
def rref(matrix: Value) -> Value:
mat = copy.deepcopy(matrix.value)
row = 0
col = 0
while r < len(mat) - 1:
if count_leading_zeroes(mat[r]) > count_leading_zeroes(mat[r + 1]):
mat[r], mat[r + 1] = mat[r + 1], mat[r]
ident[r], ident[r + 1] = ident[r + 1], ident[r]
r = 0

def count_leading_zeroes(row):
for i in range(len(row)):
if row[i] != 0:
return i
else:
r += 1

return len(row)

# Sort the matrix by the number of 0s in each row with the most 0s going to the bottom.
mat = sorted(mat, key=count_leading_zeroes)

# print(mat)
arrange_by_leading_zeroes()

while row < len(mat) and col < len(mat[row]):
# print(row, mat)

# If there is a leading 0, move column over but remain on the same row.
if mat[row][col] == 0:
col += 1
continue

# Divide each cell in the row by the first cell to ensure that the row starts with a 1.
mat[row] = [cell / mat[row][col] for cell in mat[row]]
ident[row] = [cell / mat[row][col] for cell in ident[row]]

# Multiply all lower rows as needed.
for i in range(row + 1, len(mat)):
multiplier = -mat[i][col] / mat[row][col]
mat[i] = [cell + (mat[row][c] * multiplier) for c, cell in enumerate(mat[i])]
ident[i] = [cell + (mat[row][c] * multiplier) for c, cell in enumerate(ident[i])]

row += 1
col += 1

arrange_by_leading_zeroes()

row = len(mat) - 1
col = len(mat[row]) - 1

Expand All @@ -278,13 +268,24 @@ def count_leading_zeroes(row):
# print('multiplier', multiplier)

mat[i] = [cell + (mat[row][c] * multiplier) for c, cell in enumerate(mat[i])]
ident[i] = [cell + (mat[row][c] * multiplier) for c, cell in enumerate(ident[i])]

# print('it is now', mat[i])

row -= 1
col -= 1

return Value(Matrix, mat)
arrange_by_leading_zeroes()

return mat, ident

@staticmethod
def rref(matrix: Value) -> Value:
return Value(Matrix, Matrix._rref(matrix)[0])

@staticmethod
def trnsform(matrix) -> Value:
return Value(Matrix, Matrix._rref(matrix)[1])


class MatrixRow(Type):
Expand Down

0 comments on commit 2eb5394

Please sign in to comment.