]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/cone/cone.py
Ensure that we generate min_rays generators; add more cone tests.
[sage.d.git] / mjo / cone / cone.py
index 6ade5e628f1035c99294048c7fb55b4b9c1204d9..132c6d9e8e63ebbc5e0065becd813f341e993dd0 100644 (file)
@@ -7,6 +7,13 @@ addsitedir(abspath('../../'))
 
 from sage.all import *
 
 
 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"""
 
 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.
 
     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
     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
                                       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.
 
 
     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
     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
 
         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
 
 
         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
     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
 
         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):
         ...
     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):
         ...
 
         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 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 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):
 
 
     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)
     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):
 
 
 def discrete_complementarity_set(K):