]>
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()
316 def discrete_complementarity_set(K
):
318 Compute the discrete complementarity set of this cone.
320 The complementarity set of this cone is the set of all orthogonal
321 pairs `(x,s)` such that `x` is in this cone, and `s` is in its
322 dual. The discrete complementarity set restricts `x` and `s` to be
323 generators of their respective cones.
327 A list of pairs `(x,s)` such that,
329 * `x` is in this cone.
330 * `x` is a generator of this cone.
331 * `s` is in this cone's dual.
332 * `s` is a generator of this cone's dual.
333 * `x` and `s` are orthogonal.
337 The discrete complementarity set of the nonnegative orthant consists
338 of pairs of standard basis vectors::
340 sage: K = Cone([(1,0),(0,1)])
341 sage: discrete_complementarity_set(K)
342 [((1, 0), (0, 1)), ((0, 1), (1, 0))]
344 If the cone consists of a single ray, the second components of the
345 discrete complementarity set should generate the orthogonal
346 complement of that ray::
348 sage: K = Cone([(1,0)])
349 sage: discrete_complementarity_set(K)
350 [((1, 0), (0, 1)), ((1, 0), (0, -1))]
351 sage: K = Cone([(1,0,0)])
352 sage: discrete_complementarity_set(K)
353 [((1, 0, 0), (0, 1, 0)),
354 ((1, 0, 0), (0, -1, 0)),
355 ((1, 0, 0), (0, 0, 1)),
356 ((1, 0, 0), (0, 0, -1))]
358 When the cone is the entire space, its dual is the trivial cone, so
359 the discrete complementarity set is empty::
361 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
362 sage: discrete_complementarity_set(K)
367 The complementarity set of the dual can be obtained by switching the
368 components of the complementarity set of the original cone::
370 sage: set_random_seed()
371 sage: K1 = random_cone(max_dim=6)
373 sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
374 sage: actual = discrete_complementarity_set(K1)
375 sage: sorted(actual) == sorted(expected)
379 V
= K
.lattice().vector_space()
381 # Convert the rays to vectors so that we can compute inner
383 xs
= [V(x
) for x
in K
.rays()]
384 ss
= [V(s
) for s
in K
.dual().rays()]
386 return [(x
,s
) for x
in xs
for s
in ss
if x
.inner_product(s
) == 0]
391 Compute the space `\mathbf{LL}` of all Lyapunov-like transformations
396 A list of matrices forming a basis for the space of all
397 Lyapunov-like transformations on the given cone.
401 The trivial cone has no Lyapunov-like transformations::
403 sage: L = ToricLattice(0)
404 sage: K = Cone([], lattice=L)
408 The Lyapunov-like transformations on the nonnegative orthant are
409 simply diagonal matrices::
411 sage: K = Cone([(1,)])
415 sage: K = Cone([(1,0),(0,1)])
422 sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
425 [1 0 0] [0 0 0] [0 0 0]
426 [0 0 0] [0 1 0] [0 0 0]
427 [0 0 0], [0 0 0], [0 0 1]
430 Only the identity matrix is Lyapunov-like on the `L^{3}_{1}` and
431 `L^{3}_{\infty}` cones [Rudolf et al.]_::
433 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
441 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
449 If our cone is the entire space, then every transformation on it is
452 sage: K = Cone([(1,0), (-1,0), (0,1), (0,-1)])
453 sage: M = MatrixSpace(QQ,2)
454 sage: M.basis() == LL(K)
459 The inner product `\left< L\left(x\right), s \right>` is zero for
460 every pair `\left( x,s \right)` in the discrete complementarity set
463 sage: set_random_seed()
464 sage: K = random_cone(max_dim=8)
465 sage: C_of_K = discrete_complementarity_set(K)
466 sage: l = [ (L*x).inner_product(s) for (x,s) in C_of_K for L in LL(K) ]
467 sage: sum(map(abs, l))
470 The Lyapunov-like transformations on a cone and its dual are related
471 by transposition, but we're not guaranteed to compute transposed
472 elements of `LL\left( K \right)` as our basis for `LL\left( K^{*}
475 sage: set_random_seed()
476 sage: K = random_cone(max_dim=8)
477 sage: LL2 = [ L.transpose() for L in LL(K.dual()) ]
478 sage: V = VectorSpace( K.lattice().base_field(), K.lattice_dim()^2)
479 sage: LL1_vecs = [ V(m.list()) for m in LL(K) ]
480 sage: LL2_vecs = [ V(m.list()) for m in LL2 ]
481 sage: V.span(LL1_vecs) == V.span(LL2_vecs)
485 V
= K
.lattice().vector_space()
487 C_of_K
= discrete_complementarity_set(K
)
489 tensor_products
= [ s
.tensor_product(x
) for (x
,s
) in C_of_K
]
491 # Sage doesn't think matrices are vectors, so we have to convert
492 # our matrices to vectors explicitly before we can figure out how
493 # many are linearly-indepenedent.
495 # The space W has the same base ring as V, but dimension
496 # dim(V)^2. So it has the same dimension as the space of linear
497 # transformations on V. In other words, it's just the right size
498 # to create an isomorphism between it and our matrices.
499 W
= VectorSpace(V
.base_ring(), V
.dimension()**2)
501 # Turn our matrices into long vectors...
502 vectors
= [ W(m
.list()) for m
in tensor_products
]
504 # Vector space representation of Lyapunov-like matrices
505 # (i.e. vec(L) where L is Luapunov-like).
506 LL_vector
= W
.span(vectors
).complement()
508 # Now construct an ambient MatrixSpace in which to stick our
510 M
= MatrixSpace(V
.base_ring(), V
.dimension())
512 matrix_basis
= [ M(v
.list()) for v
in LL_vector
.basis() ]
518 def lyapunov_rank(K
):
520 Compute the Lyapunov (or bilinearity) rank of this cone.
522 The Lyapunov rank of a cone can be thought of in (mainly) two ways:
524 1. The dimension of the Lie algebra of the automorphism group of the
527 2. The dimension of the linear space of all Lyapunov-like
528 transformations on the cone.
532 A closed, convex polyhedral cone.
536 An integer representing the Lyapunov rank of the cone. If the
537 dimension of the ambient vector space is `n`, then the Lyapunov rank
538 will be between `1` and `n` inclusive; however a rank of `n-1` is
539 not possible (see the first reference).
543 In the references, the cones are always assumed to be proper. We
544 do not impose this restriction.
552 The codimension formula from the second reference is used. We find
553 all pairs `(x,s)` in the complementarity set of `K` such that `x`
554 and `s` are rays of our cone. It is known that these vectors are
555 sufficient to apply the codimension formula. Once we have all such
556 pairs, we "brute force" the codimension formula by finding all
557 linearly-independent `xs^{T}`.
561 .. [Gowda/Tao] M.S. Gowda and J. Tao. On the bilinearity rank of a proper
562 cone and Lyapunov-like transformations, Mathematical Programming, 147
565 .. [Orlitzky/Gowda] M. Orlitzky and M. S. Gowda. The Lyapunov Rank of an
566 Improper Cone. Work in-progress.
568 .. [Rudolf et al.] G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
569 optimality constraints for the cone of positive polynomials,
570 Mathematical Programming, Series B, 129 (2011) 5-31.
574 The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`
577 sage: positives = Cone([(1,)])
578 sage: lyapunov_rank(positives)
580 sage: quadrant = Cone([(1,0), (0,1)])
581 sage: lyapunov_rank(quadrant)
583 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
584 sage: lyapunov_rank(octant)
587 The full space `\mathbb{R}^{n}` has Lyapunov rank `n^{2}`
590 sage: R5 = VectorSpace(QQ, 5)
591 sage: gs = R5.basis() + [ -r for r in R5.basis() ]
593 sage: lyapunov_rank(K)
596 The `L^{3}_{1}` cone is known to have a Lyapunov rank of one
599 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
600 sage: lyapunov_rank(L31)
603 Likewise for the `L^{3}_{\infty}` cone [Rudolf et al.]_::
605 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
606 sage: lyapunov_rank(L3infty)
609 A single ray in `n` dimensions should have Lyapunov rank `n^{2} - n
610 + 1` [Orlitzky/Gowda]_::
612 sage: K = Cone([(1,0,0,0,0)])
613 sage: lyapunov_rank(K)
615 sage: K.lattice_dim()**2 - K.lattice_dim() + 1
618 A subspace (of dimension `m`) in `n` dimensions should have a
619 Lyapunov rank of `n^{2} - m\left(n - m)` [Orlitzky/Gowda]_::
621 sage: e1 = (1,0,0,0,0)
622 sage: neg_e1 = (-1,0,0,0,0)
623 sage: e2 = (0,1,0,0,0)
624 sage: neg_e2 = (0,-1,0,0,0)
625 sage: z = (0,0,0,0,0)
626 sage: K = Cone([e1, neg_e1, e2, neg_e2, z, z, z])
627 sage: lyapunov_rank(K)
629 sage: K.lattice_dim()**2 - K.dim()*codim(K)
632 The Lyapunov rank should be additive on a product of proper cones
635 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
636 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
637 sage: K = L31.cartesian_product(octant)
638 sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
641 Two isomorphic cones should have the same Lyapunov rank [Rudolf et al.]_.
642 The cone ``K`` in the following example is isomorphic to the nonnegative
643 octant in `\mathbb{R}^{3}`::
645 sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
646 sage: lyapunov_rank(K)
649 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
650 itself [Rudolf et al.]_::
652 sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
653 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
658 The Lyapunov rank should be additive on a product of proper cones
661 sage: set_random_seed()
662 sage: K1 = random_cone(max_dim=8, strictly_convex=True, solid=True)
663 sage: K2 = random_cone(max_dim=8, strictly_convex=True, solid=True)
664 sage: K = K1.cartesian_product(K2)
665 sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
668 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
669 itself [Rudolf et al.]_::
671 sage: set_random_seed()
672 sage: K = random_cone(max_dim=8)
673 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
676 Make sure we exercise the non-strictly-convex/non-solid case::
678 sage: set_random_seed()
679 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
680 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
683 Let's check the other permutations as well, just to be sure::
685 sage: set_random_seed()
686 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
687 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
692 sage: set_random_seed()
693 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
694 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
699 sage: set_random_seed()
700 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
701 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
704 The Lyapunov rank of a proper polyhedral cone in `n` dimensions can
705 be any number between `1` and `n` inclusive, excluding `n-1`
706 [Gowda/Tao]_. By accident, the `n-1` restriction will hold for the
707 trivial cone in a trivial space as well. However, in zero dimensions,
708 the Lyapunov rank of the trivial cone will be zero::
710 sage: set_random_seed()
711 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
712 sage: b = lyapunov_rank(K)
713 sage: n = K.lattice_dim()
714 sage: (n == 0 or 1 <= b) and b <= n
719 In fact [Orlitzky/Gowda]_, no closed convex polyhedral cone can have
720 Lyapunov rank `n-1` in `n` dimensions::
722 sage: set_random_seed()
723 sage: K = random_cone(max_dim=8)
724 sage: b = lyapunov_rank(K)
725 sage: n = K.lattice_dim()
729 The calculation of the Lyapunov rank of an improper cone can be
730 reduced to that of a proper cone [Orlitzky/Gowda]_::
732 sage: set_random_seed()
733 sage: K = random_cone(max_dim=8)
734 sage: actual = lyapunov_rank(K)
736 sage: P = rho(K_S.dual()).dual()
737 sage: l = lineality(K)
739 sage: expected = lyapunov_rank(P) + K.dim()*(l + c) + c**2
740 sage: actual == expected
743 The Lyapunov rank of a proper cone is just the dimension of ``LL(K)``::
745 sage: set_random_seed()
746 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=True)
747 sage: lyapunov_rank(K) == len(LL(K))
750 In fact the same can be said of any cone. These additional tests
751 just increase our confidence that the reduction scheme works::
753 sage: set_random_seed()
754 sage: K = random_cone(max_dim=8, strictly_convex=True, solid=False)
755 sage: lyapunov_rank(K) == len(LL(K))
760 sage: set_random_seed()
761 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=True)
762 sage: lyapunov_rank(K) == len(LL(K))
767 sage: set_random_seed()
768 sage: K = random_cone(max_dim=8, strictly_convex=False, solid=False)
769 sage: lyapunov_rank(K) == len(LL(K))
781 # K is not solid, project onto its span.
785 beta
+= m
*(n
- m
) + (n
- m
)**2
788 # K is not pointed, project its dual onto its span.
789 # Uses a proposition from our paper, i.e. this is
790 # equivalent to K = rho(K.dual()).dual()