From 6344038e97380527bef52ba48ba54cc9180c7a82 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sat, 15 Oct 2016 21:12:16 -0400 Subject: [PATCH] Reorganize the test source code and doc building. --- doc/source/conf.py | 4 - doc/source/index.rst | 93 +++++++- makefile | 6 +- src/test/__init__.py | 1 + src/test/suite.py | 29 +++ .../test}/symmetric_linear_game_test.py | 214 ++++++++++++++---- test/suite.py | 28 --- 7 files changed, 288 insertions(+), 87 deletions(-) create mode 100644 src/test/__init__.py create mode 100644 src/test/suite.py rename {test => src/test}/symmetric_linear_game_test.py (71%) delete mode 100644 test/suite.py diff --git a/doc/source/conf.py b/doc/source/conf.py index d675255..511b821 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -8,22 +8,18 @@ import shlex # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../../src/')) -sys.path.insert(0, os.path.abspath('../../src/dunshire')) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.mathjax', 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', ] -autosummary_generate = True - # Don't automatically test every >>> block in the documentation. This # avoids testing the API docs as part of the documentation build, # which is exactly what we intend, because those are tested as part of diff --git a/doc/source/index.rst b/doc/source/index.rst index 766c5b5..0716d13 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -7,11 +7,88 @@ Dunshire API Documentation ----------------- -.. autosummary:: - :toctree: _autosummary - - dunshire.cones - dunshire.errors - dunshire.options - dunshire.matrices - dunshire.games +dunshire package +================ + +Submodules +---------- + +dunshire.cones module +--------------------- + +.. automodule:: dunshire.cones + :members: + :undoc-members: + :show-inheritance: + +dunshire.errors module +---------------------- + +.. automodule:: dunshire.errors + :members: + :undoc-members: + :show-inheritance: + +dunshire.games module +--------------------- + +.. automodule:: dunshire.games + :members: + :undoc-members: + :show-inheritance: + +dunshire.matrices module +------------------------ + +.. automodule:: dunshire.matrices + :members: + :undoc-members: + :show-inheritance: + +dunshire.options module +----------------------- + +.. automodule:: dunshire.options + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: dunshire + :members: + :undoc-members: + :show-inheritance: + +test package +============ + +Submodules +---------- + +test.suite module +----------------- + +.. automodule:: test.suite + :members: + :undoc-members: + :show-inheritance: + +test.symmetric_linear_game_test module +-------------------------------------- + +.. automodule:: test.symmetric_linear_game_test + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: test + :members: + :undoc-members: + :show-inheritance: diff --git a/makefile b/makefile index 511f2cc..e48e9a0 100644 --- a/makefile +++ b/makefile @@ -1,9 +1,9 @@ PN := dunshire SRCDIR := src/$(PN) +TESTDIR := src/test SRCS := $(SRCDIR)/*.py doc: $(SRCS) doc/source/conf.py doc/makefile - sphinx-apidoc -f -o doc/source $(SRCDIR) cd doc && $(MAKE) html .PHONY: doctest @@ -12,7 +12,7 @@ doctest: .PHONY: check check: - python test/suite.py + PYTHONPATH=src python $(TESTDIR)/suite.py .PHONY: lint lint: @@ -20,4 +20,4 @@ lint: .PHONY: clean clean: - rm -rf $(SRCDIR)/__pycache__ doc/build + rm -rf $(SRCDIR)/__pycache__ $(TESTDIR)/__pycache__ doc/build diff --git a/src/test/__init__.py b/src/test/__init__.py new file mode 100644 index 0000000..c5fbe98 --- /dev/null +++ b/src/test/__init__.py @@ -0,0 +1 @@ +# <3 git. diff --git a/src/test/suite.py b/src/test/suite.py new file mode 100644 index 0000000..078d0c9 --- /dev/null +++ b/src/test/suite.py @@ -0,0 +1,29 @@ +""" +The whole test suite. + +This module compiles the doctests and unittests from the rest of the +codebase into one big TestSuite() and the runs it. +""" + +from unittest import TestLoader, TestSuite, TextTestRunner +from doctest import DocTestSuite + +# Add '../' to our path. +from site import addsitedir +addsitedir('./src') +from dunshire import cones +from dunshire import errors +from dunshire import matrices +from dunshire import games +from test import symmetric_linear_game_test + +if __name__ == '__main__': + suite = TestSuite() + suite.addTest(DocTestSuite(cones)) + suite.addTest(DocTestSuite(errors)) + suite.addTest(DocTestSuite(matrices)) + suite.addTest(DocTestSuite(games)) + suite.addTest(DocTestSuite(symmetric_linear_game_test)) + suite.addTest(TestLoader().loadTestsFromModule(symmetric_linear_game_test)) + runner = TextTestRunner(verbosity=1) + runner.run(suite) diff --git a/test/symmetric_linear_game_test.py b/src/test/symmetric_linear_game_test.py similarity index 71% rename from test/symmetric_linear_game_test.py rename to src/test/symmetric_linear_game_test.py index 5adbb2d..f6f397f 100644 --- a/test/symmetric_linear_game_test.py +++ b/src/test/symmetric_linear_game_test.py @@ -10,42 +10,128 @@ from dunshire.matrices import (append_col, append_row, eigenvalues_re, identity, inner_product) from dunshire import options -def _random_matrix(dims): + +def random_matrix(dims): """ - Generate a random square (``dims``-by-``dims``) matrix. This is used - only by the :class:`SymmetricLinearGameTest` class. + 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 [-10, 10]. + + Examples + -------- + + >>> A = random_matrix(3) + >>> A.size + (3, 3) + """ return matrix([[uniform(-10, 10) for i in range(dims)] for j in range(dims)]) -def _random_nonnegative_matrix(dims): + +def random_nonnegative_matrix(dims): """ - Generate a random square (``dims``-by-``dims``) matrix with - nonnegative entries. This is used only by the - :class:`SymmetricLinearGameTest` class. + 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 random floats chosen uniformly from + the interval [0, 10]. + + Examples + -------- + + >>> A = random_nonnegative_matrix(3) + >>> A.size + (3, 3) + >>> all([entry >= 0 for entry in A]) + True + """ - L = _random_matrix(dims) + L = random_matrix(dims) return matrix([abs(entry) for entry in L], (dims, dims)) -def _random_diagonal_matrix(dims): + +def random_diagonal_matrix(dims): """ - Generate a random square (``dims``-by-``dims``) matrix with nonzero - entries only on the diagonal. This is used only by the - :class:`SymmetricLinearGameTest` class. + 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 + uniformly from the interval [-10, 10] 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([[uniform(-10, 10)*int(i == j) for i in range(dims)] for j in range(dims)]) -def _random_skew_symmetric_matrix(dims): +def random_skew_symmetric_matrix(dims): """ - Generate a random skew-symmetrix (``dims``-by-``dims``) matrix. + 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 uniformly from the interval [-10, 10]. Examples -------- + >>> A = random_skew_symmetric_matrix(3) + >>> A.size + (3, 3) + >>> from dunshire.matrices import norm - >>> A = _random_skew_symmetric_matrix(randint(1, 10)) + >>> A = random_skew_symmetric_matrix(randint(1, 10)) >>> norm(A + A.trans()) < options.ABS_TOL True @@ -57,38 +143,78 @@ def _random_skew_symmetric_matrix(dims): return strict_ut - strict_ut.trans() -def _random_lyapunov_like_icecream(dims): - """ - Generate a random Lyapunov-like matrix over the ice-cream cone in - ``dims`` dimensions. +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 [-10, 10]. + + 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([uniform(-10, 10)], (1, 1)) b = matrix([uniform(-10, 10) for idx in range(dims-1)], (dims-1, 1)) - D = _random_skew_symmetric_matrix(dims-1) + a*identity(dims-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_params(): +def random_orthant_params(): """ Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a - random game over the nonnegative orthant. This is only used by - the :class:`SymmetricLinearGameTest` class. + random game over the nonnegative orthant. """ ambient_dim = randint(1, 10) K = NonnegativeOrthant(ambient_dim) 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()) + L = random_matrix(K.dimension()) return (L, K, matrix(e1), matrix(e2)) -def _random_icecream_params(): +def random_icecream_params(): """ Generate the ``L``, ``K``, ``e1``, and ``e2`` parameters for a - random game over the ice cream cone. This is only used by - the :class:`SymmetricLinearGameTest` class. + random game over the ice-cream cone. """ # Use a minimum dimension of two to avoid divide-by-zero in # the fudge factor we make up later. @@ -107,7 +233,7 @@ def _random_icecream_params(): fudge_factor = 1.0 / (2.0*sqrt(K.dimension() - 1.0)) e1 += [fudge_factor*uniform(0, 1) for idx in range(K.dimension() - 1)] e2 += [fudge_factor*uniform(0, 1) for idx in range(K.dimension() - 1)] - L = _random_matrix(K.dimension()) + L = random_matrix(K.dimension()) return (L, K, matrix(e1), matrix(e2)) @@ -158,7 +284,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 optimal solutions should give us the optimal game value when we apply the payoff operator to them. """ - (L, K, e1, e2) = _random_orthant_params() + (L, K, e1, e2) = random_orthant_params() self.assert_solution_exists(L, K, e1, e2) @@ -167,7 +293,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Like :meth:`test_solution_exists_nonnegative_orthant`, except over the ice cream cone. """ - (L, K, e1, e2) = _random_icecream_params() + (L, K, e1, e2) = random_icecream_params() self.assert_solution_exists(L, K, e1, e2) @@ -203,7 +329,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Test that scaling ``L`` by a nonnegative number scales the value of the game by the same number over the nonnegative orthant. """ - (L, K, e1, e2) = _random_orthant_params() + (L, K, e1, e2) = random_orthant_params() self.assert_scaling_works(L, K, e1, e2) @@ -212,7 +338,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 The same test as :meth:`test_nonnegative_scaling_orthant`, except over the ice cream cone. """ - (L, K, e1, e2) = _random_icecream_params() + (L, K, e1, e2) = random_icecream_params() self.assert_scaling_works(L, K, e1, e2) @@ -250,7 +376,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 """ Test that translation works over the nonnegative orthant. """ - (L, K, e1, e2) = _random_orthant_params() + (L, K, e1, e2) = random_orthant_params() self.assert_translation_works(L, K, e1, e2) @@ -259,7 +385,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 The same as :meth:`test_translation_orthant`, except over the ice cream cone. """ - (L, K, e1, e2) = _random_icecream_params() + (L, K, e1, e2) = random_icecream_params() self.assert_translation_works(L, K, e1, e2) @@ -297,7 +423,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Test the value of the "opposite" game over the nonnegative orthant. """ - (L, K, e1, e2) = _random_orthant_params() + (L, K, e1, e2) = random_orthant_params() self.assert_opposite_game_works(L, K, e1, e2) @@ -306,7 +432,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Like :meth:`test_opposite_game_orthant`, except over the ice-cream cone. """ - (L, K, e1, e2) = _random_icecream_params() + (L, K, e1, e2) = random_icecream_params() self.assert_opposite_game_works(L, K, e1, e2) @@ -335,7 +461,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Check the orthgonality relationships that hold for a solution over the nonnegative orthant. """ - (L, K, e1, e2) = _random_orthant_params() + (L, K, e1, e2) = random_orthant_params() self.assert_orthogonality(L, K, e1, e2) @@ -344,7 +470,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 Check the orthgonality relationships that hold for a solution over the ice-cream cone. """ - (L, K, e1, e2) = _random_icecream_params() + (L, K, e1, e2) = random_icecream_params() self.assert_orthogonality(L, K, e1, e2) @@ -356,8 +482,8 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 This test theoretically applies to the ice-cream cone as well, but we don't know how to make positive operators on that cone. """ - (K, e1, e2) = _random_orthant_params()[1:] - L = _random_nonnegative_matrix(K.dimension()) + (K, e1, e2) = random_orthant_params()[1:] + L = random_nonnegative_matrix(K.dimension()) game = SymmetricLinearGame(L, K, e1, e2) self.assertTrue(game.solution().game_value() >= -options.ABS_TOL) @@ -392,8 +518,8 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 """ Test that a Lyapunov game on the nonnegative orthant works. """ - (K, e1, e2) = _random_orthant_params()[1:] - L = _random_diagonal_matrix(K.dimension()) + (K, e1, e2) = random_orthant_params()[1:] + L = random_diagonal_matrix(K.dimension()) self.assert_lyapunov_works(L, K, e1, e2) @@ -402,7 +528,7 @@ class SymmetricLinearGameTest(TestCase): # pylint: disable=R0904 """ Test that a Lyapunov game on the ice-cream cone works. """ - (K, e1, e2) = _random_icecream_params()[1:] - L = _random_lyapunov_like_icecream(K.dimension()) + (K, e1, e2) = random_icecream_params()[1:] + L = random_lyapunov_like_icecream(K.dimension()) self.assert_lyapunov_works(L, K, e1, e2) diff --git a/test/suite.py b/test/suite.py deleted file mode 100644 index 4025647..0000000 --- a/test/suite.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -The whole test suite. - -This module compiles the doctests and unittests from the rest of the -codebase into one big TestSuite() and the runs it. -""" - -from unittest import TestLoader, TestSuite, TextTestRunner -from doctest import DocTestSuite - -# Add '../' to our path. -from site import addsitedir -addsitedir('./src') -from dunshire import cones -from dunshire import errors -from dunshire import matrices -from dunshire import games -import symmetric_linear_game_test - -suite = TestSuite() -suite.addTest(DocTestSuite(cones)) -suite.addTest(DocTestSuite(errors)) -suite.addTest(DocTestSuite(matrices)) -suite.addTest(DocTestSuite(games)) -suite.addTest(DocTestSuite(symmetric_linear_game_test)) -suite.addTest(TestLoader().loadTestsFromModule(symmetric_linear_game_test)) -runner = TextTestRunner(verbosity=1) -runner.run(suite) -- 2.43.2