Austin Schuh | 085eab9 | 2020-11-26 13:54:51 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 2 | |
| 3 | import numpy |
| 4 | from numpy.testing import * |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 5 | import unittest |
| 6 | |
Brian Silverman | 9c89c0a | 2016-01-08 01:04:57 -0800 | [diff] [blame] | 7 | import frc971.control_loops.python.polytope as polytope |
| 8 | |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 9 | __author__ = 'Austin Schuh (austin.linux@gmail.com)' |
| 10 | |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 11 | |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 12 | def MakePoint(*args): |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 13 | """Makes a point from a set of arguments.""" |
| 14 | return numpy.matrix([[arg] for arg in args]) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 15 | |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 16 | |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 17 | class TestHPolytope(unittest.TestCase): |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 18 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 19 | def setUp(self): |
| 20 | """Builds a simple box polytope.""" |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 21 | self.H = numpy.matrix([[1, 0], [-1, 0], [0, 1], [0, -1]]) |
| 22 | self.k = numpy.matrix([[12], [12], [12], [12]]) |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 23 | self.p = polytope.HPolytope(self.H, self.k) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 24 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 25 | def test_Hk(self): |
| 26 | """Tests that H and k are saved correctly.""" |
| 27 | assert_array_equal(self.p.H, self.H) |
| 28 | assert_array_equal(self.p.k, self.k) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 29 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 30 | def test_IsInside(self): |
| 31 | """Tests IsInside for various points.""" |
| 32 | inside_points = [ |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 33 | MakePoint(0, 0), |
| 34 | MakePoint(6, 6), |
| 35 | MakePoint(12, 6), |
| 36 | MakePoint(-6, 10) |
| 37 | ] |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 38 | outside_points = [ |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 39 | MakePoint(14, 0), |
| 40 | MakePoint(-14, 0), |
| 41 | MakePoint(0, 14), |
| 42 | MakePoint(0, -14), |
| 43 | MakePoint(14, -14) |
| 44 | ] |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 45 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 46 | for inside_point in inside_points: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 47 | self.assertTrue(self.p.IsInside(inside_point), |
| 48 | msg='Point is' + str(inside_point)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 49 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 50 | for outside_point in outside_points: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 51 | self.assertFalse(self.p.IsInside(outside_point), |
| 52 | msg='Point is' + str(outside_point)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 53 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 54 | def AreVertices(self, p, vertices): |
| 55 | """Checks that all the vertices are on corners of the set.""" |
| 56 | for i in range(vertices.shape[0]): |
| 57 | # Check that all the vertices have the correct number of active |
| 58 | # constraints. |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 59 | lmda = p.H * vertices[i, :].T - p.k |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 60 | num_active_constraints = 0 |
| 61 | for j in range(lmda.shape[0]): |
| 62 | # Verify that the constraints are either active, or not violated. |
| 63 | if numpy.abs(lmda[j, 0]) <= 1e-9: |
| 64 | num_active_constraints += 1 |
| 65 | else: |
| 66 | self.assertLessEqual(lmda[j, 0], 0.0) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 67 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 68 | self.assertEqual(p.ndim, num_active_constraints) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 69 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 70 | def HasSamePoints(self, expected, actual): |
| 71 | """Verifies that the points in expected are in actual.""" |
| 72 | found_points = set() |
| 73 | self.assertEqual(expected.shape, actual.shape) |
| 74 | for index in range(expected.shape[0]): |
| 75 | expected_point = expected[index, :] |
| 76 | for actual_index in range(actual.shape[0]): |
| 77 | actual_point = actual[actual_index, :] |
| 78 | if numpy.abs(expected_point - actual_point).max() <= 1e-4: |
| 79 | found_points.add(actual_index) |
| 80 | break |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 81 | |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 82 | self.assertEqual(len(found_points), |
| 83 | actual.shape[0], |
| 84 | msg="Expected:\n" + str(expected) + "\nActual:\n" + |
| 85 | str(actual)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 86 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 87 | def test_Skewed_Nonsym_Vertices(self): |
| 88 | """Tests the vertices of a severely skewed space.""" |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 89 | self.H = numpy.matrix([[10, -1], [-1, -1], [-1, 10], [10, 10]]) |
| 90 | self.k = numpy.matrix([[2], [2], [2], [2]]) |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 91 | self.p = polytope.HPolytope(self.H, self.k) |
| 92 | vertices = self.p.Vertices() |
| 93 | self.AreVertices(self.p, vertices) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 94 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 95 | self.HasSamePoints( |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 96 | numpy.matrix([[0., 0.2], [0.2, 0.], [-2., 0.], [0., -2.]]), |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 97 | vertices) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 98 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 99 | def test_Vertices_Nonsym(self): |
| 100 | """Tests the vertices of a nonsymetric space.""" |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 101 | self.k = numpy.matrix([[6], [12], [2], [10]]) |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 102 | self.p = polytope.HPolytope(self.H, self.k) |
| 103 | vertices = self.p.Vertices() |
| 104 | self.AreVertices(self.p, vertices) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 105 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 106 | self.HasSamePoints( |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 107 | numpy.matrix([[6., 2.], [6., -10.], [-12., -10.], [-12., 2.]]), |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 108 | vertices) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 109 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 110 | def test_Vertices(self): |
| 111 | """Tests the vertices of a nonsymetric space.""" |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 112 | self.HasSamePoints( |
| 113 | self.p.Vertices(), |
| 114 | numpy.matrix([[12., 12.], [12., -12.], [-12., -12.], [-12., 12.]])) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 115 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 116 | def test_concat(self): |
| 117 | """Tests that the concat function works for simple inputs.""" |
| 118 | self.assertEqual(["asd", "qwe"], |
| 119 | list( |
| 120 | polytope._PiecewiseConcat(["a", "q"], ["s", "w"], |
| 121 | ["d", "e"]))) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 122 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 123 | def test_str(self): |
| 124 | """Verifies that the str method works for the provided p.""" |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 125 | self.assertEqual( |
| 126 | '[[ 1 0] [[12] \n' |
| 127 | ' [-1 0] [[x0] <= [12] \n' |
| 128 | ' [ 0 1] [x1]] [12] \n' |
| 129 | ' [ 0 -1]] [12]] ', str(self.p)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 130 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 131 | def MakePWithDims(self, num_constraints, num_dims): |
| 132 | """Makes a zeroed out polytope with the correct size.""" |
| 133 | self.p = polytope.HPolytope( |
| 134 | numpy.matrix(numpy.zeros((num_constraints, num_dims))), |
| 135 | numpy.matrix(numpy.zeros((num_constraints, 1)))) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 136 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 137 | def test_few_constraints_odd_constraint_even_dims_str(self): |
| 138 | """Tests printing out the set with odd constraints and even dimensions.""" |
| 139 | self.MakePWithDims(num_constraints=5, num_dims=2) |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 140 | self.assertEqual( |
| 141 | '[[0. 0.] [[0.] \n' |
| 142 | ' [0. 0.] [[x0] [0.] \n' |
| 143 | ' [0. 0.] [x1]] <= [0.] \n' |
| 144 | ' [0. 0.] [0.] \n' |
| 145 | ' [0. 0.]] [0.]] ', str(self.p)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 146 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 147 | def test_few_constraints_odd_constraint_small_dims_str(self): |
| 148 | """Tests printing out the set with odd constraints and odd dimensions.""" |
| 149 | self.MakePWithDims(num_constraints=5, num_dims=1) |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 150 | self.assertEqual( |
| 151 | '[[0.] [[0.] \n' |
| 152 | ' [0.] [0.] \n' |
| 153 | ' [0.] [[x0]] <= [0.] \n' |
| 154 | ' [0.] [0.] \n' |
| 155 | ' [0.]] [0.]] ', str(self.p)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 156 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 157 | def test_few_constraints_odd_constraint_odd_dims_str(self): |
| 158 | """Tests printing out the set with odd constraints and odd dimensions.""" |
| 159 | self.MakePWithDims(num_constraints=5, num_dims=3) |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 160 | self.assertEqual( |
| 161 | '[[0. 0. 0.] [[0.] \n' |
| 162 | ' [0. 0. 0.] [[x0] [0.] \n' |
| 163 | ' [0. 0. 0.] [x1] <= [0.] \n' |
| 164 | ' [0. 0. 0.] [x2]] [0.] \n' |
| 165 | ' [0. 0. 0.]] [0.]] ', str(self.p)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 166 | |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 167 | def test_many_constraints_even_constraint_odd_dims_str(self): |
| 168 | """Tests printing out the set with even constraints and odd dimensions.""" |
| 169 | self.MakePWithDims(num_constraints=2, num_dims=3) |
Ravago Jones | 26f7ad0 | 2021-02-05 15:45:59 -0800 | [diff] [blame] | 170 | self.assertEqual( |
| 171 | '[[0. 0. 0.] [[x0] [[0.] \n' |
| 172 | ' [0. 0. 0.]] [x1] <= [0.]] \n' |
| 173 | ' [x2]] ', str(self.p)) |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 174 | |
| 175 | |
| 176 | if __name__ == '__main__': |
Austin Schuh | 5ea4847 | 2021-02-02 20:46:41 -0800 | [diff] [blame] | 177 | unittest.main() |