from dunshire.games import SymmetricLinearGame
from dunshire.matrices import (append_col, append_row, identity)
-MAX_COND = 250
+MAX_COND = 125
"""
-The maximum condition number of a randomly-generated game.
+The maximum condition number of a randomly-generated game. When the
+condition number of the games gets too high, we start to see
+:class:`PoorScalingException` being thrown. There's no science to
+choosing the upper bound -- it got lowered until those exceptions
+stopped popping up. It's at ``125`` because ``129`` doesn't work.
"""
RANDOM_MAX = 10
def random_scalar():
"""
- Generate a random scalar in ``[-RANDOM_MAX, RANDOM_MAX]``.
+ Generate a random scalar.
Returns
-------
float
+ A random real number between ``-RANDOM_MAX`` and ``RANDOM_MAX``,
+ inclusive.
Examples
--------
def random_nn_scalar():
"""
- Generate a random nonnegative scalar in ``[0, RANDOM_MAX]``.
+ Generate a random nonnegative scalar.
Returns
-------
float
+ A random nonnegative real number between zero and ``RANDOM_MAX``,
+ inclusive.
Examples
--------
def random_natural():
"""
- Generate a random natural number between ``1 and RANDOM_MAX``
- inclusive.
+ Generate a random natural number.
Returns
-------
int
+ A random natural number between ``1`` and ``RANDOM_MAX`` inclusive.
Examples
--------
return randint(1, RANDOM_MAX)
-def random_matrix(dims):
+def random_matrix(row_count, column_count=None):
"""
- Generate a random square matrix.
+ Generate a random matrix.
Parameters
----------
- dims : int
- The number of rows/columns you want in the returned matrix.
+ row_count : int
+ The number of rows you want in the returned matrix.
+
+ column_count: int
+ The number of columns you want in the returned matrix (default:
+ the same as ``row_count``).
Returns
-------
matrix
A new matrix whose entries are random floats chosen uniformly from
- the interval [-RANDOM_MAX, RANDOM_MAX].
+ the interval ``[-RANDOM_MAX, RANDOM_MAX]``.
Examples
--------
>>> A.size
(3, 3)
+ >>> A = random_matrix(3,2)
+ >>> A.size
+ (3, 2)
+
"""
- return matrix([[random_scalar()
- for _ in range(dims)]
- for _ in range(dims)])
+ if column_count is None:
+ column_count = row_count
+
+ entries = [random_scalar() for _ in range(row_count*column_count)]
+ return matrix(entries, (row_count, column_count))
-def random_nonnegative_matrix(dims):
+def random_nonnegative_matrix(row_count, column_count=None):
"""
- Generate a random square matrix with nonnegative entries.
+ Generate a random matrix with nonnegative entries.
Parameters
----------
- dims : int
- The number of rows/columns you want in the returned matrix.
+ row_count : int
+ The number of rows you want in the returned matrix.
+
+ column_count : int
+ The number of columns you want in the returned matrix (default:
+ the same as ``row_count``).
Returns
-------
>>> all([entry >= 0 for entry in A])
True
+ >>> A = random_nonnegative_matrix(3,2)
+ >>> A.size
+ (3, 2)
+ >>> all([entry >= 0 for entry in A])
+ True
+
"""
- return matrix([[random_nn_scalar()
- for _ in range(dims)]
- for _ in range(dims)])
+ if column_count is None:
+ column_count = row_count
+
+ entries = [random_nn_scalar() for _ in range(row_count*column_count)]
+ return matrix(entries, (row_count, column_count))
def random_diagonal_matrix(dims):
>>> A.size
(3, 3)
+ >>> from dunshire.options import ABS_TOL
>>> from dunshire.matrices import norm
>>> A = random_skew_symmetric_matrix(random_natural())
- >>> norm(A + A.trans()) < options.ABS_TOL
+ >>> norm(A + A.trans()) < ABS_TOL
True
"""
matrix
A new matrix, Lyapunov-like on the ice-cream cone in ``dims``
dimensions, whose free entries are random floats chosen uniformly
- from the interval [-RANDOM_MAX, RANDOM_MAX].
+ from the interval ``[-RANDOM_MAX, RANDOM_MAX]``.
References
----------
>>> L = random_lyapunov_like_icecream(3)
>>> L.size
(3, 3)
+
+ >>> from dunshire.options import ABS_TOL
+ >>> from dunshire.matrices import inner_product
>>> x = matrix([1,1,0])
>>> s = matrix([1,-1,0])
- >>> abs(inner_product(L*x, s)) < options.ABS_TOL
+ >>> abs(inner_product(L*x, s)) < ABS_TOL
True
"""
def random_orthant_game():
"""
- Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a
- random game over the nonnegative orthant, and return the
- corresponding :class:`SymmetricLinearGame`.
+ Generate a random game over the nonnegative orthant.
+
+ We generate each of ``L``, ``K``, ``e1``, and ``e2`` randomly within
+ the constraints of the nonnegative orthant, and then construct a
+ game from them. The process is repeated until we generate a game with
+ a condition number under ``MAX_COND``.
+
+ Returns
+ -------
+
+ SymmetricLinearGame
+ A random game over some nonnegative orthant.
+
+ Examples
+ --------
+
+ >>> random_orthant_game()
+ <dunshire.games.SymmetricLinearGame object at 0x...>
- We keep going until we generate a game with a condition number under
- 5000.
"""
ambient_dim = random_natural() + 1
K = NonnegativeOrthant(ambient_dim)
- e1 = [random_nn_scalar() for _ in range(K.dimension())]
- e2 = [random_nn_scalar() for _ in range(K.dimension())]
+ e1 = [0.1 + random_nn_scalar() for _ in range(K.dimension())]
+ e2 = [0.1 + random_nn_scalar() for _ in range(K.dimension())]
L = random_matrix(K.dimension())
G = SymmetricLinearGame(L, K, e1, e2)
def random_icecream_game():
"""
- Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a
- random game over the ice-cream cone, and return the corresponding
- :class:`SymmetricLinearGame`.
+ Generate a random game over the ice-cream cone.
+
+ We generate each of ``L``, ``K``, ``e1``, and ``e2`` randomly within
+ the constraints of the ice-cream cone, and then construct a game
+ from them. The process is repeated until we generate a game with a
+ condition number under ``MAX_COND``.
+
+ Returns
+ -------
+
+ SymmetricLinearGame
+ A random game over some ice-cream cone.
+
+ Examples
+ --------
+
+ >>> random_icecream_game()
+ <dunshire.games.SymmetricLinearGame object at 0x...>
+
"""
# Use a minimum dimension of two to avoid divide-by-zero in
# the fudge factor we make up later.
def random_ll_orthant_game():
"""
Return a random Lyapunov game over some nonnegative orthant.
+
+ We first construct a :func:`random_orthant_game` and then modify it
+ to have a :func:`random_diagonal_matrix` as its operator. Such
+ things are Lyapunov-like on the nonnegative orthant. That process is
+ repeated until the condition number of the resulting game is within
+ ``MAX_COND``.
+
+ Returns
+ -------
+
+ SymmetricLinearGame
+ A random game over some nonnegative orthant whose ``payoff`` method
+ is based on a Lyapunov-like ``L`` operator.
+
+ Examples
+ --------
+
+ >>> random_ll_orthant_game()
+ <dunshire.games.SymmetricLinearGame object at 0x...>
+
"""
G = random_orthant_game()
- L = random_diagonal_matrix(G._K.dimension())
+ L = random_diagonal_matrix(G.dimension())
# Replace the totally-random ``L`` with random Lyapunov-like one.
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
while G.condition() > MAX_COND:
# Try again until the condition number is satisfactory.
G = random_orthant_game()
- L = random_diagonal_matrix(G._K.dimension())
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ L = random_diagonal_matrix(G.dimension())
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
return G
def random_ll_icecream_game():
"""
Return a random Lyapunov game over some ice-cream cone.
+
+ We first construct a :func:`random_icecream_game` and then modify it
+ to have a :func:`random_lyapunov_like_icecream` operator. That
+ process is repeated until the condition number of the resulting game
+ is within ``MAX_COND``.
+
+ Returns
+ -------
+
+ SymmetricLinearGame
+ A random game over some ice-cream cone whose ``payoff`` method
+ is based on a Lyapunov-like ``L`` operator.
+
+ Examples
+ --------
+
+ >>> random_ll_icecream_game()
+ <dunshire.games.SymmetricLinearGame object at 0x...>
+
"""
G = random_icecream_game()
- L = random_lyapunov_like_icecream(G._K.dimension())
+ L = random_lyapunov_like_icecream(G.dimension())
# Replace the totally-random ``L`` with random Lyapunov-like one.
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
while G.condition() > MAX_COND:
# Try again until the condition number is satisfactory.
G = random_icecream_game()
- L = random_lyapunov_like_icecream(G._K.dimension())
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ L = random_lyapunov_like_icecream(G.dimension())
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
return G
def random_positive_orthant_game():
+ """
+ Return a random game over the nonnegative orthant with a positive
+ operator.
+
+ We first construct a :func:`random_orthant_game` and then modify it
+ to have a :func:`random_nonnegative_matrix` as its operator. That
+ process is repeated until the condition number of the resulting game
+ is within ``MAX_COND``.
+
+ Returns
+ -------
+
+ SymmetricLinearGame
+ A random game over some nonnegative orthant whose ``payoff`` method
+ is based on a positive ``L`` operator.
+
+ Examples
+ --------
+
+ >>> random_positive_orthant_game()
+ <dunshire.games.SymmetricLinearGame object at 0x...>
+
+ """
+
G = random_orthant_game()
- L = random_nonnegative_matrix(G._K.dimension())
+ L = random_nonnegative_matrix(G.dimension())
# Replace the totally-random ``L`` with the random nonnegative one.
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
while G.condition() > MAX_COND:
# Try again until the condition number is satisfactory.
G = random_orthant_game()
- L = random_nonnegative_matrix(G._K.dimension())
- G = SymmetricLinearGame(L, G._K, G._e1, G._e2)
+ L = random_nonnegative_matrix(G.dimension())
+ G = SymmetricLinearGame(L, G.K(), G.e1(), G.e2())
return G
def random_nn_scaling(G):
+ """
+ Scale the given game by a random nonnegative amount.
+
+ We re-attempt the scaling with a new random number until the
+ resulting scaled game has an acceptable condition number.
+
+ Parameters
+ ----------
+
+ G : SymmetricLinearGame
+ The game that you would like to scale.
+
+ Returns
+ -------
+ (float, SymmetricLinearGame)
+ A pair containing the both the scaling factor and the new scaled game.
+
+ Examples
+ --------
+
+ >>> from dunshire.matrices import norm
+ >>> from dunshire.options import ABS_TOL
+ >>> G = random_orthant_game()
+ >>> (alpha, H) = random_nn_scaling(G)
+ >>> alpha >= 0
+ True
+ >>> G.K() == H.K()
+ True
+ >>> norm(G.e1() - H.e1()) < ABS_TOL
+ True
+ >>> norm(G.e2() - H.e2()) < ABS_TOL
+ True
+
+ """
alpha = random_nn_scalar()
- H = SymmetricLinearGame(alpha*G._L.trans(), G._K, G._e1, G._e2)
+ H = SymmetricLinearGame(alpha*G.L().trans(), G.K(), G.e1(), G.e2())
while H.condition() > MAX_COND:
# Loop until the condition number of H doesn't suck.
alpha = random_nn_scalar()
- H = SymmetricLinearGame(alpha*G._L.trans(), G._K, G._e1, G._e2)
+ H = SymmetricLinearGame(alpha*G.L().trans(), G.K(), G.e1(), G.e2())
return (alpha, H)
+
def random_translation(G):
+ """
+ Translate the given game by a random amount.
+
+ We re-attempt the translation with new random scalars until the
+ resulting translated game has an acceptable condition number.
+
+ Parameters
+ ----------
+
+ G : SymmetricLinearGame
+ The game that you would like to translate.
+
+ Returns
+ -------
+ (float, SymmetricLinearGame)
+ A pair containing the both the translation distance and the new
+ scaled game.
+
+ Examples
+ --------
+
+ >>> from dunshire.matrices import norm
+ >>> from dunshire.options import ABS_TOL
+ >>> G = random_orthant_game()
+ >>> (alpha, H) = random_translation(G)
+ >>> G.K() == H.K()
+ True
+ >>> norm(G.e1() - H.e1()) < ABS_TOL
+ True
+ >>> norm(G.e2() - H.e2()) < ABS_TOL
+ True
+
+ """
alpha = random_scalar()
- tensor_prod = G._e1 * G._e2.trans()
- M = G._L + alpha*tensor_prod
+ tensor_prod = G.e1() * G.e2().trans()
+ M = G.L() + alpha*tensor_prod
- H = SymmetricLinearGame(M.trans(), G._K, G._e1, G._e2)
+ H = SymmetricLinearGame(M.trans(), G.K(), G.e1(), G.e2())
while H.condition() > MAX_COND:
# Loop until the condition number of H doesn't suck.
alpha = random_scalar()
- M = G._L + alpha*tensor_prod
- H = SymmetricLinearGame(M.trans(), G._K, G._e1, G._e2)
+ M = G.L() + alpha*tensor_prod
+ H = SymmetricLinearGame(M.trans(), G.K(), G.e1(), G.e2())
return (alpha, H)