from sage.all import *
-def drop_dependent(vs):
+def _basically_the_same(K1, K2):
r"""
- Return the largest linearly-independent subset of ``vs``.
- """
- result = []
- m = matrix(vs).echelon_form()
- for idx in range(0, m.nrows()):
- if not m[idx].is_zero():
- result.append(m[idx])
+ Test whether or not ``K1`` and ``K2`` are "basically the same."
- return result
+ This is a hack to get around the fact that it's difficult to tell
+ when two cones are linearly isomorphic. We have a proposition that
+ equates two cones, but represented over `\mathbb{Q}`, they are
+ merely linearly isomorphic (not equal). So rather than test for
+ equality, we test a list of properties that should be preserved
+ under an invertible linear transformation.
+ OUTPUT:
-def basically_the_same(K1,K2):
- r"""
``True`` if ``K1`` and ``K2`` are basically the same, and ``False``
otherwise.
+
+ EXAMPLES:
+
+ Any proper cone with three generators in `\mathbb{R}^{3}` is
+ basically the same as the nonnegative orthant::
+
+ sage: K1 = Cone([(1,0,0), (0,1,0), (0,0,1)])
+ sage: K2 = Cone([(1,2,3), (3, 18, 4), (66, 51, 0)])
+ sage: _basically_the_same(K1, K2)
+ True
+
+ Negating a cone gives you another cone that is basically the same::
+
+ sage: K = Cone([(0,2,-5), (-6, 2, 4), (0, 51, 0)])
+ sage: _basically_the_same(K, -K)
+ True
+
+ TESTS:
+
+ Any cone is basically the same as itself::
+
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: _basically_the_same(K, K)
+ True
+
+ After applying an invertible matrix to the rows of a cone, the
+ result should be basically the same as the cone we started with::
+
+ sage: K1 = random_cone(max_ambient_dim = 8)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: _basically_the_same(K1, K2)
+ True
+
"""
if K1.lattice_dim() != K2.lattice_dim():
return False
if K1.dim() != K2.dim():
return False
- if lineality(K1) != lineality(K2):
+ if K1.lineality() != K2.lineality():
return False
if K1.is_solid() != K2.is_solid():
-def rho(K, K2=None):
+def _rho(K, K2=None):
r"""
Restrict ``K`` into its own span, or the span of another cone.
INPUT:
- - ``K2`` -- another cone whose lattice has the same rank as this cone.
+ - ``K2`` -- another cone whose lattice has the same rank as this
+ cone.
OUTPUT:
EXAMPLES::
sage: K = Cone([(1,)])
- sage: rho(K) == K
+ sage: _rho(K) == K
True
sage: K2 = Cone([(1,0)])
- sage: rho(K2).rays()
+ sage: _rho(K2).rays()
N(1)
in 1-d lattice N
sage: K3 = Cone([(1,0,0)])
- sage: rho(K3).rays()
+ sage: _rho(K3).rays()
N(1)
in 1-d lattice N
- sage: rho(K2) == rho(K3)
+ sage: _rho(K2) == _rho(K3)
True
TESTS:
The projected cone should always be solid::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: K_S = rho(K)
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: K_S = _rho(K)
sage: K_S.is_solid()
True
dimension as the space we restricted it to::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: K_S = rho(K, K.dual() )
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: K_S = _rho(K, K.dual() )
sage: K_S.lattice_dim() == K.dual().dim()
True
This function should not affect the dimension of a cone::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: K.dim() == rho(K).dim()
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: K.dim() == _rho(K).dim()
True
Nor should it affect the lineality of a cone::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: lineality(K) == lineality(rho(K))
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: K.lineality() == _rho(K).lineality()
True
No matter which space we restrict to, the lineality should not
increase::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: lineality(K) >= lineality(rho(K))
+ sage: K = random_cone(max_ambient_dim = 8)
+ sage: K.lineality() >= _rho(K).lineality()
True
- sage: lineality(K) >= lineality(rho(K, K.dual()))
+ sage: K.lineality() >= _rho(K, K.dual()).lineality()
True
If we do this according to our paper, then the result is proper::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=False)
- sage: K_S = rho(K)
- sage: P = rho(K_S.dual()).dual()
- sage: P.is_proper()
+ sage: K = random_cone(max_ambient_dim = 8,
+ ....: strictly_convex=False,
+ ....: solid=False)
+ sage: K_S = _rho(K)
+ sage: K_SP = _rho(K_S.dual()).dual()
+ sage: K_SP.is_proper()
True
- sage: P = rho(K_S, K_S.dual())
- sage: P.is_proper()
+ sage: K_SP = _rho(K_S, K_S.dual())
+ sage: K_SP.is_proper()
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=False)
- sage: K_S = rho(K)
- sage: P = rho(K_S.dual()).dual()
- sage: P.is_proper()
+ sage: K = random_cone(max_ambient_dim = 8,
+ ....: strictly_convex=True,
+ ....: solid=False)
+ sage: K_S = _rho(K)
+ sage: K_SP = _rho(K_S.dual()).dual()
+ sage: K_SP.is_proper()
True
- sage: P = rho(K_S, K_S.dual())
- sage: P.is_proper()
+ sage: K_SP = _rho(K_S, K_S.dual())
+ sage: K_SP.is_proper()
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=True)
- sage: K_S = rho(K)
- sage: P = rho(K_S.dual()).dual()
- sage: P.is_proper()
+ sage: K = random_cone(max_ambient_dim = 8,
+ ....: strictly_convex=False,
+ ....: solid=True)
+ sage: K_S = _rho(K)
+ sage: K_SP = _rho(K_S.dual()).dual()
+ sage: K_SP.is_proper()
True
- sage: P = rho(K_S, K_S.dual())
- sage: P.is_proper()
+ sage: K_SP = _rho(K_S, K_S.dual())
+ sage: K_SP.is_proper()
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=True)
- sage: K_S = rho(K)
- sage: P = rho(K_S.dual()).dual()
- sage: P.is_proper()
+ sage: K = random_cone(max_ambient_dim = 8,
+ ....: strictly_convex=True,
+ ....: solid=True)
+ sage: K_S = _rho(K)
+ sage: K_SP = _rho(K_S.dual()).dual()
+ sage: K_SP.is_proper()
True
- sage: P = rho(K_S, K_S.dual())
- sage: P.is_proper()
+ sage: K_SP = _rho(K_S, K_S.dual())
+ sage: K_SP.is_proper()
True
- Test the proposition in our paper concerning the duals, where the
- subspace `W` is the span of `K^{*}`::
+ Test Proposition 7 in our paper concerning the duals and
+ restrictions. Generate a random cone, then create a subcone of
+ it. The operation of dual-taking should then commute with rho::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=False)
- sage: K_W = rho(K, K.dual())
- sage: K_star_W_star = rho(K.dual()).dual()
- sage: basically_the_same(K_W, K_star_W_star)
+ sage: J = random_cone(max_ambient_dim = 8,
+ ....: solid=False,
+ ....: strictly_convex=False)
+ sage: K = Cone(random_sublist(J.rays(), 0.5), lattice=J.lattice())
+ sage: K_W_star = _rho(K, J).dual()
+ sage: K_star_W = _rho(K.dual(), J)
+ sage: _basically_the_same(K_W_star, K_star_W)
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=False)
- sage: K_W = rho(K, K.dual())
- sage: K_star_W_star = rho(K.dual()).dual()
- sage: basically_the_same(K_W, K_star_W_star)
+ sage: J = random_cone(max_ambient_dim = 8,
+ ....: solid=True,
+ ....: strictly_convex=False)
+ sage: K = Cone(random_sublist(J.rays(), 0.5), lattice=J.lattice())
+ sage: K_W_star = _rho(K, J).dual()
+ sage: K_star_W = _rho(K.dual(), J)
+ sage: _basically_the_same(K_W_star, K_star_W)
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=True)
- sage: K_W = rho(K, K.dual())
- sage: K_star_W_star = rho(K.dual()).dual()
- sage: basically_the_same(K_W, K_star_W_star)
+ sage: J = random_cone(max_ambient_dim = 8,
+ ....: solid=False,
+ ....: strictly_convex=True)
+ sage: K = Cone(random_sublist(J.rays(), 0.5), lattice=J.lattice())
+ sage: K_W_star = _rho(K, J).dual()
+ sage: K_star_W = _rho(K.dual(), J)
+ sage: _basically_the_same(K_W_star, K_star_W)
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=True)
- sage: K_W = rho(K, K.dual())
- sage: K_star_W_star = rho(K.dual()).dual()
- sage: basically_the_same(K_W, K_star_W_star)
+ sage: J = random_cone(max_ambient_dim = 8,
+ ....: solid=True,
+ ....: strictly_convex=True)
+ sage: K = Cone(random_sublist(J.rays(), 0.5), lattice=J.lattice())
+ sage: K_W_star = _rho(K, J).dual()
+ sage: K_star_W = _rho(K.dual(), J)
+ sage: _basically_the_same(K_W_star, K_star_W)
True
"""
if K2 is None:
K2 = K
- # First we project K onto the span of K2. This can be done with
- # cones (i.e. without converting to vector spaces), but it's
- # annoying to deal with lattice mismatches.
+ # First we project K onto the span of K2. This will explode if the
+ # rank of ``K2.lattice()`` doesn't match ours.
span_K2 = Cone(K2.rays() + (-K2).rays(), lattice=K.lattice())
K = K.intersection(span_K2)
- V = K.lattice().vector_space()
-
- # Create the space W \times W^{\perp} isomorphic to V.
- # First we get an orthogonal (but not normal) basis...
- W_basis = drop_dependent(K2.rays())
- W = V.subspace_with_basis(W_basis)
+ # Cheat a little to get the subspace span(K2). The paper uses the
+ # rays of K2 as a basis, but everything is invariant under linear
+ # isomorphism (i.e. a change of basis), and this is a little
+ # faster.
+ W = span_K2.linear_subspace()
# We've already intersected K with the span of K2, so every
# generator of K should belong to W now.
-def lineality(K):
- r"""
- Compute the lineality of this cone.
-
- The lineality of a cone is the dimension of the largest linear
- subspace contained in that cone.
-
- OUTPUT:
-
- A nonnegative integer; the dimension of the largest subspace
- contained within this cone.
-
- REFERENCES:
-
- .. [Rockafellar] R.T. Rockafellar. Convex Analysis. Princeton
- University Press, Princeton, 1970.
-
- EXAMPLES:
-
- The lineality of the nonnegative orthant is zero, since it clearly
- contains no lines::
-
- sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)])
- sage: lineality(K)
- 0
-
- However, if we add another ray so that the entire `x`-axis belongs
- to the cone, then the resulting cone will have lineality one::
-
- sage: K = Cone([(1,0,0), (-1,0,0), (0,1,0), (0,0,1)])
- sage: lineality(K)
- 1
-
- If our cone is all of `\mathbb{R}^{2}`, then its lineality is equal
- to the dimension of the ambient space (i.e. two)::
-
- sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
- sage: lineality(K)
- 2
-
- Per the definition, the lineality of the trivial cone in a trivial
- space is zero::
-
- sage: K = Cone([], lattice=ToricLattice(0))
- sage: lineality(K)
- 0
-
- TESTS:
-
- The lineality of a cone should be an integer between zero and the
- dimension of the ambient space, inclusive::
-
- sage: set_random_seed()
- sage: K = random_cone(max_dim = 8)
- sage: l = lineality(K)
- sage: l in ZZ
- True
- sage: (0 <= l) and (l <= K.lattice_dim())
- True
-
- A strictly convex cone should have lineality zero::
-
- sage: set_random_seed()
- sage: K = random_cone(max_dim = 8, strictly_convex = True)
- sage: lineality(K)
- 0
-
- """
- return K.linear_subspace().dimension()
-
-
def discrete_complementarity_set(K):
r"""
Compute the discrete complementarity set of this cone.
- The complementarity set of this cone is the set of all orthogonal
- pairs `(x,s)` such that `x` is in this cone, and `s` is in its
- dual. The discrete complementarity set restricts `x` and `s` to be
- generators of their respective cones.
+ The complementarity set of a cone is the set of all orthogonal pairs
+ `(x,s)` such that `x` is in the cone, and `s` is in its dual. The
+ discrete complementarity set is a subset of the complementarity set
+ where `x` and `s` are required to be generators of their respective
+ cones.
+
+ For polyhedral cones, the discrete complementarity set is always
+ finite.
OUTPUT:
A list of pairs `(x,s)` such that,
- * `x` is in this cone.
+ * Both `x` and `s` are vectors (not rays).
* `x` is a generator of this cone.
- * `s` is in this cone's dual.
* `s` is a generator of this cone's dual.
* `x` and `s` are orthogonal.
+ REFERENCES:
+
+ .. [Orlitzky/Gowda] M. Orlitzky and M. S. Gowda. The Lyapunov Rank of an
+ Improper Cone. Work in-progress.
+
EXAMPLES:
The discrete complementarity set of the nonnegative orthant consists
sage: discrete_complementarity_set(K)
[]
+ Likewise when this cone is trivial (its dual is the entire space)::
+
+ sage: L = ToricLattice(0)
+ sage: K = Cone([], ToricLattice(0))
+ sage: discrete_complementarity_set(K)
+ []
+
TESTS:
The complementarity set of the dual can be obtained by switching the
components of the complementarity set of the original cone::
sage: set_random_seed()
- sage: K1 = random_cone(max_dim=6)
+ sage: K1 = random_cone(max_ambient_dim=6)
sage: K2 = K1.dual()
sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
sage: actual = discrete_complementarity_set(K1)
sage: sorted(actual) == sorted(expected)
True
+ The pairs in the discrete complementarity set are in fact
+ complementary::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=6)
+ sage: dcs = discrete_complementarity_set(K)
+ sage: sum([x.inner_product(s).abs() for (x,s) in dcs])
+ 0
+
"""
V = K.lattice().vector_space()
- # Convert the rays to vectors so that we can compute inner
- # products.
+ # Convert rays to vectors so that we can compute inner products.
xs = [V(x) for x in K.rays()]
+
+ # We also convert the generators of the dual cone so that we
+ # return pairs of vectors and not (vector, ray) pairs.
ss = [V(s) for s in K.dual().rays()]
return [(x,s) for x in xs for s in ss if x.inner_product(s) == 0]
of the cone::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8)
+ sage: K = random_cone(max_ambient_dim=8)
sage: C_of_K = discrete_complementarity_set(K)
sage: l = [ (L*x).inner_product(s) for (x,s) in C_of_K for L in LL(K) ]
sage: sum(map(abs, l))
\right)`
sage: set_random_seed()
- sage: K = random_cone(max_dim=8)
+ sage: K = random_cone(max_ambient_dim=8)
sage: LL2 = [ L.transpose() for L in LL(K.dual()) ]
sage: V = VectorSpace( K.lattice().base_field(), K.lattice_dim()^2)
sage: LL1_vecs = [ V(m.list()) for m in LL(K) ]
sage: K = Cone([e1, neg_e1, e2, neg_e2, z, z, z])
sage: lyapunov_rank(K)
19
- sage: K.lattice_dim()**2 - K.dim()*codim(K)
+ sage: K.lattice_dim()**2 - K.dim()*K.codim()
19
The Lyapunov rank should be additive on a product of proper cones
[Rudolf et al.]_::
sage: set_random_seed()
- sage: K1 = random_cone(max_dim=8, strictly_convex=True, solid=True)
- sage: K2 = random_cone(max_dim=8, strictly_convex=True, solid=True)
+ sage: K1 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
+ sage: K2 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
sage: K = K1.cartesian_product(K2)
sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
True
+ The Lyapunov rank is invariant under a linear isomorphism
+ [Orlitzky/Gowda]_::
+
+ sage: K1 = random_cone(max_ambient_dim = 8)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: lyapunov_rank(K1) == lyapunov_rank(K2)
+ True
+
+ Just to be sure, test a few more::
+
+ sage: K1 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: lyapunov_rank(K1) == lyapunov_rank(K2)
+ True
+
+ ::
+
+ sage: K1 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=False)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: lyapunov_rank(K1) == lyapunov_rank(K2)
+ True
+
+ ::
+
+ sage: K1 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=True)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: lyapunov_rank(K1) == lyapunov_rank(K2)
+ True
+
+ ::
+
+ sage: K1 = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=False)
+ sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
+ sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
+ sage: lyapunov_rank(K1) == lyapunov_rank(K2)
+ True
+
The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
itself [Rudolf et al.]_::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8)
+ sage: K = random_cone(max_ambient_dim=8)
sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
True
Make sure we exercise the non-strictly-convex/non-solid case::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=False)
sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
True
Let's check the other permutations as well, just to be sure::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=True)
sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=False)
sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
True
the Lyapunov rank of the trivial cone will be zero::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
sage: b = lyapunov_rank(K)
sage: n = K.lattice_dim()
sage: (n == 0 or 1 <= b) and b <= n
Lyapunov rank `n-1` in `n` dimensions::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8)
+ sage: K = random_cone(max_ambient_dim=8)
sage: b = lyapunov_rank(K)
sage: n = K.lattice_dim()
sage: b == n-1
reduced to that of a proper cone [Orlitzky/Gowda]_::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8)
+ sage: K = random_cone(max_ambient_dim=8)
sage: actual = lyapunov_rank(K)
- sage: K_S = rho(K)
- sage: P = rho(K_S.dual()).dual()
- sage: l = lineality(K)
- sage: c = codim(K)
- sage: expected = lyapunov_rank(P) + K.dim()*(l + c) + c**2
+ sage: K_S = _rho(K)
+ sage: K_SP = _rho(K_S.dual()).dual()
+ sage: l = K.lineality()
+ sage: c = K.codim()
+ sage: expected = lyapunov_rank(K_SP) + K.dim()*(l + c) + c**2
sage: actual == expected
True
The Lyapunov rank of a proper cone is just the dimension of ``LL(K)``::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
sage: lyapunov_rank(K) == len(LL(K))
True
just increase our confidence that the reduction scheme works::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=False)
sage: lyapunov_rank(K) == len(LL(K))
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=True)
sage: lyapunov_rank(K) == len(LL(K))
True
::
sage: set_random_seed()
- sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=False,
+ ....: solid=False)
sage: lyapunov_rank(K) == len(LL(K))
True
+ Test Theorem 3 in [Orlitzky/Gowda]_::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8,
+ ....: strictly_convex=True,
+ ....: solid=True)
+ sage: L = ToricLattice(K.lattice_dim() + 1)
+ sage: K = Cone([ r.list() + [0] for r in K.rays() ], lattice=L)
+ sage: lyapunov_rank(K) >= K.lattice_dim()
+ True
+
"""
- K_orig = K
beta = 0
m = K.dim()
n = K.lattice_dim()
- l = lineality(K)
+ l = K.lineality()
if m < n:
- # K is not solid, project onto its span.
- K = rho(K)
+ # K is not solid, restrict to its span.
+ K = _rho(K)
# Lemma 2
beta += m*(n - m) + (n - m)**2
if l > 0:
- # K is not pointed, project its dual onto its span.
- # Uses a proposition from our paper, i.e. this is
- # equivalent to K = rho(K.dual()).dual()
- K = rho(K, K.dual())
+ # K is not pointed, restrict to the span of its dual. Uses a
+ # proposition from our paper, i.e. this is equivalent to K =
+ # _rho(K.dual()).dual().
+ K = _rho(K, K.dual())
# Lemma 3
beta += m * l