]> gitweb.michael.orlitzky.com - dunshire.git/blobdiff - test/symmetric_linear_game_test.py
Take the condition number into account when evaluating test results.
[dunshire.git] / test / symmetric_linear_game_test.py
index 470cf6a116aeb578a89450671abfbb6f73f1e9de..936a7e869283b4fd9ee2e82ddccefa5ef7253658 100644 (file)
@@ -13,18 +13,22 @@ from .randomgen import (RANDOM_MAX, random_icecream_game,
                         random_nn_scaling, random_orthant_game,
                         random_positive_orthant_game, random_translation)
 
                         random_nn_scaling, random_orthant_game,
                         random_positive_orthant_game, random_translation)
 
-EPSILON = 2*2*RANDOM_MAX*options.ABS_TOL
+EPSILON = (1 + RANDOM_MAX)*options.ABS_TOL
 """
 This is the tolerance constant including fudge factors that we use to
 determine whether or not two numbers are equal in tests.
 
 """
 This is the tolerance constant including fudge factors that we use to
 determine whether or not two numbers are equal in tests.
 
-The factor of two is because if we compare two solutions, both
-of which may be off by ``ABS_TOL``, then the result could be off
-by ``2*ABS_TOL``. The factor of ``RANDOM_MAX`` allows for
-scaling a result (by ``RANDOM_MAX``) that may be off by
-``ABS_TOL``. The final factor of two is to allow for the edge
-cases where we get an "unknown" result and need to lower the
-CVXOPT tolerance by a factor of two.
+Often we will want to compare two solutions, say for games that are
+equivalent. If the first game value is low by ``ABS_TOL`` and the second
+is high by ``ABS_TOL``, then the total could be off by ``2*ABS_TOL``. We
+also subject solutions to translations and scalings, which adds to or
+scales their error. If the first game is low by ``ABS_TOL`` and the
+second is high by ``ABS_TOL`` before scaling, then after scaling, the
+second could be high by ``RANDOM_MAX*ABS_TOL``. That is the rationale
+for the factor of ``1 + RANDOM_MAX`` in ``EPSILON``. Since ``1 +
+RANDOM_MAX`` is greater than ``2*ABS_TOL``, we don't need to handle the
+first issue mentioned (both solutions off by the same amount in opposite
+directions).
 """
 
 # Tell pylint to shut up about the large number of methods.
 """
 
 # Tell pylint to shut up about the large number of methods.
@@ -32,12 +36,28 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
     """
     Tests for the SymmetricLinearGame and Solution classes.
     """
     """
     Tests for the SymmetricLinearGame and Solution classes.
     """
-    def assert_within_tol(self, first, second):
+    def assert_within_tol(self, first, second, modifier=1):
         """
         Test that ``first`` and ``second`` are equal within a multiple of
         our default tolerances.
         """
         Test that ``first`` and ``second`` are equal within a multiple of
         our default tolerances.
+
+        Parameters
+        ----------
+
+        first : float
+            The first number to compare.
+
+        second : float
+            The second number to compare.
+
+        modifier : float
+            A scaling factor (default: 1) applied to the default
+            ``EPSILON`` for this comparison. If you have a poorly-
+            conditioned matrix, for example, you may want to set this
+            greater than one.
+
         """
         """
-        self.assertTrue(abs(first - second) < EPSILON)
+        self.assertTrue(abs(first - second) < EPSILON*modifier)
 
 
     def assert_solution_exists(self, G):
 
 
     def assert_solution_exists(self, G):
@@ -48,7 +68,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
 
         expected = inner_product(G._L*soln.player1_optimal(),
                                  soln.player2_optimal())
 
         expected = inner_product(G._L*soln.player1_optimal(),
                                  soln.player2_optimal())
-        self.assert_within_tol(soln.game_value(), expected)
+        self.assert_within_tol(soln.game_value(), expected, G.condition())
 
 
 
 
 
 
@@ -109,7 +129,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         (alpha, H) = random_nn_scaling(G)
         value1 = G.solution().game_value()
         value2 = H.solution().game_value()
         (alpha, H) = random_nn_scaling(G)
         value1 = G.solution().game_value()
         value2 = H.solution().game_value()
-        self.assert_within_tol(alpha*value1, value2)
+        self.assert_within_tol(alpha*value1, value2, H.condition())
 
 
     def test_scaling_orthant(self):
 
 
     def test_scaling_orthant(self):
@@ -147,10 +167,12 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         (alpha, H) = random_translation(G)
         value2 = H.solution().game_value()
 
         (alpha, H) = random_translation(G)
         value2 = H.solution().game_value()
 
-        self.assert_within_tol(value1 + alpha, value2)
+        self.assert_within_tol(value1 + alpha, value2, H.condition())
 
         # Make sure the same optimal pair works.
 
         # Make sure the same optimal pair works.
-        self.assert_within_tol(value2, inner_product(H._L*x_bar, y_bar))
+        self.assert_within_tol(value2,
+                               inner_product(H._L*x_bar, y_bar),
+                               H.condition())
 
 
     def test_translation_orthant(self):
 
 
     def test_translation_orthant(self):
@@ -189,11 +211,14 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         y_bar = soln1.player2_optimal()
         soln2 = H.solution()
 
         y_bar = soln1.player2_optimal()
         soln2 = H.solution()
 
-        self.assert_within_tol(-soln1.game_value(), soln2.game_value())
+        self.assert_within_tol(-soln1.game_value(),
+                               soln2.game_value(),
+                               H.condition())
 
         # Make sure the switched optimal pair works.
         self.assert_within_tol(soln2.game_value(),
 
         # Make sure the switched optimal pair works.
         self.assert_within_tol(soln2.game_value(),
-                               inner_product(M*y_bar, x_bar))
+                               inner_product(M*y_bar, x_bar),
+                               H.condition())
 
 
     def test_opposite_game_orthant(self):
 
 
     def test_opposite_game_orthant(self):
@@ -225,10 +250,10 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
         value = soln.game_value()
 
         ip1 = inner_product(y_bar, G._L*x_bar - value*G._e1)
         value = soln.game_value()
 
         ip1 = inner_product(y_bar, G._L*x_bar - value*G._e1)
-        self.assert_within_tol(ip1, 0)
+        self.assert_within_tol(ip1, 0, G.condition())
 
         ip2 = inner_product(value*G._e2 - G._L.trans()*y_bar, x_bar)
 
         ip2 = inner_product(value*G._e2 - G._L.trans()*y_bar, x_bar)
-        self.assert_within_tol(ip2, 0)
+        self.assert_within_tol(ip2, 0, G.condition())
 
 
     def test_orthogonality_orthant(self):
 
 
     def test_orthogonality_orthant(self):
@@ -286,7 +311,9 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904
 
         # The dual game's value should always equal the primal's.
         dualsoln = G.dual().solution()
 
         # The dual game's value should always equal the primal's.
         dualsoln = G.dual().solution()
-        self.assert_within_tol(dualsoln.game_value(), soln.game_value())
+        self.assert_within_tol(dualsoln.game_value(),
+                               soln.game_value(),
+                               G.condition())
 
 
     def test_lyapunov_orthant(self):
 
 
     def test_lyapunov_orthant(self):