def ccw(*points, homogeneous=True): """tests if triangle a, b, c is oriented counterclockwise. Returns 1 if ccw, 0 if colinear, -1 if cw. Also does the scary analogous n-dimensional test, which essentially tests if, from the perspective of the last point, the other points appear to be well-oriented according to the (n-1)-dimensional test. """ if not homogeneous: mtrx = Matrix(*[pt.lift(lambda v: 1).to_vector() for pt in points]) else: mtrx = None for pt in points: if pt[-1] == 1: # We need at list one non-infinite point # for this to work normally mtrx = Matrix(*[pt.to_vector() for pt in points]) break if mtrx is not None: return mtrx.sign_det() # If all points have 0 for extended homogeneous coordinate, # win by using the ccw test for one dimension higher: return ccw(*points, Point(*(0 for i in range(len(points[0]) - 1)), -1), homogeneous=False)
def circumcenter(*points, homogeneous=True): """Computes a circumcenter for a set of n+1 points in n dimensions, ignoring the extended homogeneous coordinates. Unless you pass homogeneous=False, the last coordinate of each point will be lopped off. Behavior may not be defined if any of the homogeneous coordinates is not 1. """ if homogeneous: if [pt[-1] for pt in points] != [1] * len(points): tmp = [] for v in [p.to_vector() for p in points]: if v[-1] == 0: v *= 1000000000 tmp.append(v) # Just strip off the homogeneous coordinates. tmp = [v[:-1] for v in tmp] points = [Point(*v.to_array()) for v in tmp] else: points = [pt[:-1] for pt in points] vectors = [p - points[0] for p in points[1:]] A = Matrix(*vectors).transpose() p0_squared = points[0].to_vector().norm_squared() b = Vector(*[ 0.5 * (p.to_vector().norm_squared() - p0_squared) for p in points[1:] ]) x = A.inverse() * b # If the arguments had homogeneous coordinates, we want to tack the extra # coordinate back on: return Point(*x, *([1] if homogeneous else []))
def test_subtract_matrix(self): """Tests for subtraction of two matrices""" result1 = Matrix(Vector(0, 0, 0), Vector(-3, -4, -5), Vector(-1, -1, -1)) zero_matrix = Matrix(Vector(0, 0, 0), Vector(0, 0, 0), Vector(0, 0, 0)) self.assertTrue(self.m1 - self.m6 == result1) self.assertTrue(self.m1 - self.m1 == zero_matrix)
def test_matrix_init(self): """Make sure we cannot initialize matrices that don't make sense""" # Make sure the empty matrix can't exist with self.assertRaises(ValueError): Matrix() # Ensure failure if the vectors have different dimensions with self.assertRaises(ValueError): Matrix(self.v1, self.v2, self.v4) with self.assertRaises(ValueError): Matrix(self.v4, self.zero) with self.assertRaises(ValueError): Matrix(self.v1, self.v4, self.zero)
def incircle(*points, homogeneous=True): """Returns 1 if the last point is inside the circle defined by the other three, -1 if outside, and 0 if all cocircular. The points are interpreted as having extended homogenious coordinates unless homogeneous=False is passed. """ if homogeneous: # The points already have homogeneous coordinates vectors = [pt.to_vector() for pt in points] else: # We'll give each point a homogeneous coordinate of 1 vectors = [(pt.to_vector()).lift() for pt in points] tmp = [] for v in vectors: if v[-1] == 0: v *= 1000000000 v += Vector(*([0] * (len(v) - 1)), 1) tmp.append(v) vectors = tmp vectors = [ vector.lift(lambda v: v[:-1].norm_squared()) for vector in vectors ] # At this point we could switch the last two elements of each # vector to get the matrix we want to test. But we can # also just use the fact that if you swap 2 rows of a # matrix, the sign of the determinant is flipped. So: return -Matrix(*vectors).sign_det()
def test_matrix_inversion(self): """Tests to see if matrix inversion works as expected""" inv1 = Matrix(Vector(-2, 1), Vector(1.5, -0.5)) self.assertTrue(Matrix(self.v5, self.v6).inverse() == inv1) with self.assertRaises(ValueError): Matrix(self.zero, self.v1, self.v2).inverse() t1 = Vector(-1, 1, 2) t2 = Vector(-1, 3, 2) t3 = Vector(-1, 1, 3) inv2 = Matrix(Vector(-3.5, 0.5, 2), Vector(-0.5, 0.5, 0), Vector(-1, 0, 1)) self.assertTrue(inv2 == Matrix(t1, t2, t3).inverse()) inv3 = Matrix(Vector(-3.5, 2, 0.5), Vector(-0.5, 0, 0.5), Vector(-1, 1, 0)) self.assertTrue(Matrix(t1, t3, t2).inverse() == inv3) self.assertEqual(Matrix(t1, t2, t3).inverse() * Matrix(t1, t2, t3), Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)))
def test_matrix_sign_det(self): """Tests that sign_det returns the sign of the determinant. (Rather, sign_det should return an integer with the same sign as the determinant.) """ ident_3 = Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)) # The following are just some cases where it's easy # to do the math in your head. self.assertEqual(ident_3.sign_det(), 1) self.assertEqual((ident_3 - ident_3).sign_det(), 0) # (A lower-triangular matrix. And don't forget it.) self.assertEqual(Matrix(Vector(1, 2, 3), Vector(0, -12, 5), Vector(0, 0, 0.001)).sign_det(), -1) self.assertIsInstance(Matrix(Vector(1, 2), Vector(3, 4)).sign_det(), int)
def test_matrix_power(self): """Tests for matrices to a power""" result1 = Matrix(Vector(3, 4, 5), Vector(2, 3, 4), Vector(0, 0, 0)) self.assertTrue(self.m1 ** 2 == result1) self.assertTrue(self.m1 * self.m1 * self.m1 == self.m1 ** 3)
def test_transpose(self): """Tests for transpose of a matrix""" result1 = Matrix(Vector(1, 1, 0), Vector(2, 1, 0), Vector(3, 1, 0)) result2 = Matrix(Vector(1, 4, 1), Vector(2, 5, 1), Vector(3, 6, 1)) self.assertTrue(self.m1.transpose() == result1) self.assertTrue(self.m6.transpose() == result2)
def test_add_matrix(self): """Tests for addition of two matrices""" result1 = Matrix(Vector(2, 4, 6), Vector(5, 6, 7), Vector(1, 1, 1)) self.assertTrue(self.m1 + self.m6 == result1)
def test_scalar_multiplication(self): """Tests for multiplication of a matrix and a scalar""" result1 = Matrix(Vector(2, 4, 6), Vector(2, 2, 2), Vector(0, 0, 0)) self.assertTrue(2 * self.m1 == result1) self.assertTrue(2 * self.m1 == self.m1 * 2)
def test_matrix_multiplication(self): """Tests for matrix multiplication""" result1 = Matrix(Vector(23, 34), Vector(31, 46)) self.assertTrue(self.m4 * self.m5 == result1) result2 = Matrix(Vector(3, 4, 5), Vector(9, 13, 17), Vector(2, 3, 4)) self.assertTrue(self.m1 * self.m6 == result2)
def setUp(self): """Set up some Vectors and Matrices""" self.v1 = Vector(1, 2, 3) self.v2 = Vector(1, 1, 1) self.v3 = Vector(9, 9) self.v4 = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) self.zero = Vector(0, 0, 0) self.v5 = Vector(1, 2) self.v6 = Vector(3, 4) self.v7 = Vector(5, 6) self.v8 = Vector(7, 8) self.v9 = Vector(4, 5, 6) self.m1 = Matrix(self.v1, self.v2, self.zero) self.m2 = Matrix(self.v1, self.v2) self.m3 = Matrix(self.v3, self.v3) self.m4 = Matrix(self.v5, self.v6) self.m5 = Matrix(self.v7, self.v8) self.m6 = Matrix(self.v1, self.v9, self.v2) self.I = Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1))
class MatrixTestCase(unittest.TestCase): """Unit tests for the Matrix class""" def setUp(self): """Set up some Vectors and Matrices""" self.v1 = Vector(1, 2, 3) self.v2 = Vector(1, 1, 1) self.v3 = Vector(9, 9) self.v4 = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) self.zero = Vector(0, 0, 0) self.v5 = Vector(1, 2) self.v6 = Vector(3, 4) self.v7 = Vector(5, 6) self.v8 = Vector(7, 8) self.v9 = Vector(4, 5, 6) self.m1 = Matrix(self.v1, self.v2, self.zero) self.m2 = Matrix(self.v1, self.v2) self.m3 = Matrix(self.v3, self.v3) self.m4 = Matrix(self.v5, self.v6) self.m5 = Matrix(self.v7, self.v8) self.m6 = Matrix(self.v1, self.v9, self.v2) self.I = Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)) def test_matrix_init(self): """Make sure we cannot initialize matrices that don't make sense""" # Make sure the empty matrix can't exist with self.assertRaises(ValueError): Matrix() # Ensure failure if the vectors have different dimensions with self.assertRaises(ValueError): Matrix(self.v1, self.v2, self.v4) with self.assertRaises(ValueError): Matrix(self.v4, self.zero) with self.assertRaises(ValueError): Matrix(self.v1, self.v4, self.zero) def test_matrix_width(self): """Tests for matrix width""" self.assertTrue(self.m1.width() == 3) self.assertTrue(self.m2.width() == 2) def test_matrix_height(self): """Tests for matrix height""" self.assertTrue(self.m1.height() == 3) self.assertTrue(self.m2.height() == 3) self.assertTrue(self.m3.height() == 2) def test_matrix_get(self): """Test for getting vectors of a matrix""" self.assertTrue(self.m1[0] == self.v1) self.assertTrue(self.m1[1] == self.v2) self.assertTrue(self.m1[2] == self.zero) def test_matrix_iteration(self): """Test iteration of a matrix""" orig_list = [self.v1, self.v2, self.zero] other_list = [vector for vector in self.m1] self.assertTrue(orig_list == other_list) def test_matrix_multiplication(self): """Tests for matrix multiplication""" result1 = Matrix(Vector(23, 34), Vector(31, 46)) self.assertTrue(self.m4 * self.m5 == result1) result2 = Matrix(Vector(3, 4, 5), Vector(9, 13, 17), Vector(2, 3, 4)) self.assertTrue(self.m1 * self.m6 == result2) def test_vector_multiplication(self): """Tests for matrix, vector multiplication""" self.assertTrue(self.I*self.v1 == self.v1) result1 = Vector(7, 10) self.assertTrue(self.m4 * self.v5 == result1) def test_scalar_multiplication(self): """Tests for multiplication of a matrix and a scalar""" result1 = Matrix(Vector(2, 4, 6), Vector(2, 2, 2), Vector(0, 0, 0)) self.assertTrue(2 * self.m1 == result1) self.assertTrue(2 * self.m1 == self.m1 * 2) def test_determinant(self): """Tests for calculating determinant of matrix""" self.assertTrue(self.m1.det() == 0) self.assertAlmostEqual(self.m5.det(), -2.0) # almostEqual to account for floating point def test_add_matrix(self): """Tests for addition of two matrices""" result1 = Matrix(Vector(2, 4, 6), Vector(5, 6, 7), Vector(1, 1, 1)) self.assertTrue(self.m1 + self.m6 == result1) def test_subtract_matrix(self): """Tests for subtraction of two matrices""" result1 = Matrix(Vector(0, 0, 0), Vector(-3, -4, -5), Vector(-1, -1, -1)) zero_matrix = Matrix(Vector(0, 0, 0), Vector(0, 0, 0), Vector(0, 0, 0)) self.assertTrue(self.m1 - self.m6 == result1) self.assertTrue(self.m1 - self.m1 == zero_matrix) def test_equal(self): """Tests for equality of matrices""" self.assertTrue(self.m1 == self.m1) self.assertFalse(self.m1 == self.m6) self.assertFalse(self.m1 == self.m2) def test_transpose(self): """Tests for transpose of a matrix""" result1 = Matrix(Vector(1, 1, 0), Vector(2, 1, 0), Vector(3, 1, 0)) result2 = Matrix(Vector(1, 4, 1), Vector(2, 5, 1), Vector(3, 6, 1)) self.assertTrue(self.m1.transpose() == result1) self.assertTrue(self.m6.transpose() == result2) def test_matrix_power(self): """Tests for matrices to a power""" result1 = Matrix(Vector(3, 4, 5), Vector(2, 3, 4), Vector(0, 0, 0)) self.assertTrue(self.m1 ** 2 == result1) self.assertTrue(self.m1 * self.m1 * self.m1 == self.m1 ** 3) def test_matrix_inversion(self): """Tests to see if matrix inversion works as expected""" inv1 = Matrix(Vector(-2, 1), Vector(1.5, -0.5)) self.assertTrue(Matrix(self.v5, self.v6).inverse() == inv1) with self.assertRaises(ValueError): Matrix(self.zero, self.v1, self.v2).inverse() t1 = Vector(-1, 1, 2) t2 = Vector(-1, 3, 2) t3 = Vector(-1, 1, 3) inv2 = Matrix(Vector(-3.5, 0.5, 2), Vector(-0.5, 0.5, 0), Vector(-1, 0, 1)) self.assertTrue(inv2 == Matrix(t1, t2, t3).inverse()) inv3 = Matrix(Vector(-3.5, 2, 0.5), Vector(-0.5, 0, 0.5), Vector(-1, 1, 0)) self.assertTrue(Matrix(t1, t3, t2).inverse() == inv3) self.assertEqual(Matrix(t1, t2, t3).inverse() * Matrix(t1, t2, t3), Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1))) def test_matrix_sign_det(self): """Tests that sign_det returns the sign of the determinant. (Rather, sign_det should return an integer with the same sign as the determinant.) """ ident_3 = Matrix(Vector(1, 0, 0), Vector(0, 1, 0), Vector(0, 0, 1)) # The following are just some cases where it's easy # to do the math in your head. self.assertEqual(ident_3.sign_det(), 1) self.assertEqual((ident_3 - ident_3).sign_det(), 0) # (A lower-triangular matrix. And don't forget it.) self.assertEqual(Matrix(Vector(1, 2, 3), Vector(0, -12, 5), Vector(0, 0, 0.001)).sign_det(), -1) self.assertIsInstance(Matrix(Vector(1, 2), Vector(3, 4)).sign_det(), int)