+"""
+Random thing generators used in the rest of the test suite.
+"""
+from random import randint, uniform
+
+from math import sqrt
+from cvxopt import matrix
+from dunshire.cones import NonnegativeOrthant, IceCream
+from dunshire.games import SymmetricLinearGame
+from dunshire.matrices import (append_col, append_row, identity)
+
+MAX_COND = 250
+"""
+The maximum condition number of a randomly-generated game.
+"""
+
+RANDOM_MAX = 10
+"""
+When generating random real numbers or integers, this is used as the
+largest allowed magnitude. It keeps our condition numbers down and other
+properties within reason.
+"""
+
+def random_scalar():
+ """
+ Generate a random scalar in ``[-RANDOM_MAX, RANDOM_MAX]``.
+
+ Returns
+ -------
+
+ float
+
+ Examples
+ --------
+
+ >>> abs(random_scalar()) <= RANDOM_MAX
+ True
+
+ """
+ return uniform(-RANDOM_MAX, RANDOM_MAX)
+
+
+def random_nn_scalar():
+ """
+ Generate a random nonnegative scalar in ``[0, RANDOM_MAX]``.
+
+ Returns
+ -------
+
+ float
+
+ Examples
+ --------
+
+ >>> 0 <= random_nn_scalar() <= RANDOM_MAX
+ True
+
+ """
+ return abs(random_scalar())
+
+
+def random_natural():
+ """
+ Generate a random natural number between ``1 and RANDOM_MAX``
+ inclusive.
+
+ Returns
+ -------
+
+ int
+
+ Examples
+ --------
+
+ >>> 1 <= random_natural() <= RANDOM_MAX
+ True
+
+ """
+ return randint(1, RANDOM_MAX)
+
+
+def random_matrix(dims):
+ """
+ Generate a random square matrix.
+
+ Parameters
+ ----------
+
+ dims : int
+ The number of rows/columns you want in the returned matrix.
+
+ Returns
+ -------
+
+ matrix
+ A new matrix whose entries are random floats chosen uniformly from
+ the interval [-RANDOM_MAX, RANDOM_MAX].
+
+ Examples
+ --------
+
+ >>> A = random_matrix(3)
+ >>> A.size
+ (3, 3)
+
+ """
+ return matrix([[random_scalar()
+ for _ in range(dims)]
+ for _ in range(dims)])
+
+
+def random_nonnegative_matrix(dims):
+ """
+ Generate a random square matrix with nonnegative entries.
+
+ Parameters
+ ----------
+
+ dims : int
+ The number of rows/columns you want in the returned matrix.
+
+ Returns
+ -------
+
+ matrix
+ A new matrix whose entries are chosen by :func:`random_nn_scalar`.
+
+ Examples
+ --------
+
+ >>> A = random_nonnegative_matrix(3)
+ >>> A.size
+ (3, 3)
+ >>> all([entry >= 0 for entry in A])
+ True
+
+ """
+ return matrix([[random_nn_scalar()
+ for _ in range(dims)]
+ for _ in range(dims)])
+
+
+def random_diagonal_matrix(dims):
+ """
+ Generate a random square matrix with zero off-diagonal entries.
+
+ These matrices are Lyapunov-like on the nonnegative orthant, as is
+ fairly easy to see.
+
+ Parameters
+ ----------
+
+ dims : int
+ The number of rows/columns you want in the returned matrix.
+
+ Returns
+ -------
+
+ matrix
+ A new matrix whose diagonal entries are random floats chosen
+ using func:`random_scalar` and whose off-diagonal entries are
+ zero.
+
+ Examples
+ --------
+
+ >>> A = random_diagonal_matrix(3)
+ >>> A.size
+ (3, 3)
+ >>> A[0,1] == A[0,2] == A[1,0] == A[2,0] == A[1,2] == A[2,1] == 0
+ True
+
+ """
+ return matrix([[random_scalar()*int(i == j)
+ for i in range(dims)]
+ for j in range(dims)])
+
+
+def random_skew_symmetric_matrix(dims):
+ """
+ Generate a random skew-symmetrix matrix.
+
+ Parameters
+ ----------
+
+ dims : int
+ The number of rows/columns you want in the returned matrix.
+
+ Returns
+ -------
+
+ matrix
+ A new skew-matrix whose strictly above-diagonal entries are
+ random floats chosen with :func:`random_scalar`.
+
+ Examples
+ --------
+
+ >>> A = random_skew_symmetric_matrix(3)
+ >>> A.size
+ (3, 3)
+
+ >>> from dunshire.matrices import norm
+ >>> A = random_skew_symmetric_matrix(random_natural())
+ >>> norm(A + A.trans()) < options.ABS_TOL
+ True
+
+ """
+ strict_ut = [[random_scalar()*int(i < j)
+ for i in range(dims)]
+ for j in range(dims)]
+
+ strict_ut = matrix(strict_ut, (dims, dims))
+ return strict_ut - strict_ut.trans()
+
+
+def random_lyapunov_like_icecream(dims):
+ r"""
+ Generate a random matrix Lyapunov-like on the ice-cream cone.
+
+ The form of these matrices is cited in Gowda and Tao
+ [GowdaTao]_. The scalar ``a`` and the vector ``b`` (using their
+ notation) are easy to generate. The submatrix ``D`` is a little
+ trickier, but it can be found noticing that :math:`C + C^{T} = 0`
+ for a skew-symmetric matrix :math:`C` implying that :math:`C + C^{T}
+ + \left(2a\right)I = \left(2a\right)I`. Thus we can stick an
+ :math:`aI` with each of :math:`C,C^{T}` and let those be our
+ :math:`D,D^{T}`.
+
+ Parameters
+ ----------
+
+ dims : int
+ The dimension of the ice-cream cone (not of the matrix you want!)
+ on which the returned matrix should be Lyapunov-like.
+
+ Returns
+ -------
+
+ 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].
+
+ References
+ ----------
+
+ .. [GowdaTao] M. S. Gowda and J. Tao. On the bilinearity rank of a
+ proper cone and Lyapunov-like transformations. Mathematical
+ Programming, 147:155-170, 2014.
+
+ Examples
+ --------
+
+ >>> L = random_lyapunov_like_icecream(3)
+ >>> L.size
+ (3, 3)
+ >>> x = matrix([1,1,0])
+ >>> s = matrix([1,-1,0])
+ >>> abs(inner_product(L*x, s)) < options.ABS_TOL
+ True
+
+ """
+ a = matrix([random_scalar()], (1, 1))
+ b = matrix([random_scalar() for _ in range(dims-1)], (dims-1, 1))
+ D = random_skew_symmetric_matrix(dims-1) + a*identity(dims-1)
+ row1 = append_col(a, b.trans())
+ row2 = append_col(b, D)
+ return append_row(row1, row2)
+
+
+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`.
+
+ 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())]
+ L = random_matrix(K.dimension())
+ G = SymmetricLinearGame(L, K, e1, e2)
+
+ if G.condition() <= MAX_COND:
+ return G
+ else:
+ return 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`.
+ """
+ # Use a minimum dimension of two to avoid divide-by-zero in
+ # the fudge factor we make up later.
+ ambient_dim = random_natural() + 1
+ K = IceCream(ambient_dim)
+ e1 = [1] # Set the "height" of e1 to one
+ e2 = [1] # And the same for e2
+
+ # If we choose the rest of the components of e1,e2 randomly
+ # between 0 and 1, then the largest the squared norm of the
+ # non-height part of e1,e2 could be is the 1*(dim(K) - 1). We
+ # need to make it less than one (the height of the cone) so
+ # that the whole thing is in the cone. The norm of the
+ # non-height part is sqrt(dim(K) - 1), and we can divide by
+ # twice that.
+ fudge_factor = 1.0 / (2.0*sqrt(K.dimension() - 1.0))
+ e1 += [fudge_factor*uniform(0, 1) for _ in range(K.dimension() - 1)]
+ e2 += [fudge_factor*uniform(0, 1) for _ in range(K.dimension() - 1)]
+ L = random_matrix(K.dimension())
+ G = SymmetricLinearGame(L, K, e1, e2)
+
+ if G.condition() <= MAX_COND:
+ return G
+ else:
+ return random_icecream_game()
+
+
+def random_ll_orthant_game():
+ """
+ Return a random Lyapunov game over some nonnegative orthant.
+ """
+ G = random_orthant_game()
+ L = random_diagonal_matrix(G._K.dimension())
+
+ # Replace the totally-random ``L`` with random Lyapunov-like one.
+ 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)
+
+ return G
+
+
+def random_ll_icecream_game():
+ """
+ Return a random Lyapunov game over some ice-cream cone.
+ """
+ G = random_icecream_game()
+ L = random_lyapunov_like_icecream(G._K.dimension())
+
+ # Replace the totally-random ``L`` with random Lyapunov-like one.
+ 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)
+
+ return G
+
+
+def random_positive_orthant_game():
+ G = random_orthant_game()
+ L = random_nonnegative_matrix(G._K.dimension())
+
+ # Replace the totally-random ``L`` with the random nonnegative one.
+ 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)
+
+ return G
+
+
+def random_nn_scaling(G):
+ alpha = random_nn_scalar()
+ 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)
+
+ return (alpha, H)
+
+def random_translation(G):
+ alpha = random_scalar()
+ tensor_prod = G._e1 * G._e2.trans()
+ M = G._L + alpha*tensor_prod
+
+ 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)
+
+ return (alpha, H)