]>
gitweb.michael.orlitzky.com - dunshire.git/blob - test/randomgen.py
afe0dae25304b33e2f01e3fd2cb46ca824cc8771
2 Random thing generators used in the rest of the test suite.
4 from random
import randint
, uniform
7 from cvxopt
import matrix
8 from dunshire
.cones
import NonnegativeOrthant
, IceCream
9 from dunshire
.games
import SymmetricLinearGame
10 from dunshire
.matrices
import (append_col
, append_row
, identity
)
14 The maximum condition number of a randomly-generated game.
19 When generating random real numbers or integers, this is used as the
20 largest allowed magnitude. It keeps our condition numbers down and other
21 properties within reason.
26 Generate a random scalar in ``[-RANDOM_MAX, RANDOM_MAX]``.
36 >>> abs(random_scalar()) <= RANDOM_MAX
40 return uniform(-RANDOM_MAX
, RANDOM_MAX
)
43 def random_nn_scalar():
45 Generate a random nonnegative scalar in ``[0, RANDOM_MAX]``.
55 >>> 0 <= random_nn_scalar() <= RANDOM_MAX
59 return abs(random_scalar())
64 Generate a random natural number between ``1 and RANDOM_MAX``
75 >>> 1 <= random_natural() <= RANDOM_MAX
79 return randint(1, RANDOM_MAX
)
82 def random_matrix(row_count
, column_count
=None):
84 Generate a random matrix.
90 The number of rows you want in the returned matrix.
93 The number of columns you want in the returned matrix (default:
94 the same as ``row_count``).
100 A new matrix whose entries are random floats chosen uniformly from
101 the interval [-RANDOM_MAX, RANDOM_MAX].
106 >>> A = random_matrix(3)
110 >>> A = random_matrix(3,2)
115 if column_count
is None:
116 column_count
= row_count
118 entries
= [random_scalar() for _
in range(row_count
*column_count
)]
119 return matrix(entries
, (row_count
, column_count
))
122 def random_nonnegative_matrix(row_count
, column_count
=None):
124 Generate a random matrix with nonnegative entries.
130 The number of rows you want in the returned matrix.
133 The number of columns you want in the returned matrix (default:
134 the same as ``row_count``).
140 A new matrix whose entries are chosen by :func:`random_nn_scalar`.
145 >>> A = random_nonnegative_matrix(3)
148 >>> all([entry >= 0 for entry in A])
151 >>> A = random_nonnegative_matrix(3,2)
154 >>> all([entry >= 0 for entry in A])
158 if column_count
is None:
159 column_count
= row_count
161 entries
= [random_nn_scalar() for _
in range(row_count
*column_count
)]
162 return matrix(entries
, (row_count
, column_count
))
165 def random_diagonal_matrix(dims
):
167 Generate a random square matrix with zero off-diagonal entries.
169 These matrices are Lyapunov-like on the nonnegative orthant, as is
176 The number of rows/columns you want in the returned matrix.
182 A new matrix whose diagonal entries are random floats chosen
183 using func:`random_scalar` and whose off-diagonal entries are
189 >>> A = random_diagonal_matrix(3)
192 >>> A[0,1] == A[0,2] == A[1,0] == A[2,0] == A[1,2] == A[2,1] == 0
196 return matrix([[random_scalar()*int(i
== j
)
197 for i
in range(dims
)]
198 for j
in range(dims
)])
201 def random_skew_symmetric_matrix(dims
):
203 Generate a random skew-symmetrix matrix.
209 The number of rows/columns you want in the returned matrix.
215 A new skew-matrix whose strictly above-diagonal entries are
216 random floats chosen with :func:`random_scalar`.
221 >>> A = random_skew_symmetric_matrix(3)
225 >>> from dunshire.options import ABS_TOL
226 >>> from dunshire.matrices import norm
227 >>> A = random_skew_symmetric_matrix(random_natural())
228 >>> norm(A + A.trans()) < ABS_TOL
232 strict_ut
= [[random_scalar()*int(i
< j
)
233 for i
in range(dims
)]
234 for j
in range(dims
)]
236 strict_ut
= matrix(strict_ut
, (dims
, dims
))
237 return strict_ut
- strict_ut
.trans()
240 def random_lyapunov_like_icecream(dims
):
242 Generate a random matrix Lyapunov-like on the ice-cream cone.
244 The form of these matrices is cited in Gowda and Tao
245 [GowdaTao]_. The scalar ``a`` and the vector ``b`` (using their
246 notation) are easy to generate. The submatrix ``D`` is a little
247 trickier, but it can be found noticing that :math:`C + C^{T} = 0`
248 for a skew-symmetric matrix :math:`C` implying that :math:`C + C^{T}
249 + \left(2a\right)I = \left(2a\right)I`. Thus we can stick an
250 :math:`aI` with each of :math:`C,C^{T}` and let those be our
257 The dimension of the ice-cream cone (not of the matrix you want!)
258 on which the returned matrix should be Lyapunov-like.
264 A new matrix, Lyapunov-like on the ice-cream cone in ``dims``
265 dimensions, whose free entries are random floats chosen uniformly
266 from the interval [-RANDOM_MAX, RANDOM_MAX].
271 .. [GowdaTao] M. S. Gowda and J. Tao. On the bilinearity rank of a
272 proper cone and Lyapunov-like transformations. Mathematical
273 Programming, 147:155-170, 2014.
278 >>> L = random_lyapunov_like_icecream(3)
282 >>> from dunshire.options import ABS_TOL
283 >>> from dunshire.matrices import inner_product
284 >>> x = matrix([1,1,0])
285 >>> s = matrix([1,-1,0])
286 >>> abs(inner_product(L*x, s)) < ABS_TOL
290 a
= matrix([random_scalar()], (1, 1))
291 b
= matrix([random_scalar() for _
in range(dims
-1)], (dims
-1, 1))
292 D
= random_skew_symmetric_matrix(dims
-1) + a
*identity(dims
-1)
293 row1
= append_col(a
, b
.trans())
294 row2
= append_col(b
, D
)
295 return append_row(row1
, row2
)
298 def random_orthant_game():
300 Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a
301 random game over the nonnegative orthant, and return the
302 corresponding :class:`SymmetricLinearGame`.
304 We keep going until we generate a game with a condition number under
307 ambient_dim
= random_natural() + 1
308 K
= NonnegativeOrthant(ambient_dim
)
309 e1
= [random_nn_scalar() for _
in range(K
.dimension())]
310 e2
= [random_nn_scalar() for _
in range(K
.dimension())]
311 L
= random_matrix(K
.dimension())
312 G
= SymmetricLinearGame(L
, K
, e1
, e2
)
314 if G
.condition() <= MAX_COND
:
317 return random_orthant_game()
320 def random_icecream_game():
322 Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a
323 random game over the ice-cream cone, and return the corresponding
324 :class:`SymmetricLinearGame`.
326 We keep going until we generate a game with a condition number under
329 # Use a minimum dimension of two to avoid divide-by-zero in
330 # the fudge factor we make up later.
331 ambient_dim
= random_natural() + 1
332 K
= IceCream(ambient_dim
)
333 e1
= [1] # Set the "height" of e1 to one
334 e2
= [1] # And the same for e2
336 # If we choose the rest of the components of e1,e2 randomly
337 # between 0 and 1, then the largest the squared norm of the
338 # non-height part of e1,e2 could be is the 1*(dim(K) - 1). We
339 # need to make it less than one (the height of the cone) so
340 # that the whole thing is in the cone. The norm of the
341 # non-height part is sqrt(dim(K) - 1), and we can divide by
343 fudge_factor
= 1.0 / (2.0*sqrt(K
.dimension() - 1.0))
344 e1
+= [fudge_factor
*uniform(0, 1) for _
in range(K
.dimension() - 1)]
345 e2
+= [fudge_factor
*uniform(0, 1) for _
in range(K
.dimension() - 1)]
346 L
= random_matrix(K
.dimension())
347 G
= SymmetricLinearGame(L
, K
, e1
, e2
)
349 if G
.condition() <= MAX_COND
:
352 return random_icecream_game()
355 def random_ll_orthant_game():
357 Return a random Lyapunov game over some nonnegative orthant.
359 We first construct a :func:`random_orthant_game` and then modify it
360 to have a :func:`random_diagonal_matrix` as its operator. Such
361 things are Lyapunov-like on the nonnegative orthant. That process is
362 repeated until the condition number of the resulting game is within
365 G
= random_orthant_game()
366 L
= random_diagonal_matrix(G
._K
.dimension())
368 # Replace the totally-random ``L`` with random Lyapunov-like one.
369 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
371 while G
.condition() > MAX_COND
:
372 # Try again until the condition number is satisfactory.
373 G
= random_orthant_game()
374 L
= random_diagonal_matrix(G
._K
.dimension())
375 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
380 def random_ll_icecream_game():
382 Return a random Lyapunov game over some ice-cream cone.
384 We first construct a :func:`random_icecream_game` and then modify it
385 to have a :func:`random_lyapunov_like_icecream` operator. That
386 process is repeated until the condition number of the resulting game
387 is within ``MAX_COND``.
389 G
= random_icecream_game()
390 L
= random_lyapunov_like_icecream(G
._K
.dimension())
392 # Replace the totally-random ``L`` with random Lyapunov-like one.
393 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
395 while G
.condition() > MAX_COND
:
396 # Try again until the condition number is satisfactory.
397 G
= random_icecream_game()
398 L
= random_lyapunov_like_icecream(G
._K
.dimension())
399 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
404 def random_positive_orthant_game():
406 Return a random game over the nonnegative orthant with a positive
409 We first construct a :func:`random_orthant_game` and then modify it
410 to have a :func:`random_nonnegative_matrix` as its operator. That
411 process is repeated until the condition number of the resulting game
412 is within ``MAX_COND``.
415 G
= random_orthant_game()
416 L
= random_nonnegative_matrix(G
._K
.dimension())
418 # Replace the totally-random ``L`` with the random nonnegative one.
419 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
421 while G
.condition() > MAX_COND
:
422 # Try again until the condition number is satisfactory.
423 G
= random_orthant_game()
424 L
= random_nonnegative_matrix(G
._K
.dimension())
425 G
= SymmetricLinearGame(L
, G
._K
, G
._e
1, G
._e
2)
430 def random_nn_scaling(G
):
432 Scale the given game by a random nonnegative amount.
437 G : :class:`SymmetricLinearGame`
438 The game that you would like to scale.
442 (float, :class:`SymmetricLinearGame`)
443 A pair containing the both the scaling factor and the new scaled game.
446 alpha
= random_nn_scalar()
447 H
= SymmetricLinearGame(alpha
*G
._L.trans(), G
._K
, G
._e
1, G
._e
2)
449 while H
.condition() > MAX_COND
:
450 # Loop until the condition number of H doesn't suck.
451 alpha
= random_nn_scalar()
452 H
= SymmetricLinearGame(alpha
*G
._L.trans(), G
._K
, G
._e
1, G
._e
2)
457 def random_translation(G
):
459 Translate the given game by a random amount.
464 G : :class:`SymmetricLinearGame`
465 The game that you would like to translate.
469 (float, :class:`SymmetricLinearGame`)
470 A pair containing the both the translation distance and the new
474 alpha
= random_scalar()
475 tensor_prod
= G
._e
1 * G
._e
2.trans()
476 M
= G
._L + alpha
*tensor_prod
478 H
= SymmetricLinearGame(M
.trans(), G
._K
, G
._e
1, G
._e
2)
479 while H
.condition() > MAX_COND
:
480 # Loop until the condition number of H doesn't suck.
481 alpha
= random_scalar()
482 M
= G
._L + alpha
*tensor_prod
483 H
= SymmetricLinearGame(M
.trans(), G
._K
, G
._e
1, G
._e
2)