-# Sage doesn't load ~/.sage/init.sage during testing (sage -t), so we
-# have to explicitly mangle our sitedir here so that "mjo.cone"
-# resolves.
-from os.path import abspath
-from site import addsitedir
-addsitedir(abspath('../../'))
-
from sage.all import *
+from sage.geometry.cone import is_Cone
-
-def random_cone(min_dim=None, max_dim=None, min_rays=None, max_rays=None):
+def is_positive_on(L,K):
r"""
- Generate a random rational convex polyhedral cone.
+ Determine whether or not ``L`` is positive on ``K``.
+
+ We say that ``L`` is positive on a closed convex cone ``K`` if
+ `L\left\lparen x \right\rparen` belongs to ``K`` for all `x` in
+ ``K``. This property need only be checked for generators of ``K``.
- Lower and upper bounds may be provided for both the dimension of the
- ambient space and the number of generating rays of the cone. Any
- parameters left unspecified will be chosen randomly.
+ To reliably check whether or not ``L`` is positive, its base ring
+ must be either exact (for example, the rationals) or ``SR``. An
+ exact ring is more reliable, but in some cases a matrix whose
+ entries contain symbolic constants like ``e`` and ``pi`` will work.
INPUT:
- - ``min_dim`` (default: random) -- The minimum dimension of the ambient
- lattice.
+ - ``L`` -- A matrix over either an exact ring or ``SR``.
- - ``max_dim`` (default: random) -- The maximum dimension of the ambient
- lattice.
+ - ``K`` -- A polyhedral closed convex cone.
- - ``min_rays`` (default: random) -- The minimum number of generating rays
- of the cone.
+ OUTPUT:
- - ``max_rays`` (default: random) -- The maximum number of generating rays
- of the cone.
+ If the base ring of ``L`` is exact, then ``True`` will be returned if
+ and only if ``L`` is positive on ``K``.
- OUTPUT:
+ If the base ring of ``L`` is ``SR``, then the situation is more
+ complicated:
+
+ - ``True`` will be returned if it can be proven that ``L``
+ is positive on ``K``.
+ - ``False`` will be returned if it can be proven that ``L``
+ is not positive on ``K``.
+ - ``False`` will also be returned if we can't decide; specifically
+ if we arrive at a symbolic inequality that cannot be resolved.
+
+ .. SEEALSO::
+
+ :func:`is_cross_positive_on`,
+ :func:`is_Z_operator_on`,
+ :func:`is_lyapunov_like_on`
+
+ EXAMPLES:
+
+ Nonnegative matrices are positive operators on the nonnegative
+ orthant::
- A new, randomly generated cone.
+ sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
+ sage: L = random_matrix(QQ,3).apply_map(abs)
+ sage: is_positive_on(L,K)
+ True
+
+ Your matrix can be over any exact ring, but you may get unexpected
+ answers with weirder rings. For example, any rational matrix is
+ positive on the plane, but if your matrix contains polynomial
+ variables, the answer will be negative::
+
+ sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
+ sage: K.is_full_space()
+ True
+ sage: L = matrix(QQ[x], [[x,0],[0,1]])
+ sage: is_positive_on(L,K)
+ False
+
+ The previous example is "unexpected" because it depends on how we
+ check whether or not ``L`` is positive. For exact base rings, we
+ check whether or not ``L*z`` belongs to ``K`` for each ``z in K``.
+ If ``K`` is closed, then an equally-valid test would be to check
+ whether the inner product of ``L*z`` and ``s`` is nonnegative for
+ every ``z`` in ``K`` and ``s`` in ``K.dual()``. In fact, that is
+ what we do over inexact rings. In the previous example, that test
+ would return an affirmative answer::
+
+ sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
+ sage: L = matrix(QQ[x], [[x,0],[0,1]])
+ sage: all([ (L*z).inner_product(s) for z in K for s in K.dual() ])
+ True
+ sage: is_positive_on(L.change_ring(SR), K)
+ True
TESTS:
- It's hard to test the output of a random process, but we can at
- least make sure that we get a cone back::
+ The identity operator is always positive::
- sage: from sage.geometry.cone import is_Cone
- sage: K = random_cone()
- sage: is_Cone(K) # long time
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: L = identity_matrix(K.lattice_dim())
+ sage: is_positive_on(L,K)
True
+ The "zero" operator is always positive::
+
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: R = K.lattice().vector_space().base_ring()
+ sage: L = zero_matrix(R, K.lattice_dim())
+ sage: is_positive_on(L,K)
+ True
+
+ Everything in ``K.positive_operators_gens()`` should be
+ positive on ``K``::
+
+ sage: K = random_cone(max_ambient_dim=5)
+ sage: all([ is_positive_on(L,K) # long time
+ ....: for L in K.positive_operators_gens() ]) # long time
+ True
+ sage: all([ is_positive_on(L.change_ring(SR),K) # long time
+ ....: for L in K.positive_operators_gens() ]) # long time
+ True
+
+ Technically we could test this, but for now only closed convex cones
+ are supported as our ``K`` argument::
+
+ sage: K = [ vector([1,2,3]), vector([5,-1,7]) ]
+ sage: L = identity_matrix(3)
+ sage: is_positive_on(L,K)
+ Traceback (most recent call last):
+ ...
+ TypeError: K must be a Cone.
+
+ We can't give reliable answers over inexact rings::
+
+ sage: K = Cone([(1,2,3), (4,5,6)])
+ sage: L = identity_matrix(RR,3)
+ sage: is_positive_on(L,K)
+ Traceback (most recent call last):
+ ...
+ ValueError: The base ring of L is neither SR nor exact.
+
"""
- def random_min_max(l,u):
- r"""
- We need to handle four cases to prevent us from doing
- something stupid like having an upper bound that's lower than
- our lower bound. And we would need to repeat all of that logic
- for the dimension/rays, so we consolidate it here.
- """
- if l is None and u is None:
- # They're both random, just return a random nonnegative
- # integer.
- return ZZ.random_element().abs()
-
- if l is not None and u is not None:
- # Both were specified. Again, just make up a number and
- # return it. If the user wants to give us u < l then he
- # can have an exception.
- return ZZ.random_element(l,u)
-
- if l is not None and u is None:
- # In this case, we're generating the upper bound randomly
- # GIVEN A LOWER BOUND. So we add a random nonnegative
- # integer to the given lower bound.
- u = l + ZZ.random_element().abs()
- return ZZ.random_element(l,u)
-
- # Here we must be in the only remaining case, where we are
- # given an upper bound but no lower bound. We might as well
- # use zero.
- return ZZ.random_element(0,u)
-
- d = random_min_max(min_dim, max_dim)
- r = random_min_max(min_rays, max_rays)
-
- L = ToricLattice(d)
- rays = [L.random_element() for i in range(0,r)]
-
- # We pass the lattice in case there are no rays.
- return Cone(rays, lattice=L)
-
-
-def discrete_complementarity_set(K):
+ if not is_Cone(K):
+ raise TypeError('K must be a Cone.')
+ if not L.base_ring().is_exact() and not L.base_ring() is SR:
+ raise ValueError('The base ring of L is neither SR nor exact.')
+
+ if L.base_ring().is_exact():
+ # This should be way faster than computing the dual and
+ # checking a bunch of inequalities, but it doesn't work if
+ # ``L*x`` is symbolic. For example, ``e in Cone([(1,)])``
+ # is true, but returns ``False``.
+ return all([ L*x in K for x in K ])
+ else:
+ # Fall back to inequality-checking when the entries of ``L``
+ # might be symbolic.
+ return all([ s*(L*x) >= 0 for x in K for s in K.dual() ])
+
+
+def is_cross_positive_on(L,K):
r"""
- Compute the discrete complementarity set of this cone.
+ Determine whether or not ``L`` is cross-positive on ``K``.
+
+ We say that ``L`` is cross-positive on a closed convex cone``K`` if
+ `\left\langle L\left\lparenx\right\rparen,s\right\rangle \ge 0` for
+ all pairs `\left\langle x,s \right\rangle` in the complementarity
+ set of ``K``. This property need only be checked for generators of
+ ``K`` and its dual.
+
+ To reliably check whether or not ``L`` is cross-positive, its base
+ ring must be either exact (for example, the rationals) or ``SR``. An
+ exact ring is more reliable, but in some cases a matrix whose
+ entries contain symbolic constants like ``e`` and ``pi`` will work.
+
+ INPUT:
- 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.
+ - ``L`` -- A matrix over either an exact ring or ``SR``.
+
+ - ``K`` -- A polyhedral closed convex cone.
OUTPUT:
- A list of pairs `(x,s)` such that,
+ If the base ring of ``L`` is exact, then ``True`` will be returned if
+ and only if ``L`` is cross-positive on ``K``.
- * `x` is in this cone.
- * `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.
+ If the base ring of ``L`` is ``SR``, then the situation is more
+ complicated:
- EXAMPLES:
+ - ``True`` will be returned if it can be proven that ``L``
+ is cross-positive on ``K``.
+ - ``False`` will be returned if it can be proven that ``L``
+ is not cross-positive on ``K``.
+ - ``False`` will also be returned if we can't decide; specifically
+ if we arrive at a symbolic inequality that cannot be resolved.
- The discrete complementarity set of the nonnegative orthant consists
- of pairs of standard basis vectors::
+ .. SEEALSO::
- sage: K = Cone([(1,0),(0,1)])
- sage: discrete_complementarity_set(K)
- [((1, 0), (0, 1)), ((0, 1), (1, 0))]
+ :func:`is_positive_on`,
+ :func:`is_Z_operator_on`,
+ :func:`is_lyapunov_like_on`
- If the cone consists of a single ray, the second components of the
- discrete complementarity set should generate the orthogonal
- complement of that ray::
+ EXAMPLES:
- sage: K = Cone([(1,0)])
- sage: discrete_complementarity_set(K)
- [((1, 0), (0, 1)), ((1, 0), (0, -1))]
- sage: K = Cone([(1,0,0)])
- sage: discrete_complementarity_set(K)
- [((1, 0, 0), (0, 1, 0)),
- ((1, 0, 0), (0, -1, 0)),
- ((1, 0, 0), (0, 0, 1)),
- ((1, 0, 0), (0, 0, -1))]
+ The identity operator is always cross-positive::
- When the cone is the entire space, its dual is the trivial cone, so
- the discrete complementarity set is empty::
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: L = identity_matrix(K.lattice_dim())
+ sage: is_cross_positive_on(L,K)
+ True
- sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
- sage: discrete_complementarity_set(K)
- []
+ The "zero" operator is always cross-positive::
+
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: R = K.lattice().vector_space().base_ring()
+ sage: L = zero_matrix(R, K.lattice_dim())
+ sage: is_cross_positive_on(L,K)
+ True
TESTS:
- The complementarity set of the dual can be obtained by switching the
- components of the complementarity set of the original cone::
+ Everything in ``K.cross_positive_operators_gens()`` should be
+ cross-positive on ``K``::
- sage: K1 = random_cone(0,10,0,10)
- sage: K2 = K1.dual()
- sage: expected = [(x,s) for (s,x) in discrete_complementarity_set(K2)]
- sage: actual = discrete_complementarity_set(K1)
- sage: actual == expected
+ sage: K = random_cone(max_ambient_dim=5)
+ sage: all([ is_cross_positive_on(L,K) # long time
+ ....: for L in K.cross_positive_operators_gens() ]) # long time
+ True
+ sage: all([ is_cross_positive_on(L.change_ring(SR),K) # long time
+ ....: for L in K.cross_positive_operators_gens() ]) # long time
True
- """
- V = K.lattice().vector_space()
+ Technically we could test this, but for now only closed convex cones
+ are supported as our ``K`` argument::
+
+ sage: L = identity_matrix(3)
+ sage: K = [ vector([8,2,-8]), vector([5,-5,7]) ]
+ sage: is_cross_positive_on(L,K)
+ Traceback (most recent call last):
+ ...
+ TypeError: K must be a Cone.
- # Convert the rays to vectors so that we can compute inner
- # products.
- xs = [V(x) for x in K.rays()]
- ss = [V(s) for s in K.dual().rays()]
+ We can't give reliable answers over inexact rings::
- return [(x,s) for x in xs for s in ss if x.inner_product(s) == 0]
+ sage: K = Cone([(1,2,3), (4,5,6)])
+ sage: L = identity_matrix(RR,3)
+ sage: is_cross_positive_on(L,K)
+ Traceback (most recent call last):
+ ...
+ ValueError: The base ring of L is neither SR nor exact.
+ """
+ if not is_Cone(K):
+ raise TypeError('K must be a Cone.')
+ if not L.base_ring().is_exact() and not L.base_ring() is SR:
+ raise ValueError('The base ring of L is neither SR nor exact.')
-def lyapunov_rank(K):
+ return all([ s*(L*x) >= 0
+ for (x,s) in K.discrete_complementarity_set() ])
+
+def is_Z_operator_on(L,K):
r"""
- Compute the Lyapunov (or bilinearity) rank of this cone.
+ Determine whether or not ``L`` is a Z-operator on ``K``.
- The Lyapunov rank of a cone can be thought of in (mainly) two ways:
+ We say that ``L`` is a Z-operator on a closed convex cone``K`` if
+ `\left\langle L\left\lparenx\right\rparen,s\right\rangle \le 0` for
+ all pairs `\left\langle x,s \right\rangle` in the complementarity
+ set of ``K``. It is known that this property need only be checked
+ for generators of ``K`` and its dual.
- 1. The dimension of the Lie algebra of the automorphism group of the
- cone.
+ A matrix is a Z-operator on ``K`` if and only if its negation is a
+ cross-positive operator on ``K``.
- 2. The dimension of the linear space of all Lyapunov-like
- transformations on the cone.
+ To reliably check whether or not ``L`` is a Z operator, its base
+ ring must be either exact (for example, the rationals) or ``SR``. An
+ exact ring is more reliable, but in some cases a matrix whose
+ entries contain symbolic constants like ``e`` and ``pi`` will work.
INPUT:
- A closed, convex polyhedral cone.
+ - ``L`` -- A matrix over either an exact ring or ``SR``.
+
+ - ``K`` -- A polyhedral closed convex cone.
OUTPUT:
- An integer representing the Lyapunov rank of the cone. If the
- dimension of the ambient vector space is `n`, then the Lyapunov rank
- will be between `1` and `n` inclusive; however a rank of `n-1` is
- not possible (see the first reference).
+ If the base ring of ``L`` is exact, then ``True`` will be returned if
+ and only if ``L`` is a Z-operator on ``K``.
- .. note::
+ If the base ring of ``L`` is ``SR``, then the situation is more
+ complicated:
- In the references, the cones are always assumed to be proper. We
- do not impose this restriction.
+ - ``True`` will be returned if it can be proven that ``L``
+ is a Z-operator on ``K``.
+ - ``False`` will be returned if it can be proven that ``L``
+ is not a Z-operator on ``K``.
+ - ``False`` will also be returned if we can't decide; specifically
+ if we arrive at a symbolic inequality that cannot be resolved.
- .. seealso::
+ .. SEEALSO::
- :meth:`is_proper`
+ :func:`is_positive_on`,
+ :func:`is_cross_positive_on`,
+ :func:`is_lyapunov_like_on`
- ALGORITHM:
+ EXAMPLES:
- The codimension formula from the second reference is used. We find
- all pairs `(x,s)` in the complementarity set of `K` such that `x`
- and `s` are rays of our cone. It is known that these vectors are
- sufficient to apply the codimension formula. Once we have all such
- pairs, we "brute force" the codimension formula by finding all
- linearly-independent `xs^{T}`.
+ The identity operator is always a Z-operator::
- REFERENCES:
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: L = identity_matrix(K.lattice_dim())
+ sage: is_Z_operator_on(L,K)
+ True
- 1. M.S. Gowda and J. Tao. On the bilinearity rank of a proper cone
- and Lyapunov-like transformations, Mathematical Programming, 147
- (2014) 155-170.
+ The "zero" operator is always a Z-operator::
- 2. G. Rudolf, N. Noyan, D. Papp, and F. Alizadeh, Bilinear
- optimality constraints for the cone of positive polynomials,
- Mathematical Programming, Series B, 129 (2011) 5-31.
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: R = K.lattice().vector_space().base_ring()
+ sage: L = zero_matrix(R, K.lattice_dim())
+ sage: is_Z_operator_on(L,K)
+ True
- EXAMPLES:
+ TESTS:
- The nonnegative orthant in `\mathbb{R}^{n}` always has rank `n`::
+ Everything in ``K.Z_operators_gens()`` should be a Z-operator
+ on ``K``::
- sage: positives = Cone([(1,)])
- sage: lyapunov_rank(positives)
- 1
- sage: quadrant = Cone([(1,0), (0,1)])
- sage: lyapunov_rank(quadrant)
- 2
- sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
- sage: lyapunov_rank(octant)
- 3
+ sage: K = random_cone(max_ambient_dim=5)
+ sage: all([ is_Z_operator_on(L,K) # long time
+ ....: for L in K.Z_operators_gens() ]) # long time
+ True
+ sage: all([ is_Z_operator_on(L.change_ring(SR),K) # long time
+ ....: for L in K.Z_operators_gens() ]) # long time
+ True
- The `L^{3}_{1}` cone is known to have a Lyapunov rank of one::
+ Technically we could test this, but for now only closed convex cones
+ are supported as our ``K`` argument::
- sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
- sage: lyapunov_rank(L31)
- 1
+ sage: L = identity_matrix(3)
+ sage: K = [ vector([-4,20,3]), vector([1,-5,2]) ]
+ sage: is_Z_operator_on(L,K)
+ Traceback (most recent call last):
+ ...
+ TypeError: K must be a Cone.
- Likewise for the `L^{3}_{\infty}` cone::
- sage: L3infty = Cone([(0,1,1), (1,0,1), (0,-1,1), (-1,0,1)])
- sage: lyapunov_rank(L3infty)
- 1
+ We can't give reliable answers over inexact rings::
- The Lyapunov rank should be additive on a product of cones::
+ sage: K = Cone([(1,2,3), (4,5,6)])
+ sage: L = identity_matrix(RR,3)
+ sage: is_Z_operator_on(L,K)
+ Traceback (most recent call last):
+ ...
+ ValueError: The base ring of L is neither SR nor exact.
- sage: L31 = Cone([(1,0,1), (0,-1,1), (-1,0,1), (0,1,1)])
- sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)])
- sage: K = L31.cartesian_product(octant)
- sage: lyapunov_rank(K) == lyapunov_rank(L31) + lyapunov_rank(octant)
- True
+ """
+ return is_cross_positive_on(-L,K)
- Two isomorphic cones should have the same Lyapunov rank. The cone
- ``K`` in the following example is isomorphic to the nonnegative
- octant in `\mathbb{R}^{3}`::
- sage: K = Cone([(1,2,3), (-1,1,0), (1,0,6)])
- sage: lyapunov_rank(K)
- 3
+def is_lyapunov_like_on(L,K):
+ r"""
+ Determine whether or not ``L`` is Lyapunov-like on ``K``.
- The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
- itself::
+ We say that ``L`` is Lyapunov-like on a closed convex cone ``K`` if
+ `\left\langle L\left\lparenx\right\rparen,s\right\rangle = 0` for
+ all pairs `\left\langle x,s \right\rangle` in the complementarity
+ set of ``K``. This property need only be checked for generators of
+ ``K`` and its dual.
- sage: K = Cone([(2,2,4), (-1,9,0), (2,0,6)])
- sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
- True
+ An operator is Lyapunov-like on ``K`` if and only if both the
+ operator itself and its negation are cross-positive on ``K``.
- TESTS:
+ To reliably check whether or not ``L`` is Lyapunov-like, its base
+ ring must be either exact (for example, the rationals) or ``SR``. An
+ exact ring is more reliable, but in some cases a matrix whose
+ entries contain symbolic constants like ``e`` and ``pi`` will work.
+
+ INPUT:
- The Lyapunov rank should be additive on a product of cones::
+ - ``L`` -- A matrix over either an exact ring or ``SR``.
- sage: K1 = random_cone(0,10,0,10)
- sage: K2 = random_cone(0,10,0,10)
- sage: K = K1.cartesian_product(K2)
- sage: lyapunov_rank(K) == lyapunov_rank(K1) + lyapunov_rank(K2)
- True
+ - ``K`` -- A polyhedral closed convex cone.
+
+ OUTPUT:
+
+ If the base ring of ``L`` is exact, then ``True`` will be returned if
+ and only if ``L`` is Lyapunov-like on ``K``.
+
+ If the base ring of ``L`` is ``SR``, then the situation is more
+ complicated:
+
+ - ``True`` will be returned if it can be proven that ``L``
+ is Lyapunov-like on ``K``.
+ - ``False`` will be returned if it can be proven that ``L``
+ is not Lyapunov-like on ``K``.
+ - ``False`` will also be returned if we can't decide; specifically
+ if we arrive at a symbolic inequality that cannot be resolved.
+
+ .. SEEALSO::
+
+ :func:`is_positive_on`,
+ :func:`is_cross_positive_on`,
+ :func:`is_Z_operator_on`
+
+ EXAMPLES:
- The dual cone `K^{*}` of ``K`` should have the same Lyapunov rank as ``K``
- itself::
+ Diagonal matrices are Lyapunov-like operators on the nonnegative
+ orthant::
- sage: K = random_cone(0,10,0,10)
- sage: lyapunov_rank(K) == lyapunov_rank(K.dual())
+ sage: K = Cone([(1,0,0),(0,1,0),(0,0,1)])
+ sage: L = diagonal_matrix(random_vector(QQ,3))
+ sage: is_lyapunov_like_on(L,K)
True
- """
- V = K.lattice().vector_space()
+ TESTS:
+
+ The identity operator is always Lyapunov-like::
+
+ sage: set_random_seed()
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: L = identity_matrix(K.lattice_dim())
+ sage: is_lyapunov_like_on(L,K)
+ True
- C_of_K = discrete_complementarity_set(K)
+ The "zero" operator is always Lyapunov-like::
- matrices = [x.tensor_product(s) for (x,s) in C_of_K]
+ sage: K = random_cone(max_ambient_dim=8)
+ sage: R = K.lattice().vector_space().base_ring()
+ sage: L = zero_matrix(R, K.lattice_dim())
+ sage: is_lyapunov_like_on(L,K)
+ True
- # Sage doesn't think matrices are vectors, so we have to convert
- # our matrices to vectors explicitly before we can figure out how
- # many are linearly-indepenedent.
- #
- # The space W has the same base ring as V, but dimension
- # dim(V)^2. So it has the same dimension as the space of linear
- # transformations on V. In other words, it's just the right size
- # to create an isomorphism between it and our matrices.
- W = VectorSpace(V.base_ring(), V.dimension()**2)
+ Everything in ``K.lyapunov_like_basis()`` should be Lyapunov-like
+ on ``K``::
- def phi(m):
- r"""
- Convert a matrix to a vector isomorphically.
- """
- return W(m.list())
+ sage: K = random_cone(max_ambient_dim=5)
+ sage: all([ is_lyapunov_like_on(L,K) # long time
+ ....: for L in K.lyapunov_like_basis() ]) # long time
+ True
+ sage: all([ is_lyapunov_like_on(L.change_ring(SR),K) # long time
+ ....: for L in K.lyapunov_like_basis() ]) # long time
+ True
- vectors = [phi(m) for m in matrices]
+ Technically we could test this, but for now only closed convex cones
+ are supported as our ``K`` argument::
+
+ sage: L = identity_matrix(3)
+ sage: K = [ vector([2,2,-1]), vector([5,4,-3]) ]
+ sage: is_lyapunov_like_on(L,K)
+ Traceback (most recent call last):
+ ...
+ TypeError: K must be a Cone.
+
+ We can't give reliable answers over inexact rings::
+
+ sage: K = Cone([(1,2,3), (4,5,6)])
+ sage: L = identity_matrix(RR,3)
+ sage: is_lyapunov_like_on(L,K)
+ Traceback (most recent call last):
+ ...
+ ValueError: The base ring of L is neither SR nor exact.
+
+ An operator is Lyapunov-like on a cone if and only if both the
+ operator and its negation are cross-positive on the cone::
+
+ sage: K = random_cone(max_ambient_dim=5)
+ sage: R = K.lattice().vector_space().base_ring()
+ sage: L = random_matrix(R, K.lattice_dim())
+ sage: actual = is_lyapunov_like_on(L,K) # long time
+ sage: expected = (is_cross_positive_on(L,K) and # long time
+ ....: is_cross_positive_on(-L,K)) # long time
+ sage: actual == expected # long time
+ True
- return (W.dimension() - W.span(vectors).rank())
+ """
+ if not is_Cone(K):
+ raise TypeError('K must be a Cone.')
+ if not L.base_ring().is_exact() and not L.base_ring() is SR:
+ raise ValueError('The base ring of L is neither SR nor exact.')
+
+ # Even though ``discrete_complementarity_set`` is a cached method
+ # of cones, this is faster than calling ``is_cross_positive_on``
+ # twice: doing so checks twice as many inequalities as the number
+ # of equalities that we're about to check.
+ return all([ s*(L*x) == 0
+ for (x,s) in K.discrete_complementarity_set() ])
+
+
+def LL_cone(K):
+ gens = K.lyapunov_like_basis()
+ L = ToricLattice(K.lattice_dim()**2)
+ return Cone([ g.list() for g in gens ], lattice=L, check=False)
+
+def Sigma_cone(K):
+ gens = K.cross_positive_operators_gens()
+ L = ToricLattice(K.lattice_dim()**2)
+ return Cone([ g.list() for g in gens ], lattice=L, check=False)
+
+def Z_cone(K):
+ gens = K.Z_operators_gens()
+ L = ToricLattice(K.lattice_dim()**2)
+ return Cone([ g.list() for g in gens ], lattice=L, check=False)
+
+def pi_cone(K1, K2=None):
+ if K2 is None:
+ K2 = K1
+ gens = K1.positive_operators_gens(K2)
+ L = ToricLattice(K1.lattice_dim()*K2.lattice_dim())
+ return Cone([ g.list() for g in gens ], lattice=L, check=False)