def _random_matrix(dims):
"""
- Generate a random square (``dims``-by-``dims``) matrix,
- represented as a list of rows. This is used only by the
- :class:`SymmetricLinearGameTest` class.
+ Generate a random square (``dims``-by-``dims``) matrix. This is used
+ only by the :class:`SymmetricLinearGameTest` class.
"""
- return [[uniform(-10, 10) for i in range(dims)] for j in range(dims)]
+ return matrix([[uniform(-10, 10) for i in range(dims)]
+ for j in range(dims)])
def _random_nonnegative_matrix(dims):
"""
Generate a random square (``dims``-by-``dims``) matrix with
- nonnegative entries, represented as a list of rows. This is used
- only by the :class:`SymmetricLinearGameTest` class.
+ nonnegative entries. This is used only by the
+ :class:`SymmetricLinearGameTest` class.
"""
L = _random_matrix(dims)
- return [[abs(entry) for entry in row] for row in L]
+ return matrix([abs(entry) for entry in L], (dims, dims))
def _random_diagonal_matrix(dims):
"""
Generate a random square (``dims``-by-``dims``) matrix with nonzero
- entries only on the diagonal, represented as a list of rows. This is
- used only by the :class:`SymmetricLinearGameTest` class.
+ entries only on the diagonal. This is used only by the
+ :class:`SymmetricLinearGameTest` class.
"""
- return [[uniform(-10, 10)*int(i == j) for i in range(dims)]
- for j in range(dims)]
+ return matrix([[uniform(-10, 10)*int(i == j) for i in range(dims)]
+ for j in range(dims)])
def _random_orthant_params():
"""
e1 = [uniform(0.5, 10) for idx in range(K.dimension())]
e2 = [uniform(0.5, 10) for idx in range(K.dimension())]
L = _random_matrix(K.dimension())
- return (L, K, e1, e2)
+ return (L, K, matrix(e1), matrix(e2))
def _random_icecream_params():
e2 += [fudge_factor*uniform(0, 1) for idx in range(K.dimension() - 1)]
L = _random_matrix(K.dimension())
- return (L, K, e1, e2)
+ return (L, K, matrix(e1), matrix(e2))
class SymmetricLinearGameTest(TestCase):
Given the parameters needed to construct a SymmetricLinearGame,
ensure that that game has a solution.
"""
- G = SymmetricLinearGame(L, K, e1, e2)
- soln = G.solution()
-
# The matrix() constructor assumes that ``L`` is a list of
# columns, so we transpose it to agree with what
# SymmetricLinearGame() thinks.
- L_matrix = matrix(L).trans()
- expected = inner_product(L_matrix*soln.player1_optimal(),
+ G = SymmetricLinearGame(L.trans(), K, e1, e2)
+ soln = G.solution()
+
+ expected = inner_product(L*soln.player1_optimal(),
soln.player2_optimal())
self.assert_within_tol(soln.game_value(), expected)
Test that scaling ``L`` by a nonnegative number scales the value
of the game by the same number.
"""
- # Make ``L`` a matrix so that we can scale it by alpha. Its
- # random, so who cares if it gets transposed.
- L = matrix(L)
game1 = SymmetricLinearGame(L, K, e1, e2)
value1 = game1.solution().game_value()
Check that translating ``L`` by alpha*(e1*e2.trans()) increases
the value of the associated game by alpha.
"""
- e1 = matrix(e1, (K.dimension(), 1))
- e2 = matrix(e2, (K.dimension(), 1))
- game1 = SymmetricLinearGame(L, K, e1, e2)
+ # We need to use ``L`` later, so make sure we transpose it
+ # before passing it in as a column-indexed matrix.
+ game1 = SymmetricLinearGame(L.trans(), K, e1, e2)
soln1 = game1.solution()
value1 = soln1.game_value()
x_bar = soln1.player1_optimal()
y_bar = soln1.player2_optimal()
- # Make ``L`` a CVXOPT matrix so that we can do math with
- # it. Note that this gives us the "correct" representation of
- # ``L`` (in agreement with what G has), but COLUMN indexed.
alpha = uniform(-10, 10)
- L = matrix(L).trans()
tensor_prod = e1*e2.trans()
- # Likewise, this is the "correct" representation of ``M``, but
- # COLUMN indexed...
+ # This is the "correct" representation of ``M``, but COLUMN
+ # indexed...
M = L + alpha*tensor_prod
# so we have to transpose it when we feed it to the constructor.
value that is the negation of the original game. Comes from
some corollary.
"""
- e1 = matrix(e1, (K.dimension(), 1))
- e2 = matrix(e2, (K.dimension(), 1))
- game1 = SymmetricLinearGame(L, K, e1, e2)
+ # We need to use ``L`` later, so make sure we transpose it
+ # before passing it in as a column-indexed matrix.
+ game1 = SymmetricLinearGame(L.trans(), K, e1, e2)
- # Make ``L`` a CVXOPT matrix so that we can do math with
- # it. Note that this gives us the "correct" representation of
- # ``L`` (in agreement with what G has), but COLUMN indexed.
- L = matrix(L).trans()
-
- # Likewise, this is the "correct" representation of ``M``, but
+ # This is the "correct" representation of ``M``, but
# COLUMN indexed...
M = -L.trans()
Two orthogonality relations hold at an optimal solution, and we
check them here.
"""
- game = SymmetricLinearGame(L, K, e1, e2)
+ # We need to use ``L`` later, so make sure we transpose it
+ # before passing it in as a column-indexed matrix.
+ game = SymmetricLinearGame(L.trans(), K, e1, e2)
soln = game.solution()
x_bar = soln.player1_optimal()
y_bar = soln.player2_optimal()
value = soln.game_value()
- # Make these matrices so that we can compute with them.
- L = matrix(L).trans()
- e1 = matrix(e1, (K.dimension(), 1))
- e2 = matrix(e2, (K.dimension(), 1))
-
ip1 = inner_product(y_bar, L*x_bar - value*e1)
self.assert_within_tol(ip1, 0)
game = SymmetricLinearGame(L, K, e1, e2)
self.assertTrue(game.solution().game_value() >= -options.ABS_TOL)
+
def test_lyapunov_orthant(self):
"""
Test that a Lyapunov game on the nonnegative orthant works.
"""
- (_, K, e1, e2) = _random_orthant_params()
+ (L, K, e1, e2) = _random_orthant_params()
# Ignore that L, we need a diagonal (Lyapunov-like) one.
+ # (And we don't need to transpose those.)
L = _random_diagonal_matrix(K.dimension())
game = SymmetricLinearGame(L, K, e1, e2)
soln = game.solution()
# We only check for positive/negative stability if the game
# value is not basically zero. If the value is that close to
# zero, we just won't check any assertions.
- L = matrix(L).trans()
if soln.game_value() > options.ABS_TOL:
# L should be positive stable
ps = all([eig > -options.ABS_TOL for eig in eigenvalues_re(L)])