]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
Ensure that we generate min_rays generators; add more cone tests.
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 19 May 2015 15:02:07 +0000 (11:02 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 19 May 2015 15:02:07 +0000 (11:02 -0400)
mjo/cone/cone.py

index 6ade5e628f1035c99294048c7fb55b4b9c1204d9..132c6d9e8e63ebbc5e0065becd813f341e993dd0 100644 (file)
@@ -7,6 +7,13 @@ addsitedir(abspath('../../'))
 
 from sage.all import *
 
+# TODO: This test fails, maybe due to a bug in the existing cone code.
+#     If we request enough generators to span the space, then the returned
+#     cone should equal the ambient space::
+#
+#        sage: K = random_cone(min_dim=5, max_dim=5, min_rays=10, max_rays=10)
+#        sage: K.lines().dimension() == K.lattice_dim()
+#        True
 
 def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
     r"""
@@ -17,6 +24,18 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
     lower bound is left unspecified, it defaults to zero. Unspecified
     upper bounds will be chosen randomly.
 
+    The number of generating rays is naturally limited to twice the
+    dimension of the ambient space. Take for example $\mathbb{R}^{2}$.
+    You could have the generators $\left\{ \pm e_{1}, \pm e_{2}
+    \right\}$, with cardinality $4 = 2 \cdot 2$; however any other ray
+    in the space is a nonnegative linear combination of those four.
+
+    .. NOTE:
+
+        If you do not explicitly request more than ``2 * max_dim`` rays,
+        a larger number may still be randomly generated. In that case,
+        the returned cone will simply be equal to the entire space.
+
     INPUT:
 
     - ``min_dim`` (default: zero) -- A nonnegative integer representing the
@@ -31,13 +50,24 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
                                       cone.
 
     - ``max_rays`` (default: random) -- A nonnegative integer representing the
-                                        maximum number of generating rays of the
-                                        cone.
+                                        maximum number of generating rays of
+                                        the cone.
 
     OUTPUT:
 
     A new, randomly generated cone.
 
+    A ``ValueError` will be thrown under the following conditions:
+
+      * Any of ``min_dim``, ``max_dim``, ``min_rays``, or ``max_rays``
+        are negative.
+
+      * ``max_dim`` is less than ``min_dim``.
+
+      * ``max_rays`` is less than ``min_rays``.
+
+      * ``min_rays`` is greater than twice ``max_dim``.
+
     EXAMPLES:
 
     If we set the lower/upper bounds to zero, then our result is
@@ -46,12 +76,16 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
         sage: random_cone(0,0,0,0)
         0-d cone in 0-d lattice N
 
-    In fact, as long as we ask for zero rays, we should be able to predict
-    the output when ``min_dim == max_dim``::
+    We can predict the dimension when ``min_dim == max_dim``::
 
         sage: random_cone(min_dim=4, max_dim=4, min_rays=0, max_rays=0)
         0-d cone in 4-d lattice N
 
+    Likewise for the number of rays when ``min_rays == max_rays``::
+
+        sage: random_cone(min_dim=10, max_dim=10, min_rays=10, max_rays=10)
+        10-d cone in 10-d lattice N
+
     TESTS:
 
     It's hard to test the output of a random process, but we can at
@@ -62,18 +96,33 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
         sage: is_Cone(K)        # long time
         True
 
+    The upper/lower bounds are respected::
+
+        sage: K = random_cone(min_dim=5, max_dim=10, min_rays=3, max_rays=4)
+        sage: 5 <= K.lattice_dim() and K.lattice_dim() <= 10
+        True
+        sage: 3 <= K.nrays() and K.nrays() <= 4
+        True
+
     Ensure that an exception is raised when either lower bound is greater
     than its respective upper bound::
 
         sage: random_cone(min_dim=5, max_dim=2)
         Traceback (most recent call last):
         ...
-        ValueError: max_dim must be greater than or equal to min_dim.
+        ValueError: max_dim cannot be less than min_dim.
 
         sage: random_cone(min_rays=5, max_rays=2)
         Traceback (most recent call last):
         ...
-        ValueError: max_rays must be greater than or equal to min_rays.
+        ValueError: max_rays cannot be less than min_rays.
+
+    And if we request too many rays::
+
+        sage: random_cone(min_rays=5, max_dim=1)
+        Traceback (most recent call last):
+        ...
+        ValueError: min_rays cannot be larger than twice max_dim.
 
     """
 
@@ -89,14 +138,16 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
     if max_dim is not None:
         if max_dim < 0:
             raise ValueError('max_dim must be nonnegative.')
-        if (min_dim > max_dim):
-            raise ValueError('max_dim must be greater than or equal to min_dim.')
+        if (max_dim < min_dim):
+            raise ValueError('max_dim cannot be less than min_dim.')
+        if min_rays > 2*max_dim:
+            raise ValueError('min_rays cannot be larger than twice max_dim.')
 
     if max_rays is not None:
         if max_rays < 0:
             raise ValueError('max_rays must be nonnegative.')
-        if (min_rays > max_rays):
-            raise ValueError('max_rays must be greater than or equal to min_rays.')
+        if (max_rays < min_rays):
+            raise ValueError('max_rays cannot be less than min_rays.')
 
 
     def random_min_max(l,u):
@@ -121,11 +172,33 @@ def random_cone(min_dim=0, max_dim=None, min_rays=0, max_rays=None):
     r = random_min_max(min_rays, max_rays)
 
     L = ToricLattice(d)
-    rays = [L.random_element() for i in range(0,r)]
 
-    # The lattice parameter is required when no rays are given, so we
-    # pass it just in case.
-    return Cone(rays, lattice=L)
+    # The rays are trickier to generate, since we could generate v and
+    # 2*v as our "two rays." In that case, the resuting cone would
+    # have one generating ray. To avoid such a situation, we start by
+    # generating ``r`` rays where ``r`` is the number we want to end
+    # up with.
+    #
+    # However, since we're going to *check* whether or not we actually
+    # have ``r``, we need ``r`` rays to be attainable. So we need to
+    # limit ``r`` to twice the dimension of the ambient space.
+    #
+    r = min(r, 2*d)
+    rays = [L.random_element() for i in range(0, r)]
+
+    # (The lattice parameter is required when no rays are given, so we
+    # pass it just in case ``r == 0``).
+    K = Cone(rays, lattice=L)
+
+    # Now if we generated two of the "same" rays, we'll have fewer
+    # generating rays than ``r``. In that case, we keep making up new
+    # rays and recreating the cone until we get the right number of
+    # independent generators.
+    while r > K.nrays():
+        rays.append(L.random_element())
+        K = Cone(rays)
+
+    return K
 
 
 def discrete_complementarity_set(K):