]>
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 _restrict_to_space(K
, W
):
13 Restrict this cone (up to linear isomorphism) to a vector subspace.
15 This operation not only restricts the cone to a subspace of its
16 ambient space, but also represents the rays of the cone in a new
17 (smaller) lattice corresponding to the subspace. The resulting cone
18 will be linearly isomorphic **but not equal** to the desired
19 restriction, since it has likely undergone a change of basis.
21 To explain the difficulty, consider the cone ``K = Cone([(1,1,1)])``
22 having a single ray. The span of ``K`` is a one-dimensional subspace
23 containing ``K``, yet we have no way to perform operations like
24 :meth:`dual` in the subspace. To represent ``K`` in the space
25 ``K.span()``, we must perform a change of basis and write its sole
26 ray as ``(1,0,0)``. Now the restricted ``Cone([(1,)])`` is linearly
27 isomorphic (but of course not equal) to ``K`` interpreted as living
32 - ``W`` -- The subspace into which this cone will be restricted.
36 A new cone in a sublattice corresponding to ``W``.
40 M. Orlitzky. The Lyapunov rank of an improper cone.
41 http://www.optimization-online.org/DB_HTML/2015/10/5135.html
45 Restricting a solid cone to its own span returns a cone linearly
46 isomorphic to the original::
48 sage: K = Cone([(1,2,3),(-1,1,0),(9,0,-2)])
51 sage: _restrict_to_space(K, K.span()).rays()
57 A single ray restricted to its own span has the same representation
58 regardless of the ambient space::
60 sage: K2 = Cone([(1,0)])
61 sage: K2_S = _restrict_to_space(K2, K2.span()).rays()
65 sage: K3 = Cone([(1,1,1)])
66 sage: K3_S = _restrict_to_space(K3, K3.span()).rays()
73 Restricting to a trivial space gives the trivial cone::
75 sage: K = Cone([(8,3,-1,0),(9,2,2,0),(-4,6,7,0)])
76 sage: trivial_space = K.lattice().vector_space().span([])
77 sage: _restrict_to_space(K, trivial_space)
78 0-d cone in 0-d lattice N
82 Restricting a cone to its own span results in a solid cone::
84 sage: set_random_seed()
85 sage: K = random_cone(max_ambient_dim = 8)
86 sage: K_S = _restrict_to_space(K, K.span())
90 Restricting a cone to its own span should not affect the number of
93 sage: set_random_seed()
94 sage: K = random_cone(max_ambient_dim = 8)
95 sage: K_S = _restrict_to_space(K, K.span())
96 sage: K.nrays() == K_S.nrays()
99 Restricting a cone to its own span should not affect its dimension::
101 sage: set_random_seed()
102 sage: K = random_cone(max_ambient_dim = 8)
103 sage: K_S = _restrict_to_space(K, K.span())
104 sage: K.dim() == K_S.dim()
107 Restricting a cone to its own span should not affects its lineality::
109 sage: set_random_seed()
110 sage: K = random_cone(max_ambient_dim = 8)
111 sage: K_S = _restrict_to_space(K, K.span())
112 sage: K.lineality() == K_S.lineality()
115 Restricting a cone to its own span should not affect the number of
118 sage: set_random_seed()
119 sage: K = random_cone(max_ambient_dim = 8)
120 sage: K_S = _restrict_to_space(K, K.span())
121 sage: len(K.facets()) == len(K_S.facets())
124 Restricting a solid cone to its own span is a linear isomorphism and
125 should not affect the dimension of its ambient space::
127 sage: set_random_seed()
128 sage: K = random_cone(max_ambient_dim = 8, solid = True)
129 sage: K_S = _restrict_to_space(K, K.span())
130 sage: K.lattice_dim() == K_S.lattice_dim()
133 Restricting a solid cone to its own span is a linear isomorphism
134 that establishes a one-to-one correspondence of discrete
135 complementarity sets::
137 sage: set_random_seed()
138 sage: K = random_cone(max_ambient_dim = 8, solid = True)
139 sage: K_S = _restrict_to_space(K, K.span())
140 sage: dcs_K = K.discrete_complementarity_set()
141 sage: dcs_K_S = K_S.discrete_complementarity_set()
142 sage: len(dcs_K) == len(dcs_K_S)
145 Restricting a solid cone to its own span is a linear isomorphism
146 under which the Lyapunov rank (the length of a Lyapunov-like basis)
149 sage: set_random_seed()
150 sage: K = random_cone(max_ambient_dim = 8, solid = True)
151 sage: K_S = _restrict_to_space(K, K.span())
152 sage: len(K.lyapunov_like_basis()) == len(K_S.lyapunov_like_basis())
155 If we restrict a cone to a subspace of its span, the resulting cone
156 should have the same dimension as the space we restricted it to::
158 sage: set_random_seed()
159 sage: K = random_cone(max_ambient_dim = 8)
160 sage: W_basis = random_sublist(K.rays(), 0.5)
161 sage: W = K.lattice().vector_space().span(W_basis)
162 sage: K_W = _restrict_to_space(K, W)
163 sage: K_W.lattice_dim() == W.dimension()
166 Through a series of restrictions, any closed convex cone can be
167 reduced to a cartesian product with a proper factor [Orlitzky]_::
169 sage: set_random_seed()
170 sage: K = random_cone(max_ambient_dim = 8)
171 sage: K_S = _restrict_to_space(K, K.span())
172 sage: K_SP = _restrict_to_space(K_S, K_S.dual().span())
173 sage: K_SP.is_proper()
176 # We want to intersect ``K`` with ``W``. An easy way to do this is
177 # via cone intersection, so we turn the space ``W`` into a cone.
178 W_cone
= Cone(W
.basis() + [-b
for b
in W
.basis()], lattice
=K
.lattice())
179 K
= K
.intersection(W_cone
)
181 # We've already intersected K with W, so every generator of K
182 # should belong to W now.
183 K_W_rays
= [ W
.coordinate_vector(r
) for r
in K
.rays() ]
185 L
= ToricLattice(W
.dimension())
186 return Cone(K_W_rays
, lattice
=L
)
189 def lyapunov_rank(K
):
191 Compute the Lyapunov rank of this cone.
193 The Lyapunov rank of a cone is the dimension of the space of its
194 Lyapunov-like transformations -- that is, the length of a
195 :meth:`lyapunov_like_basis`. Equivalently, the Lyapunov rank is the
196 dimension of the Lie algebra of the automorphism group of the cone.
200 A nonnegative integer representing the Lyapunov rank of this cone.
202 If the ambient space is trivial, the Lyapunov rank will be zero.
203 Otherwise, if the dimension of the ambient vector space is `n`, then
204 the resulting Lyapunov rank will be between `1` and `n` inclusive. A
205 Lyapunov rank of `n-1` is not possible [Orlitzky]_.
209 The codimension formula from the second reference is used. We find
210 all pairs `(x,s)` in the complementarity set of `K` such that `x`
211 and `s` are rays of our cone. It is known that these vectors are
212 sufficient to apply the codimension formula. Once we have all such
213 pairs, we "brute force" the codimension formula by finding all
214 linearly-independent `xs^{T}`.
218 .. [Gowda/Tao] M.S. Gowda and J. Tao. On the bilinearity rank of
219 a proper cone and Lyapunov-like transformations. Mathematical
220 Programming, 147 (2014) 155-170.
222 M. Orlitzky. The Lyapunov rank of an improper cone.
223 http://www.optimization-online.org/DB_HTML/2015/10/5135.html
225 G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
226 optimality constraints for the cone of positive polynomials,
227 Mathematical Programming, Series B, 129 (2011) 5-31.
231 The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`
234 sage: positives = Cone([(1,)])
235 sage: lyapunov_rank(positives)
237 sage: quadrant = Cone([(1,0), (0,1)])
238 sage: lyapunov_rank(quadrant)
240 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
241 sage: lyapunov_rank(octant)
244 The full space `\mathbb{R}^{n}` has Lyapunov rank `n^{2}`
247 sage: R5 = VectorSpace(QQ, 5)
248 sage: gs = R5.basis() + [ -r for r in R5.basis() ]
250 sage: lyapunov_rank(K)
253 The `L^{3}_{1}` cone is known to have a Lyapunov rank of one
256 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
257 sage: lyapunov_rank(L31)
260 Likewise for the `L^{3}_{\infty}` cone [Rudolf]_::
262 sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
263 sage: lyapunov_rank(L3infty)
266 A single ray in `n` dimensions should have Lyapunov rank `n^{2} - n
269 sage: K = Cone([(1,0,0,0,0)])
270 sage: lyapunov_rank(K)
272 sage: K.lattice_dim()**2 - K.lattice_dim() + 1
275 A subspace (of dimension `m`) in `n` dimensions should have a
276 Lyapunov rank of `n^{2} - m\left(n - m)` [Orlitzky]_::
278 sage: e1 = (1,0,0,0,0)
279 sage: neg_e1 = (-1,0,0,0,0)
280 sage: e2 = (0,1,0,0,0)
281 sage: neg_e2 = (0,-1,0,0,0)
282 sage: z = (0,0,0,0,0)
283 sage: K = Cone([e1, neg_e1, e2, neg_e2, z, z, z])
284 sage: lyapunov_rank(K)
286 sage: K.lattice_dim()**2 - K.dim()*K.codim()
289 The Lyapunov rank should be additive on a product of proper cones
292 sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
293 sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
294 sage: K = L31.cartesian_product(octant)
295 sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
298 Two isomorphic cones should have the same Lyapunov rank [Rudolf]_.
299 The cone ``K`` in the following example is isomorphic to the nonnegative
300 octant in `\mathbb{R}^{3}`::
302 sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
303 sage: lyapunov_rank(K)
306 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
309 sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
310 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
315 The Lyapunov rank should be additive on a product of proper cones
318 sage: set_random_seed()
319 sage: K1 = random_cone(max_ambient_dim=8,
320 ....: strictly_convex=True,
322 sage: K2 = random_cone(max_ambient_dim=8,
323 ....: strictly_convex=True,
325 sage: K = K1.cartesian_product(K2)
326 sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
329 The Lyapunov rank is invariant under a linear isomorphism
332 sage: K1 = random_cone(max_ambient_dim = 8)
333 sage: A = random_matrix(QQ, K1.lattice_dim(), algorithm='unimodular')
334 sage: K2 = Cone( [ A*r for r in K1.rays() ], lattice=K1.lattice())
335 sage: lyapunov_rank(K1) == lyapunov_rank(K2)
338 The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
341 sage: set_random_seed()
342 sage: K = random_cone(max_ambient_dim=8)
343 sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
346 The Lyapunov rank of a proper polyhedral cone in `n` dimensions can
347 be any number between `1` and `n` inclusive, excluding `n-1`
348 [Gowda/Tao]_. By accident, the `n-1` restriction will hold for the
349 trivial cone in a trivial space as well. However, in zero dimensions,
350 the Lyapunov rank of the trivial cone will be zero::
352 sage: set_random_seed()
353 sage: K = random_cone(max_ambient_dim=8,
354 ....: strictly_convex=True,
356 sage: b = lyapunov_rank(K)
357 sage: n = K.lattice_dim()
358 sage: (n == 0 or 1 <= b) and b <= n
363 In fact [Orlitzky]_, no closed convex polyhedral cone can have
364 Lyapunov rank `n-1` in `n` dimensions::
366 sage: set_random_seed()
367 sage: K = random_cone(max_ambient_dim=8)
368 sage: b = lyapunov_rank(K)
369 sage: n = K.lattice_dim()
373 The calculation of the Lyapunov rank of an improper cone can be
374 reduced to that of a proper cone [Orlitzky]_::
376 sage: set_random_seed()
377 sage: K = random_cone(max_ambient_dim=8)
378 sage: actual = lyapunov_rank(K)
379 sage: K_S = _restrict_to_space(K, K.span())
380 sage: K_SP = _restrict_to_space(K_S.dual(), K_S.dual().span()).dual()
381 sage: l = K.lineality()
383 sage: expected = lyapunov_rank(K_SP) + K.dim()*(l + c) + c**2
384 sage: actual == expected
387 The Lyapunov rank of a cone is the size of a :meth:`lyapunov_like_basis`::
389 sage: set_random_seed()
390 sage: K = random_cone(max_ambient_dim=8)
391 sage: lyapunov_rank(K) == len(K.lyapunov_like_basis())
394 We can make an imperfect cone perfect by adding a slack variable
395 (a Theorem in [Orlitzky]_)::
397 sage: set_random_seed()
398 sage: K = random_cone(max_ambient_dim=8,
399 ....: strictly_convex=True,
401 sage: L = ToricLattice(K.lattice_dim() + 1)
402 sage: K = Cone([ r.list() + [0] for r in K.rays() ], lattice=L)
403 sage: lyapunov_rank(K) >= K.lattice_dim()
407 beta
= 0 # running tally of the Lyapunov rank
414 # K is not solid, restrict to its span.
415 K
= _restrict_to_space(K
, K
.span())
417 # Non-solid reduction lemma.
421 # K is not pointed, restrict to the span of its dual. Uses a
422 # proposition from our paper, i.e. this is equivalent to K =
423 # _rho(K.dual()).dual().
424 K
= _restrict_to_space(K
, K
.dual().span())
426 # Non-pointed reduction lemma.
429 beta
+= len(K
.lyapunov_like_basis())
434 def is_lyapunov_like(L
,K
):
436 Determine whether or not ``L`` is Lyapunov-like on ``K``.
438 We say that ``L`` is Lyapunov-like on ``K`` if `\left\langle
439 L\left\lparenx\right\rparen,s\right\rangle = 0` for all pairs
440 `\left\langle x,s \right\rangle` in the complementarity set of
441 ``K``. It is known [Orlitzky]_ that this property need only be
442 checked for generators of ``K`` and its dual.
446 - ``L`` -- A linear transformation or matrix.
448 - ``K`` -- A polyhedral closed convex cone.
452 ``True`` if it can be proven that ``L`` is Lyapunov-like on ``K``,
453 and ``False`` otherwise.
457 If this function returns ``True``, then ``L`` is Lyapunov-like
458 on ``K``. However, if ``False`` is returned, that could mean one
459 of two things. The first is that ``L`` is definitely not
460 Lyapunov-like on ``K``. The second is more of an "I don't know"
461 answer, returned (for example) if we cannot prove that an inner
466 M. Orlitzky. The Lyapunov rank of an improper cone.
467 http://www.optimization-online.org/DB_HTML/2015/10/5135.html
471 The identity is always Lyapunov-like in a nontrivial space::
473 sage: set_random_seed()
474 sage: K = random_cone(min_ambient_dim = 1, max_rays = 8)
475 sage: L = identity_matrix(K.lattice_dim())
476 sage: is_lyapunov_like(L,K)
479 As is the "zero" transformation::
481 sage: K = random_cone(min_ambient_dim = 1, max_rays = 5)
482 sage: R = K.lattice().vector_space().base_ring()
483 sage: L = zero_matrix(R, K.lattice_dim())
484 sage: is_lyapunov_like(L,K)
487 Everything in ``K.lyapunov_like_basis()`` should be Lyapunov-like
490 sage: K = random_cone(min_ambient_dim = 1, max_rays = 5)
491 sage: all([ is_lyapunov_like(L,K) for L in K.lyapunov_like_basis() ])
495 return all([(L
*x
).inner_product(s
) == 0
496 for (x
,s
) in K
.discrete_complementarity_set()])
499 def random_element(K
):
501 Return a random element of ``K`` from its ambient vector space.
505 The cone ``K`` is specified in terms of its generators, so that
506 ``K`` is equal to the convex conic combination of those generators.
507 To choose a random element of ``K``, we assign random nonnegative
508 coefficients to each generator of ``K`` and construct a new vector
509 from the scaled rays.
511 A vector, rather than a ray, is returned so that the element may
512 have non-integer coordinates. Thus the element may have an
513 arbitrarily small norm.
517 A random element of the trivial cone is zero::
519 sage: set_random_seed()
520 sage: K = Cone([], ToricLattice(0))
521 sage: random_element(K)
523 sage: K = Cone([(0,)])
524 sage: random_element(K)
526 sage: K = Cone([(0,0)])
527 sage: random_element(K)
529 sage: K = Cone([(0,0,0)])
530 sage: random_element(K)
535 Any cone should contain an element of itself::
537 sage: set_random_seed()
538 sage: K = random_cone(max_rays = 8)
539 sage: K.contains(random_element(K))
543 V
= K
.lattice().vector_space()
545 coefficients
= [ F
.random_element().abs() for i
in range(K
.nrays()) ]
546 vector_gens
= map(V
, K
.rays())
547 scaled_gens
= [ coefficients
[i
]*vector_gens
[i
]
548 for i
in range(len(vector_gens
)) ]
550 # Make sure we return a vector. Without the coercion, we might
551 # return ``0`` when ``K`` has no rays.
552 v
= V(sum(scaled_gens
))
556 def positive_operators(K
):
558 Compute generators of the cone of positive operators on this cone.
562 A list of `n`-by-``n`` matrices where ``n == K.lattice_dim()``.
563 Each matrix ``P`` in the list should have the property that ``P*x``
564 is an element of ``K`` whenever ``x`` is an element of
565 ``K``. Moreover, any nonnegative linear combination of these
566 matrices shares the same property.
570 The trivial cone in a trivial space has no positive operators::
572 sage: K = Cone([], ToricLattice(0))
573 sage: positive_operators(K)
576 Positive operators on the nonnegative orthant are nonnegative matrices::
578 sage: K = Cone([(1,)])
579 sage: positive_operators(K)
582 sage: K = Cone([(1,0),(0,1)])
583 sage: positive_operators(K)
585 [1 0] [0 1] [0 0] [0 0]
586 [0 0], [0 0], [1 0], [0 1]
589 Every operator is positive on the ambient vector space::
591 sage: K = Cone([(1,),(-1,)])
592 sage: K.is_full_space()
594 sage: positive_operators(K)
597 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
598 sage: K.is_full_space()
600 sage: positive_operators(K)
602 [1 0] [-1 0] [0 1] [ 0 -1] [0 0] [ 0 0] [0 0] [ 0 0]
603 [0 0], [ 0 0], [0 0], [ 0 0], [1 0], [-1 0], [0 1], [ 0 -1]
608 A positive operator on a cone should send its generators into the cone::
610 sage: K = random_cone(max_ambient_dim = 6)
611 sage: pi_of_K = positive_operators(K)
612 sage: all([K.contains(p*x) for p in pi_of_K for x in K.rays()])
616 # Sage doesn't think matrices are vectors, so we have to convert
617 # our matrices to vectors explicitly before we can figure out how
618 # many are linearly-indepenedent.
620 # The space W has the same base ring as V, but dimension
621 # dim(V)^2. So it has the same dimension as the space of linear
622 # transformations on V. In other words, it's just the right size
623 # to create an isomorphism between it and our matrices.
624 V
= K
.lattice().vector_space()
625 W
= VectorSpace(V
.base_ring(), V
.dimension()**2)
627 tensor_products
= [ s
.tensor_product(x
) for x
in K
for s
in K
.dual() ]
629 # Turn our matrices into long vectors...
630 vectors
= [ W(m
.list()) for m
in tensor_products
]
632 # Create the *dual* cone of the positive operators, expressed as
634 L
= ToricLattice(W
.dimension())
635 pi_dual
= Cone(vectors
, lattice
=L
)
637 # Now compute the desired cone from its dual...
638 pi_cone
= pi_dual
.dual()
640 # And finally convert its rays back to matrix representations.
641 M
= MatrixSpace(V
.base_ring(), V
.dimension())
643 return [ M(v
.list()) for v
in pi_cone
.rays() ]
646 def Z_transformations(K
):
648 Compute generators of the cone of Z-transformations on this cone.
652 A list of `n`-by-``n`` matrices where ``n == K.lattice_dim()``.
653 Each matrix ``L`` in the list should have the property that
654 ``(L*x).inner_product(s) <= 0`` whenever ``(x,s)`` is an element the
655 discrete complementarity set of ``K``. Moreover, any nonnegative
656 linear combination of these matrices shares the same property.
660 Z-transformations on the nonnegative orthant are just Z-matrices.
661 That is, matrices whose off-diagonal elements are nonnegative::
663 sage: K = Cone([(1,0),(0,1)])
664 sage: Z_transformations(K)
666 [ 0 -1] [ 0 0] [-1 0] [1 0] [ 0 0] [0 0]
667 [ 0 0], [-1 0], [ 0 0], [0 0], [ 0 -1], [0 1]
669 sage: K = Cone([(1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)])
670 sage: all([ z[i][j] <= 0 for z in Z_transformations(K)
671 ....: for i in range(z.nrows())
672 ....: for j in range(z.ncols())
676 The trivial cone in a trivial space has no Z-transformations::
678 sage: K = Cone([], ToricLattice(0))
679 sage: Z_transformations(K)
682 Z-transformations on a subspace are Lyapunov-like and vice-versa::
684 sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
685 sage: K.is_full_space()
687 sage: lls = span([ vector(l.list()) for l in K.lyapunov_like_basis() ])
688 sage: zs = span([ vector(z.list()) for z in Z_transformations(K) ])
694 The Z-property is possessed by every Z-transformation::
696 sage: set_random_seed()
697 sage: K = random_cone(max_ambient_dim = 6)
698 sage: Z_of_K = Z_transformations(K)
699 sage: dcs = K.discrete_complementarity_set()
700 sage: all([(z*x).inner_product(s) <= 0 for z in Z_of_K
701 ....: for (x,s) in dcs])
704 The lineality space of Z is LL::
706 sage: set_random_seed()
707 sage: K = random_cone(min_ambient_dim = 1, max_ambient_dim = 6)
708 sage: lls = span([ vector(l.list()) for l in K.lyapunov_like_basis() ])
709 sage: z_cone = Cone([ z.list() for z in Z_transformations(K) ])
710 sage: z_cone.linear_subspace() == lls
714 # Sage doesn't think matrices are vectors, so we have to convert
715 # our matrices to vectors explicitly before we can figure out how
716 # many are linearly-indepenedent.
718 # The space W has the same base ring as V, but dimension
719 # dim(V)^2. So it has the same dimension as the space of linear
720 # transformations on V. In other words, it's just the right size
721 # to create an isomorphism between it and our matrices.
722 V
= K
.lattice().vector_space()
723 W
= VectorSpace(V
.base_ring(), V
.dimension()**2)
725 C_of_K
= K
.discrete_complementarity_set()
726 tensor_products
= [ s
.tensor_product(x
) for (x
,s
) in C_of_K
]
728 # Turn our matrices into long vectors...
729 vectors
= [ W(m
.list()) for m
in tensor_products
]
731 # Create the *dual* cone of the cross-positive operators,
732 # expressed as long vectors..
733 L
= ToricLattice(W
.dimension())
734 Sigma_dual
= Cone(vectors
, lattice
=L
)
736 # Now compute the desired cone from its dual...
737 Sigma_cone
= Sigma_dual
.dual()
739 # And finally convert its rays back to matrix representations.
740 # But first, make them negative, so we get Z-transformations and
741 # not cross-positive ones.
742 M
= MatrixSpace(V
.base_ring(), V
.dimension())
744 return [ -M(v
.list()) for v
in Sigma_cone
.rays() ]