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"""
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
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
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
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.
"""
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):
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):