]>
gitweb.michael.orlitzky.com - sage.d.git/blob - mjo/cone/cone.py
1 # Sage doesn't load ~/.sage/init.sage during testing (sage -t), so we
2 # have to explicitly mangle our sitedir here so that "mjo.cone"
4 from os
.path
import abspath
5 from site
import addsitedir
6 addsitedir(abspath('../../'))
11 def drop_dependent(vs
):
13 Return the largest linearly-independent subset of ``vs``.
16 # ...for lazy enough definitions of linearly-independent
20 old_V
= VectorSpace(vs
[0].parent().base_field(), 0)
23 new_V
= span(result
+ [v
])
24 if new_V
.dimension() > old_V
.dimension():
31 def basically_the_same(K1
,K2
):
33 ``True`` if ``K1`` and ``K2`` are basically the same, and ``False``
36 if K1
.lattice_dim() != K2
.lattice_dim():
39 if K1
.nrays() != K2
.nrays():
42 if K1
.dim() != K2
.dim():
45 if lineality(K1
) != lineality(K2
):
48 if K1
.is_solid() != K2
.is_solid():
51 if K1
.is_strictly_convex() != K2
.is_strictly_convex():
54 if len(LL(K1
)) != len(LL(K2
)):
57 C_of_K1
= discrete_complementarity_set(K1
)
58 C_of_K2
= discrete_complementarity_set(K2
)
59 if len(C_of_K1
) != len(C_of_K2
):
62 if len(K1
.facets()) != len(K2
.facets()):
71 Construct the space `W \times W^{\perp}` isomorphic to the ambient space
72 of ``K`` where `W` is equal to the span of ``K``.
74 V
= K
.lattice().vector_space()
76 # Create the space W \times W^{\perp} isomorphic to V.
77 # First we get an orthogonal (but not normal) basis...
78 M
= matrix(V
.base_field(), K
.rays())
79 W_basis
= drop_dependent(K
.rays())
81 W
= V
.subspace_with_basis(W_basis
)
82 W_perp
= W
.complement()
84 return W
.cartesian_product(W_perp
)
89 Construct the IPS isomorphism and its inverse from our paper.
91 Given a cone ``K``, the returned isomorphism will split its ambient
92 vector space `V` into a cartesian product `W \times W^{\perp}` where
93 `W` equals the span of ``K``.
95 V
= K
.lattice().vector_space()
97 (W
, W_perp
) = V_iso
.cartesian_factors()
99 # A space equivalent to V, but using our basis.
100 V_user
= V
.subspace_with_basis( W
.basis() + W_perp
.basis() )
103 # Write v in terms of our custom basis, where the first dim(W)
104 # coordinates are for the W-part of the basis.
105 cs
= V_user
.coordinates(v
)
107 w1
= sum([ V_user
.basis()[idx
]*cs
[idx
]
108 for idx
in range(0, W
.dimension()) ])
109 w2
= sum([ V_user
.basis()[idx
]*cs
[idx
]
110 for idx
in range(W
.dimension(), V
.dimension()) ])
112 return V_iso( (w1
, w2
) )
116 # Crash if the arguments are in the wrong spaces.
119 #w = sum([ sub_w[idx]*W.basis()[idx] for idx in range(0,m) ])
120 #w_prime = sum([ sub_w_prime[idx]*W_perp.basis()[idx]
121 # for idx in range(0,n-m) ])
123 return sum( pair
.cartesian_factors() )
130 def unrestrict_span(K
, K2
=None):
134 _
,phi_inv
= ips_iso(K2
)
135 V_iso
= iso_space(K2
)
136 (W
, W_perp
) = V_iso
.cartesian_factors()
140 w
= sum([ r
[idx
]*W
.basis()[idx
] for idx
in range(0,len(r
)) ])
141 pair
= V_iso( (w
, W_perp
.zero()) )
142 rays
.append( phi_inv(pair
) )
144 L
= ToricLattice(W
.dimension() + W_perp
.dimension())
146 return Cone(rays
, lattice
=L
)
150 def restrict_span(K
, K2
=None):
152 Restrict ``K`` into its own span, or the span of another cone.
156 - ``K2`` -- another cone whose lattice has the same rank as this cone.
160 A new cone in a sublattice.
164 sage: K = Cone([(1,)])
165 sage: restrict_span(K) == K
168 sage: K2 = Cone([(1,0)])
169 sage: restrict_span(K2).rays()
172 sage: K3 = Cone([(1,0,0)])
173 sage: restrict_span(K3).rays()
176 sage: restrict_span(K2) == restrict_span(K3)
181 The projected cone should always be solid::
183 sage: set_random_seed()
184 sage: K = random_cone(max_dim = 8)
185 sage: K_S = restrict_span(K)
189 And the resulting cone should live in a space having the same
190 dimension as the space we restricted it to::
192 sage: set_random_seed()
193 sage: K = random_cone(max_dim = 8)
194 sage: K_S = restrict_span(K, K.dual() )
195 sage: K_S.lattice_dim() == K.dual().dim()
198 This function has ``unrestrict_span()`` as its inverse::
200 sage: set_random_seed()
201 sage: K = random_cone(max_dim = 8, solid=True)
202 sage: J = restrict_span(K)
203 sage: K == unrestrict_span(J,K)
206 This function should not affect the dimension of a cone::
208 sage: set_random_seed()
209 sage: K = random_cone(max_dim = 8)
210 sage: K.dim() == restrict_span(K).dim()
213 Nor should it affect the lineality of a cone::
215 sage: set_random_seed()
216 sage: K = random_cone(max_dim = 8)
217 sage: lineality(K) == lineality(restrict_span(K))
220 No matter which space we restrict to, the lineality should not
223 sage: set_random_seed()
224 sage: K = random_cone(max_dim = 8)
225 sage: lineality(K) >= lineality(restrict_span(K))
227 sage: lineality(K) >= lineality(restrict_span(K, K.dual()))
230 If we do this according to our paper, then the result is proper::
232 sage: set_random_seed()
233 sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=False)
234 sage: K_S = restrict_span(K)
235 sage: P = restrict_span(K_S.dual()).dual()
238 sage: P = restrict_span(K_S, K_S.dual())
244 sage: set_random_seed()
245 sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=False)
246 sage: K_S = restrict_span(K)
247 sage: P = restrict_span(K_S.dual()).dual()
250 sage: P = restrict_span(K_S, K_S.dual())
256 sage: set_random_seed()
257 sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=True)
258 sage: K_S = restrict_span(K)
259 sage: P = restrict_span(K_S.dual()).dual()
262 sage: P = restrict_span(K_S, K_S.dual())
268 sage: set_random_seed()
269 sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=True)
270 sage: K_S = restrict_span(K)
271 sage: P = restrict_span(K_S.dual()).dual()
274 sage: P = restrict_span(K_S, K_S.dual())
278 Test the proposition in our paper concerning the duals, where the
279 subspace `W` is the span of `K^{*}`::
281 sage: set_random_seed()
282 sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=False)
283 sage: K_W = restrict_span(K, K.dual())
284 sage: K_star_W_star = restrict_span(K.dual()).dual()
285 sage: basically_the_same(K_W, K_star_W_star)
290 sage: set_random_seed()
291 sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=False)
292 sage: K_W = restrict_span(K, K.dual())
293 sage: K_star_W_star = restrict_span(K.dual()).dual()
294 sage: basically_the_same(K_W, K_star_W_star)
299 sage: set_random_seed()
300 sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=True)
301 sage: K_W = restrict_span(K, K.dual())
302 sage: K_star_W_star = restrict_span(K.dual()).dual()
303 sage: basically_the_same(K_W, K_star_W_star)
308 sage: set_random_seed()
309 sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=True)
310 sage: K_W = restrict_span(K, K.dual())
311 sage: K_star_W_star = restrict_span(K.dual()).dual()
312 sage: basically_the_same(K_W, K_star_W_star)
320 (W
, W_perp
) = iso_space(K2
).cartesian_factors()
322 ray_pairs
= [ phi(r
) for r
in K
.rays() ]
326 #if any([ w2 != W_perp.zero() for (_, w2) in ray_pairs ]):
327 # msg = 'Cone has nonzero components in W-perp!'
328 # raise ValueError(msg)
330 # Represent the cone in terms of a basis for W, i.e. with smaller
332 ws
= [ W
.coordinate_vector(w1
) for (w1
, _
) in ray_pairs
]
334 L
= ToricLattice(W
.dimension())
336 return Cone(ws
, lattice
=L
)
342 Compute the lineality of this cone.
344 The lineality of a cone is the dimension of the largest linear
345 subspace contained in that cone.
349 A nonnegative integer; the dimension of the largest subspace
350 contained within this cone.
354 .. [Rockafellar] R.T. Rockafellar. Convex Analysis. Princeton
355 University Press, Princeton, 1970.
359 The lineality of the nonnegative orthant is zero, since it clearly
362 sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)])
366 However, if we add another ray so that the entire `x`-axis belongs
367 to the cone, then the resulting cone will have lineality one::
369 sage: K = Cone([(1,0,0), (-1,0,0), (0,1,0), (0,0,1)])
373 If our cone is all of `\mathbb{R}^{2}`, then its lineality is equal
374 to the dimension of the ambient space (i.e. two)::
376 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
380 Per the definition, the lineality of the trivial cone in a trivial
383 sage: K = Cone([], lattice=ToricLattice(0))
389 The lineality of a cone should be an integer between zero and the
390 dimension of the ambient space, inclusive::
392 sage: set_random_seed()
393 sage: K = random_cone(max_dim = 8)
394 sage: l = lineality(K)
397 sage: (0 <= l) and (l <= K.lattice_dim())
400 A strictly convex cone should have lineality zero::
402 sage: set_random_seed()
403 sage: K = random_cone(max_dim = 8, strictly_convex = True)
408 return K
.linear_subspace().dimension()
413 Compute the codimension of this cone.
415 The codimension of a cone is the dimension of the space of all
416 elements perpendicular to every element of the cone. In other words,
417 the codimension is the difference between the dimension of the
418 ambient space and the dimension of the cone itself.
422 A nonnegative integer representing the dimension of the space of all
423 elements perpendicular to this cone.
427 :meth:`dim`, :meth:`lattice_dim`
431 The codimension of the nonnegative orthant is zero, since the span of
432 its generators equals the entire ambient space::
434 sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)])
438 However, if we remove a ray so that the entire cone is contained
439 within the `x-y`-plane, then the resulting cone will have
440 codimension one, because the `z`-axis is perpendicular to every
441 element of the cone::
443 sage: K = Cone([(1,0,0), (0,1,0)])
447 If our cone is all of `\mathbb{R}^{2}`, then its codimension is zero::
449 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
453 And if the cone is trivial in any space, then its codimension is
454 equal to the dimension of the ambient space::
456 sage: K = Cone([], lattice=ToricLattice(0))
457 sage: K.lattice_dim()
462 sage: K = Cone([(0,)])
463 sage: K.lattice_dim()
468 sage: K = Cone([(0,0)])
469 sage: K.lattice_dim()
476 The codimension of a cone should be an integer between zero and
477 the dimension of the ambient space, inclusive::
479 sage: set_random_seed()
480 sage: K = random_cone(max_dim = 8)
484 sage: (0 <= c) and (c <= K.lattice_dim())
487 A solid cone should have codimension zero::
489 sage: set_random_seed()
490 sage: K = random_cone(max_dim = 8, solid = True)
494 The codimension of a cone is equal to the lineality of its dual::
496 sage: set_random_seed()
497 sage: K = random_cone(max_dim = 8, solid = True)
498 sage: codim(K) == lineality(K.dual())
502 return (K
.lattice_dim() - K
.dim())
505 def discrete_complementarity_set(K
):
507 Compute the discrete complementarity set of this cone.
509 The complementarity set of this cone is the set of all orthogonal
510 pairs `(x,s)` such that `x` is in this cone, and `s` is in its
511 dual. The discrete complementarity set restricts `x` and `s` to be
512 generators of their respective cones.
516 A list of pairs `(x,s)` such that,
518 * `x` is in this cone.
519 * `x` is a generator of this cone.
520 * `s` is in this cone's dual.
521 * `s` is a generator of this cone's dual.
522 * `x` and `s` are orthogonal.
526 The discrete complementarity set of the nonnegative orthant consists
527 of pairs of standard basis vectors::
529 sage: K = Cone([(1,0),(0,1)])
530 sage: discrete_complementarity_set(K)
531 [((1, 0), (0, 1)), ((0, 1), (1, 0))]
533 If the cone consists of a single ray, the second components of the
534 discrete complementarity set should generate the orthogonal
535 complement of that ray::
537 sage: K = Cone([(1,0)])
538 sage: discrete_complementarity_set(K)
539 [((1, 0), (0, 1)), ((1, 0), (0, -1))]
540 sage: K = Cone([(1,0,0)])
541 sage: discrete_complementarity_set(K)
542 [((1, 0, 0), (0, 1, 0)),
543 ((1, 0, 0), (0, -1, 0)),
544 ((1, 0, 0), (0, 0, 1)),
545 ((1, 0, 0), (0, 0, -1))]
547 When the cone is the entire space, its dual is the trivial cone, so
548 the discrete complementarity set is empty::
550 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
551 sage: discrete_complementarity_set(K)
556 The complementarity set of the dual can be obtained by switching the
557 components of the complementarity set of the original cone::
559 sage: set_random_seed()
560 sage: K1 = random_cone(max_dim=6)
562 sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
563 sage: actual = discrete_complementarity_set(K1)
564 sage: sorted(actual) == sorted(expected)
568 V
= K
.lattice().vector_space()
570 # Convert the rays to vectors so that we can compute inner
572 xs
= [V(x
) for x
in K
.rays()]
573 ss
= [V(s
) for s
in K
.dual().rays()]
575 return [(x
,s
) for x
in xs
for s
in ss
if x
.inner_product(s
) == 0]
580 Compute the space `\mathbf{LL}` of all Lyapunov-like transformations
585 A list of matrices forming a basis for the space of all
586 Lyapunov-like transformations on the given cone.
590 The trivial cone has no Lyapunov-like transformations::
592 sage: L = ToricLattice(0)
593 sage: K = Cone([], lattice=L)
597 The Lyapunov-like transformations on the nonnegative orthant are
598 simply diagonal matrices::
600 sage: K = Cone([(1,)])
604 sage: K = Cone([(1,0),(0,1)])
611 sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
614 [1 0 0] [0 0 0] [0 0 0]
615 [0 0 0] [0 1 0] [0 0 0]
616 [0 0 0], [0 0 0], [0 0 1]
619 Only the identity matrix is Lyapunov-like on the `L^{3}_{1}` and
620 `L^{3}_{\infty}` cones [Rudolf et al.]_::
622 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
630 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
638 If our cone is the entire space, then every transformation on it is
641 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
642 sage: M = MatrixSpace(QQ,2)
643 sage: M.basis() == LL(K)
648 The inner product `\left< L\left(x\right), s \right>` is zero for
649 every pair `\left( x,s \right)` in the discrete complementarity set
652 sage: set_random_seed()
653 sage: K = random_cone(max_dim=8)
654 sage: C_of_K = discrete_complementarity_set(K)
655 sage: l = [ (L*x).inner_product(s) for (x,s) in C_of_K for L in LL(K) ]
656 sage: sum(map(abs, l))
659 The Lyapunov-like transformations on a cone and its dual are related
660 by transposition, but we're not guaranteed to compute transposed
661 elements of `LL\left( K \right)` as our basis for `LL\left( K^{*}
664 sage: set_random_seed()
665 sage: K = random_cone(max_dim=8)
666 sage: LL2 = [ L.transpose() for L in LL(K.dual()) ]
667 sage: V = VectorSpace( K.lattice().base_field(), K.lattice_dim()^2)
668 sage: LL1_vecs = [ V(m.list()) for m in LL(K) ]
669 sage: LL2_vecs = [ V(m.list()) for m in LL2 ]
670 sage: V.span(LL1_vecs) == V.span(LL2_vecs)
674 V
= K
.lattice().vector_space()
676 C_of_K
= discrete_complementarity_set(K
)
678 tensor_products
= [ s
.tensor_product(x
) for (x
,s
) in C_of_K
]
680 # Sage doesn't think matrices are vectors, so we have to convert
681 # our matrices to vectors explicitly before we can figure out how
682 # many are linearly-indepenedent.
684 # The space W has the same base ring as V, but dimension
685 # dim(V)^2. So it has the same dimension as the space of linear
686 # transformations on V. In other words, it's just the right size
687 # to create an isomorphism between it and our matrices.
688 W
= VectorSpace(V
.base_ring(), V
.dimension()**2)
690 # Turn our matrices into long vectors...
691 vectors
= [ W(m
.list()) for m
in tensor_products
]
693 # Vector space representation of Lyapunov-like matrices
694 # (i.e. vec(L) where L is Luapunov-like).
695 LL_vector
= W
.span(vectors
).complement()
697 # Now construct an ambient MatrixSpace in which to stick our
699 M
= MatrixSpace(V
.base_ring(), V
.dimension())
701 matrix_basis
= [ M(v
.list()) for v
in LL_vector
.basis() ]
707 def lyapunov_rank(K
):
709 Compute the Lyapunov (or bilinearity) rank of this cone.
711 The Lyapunov rank of a cone can be thought of in (mainly) two ways:
713 1. The dimension of the Lie algebra of the automorphism group of the
716 2. The dimension of the linear space of all Lyapunov-like
717 transformations on the cone.
721 A closed, convex polyhedral cone.
725 An integer representing the Lyapunov rank of the cone. If the
726 dimension of the ambient vector space is `n`, then the Lyapunov rank
727 will be between `1` and `n` inclusive; however a rank of `n-1` is
728 not possible (see the first reference).
732 In the references, the cones are always assumed to be proper. We
733 do not impose this restriction.
741 The codimension formula from the second reference is used. We find
742 all pairs `(x,s)` in the complementarity set of `K` such that `x`
743 and `s` are rays of our cone. It is known that these vectors are
744 sufficient to apply the codimension formula. Once we have all such
745 pairs, we "brute force" the codimension formula by finding all
746 linearly-independent `xs^{T}`.
750 .. [Gowda/Tao] M.S. Gowda and J. Tao. On the bilinearity rank of a proper
751 cone and Lyapunov-like transformations, Mathematical Programming, 147
754 .. [Orlitzky/Gowda] M. Orlitzky and M. S. Gowda. The Lyapunov Rank of an
755 Improper Cone. Work in-progress.
757 .. [Rudolf et al.] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
758 optimality constraints for the cone of positive polynomials,
759 Mathematical Programming, Series B, 129 (2011) 5-31.
763 The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`
766 sage: positives = Cone([(1,)])
767 sage: lyapunov_rank(positives)
769 sage: quadrant = Cone([(1,0), (0,1)])
770 sage: lyapunov_rank(quadrant)
772 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
773 sage: lyapunov_rank(octant)
776 The full space `\mathbb{R}^{n}` has Lyapunov rank `n^{2}`
779 sage: R5 = VectorSpace(QQ, 5)
780 sage: gs = R5.basis() + [ -r for r in R5.basis() ]
782 sage: lyapunov_rank(K)
785 The `L^{3}_{1}` cone is known to have a Lyapunov rank of one
788 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
789 sage: lyapunov_rank(L31)
792 Likewise for the `L^{3}_{\infty}` cone [Rudolf et al.]_::
794 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
795 sage: lyapunov_rank(L3infty)
798 A single ray in `n` dimensions should have Lyapunov rank `n^{2} - n
799 + 1` [Orlitzky/Gowda]_::
801 sage: K = Cone([(1,0,0,0,0)])
802 sage: lyapunov_rank(K)
804 sage: K.lattice_dim()**2 - K.lattice_dim() + 1
807 A subspace (of dimension `m`) in `n` dimensions should have a
808 Lyapunov rank of `n^{2} - m\left(n - m)` [Orlitzky/Gowda]_::
810 sage: e1 = (1,0,0,0,0)
811 sage: neg_e1 = (-1,0,0,0,0)
812 sage: e2 = (0,1,0,0,0)
813 sage: neg_e2 = (0,-1,0,0,0)
814 sage: z = (0,0,0,0,0)
815 sage: K = Cone([e1, neg_e1, e2, neg_e2, z, z, z])
816 sage: lyapunov_rank(K)
818 sage: K.lattice_dim()**2 - K.dim()*codim(K)
821 The Lyapunov rank should be additive on a product of proper cones
824 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
825 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
826 sage: K = L31.cartesian_product(octant)
827 sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
830 Two isomorphic cones should have the same Lyapunov rank [Rudolf et al.]_.
831 The cone ``K`` in the following example is isomorphic to the nonnegative
832 octant in `\mathbb{R}^{3}`::
834 sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
835 sage: lyapunov_rank(K)
838 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
839 itself [Rudolf et al.]_::
841 sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
842 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
847 The Lyapunov rank should be additive on a product of proper cones
850 sage: set_random_seed()
851 sage: K1 = random_cone(max_dim=8, strictly_convex=True, solid=True)
852 sage: K2 = random_cone(max_dim=8, strictly_convex=True, solid=True)
853 sage: K = K1.cartesian_product(K2)
854 sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
857 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
858 itself [Rudolf et al.]_::
860 sage: set_random_seed()
861 sage: K = random_cone(max_dim=8)
862 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
865 Make sure we exercise the non-strictly-convex/non-solid case::
867 sage: set_random_seed()
868 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
869 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
872 Let's check the other permutations as well, just to be sure::
874 sage: set_random_seed()
875 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
876 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
881 sage: set_random_seed()
882 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
883 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
888 sage: set_random_seed()
889 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
890 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
893 The Lyapunov rank of a proper polyhedral cone in `n` dimensions can
894 be any number between `1` and `n` inclusive, excluding `n-1`
895 [Gowda/Tao]_. By accident, the `n-1` restriction will hold for the
896 trivial cone in a trivial space as well. However, in zero dimensions,
897 the Lyapunov rank of the trivial cone will be zero::
899 sage: set_random_seed()
900 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
901 sage: b = lyapunov_rank(K)
902 sage: n = K.lattice_dim()
903 sage: (n == 0 or 1 <= b) and b <= n
908 In fact [Orlitzky/Gowda]_, no closed convex polyhedral cone can have
909 Lyapunov rank `n-1` in `n` dimensions::
911 sage: set_random_seed()
912 sage: K = random_cone(max_dim=8)
913 sage: b = lyapunov_rank(K)
914 sage: n = K.lattice_dim()
918 The calculation of the Lyapunov rank of an improper cone can be
919 reduced to that of a proper cone [Orlitzky/Gowda]_::
921 sage: set_random_seed()
922 sage: K = random_cone(max_dim=8)
923 sage: actual = lyapunov_rank(K)
924 sage: K_S = restrict_span(K)
925 sage: P = restrict_span(K_S.dual()).dual()
926 sage: l = lineality(K)
928 sage: expected = lyapunov_rank(P) + K.dim()*(l + c) + c**2
929 sage: actual == expected
932 The Lyapunov rank of a proper cone is just the dimension of ``LL(K)``::
934 sage: set_random_seed()
935 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
936 sage: lyapunov_rank(K) == len(LL(K))
939 In fact the same can be said of any cone. These additional tests
940 just increase our confidence that the reduction scheme works::
942 sage: set_random_seed()
943 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
944 sage: lyapunov_rank(K) == len(LL(K))
949 sage: set_random_seed()
950 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
951 sage: lyapunov_rank(K) == len(LL(K))
956 sage: set_random_seed()
957 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
958 sage: lyapunov_rank(K) == len(LL(K))
970 # K is not solid, project onto its span.
974 beta
+= m
*(n
- m
) + (n
- m
)**2
977 # K is not pointed, project its dual onto its span.
978 # Uses a proposition from our paper, i.e. this is
979 # equivalent to K = restrict_span(K.dual()).dual()
980 #K = restrict_span(intersect_span(K,K.dual()), K.dual())
981 K
= restrict_span(K
, K
.dual())