X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=test%2Frandomgen.py;h=76d5f7874b38a7c0d45d2f57355be29053409af2;hb=ee3dc9fe4339b2073253b601c2d9e4b0f0900e8c;hp=6513440152d3e693618267d6b839faabaf8417f4;hpb=7bbec60c0c6be1b7ac28140a51c53fbad19247bf;p=dunshire.git diff --git a/test/randomgen.py b/test/randomgen.py index 6513440..76d5f78 100644 --- a/test/randomgen.py +++ b/test/randomgen.py @@ -9,9 +9,13 @@ from dunshire.cones import NonnegativeOrthant, IceCream from dunshire.games import SymmetricLinearGame from dunshire.matrices import (append_col, append_row, identity) -MAX_COND = 250 +MAX_COND = 150 """ -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. """ RANDOM_MAX = 10 @@ -23,12 +27,14 @@ properties within reason. 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 -------- @@ -42,12 +48,14 @@ def random_scalar(): 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 -------- @@ -61,13 +69,13 @@ def random_nn_scalar(): 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 -------- @@ -79,22 +87,26 @@ def random_natural(): 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 -------- @@ -103,21 +115,31 @@ def random_matrix(dims): >>> 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 ------- @@ -134,10 +156,18 @@ def random_nonnegative_matrix(dims): >>> 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): @@ -241,7 +271,7 @@ def random_lyapunov_like_icecream(dims): 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 ---------- @@ -275,17 +305,30 @@ def random_lyapunov_like_icecream(dims): 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() + - 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) @@ -297,9 +340,25 @@ def random_orthant_game(): 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() + + """ # Use a minimum dimension of two to avoid divide-by-zero in # the fudge factor we make up later. @@ -330,18 +389,38 @@ def random_icecream_game(): 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() + + """ 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 @@ -349,59 +428,170 @@ def random_ll_orthant_game(): 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() + + """ 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() + + + """ + 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)