]>
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 m
= matrix(vs
).echelon_form()
17 for idx
in range(0, m
.nrows()):
18 if not m
[idx
].is_zero():
24 def basically_the_same(K1
,K2
):
26 ``True`` if ``K1`` and ``K2`` are basically the same, and ``False``
29 if K1
.lattice_dim() != K2
.lattice_dim():
32 if K1
.nrays() != K2
.nrays():
35 if K1
.dim() != K2
.dim():
38 if lineality(K1
) != lineality(K2
):
41 if K1
.is_solid() != K2
.is_solid():
44 if K1
.is_strictly_convex() != K2
.is_strictly_convex():
47 if len(LL(K1
)) != len(LL(K2
)):
50 C_of_K1
= discrete_complementarity_set(K1
)
51 C_of_K2
= discrete_complementarity_set(K2
)
52 if len(C_of_K1
) != len(C_of_K2
):
55 if len(K1
.facets()) != len(K2
.facets()):
64 Restrict ``K`` into its own span, or the span of another cone.
68 - ``K2`` -- another cone whose lattice has the same rank as this cone.
72 A new cone in a sublattice.
76 sage: K = Cone([(1,)])
80 sage: K2 = Cone([(1,0)])
84 sage: K3 = Cone([(1,0,0)])
88 sage: rho(K2) == rho(K3)
93 The projected cone should always be solid::
95 sage: set_random_seed()
96 sage: K = random_cone(max_dim = 8)
101 And the resulting cone should live in a space having the same
102 dimension as the space we restricted it to::
104 sage: set_random_seed()
105 sage: K = random_cone(max_dim = 8)
106 sage: K_S = rho(K, K.dual() )
107 sage: K_S.lattice_dim() == K.dual().dim()
110 This function should not affect the dimension of a cone::
112 sage: set_random_seed()
113 sage: K = random_cone(max_dim = 8)
114 sage: K.dim() == rho(K).dim()
117 Nor should it affect the lineality of a cone::
119 sage: set_random_seed()
120 sage: K = random_cone(max_dim = 8)
121 sage: lineality(K) == lineality(rho(K))
124 No matter which space we restrict to, the lineality should not
127 sage: set_random_seed()
128 sage: K = random_cone(max_dim = 8)
129 sage: lineality(K) >= lineality(rho(K))
131 sage: lineality(K) >= lineality(rho(K, K.dual()))
134 If we do this according to our paper, then the result is proper::
136 sage: set_random_seed()
137 sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=False)
139 sage: P = rho(K_S.dual()).dual()
142 sage: P = rho(K_S, K_S.dual())
148 sage: set_random_seed()
149 sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=False)
151 sage: P = rho(K_S.dual()).dual()
154 sage: P = rho(K_S, K_S.dual())
160 sage: set_random_seed()
161 sage: K = random_cone(max_dim = 8, strictly_convex=False, solid=True)
163 sage: P = rho(K_S.dual()).dual()
166 sage: P = rho(K_S, K_S.dual())
172 sage: set_random_seed()
173 sage: K = random_cone(max_dim = 8, strictly_convex=True, solid=True)
175 sage: P = rho(K_S.dual()).dual()
178 sage: P = rho(K_S, K_S.dual())
182 Test the proposition in our paper concerning the duals, where the
183 subspace `W` is the span of `K^{*}`::
185 sage: set_random_seed()
186 sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=False)
187 sage: K_W = rho(K, K.dual())
188 sage: K_star_W_star = rho(K.dual()).dual()
189 sage: basically_the_same(K_W, K_star_W_star)
194 sage: set_random_seed()
195 sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=False)
196 sage: K_W = rho(K, K.dual())
197 sage: K_star_W_star = rho(K.dual()).dual()
198 sage: basically_the_same(K_W, K_star_W_star)
203 sage: set_random_seed()
204 sage: K = random_cone(max_dim = 8, solid=False, strictly_convex=True)
205 sage: K_W = rho(K, K.dual())
206 sage: K_star_W_star = rho(K.dual()).dual()
207 sage: basically_the_same(K_W, K_star_W_star)
212 sage: set_random_seed()
213 sage: K = random_cone(max_dim = 8, solid=True, strictly_convex=True)
214 sage: K_W = rho(K, K.dual())
215 sage: K_star_W_star = rho(K.dual()).dual()
216 sage: basically_the_same(K_W, K_star_W_star)
223 # First we project K onto the span of K2. This can be done with
224 # cones (i.e. without converting to vector spaces), but it's
225 # annoying to deal with lattice mismatches.
226 span_K2
= Cone(K2
.rays() + (-K2
).rays(), lattice
=K
.lattice())
227 K
= K
.intersection(span_K2
)
229 V
= K
.lattice().vector_space()
231 # Create the space W \times W^{\perp} isomorphic to V.
232 # First we get an orthogonal (but not normal) basis...
233 W_basis
= drop_dependent(K2
.rays())
234 W
= V
.subspace_with_basis(W_basis
)
236 # We've already intersected K with the span of K2, so every
237 # generator of K should belong to W now.
238 W_rays
= [ W
.coordinate_vector(r
) for r
in K
.rays() ]
240 L
= ToricLattice(K2
.dim())
241 return Cone(W_rays
, lattice
=L
)
247 Compute the lineality of this cone.
249 The lineality of a cone is the dimension of the largest linear
250 subspace contained in that cone.
254 A nonnegative integer; the dimension of the largest subspace
255 contained within this cone.
259 .. [Rockafellar] R.T. Rockafellar. Convex Analysis. Princeton
260 University Press, Princeton, 1970.
264 The lineality of the nonnegative orthant is zero, since it clearly
267 sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)])
271 However, if we add another ray so that the entire `x`-axis belongs
272 to the cone, then the resulting cone will have lineality one::
274 sage: K = Cone([(1,0,0), (-1,0,0), (0,1,0), (0,0,1)])
278 If our cone is all of `\mathbb{R}^{2}`, then its lineality is equal
279 to the dimension of the ambient space (i.e. two)::
281 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
285 Per the definition, the lineality of the trivial cone in a trivial
288 sage: K = Cone([], lattice=ToricLattice(0))
294 The lineality of a cone should be an integer between zero and the
295 dimension of the ambient space, inclusive::
297 sage: set_random_seed()
298 sage: K = random_cone(max_dim = 8)
299 sage: l = lineality(K)
302 sage: (0 <= l) and (l <= K.lattice_dim())
305 A strictly convex cone should have lineality zero::
307 sage: set_random_seed()
308 sage: K = random_cone(max_dim = 8, strictly_convex = True)
313 return K
.linear_subspace().dimension()
318 Compute the codimension of this cone.
320 The codimension of a cone is the dimension of the space of all
321 elements perpendicular to every element of the cone. In other words,
322 the codimension is the difference between the dimension of the
323 ambient space and the dimension of the cone itself.
327 A nonnegative integer representing the dimension of the space of all
328 elements perpendicular to this cone.
332 :meth:`dim`, :meth:`lattice_dim`
336 The codimension of the nonnegative orthant is zero, since the span of
337 its generators equals the entire ambient space::
339 sage: K = Cone([(1,0,0), (0,1,0), (0,0,1)])
343 However, if we remove a ray so that the entire cone is contained
344 within the `x-y`-plane, then the resulting cone will have
345 codimension one, because the `z`-axis is perpendicular to every
346 element of the cone::
348 sage: K = Cone([(1,0,0), (0,1,0)])
352 If our cone is all of `\mathbb{R}^{2}`, then its codimension is zero::
354 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
358 And if the cone is trivial in any space, then its codimension is
359 equal to the dimension of the ambient space::
361 sage: K = Cone([], lattice=ToricLattice(0))
362 sage: K.lattice_dim()
367 sage: K = Cone([(0,)])
368 sage: K.lattice_dim()
373 sage: K = Cone([(0,0)])
374 sage: K.lattice_dim()
381 The codimension of a cone should be an integer between zero and
382 the dimension of the ambient space, inclusive::
384 sage: set_random_seed()
385 sage: K = random_cone(max_dim = 8)
389 sage: (0 <= c) and (c <= K.lattice_dim())
392 A solid cone should have codimension zero::
394 sage: set_random_seed()
395 sage: K = random_cone(max_dim = 8, solid = True)
399 The codimension of a cone is equal to the lineality of its dual::
401 sage: set_random_seed()
402 sage: K = random_cone(max_dim = 8, solid = True)
403 sage: codim(K) == lineality(K.dual())
407 return (K
.lattice_dim() - K
.dim())
410 def discrete_complementarity_set(K
):
412 Compute the discrete complementarity set of this cone.
414 The complementarity set of this cone is the set of all orthogonal
415 pairs `(x,s)` such that `x` is in this cone, and `s` is in its
416 dual. The discrete complementarity set restricts `x` and `s` to be
417 generators of their respective cones.
421 A list of pairs `(x,s)` such that,
423 * `x` is in this cone.
424 * `x` is a generator of this cone.
425 * `s` is in this cone's dual.
426 * `s` is a generator of this cone's dual.
427 * `x` and `s` are orthogonal.
431 The discrete complementarity set of the nonnegative orthant consists
432 of pairs of standard basis vectors::
434 sage: K = Cone([(1,0),(0,1)])
435 sage: discrete_complementarity_set(K)
436 [((1, 0), (0, 1)), ((0, 1), (1, 0))]
438 If the cone consists of a single ray, the second components of the
439 discrete complementarity set should generate the orthogonal
440 complement of that ray::
442 sage: K = Cone([(1,0)])
443 sage: discrete_complementarity_set(K)
444 [((1, 0), (0, 1)), ((1, 0), (0, -1))]
445 sage: K = Cone([(1,0,0)])
446 sage: discrete_complementarity_set(K)
447 [((1, 0, 0), (0, 1, 0)),
448 ((1, 0, 0), (0, -1, 0)),
449 ((1, 0, 0), (0, 0, 1)),
450 ((1, 0, 0), (0, 0, -1))]
452 When the cone is the entire space, its dual is the trivial cone, so
453 the discrete complementarity set is empty::
455 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
456 sage: discrete_complementarity_set(K)
461 The complementarity set of the dual can be obtained by switching the
462 components of the complementarity set of the original cone::
464 sage: set_random_seed()
465 sage: K1 = random_cone(max_dim=6)
467 sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
468 sage: actual = discrete_complementarity_set(K1)
469 sage: sorted(actual) == sorted(expected)
473 V
= K
.lattice().vector_space()
475 # Convert the rays to vectors so that we can compute inner
477 xs
= [V(x
) for x
in K
.rays()]
478 ss
= [V(s
) for s
in K
.dual().rays()]
480 return [(x
,s
) for x
in xs
for s
in ss
if x
.inner_product(s
) == 0]
485 Compute the space `\mathbf{LL}` of all Lyapunov-like transformations
490 A list of matrices forming a basis for the space of all
491 Lyapunov-like transformations on the given cone.
495 The trivial cone has no Lyapunov-like transformations::
497 sage: L = ToricLattice(0)
498 sage: K = Cone([], lattice=L)
502 The Lyapunov-like transformations on the nonnegative orthant are
503 simply diagonal matrices::
505 sage: K = Cone([(1,)])
509 sage: K = Cone([(1,0),(0,1)])
516 sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
519 [1 0 0] [0 0 0] [0 0 0]
520 [0 0 0] [0 1 0] [0 0 0]
521 [0 0 0], [0 0 0], [0 0 1]
524 Only the identity matrix is Lyapunov-like on the `L^{3}_{1}` and
525 `L^{3}_{\infty}` cones [Rudolf et al.]_::
527 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
535 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
543 If our cone is the entire space, then every transformation on it is
546 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
547 sage: M = MatrixSpace(QQ,2)
548 sage: M.basis() == LL(K)
553 The inner product `\left< L\left(x\right), s \right>` is zero for
554 every pair `\left( x,s \right)` in the discrete complementarity set
557 sage: set_random_seed()
558 sage: K = random_cone(max_dim=8)
559 sage: C_of_K = discrete_complementarity_set(K)
560 sage: l = [ (L*x).inner_product(s) for (x,s) in C_of_K for L in LL(K) ]
561 sage: sum(map(abs, l))
564 The Lyapunov-like transformations on a cone and its dual are related
565 by transposition, but we're not guaranteed to compute transposed
566 elements of `LL\left( K \right)` as our basis for `LL\left( K^{*}
569 sage: set_random_seed()
570 sage: K = random_cone(max_dim=8)
571 sage: LL2 = [ L.transpose() for L in LL(K.dual()) ]
572 sage: V = VectorSpace( K.lattice().base_field(), K.lattice_dim()^2)
573 sage: LL1_vecs = [ V(m.list()) for m in LL(K) ]
574 sage: LL2_vecs = [ V(m.list()) for m in LL2 ]
575 sage: V.span(LL1_vecs) == V.span(LL2_vecs)
579 V
= K
.lattice().vector_space()
581 C_of_K
= discrete_complementarity_set(K
)
583 tensor_products
= [ s
.tensor_product(x
) for (x
,s
) in C_of_K
]
585 # Sage doesn't think matrices are vectors, so we have to convert
586 # our matrices to vectors explicitly before we can figure out how
587 # many are linearly-indepenedent.
589 # The space W has the same base ring as V, but dimension
590 # dim(V)^2. So it has the same dimension as the space of linear
591 # transformations on V. In other words, it's just the right size
592 # to create an isomorphism between it and our matrices.
593 W
= VectorSpace(V
.base_ring(), V
.dimension()**2)
595 # Turn our matrices into long vectors...
596 vectors
= [ W(m
.list()) for m
in tensor_products
]
598 # Vector space representation of Lyapunov-like matrices
599 # (i.e. vec(L) where L is Luapunov-like).
600 LL_vector
= W
.span(vectors
).complement()
602 # Now construct an ambient MatrixSpace in which to stick our
604 M
= MatrixSpace(V
.base_ring(), V
.dimension())
606 matrix_basis
= [ M(v
.list()) for v
in LL_vector
.basis() ]
612 def lyapunov_rank(K
):
614 Compute the Lyapunov (or bilinearity) rank of this cone.
616 The Lyapunov rank of a cone can be thought of in (mainly) two ways:
618 1. The dimension of the Lie algebra of the automorphism group of the
621 2. The dimension of the linear space of all Lyapunov-like
622 transformations on the cone.
626 A closed, convex polyhedral cone.
630 An integer representing the Lyapunov rank of the cone. If the
631 dimension of the ambient vector space is `n`, then the Lyapunov rank
632 will be between `1` and `n` inclusive; however a rank of `n-1` is
633 not possible (see the first reference).
637 In the references, the cones are always assumed to be proper. We
638 do not impose this restriction.
646 The codimension formula from the second reference is used. We find
647 all pairs `(x,s)` in the complementarity set of `K` such that `x`
648 and `s` are rays of our cone. It is known that these vectors are
649 sufficient to apply the codimension formula. Once we have all such
650 pairs, we "brute force" the codimension formula by finding all
651 linearly-independent `xs^{T}`.
655 .. [Gowda/Tao] M.S. Gowda and J. Tao. On the bilinearity rank of a proper
656 cone and Lyapunov-like transformations, Mathematical Programming, 147
659 .. [Orlitzky/Gowda] M. Orlitzky and M. S. Gowda. The Lyapunov Rank of an
660 Improper Cone. Work in-progress.
662 .. [Rudolf et al.] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
663 optimality constraints for the cone of positive polynomials,
664 Mathematical Programming, Series B, 129 (2011) 5-31.
668 The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`
671 sage: positives = Cone([(1,)])
672 sage: lyapunov_rank(positives)
674 sage: quadrant = Cone([(1,0), (0,1)])
675 sage: lyapunov_rank(quadrant)
677 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
678 sage: lyapunov_rank(octant)
681 The full space `\mathbb{R}^{n}` has Lyapunov rank `n^{2}`
684 sage: R5 = VectorSpace(QQ, 5)
685 sage: gs = R5.basis() + [ -r for r in R5.basis() ]
687 sage: lyapunov_rank(K)
690 The `L^{3}_{1}` cone is known to have a Lyapunov rank of one
693 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
694 sage: lyapunov_rank(L31)
697 Likewise for the `L^{3}_{\infty}` cone [Rudolf et al.]_::
699 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
700 sage: lyapunov_rank(L3infty)
703 A single ray in `n` dimensions should have Lyapunov rank `n^{2} - n
704 + 1` [Orlitzky/Gowda]_::
706 sage: K = Cone([(1,0,0,0,0)])
707 sage: lyapunov_rank(K)
709 sage: K.lattice_dim()**2 - K.lattice_dim() + 1
712 A subspace (of dimension `m`) in `n` dimensions should have a
713 Lyapunov rank of `n^{2} - m\left(n - m)` [Orlitzky/Gowda]_::
715 sage: e1 = (1,0,0,0,0)
716 sage: neg_e1 = (-1,0,0,0,0)
717 sage: e2 = (0,1,0,0,0)
718 sage: neg_e2 = (0,-1,0,0,0)
719 sage: z = (0,0,0,0,0)
720 sage: K = Cone([e1, neg_e1, e2, neg_e2, z, z, z])
721 sage: lyapunov_rank(K)
723 sage: K.lattice_dim()**2 - K.dim()*codim(K)
726 The Lyapunov rank should be additive on a product of proper cones
729 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
730 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
731 sage: K = L31.cartesian_product(octant)
732 sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
735 Two isomorphic cones should have the same Lyapunov rank [Rudolf et al.]_.
736 The cone ``K`` in the following example is isomorphic to the nonnegative
737 octant in `\mathbb{R}^{3}`::
739 sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
740 sage: lyapunov_rank(K)
743 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
744 itself [Rudolf et al.]_::
746 sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
747 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
752 The Lyapunov rank should be additive on a product of proper cones
755 sage: set_random_seed()
756 sage: K1 = random_cone(max_dim=8, strictly_convex=True, solid=True)
757 sage: K2 = random_cone(max_dim=8, strictly_convex=True, solid=True)
758 sage: K = K1.cartesian_product(K2)
759 sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
762 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
763 itself [Rudolf et al.]_::
765 sage: set_random_seed()
766 sage: K = random_cone(max_dim=8)
767 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
770 Make sure we exercise the non-strictly-convex/non-solid case::
772 sage: set_random_seed()
773 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
774 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
777 Let's check the other permutations as well, just to be sure::
779 sage: set_random_seed()
780 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
781 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
786 sage: set_random_seed()
787 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
788 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
793 sage: set_random_seed()
794 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
795 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
798 The Lyapunov rank of a proper polyhedral cone in `n` dimensions can
799 be any number between `1` and `n` inclusive, excluding `n-1`
800 [Gowda/Tao]_. By accident, the `n-1` restriction will hold for the
801 trivial cone in a trivial space as well. However, in zero dimensions,
802 the Lyapunov rank of the trivial cone will be zero::
804 sage: set_random_seed()
805 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
806 sage: b = lyapunov_rank(K)
807 sage: n = K.lattice_dim()
808 sage: (n == 0 or 1 <= b) and b <= n
813 In fact [Orlitzky/Gowda]_, no closed convex polyhedral cone can have
814 Lyapunov rank `n-1` in `n` dimensions::
816 sage: set_random_seed()
817 sage: K = random_cone(max_dim=8)
818 sage: b = lyapunov_rank(K)
819 sage: n = K.lattice_dim()
823 The calculation of the Lyapunov rank of an improper cone can be
824 reduced to that of a proper cone [Orlitzky/Gowda]_::
826 sage: set_random_seed()
827 sage: K = random_cone(max_dim=8)
828 sage: actual = lyapunov_rank(K)
830 sage: P = rho(K_S.dual()).dual()
831 sage: l = lineality(K)
833 sage: expected = lyapunov_rank(P) + K.dim()*(l + c) + c**2
834 sage: actual == expected
837 The Lyapunov rank of a proper cone is just the dimension of ``LL(K)``::
839 sage: set_random_seed()
840 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
841 sage: lyapunov_rank(K) == len(LL(K))
844 In fact the same can be said of any cone. These additional tests
845 just increase our confidence that the reduction scheme works::
847 sage: set_random_seed()
848 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
849 sage: lyapunov_rank(K) == len(LL(K))
854 sage: set_random_seed()
855 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
856 sage: lyapunov_rank(K) == len(LL(K))
861 sage: set_random_seed()
862 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
863 sage: lyapunov_rank(K) == len(LL(K))
875 # K is not solid, project onto its span.
879 beta
+= m
*(n
- m
) + (n
- m
)**2
882 # K is not pointed, project its dual onto its span.
883 # Uses a proposition from our paper, i.e. this is
884 # equivalent to K = rho(K.dual()).dual()