]> gitweb.michael.orlitzky.com - sage.d.git/blobdiff - mjo/cone/cone.py
Fix the is_positive_on test and give better examples.
[sage.d.git] / mjo / cone / cone.py
index 3d74b5f17adc095dc43c7e421f511ab84308c4ac..01ef5f9d5cc56b087344237b9b5c915423ac0921 100644 (file)
@@ -1,14 +1,12 @@
 from sage.all import *
 
 from sage.all import *
 
-def is_lyapunov_like(L,K):
+def is_positive_on(L,K):
     r"""
     r"""
-    Determine whether or not ``L`` is Lyapunov-like on ``K``.
+    Determine whether or not ``L`` is positive on ``K``.
 
 
-    We say that ``L`` is Lyapunov-like on ``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``. It is known [Orlitzky]_ that this property need only be
-    checked for generators of ``K`` and its dual.
+    We say that ``L`` is positive on ``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``.
 
     INPUT:
 
 
     INPUT:
 
@@ -18,31 +16,36 @@ def is_lyapunov_like(L,K):
 
     OUTPUT:
 
 
     OUTPUT:
 
-    ``True`` if it can be proven that ``L`` is Lyapunov-like on ``K``,
+    ``True`` if it can be proven that ``L`` is positive on ``K``,
     and ``False`` otherwise.
 
     .. WARNING::
 
     and ``False`` otherwise.
 
     .. WARNING::
 
-        If this function returns ``True``, then ``L`` is Lyapunov-like
+        If this function returns ``True``, then ``L`` is positive
         on ``K``. However, if ``False`` is returned, that could mean one
         of two things. The first is that ``L`` is definitely not
         on ``K``. However, if ``False`` is returned, that could mean one
         of two things. The first is that ``L`` is definitely not
-        Lyapunov-like on ``K``. The second is more of an "I don't know"
+        positive on ``K``. The second is more of an "I don't know"
         answer, returned (for example) if we cannot prove that an inner
         answer, returned (for example) if we cannot prove that an inner
-        product is zero.
+        product is nonnegative.
 
 
-    REFERENCES:
+    EXAMPLES:
 
 
-    M. Orlitzky. The Lyapunov rank of an improper cone.
-    http://www.optimization-online.org/DB_HTML/2015/10/5135.html
+    Positive operators on the nonnegative orthant are nonnegative
+    matrices::
 
 
-    EXAMPLES:
+        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
 
 
-    The identity is always Lyapunov-like in a nontrivial space::
+    TESTS:
+
+    The identity is always positive in a nontrivial space::
 
         sage: set_random_seed()
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
         sage: L = identity_matrix(K.lattice_dim())
 
         sage: set_random_seed()
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
         sage: L = identity_matrix(K.lattice_dim())
-        sage: is_lyapunov_like(L,K)
+        sage: is_positive_on(L,K)
         True
 
     As is the "zero" transformation::
         True
 
     As is the "zero" transformation::
@@ -50,642 +53,274 @@ def is_lyapunov_like(L,K):
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
         sage: R = K.lattice().vector_space().base_ring()
         sage: L = zero_matrix(R, K.lattice_dim())
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
         sage: R = K.lattice().vector_space().base_ring()
         sage: L = zero_matrix(R, K.lattice_dim())
-        sage: is_lyapunov_like(L,K)
+        sage: is_positive_on(L,K)
         True
 
         True
 
-        Everything in ``K.lyapunov_like_basis()`` should be Lyapunov-like
-        on ``K``::
+    Everything in ``K.positive_operators_gens()`` should be
+    positive on ``K``::
 
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=6)
 
         sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=6)
-        sage: all([ is_lyapunov_like(L,K) for L in K.lyapunov_like_basis() ])
+        sage: all([ is_positive_on(L,K)
+        ....:       for L in K.positive_operators_gens() ])
+        True
+        sage: all([ is_positive_on(L.change_ring(SR),K)
+        ....:       for L in K.positive_operators_gens() ])
         True
 
     """
         True
 
     """
-    return all([(L*x).inner_product(s) == 0
-                for (x,s) in K.discrete_complementarity_set()])
-
-
-def positive_operator_gens(K):
+    if L.base_ring().is_exact():
+        # This could potentially be extended to other types of ``K``...
+        return all([ L*x in K for x in K ])
+    elif L.base_ring() is SR:
+        # 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() ])
+    else:
+        # The only inexact ring that we're willing to work with is SR,
+        # since it can still be exact when working with symbolic
+        # constants like pi and e.
+        raise ValueError('base ring of operator L is neither SR nor exact')
+
+
+def is_cross_positive_on(L,K):
     r"""
     r"""
-    Compute generators of the cone of positive operators on this cone.
-
-    OUTPUT:
-
-    A list of `n`-by-``n`` matrices where ``n == K.lattice_dim()``.
-    Each matrix ``P`` in the list should have the property that ``P*x``
-    is an element of ``K`` whenever ``x`` is an element of
-    ``K``. Moreover, any nonnegative linear combination of these
-    matrices shares the same property.
+    Determine whether or not ``L`` is cross-positive on ``K``.
 
 
-    REFERENCES:
-
-    .. [Orlitzky-Pi-Z]
-       M. Orlitzky.
-       Positive operators and Z-transformations on closed convex cones.
+    We say that ``L`` is cross-positive on ``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.
 
 
-    .. [Tam]
-       B.-S. Tam.
-       Some results of polyhedral cones and simplicial cones.
-       Linear and Multilinear Algebra, 4:4 (1977) 281--284.
+    INPUT:
 
 
-    EXAMPLES:
+    - ``L`` -- A linear transformation or matrix.
 
 
-    Positive operators on the nonnegative orthant are nonnegative matrices::
+    - ``K`` -- A polyhedral closed convex cone.
 
 
-        sage: K = Cone([(1,)])
-        sage: positive_operator_gens(K)
-        [[1]]
+    OUTPUT:
 
 
-        sage: K = Cone([(1,0),(0,1)])
-        sage: positive_operator_gens(K)
-        [
-        [1 0]  [0 1]  [0 0]  [0 0]
-        [0 0], [0 0], [1 0], [0 1]
-        ]
+    ``True`` if it can be proven that ``L`` is cross-positive on ``K``,
+    and ``False`` otherwise.
 
 
-    The trivial cone in a trivial space has no positive operators::
+    .. WARNING::
 
 
-        sage: K = Cone([], ToricLattice(0))
-        sage: positive_operator_gens(K)
-        []
+        If this function returns ``True``, then ``L`` is cross-positive
+        on ``K``. However, if ``False`` is returned, that could mean one
+        of two things. The first is that ``L`` is definitely not
+        cross-positive on ``K``. The second is more of an "I don't know"
+        answer, returned (for example) if we cannot prove that an inner
+        product is nonnegative.
 
 
-    Every operator is positive on the trivial cone::
+    EXAMPLES:
 
 
-        sage: K = Cone([(0,)])
-        sage: positive_operator_gens(K)
-        [[1], [-1]]
+    The identity is always cross-positive in a nontrivial space::
 
 
-        sage: K = Cone([(0,0)])
-        sage: K.is_trivial()
+        sage: set_random_seed()
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
+        sage: L = identity_matrix(K.lattice_dim())
+        sage: is_cross_positive_on(L,K)
         True
         True
-        sage: positive_operator_gens(K)
-        [
-        [1 0]  [-1  0]  [0 1]  [ 0 -1]  [0 0]  [ 0  0]  [0 0]  [ 0  0]
-        [0 0], [ 0  0], [0 0], [ 0  0], [1 0], [-1  0], [0 1], [ 0 -1]
-        ]
-
-    Every operator is positive on the ambient vector space::
 
 
-        sage: K = Cone([(1,),(-1,)])
-        sage: K.is_full_space()
-        True
-        sage: positive_operator_gens(K)
-        [[1], [-1]]
+    As is the "zero" transformation::
 
 
-        sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
-        sage: K.is_full_space()
+        sage: K = random_cone(min_ambient_dim=1, 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
         True
-        sage: positive_operator_gens(K)
-        [
-        [1 0]  [-1  0]  [0 1]  [ 0 -1]  [0 0]  [ 0  0]  [0 0]  [ 0  0]
-        [0 0], [ 0  0], [0 0], [ 0  0], [1 0], [-1  0], [0 1], [ 0 -1]
-        ]
-
-    A non-obvious application is to find the positive operators on the
-    right half-plane::
-
-        sage: K = Cone([(1,0),(0,1),(0,-1)])
-        sage: positive_operator_gens(K)
-        [
-        [1 0]  [0 0]  [ 0  0]  [0 0]  [ 0  0]
-        [0 0], [1 0], [-1  0], [0 1], [ 0 -1]
-        ]
 
     TESTS:
 
 
     TESTS:
 
-    Each positive operator generator should send the generators of the
-    cone into the cone::
+    Everything in ``K.cross_positive_operators_gens()`` should be
+    cross-positive on ``K``::
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: all([ K.contains(P*x) for P in pi_of_K for x in K ])
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=6)
+        sage: all([ is_cross_positive_on(L,K)
+        ....:       for L in K.cross_positive_operators_gens() ])
         True
         True
-
-    Each positive operator generator should send a random element of the
-    cone into the cone::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: all([ K.contains(P*K.random_element(QQ)) for P in pi_of_K ])
+        sage: all([ is_cross_positive_on(L.change_ring(SR),K)
+        ....:       for L in K.cross_positive_operators_gens() ])
         True
 
         True
 
-    A random element of the positive operator cone should send the
-    generators of the cone into the cone::
+    """
+    if L.base_ring().is_exact() or L.base_ring() is SR:
+        return all([ s*(L*x) >= 0
+                     for (x,s) in K.discrete_complementarity_set() ])
+    else:
+        # The only inexact ring that we're willing to work with is SR,
+        # since it can still be exact when working with symbolic
+        # constants like pi and e.
+        raise ValueError('base ring of operator L is neither SR nor exact')
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: pi_cone = Cone([ g.list() for g in pi_of_K ],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: P = matrix(K.lattice_dim(), pi_cone.random_element(QQ).list())
-        sage: all([ K.contains(P*x) for x in K ])
-        True
 
 
-    A random element of the positive operator cone should send a random
-    element of the cone into the cone::
+def is_Z_on(L,K):
+    r"""
+    Determine whether or not ``L`` is a Z-operator on ``K``.
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: pi_cone = Cone([ g.list() for g in pi_of_K ],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: P = matrix(K.lattice_dim(), pi_cone.random_element(QQ).list())
-        sage: K.contains(P*K.random_element(ring=QQ))
-        True
+    We say that ``L`` is a Z-operator on ``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.
 
 
-    The lineality space of the dual of the cone of positive operators
-    can be computed from the lineality spaces of the cone and its dual::
+    A matrix is a Z-operator on ``K`` if and only if its negation is a
+    cross-positive operator on ``K``.
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: pi_cone = Cone([ g.list() for g in pi_of_K ],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.dual().linear_subspace()
-        sage: U1 = [ vector((s.tensor_product(x)).list())
-        ....:        for x in K.lines()
-        ....:        for s in K.dual() ]
-        sage: U2 = [ vector((s.tensor_product(x)).list())
-        ....:        for x in K
-        ....:        for s in K.dual().lines() ]
-        sage: expected = pi_cone.lattice().vector_space().span(U1 + U2)
-        sage: actual == expected
-        True
+    INPUT:
 
 
-    The lineality of the dual of the cone of positive operators
-    is known from its lineality space::
+    - ``L`` -- A linear transformation or matrix.
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: n = K.lattice_dim()
-        sage: m = K.dim()
-        sage: l = K.lineality()
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(n**2)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                 lattice=L,
-        ....:                 check=False)
-        sage: actual = pi_cone.dual().lineality()
-        sage: expected = l*(m - l) + m*(n - m)
-        sage: actual == expected
-        True
+    - ``K`` -- A polyhedral closed convex cone.
 
 
-    The dimension of the cone of positive operators is given by the
-    corollary in my paper::
+    OUTPUT:
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: n = K.lattice_dim()
-        sage: m = K.dim()
-        sage: l = K.lineality()
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(n**2)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.dim()
-        sage: expected = n**2 - l*(m - l) - (n - m)*m
-        sage: actual == expected
-        True
+    ``True`` if it can be proven that ``L`` is a Z-operator on ``K``,
+    and ``False`` otherwise.
 
 
-    The trivial cone, full space, and half-plane all give rise to the
-    expected dimensions::
+    .. WARNING::
 
 
-        sage: n = ZZ.random_element().abs()
-        sage: K = Cone([[0] * n], ToricLattice(n))
-        sage: K.is_trivial()
-        True
-        sage: L = ToricLattice(n^2)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.dim()
-        sage: actual == n^2
-        True
-        sage: K = K.dual()
-        sage: K.is_full_space()
-        True
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.dim()
-        sage: actual == n^2
-        True
-        sage: K = Cone([(1,0),(0,1),(0,-1)])
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: actual = Cone([p.list() for p in pi_of_K], check=False).dim()
-        sage: actual == 3
-        True
+        If this function returns ``True``, then ``L`` is a Z-operator
+        on ``K``. However, if ``False`` is returned, that could mean one
+        of two things. The first is that ``L`` is definitely not
+        a Z-operator on ``K``. The second is more of an "I don't know"
+        answer, returned (for example) if we cannot prove that an inner
+        product is nonnegative.
 
 
-    The lineality of the cone of positive operators follows from the
-    description of its generators::
+    EXAMPLES:
+
+    The identity is always a Z-operator in a nontrivial space::
 
         sage: set_random_seed()
 
         sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: n = K.lattice_dim()
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(n**2)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.lineality()
-        sage: expected = n**2 - K.dim()*K.dual().dim()
-        sage: actual == expected
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
+        sage: L = identity_matrix(K.lattice_dim())
+        sage: is_Z_on(L,K)
         True
 
         True
 
-    The trivial cone, full space, and half-plane all give rise to the
-    expected linealities::
+    As is the "zero" transformation::
 
 
-        sage: n = ZZ.random_element().abs()
-        sage: K = Cone([[0] * n], ToricLattice(n))
-        sage: K.is_trivial()
-        True
-        sage: L = ToricLattice(n^2)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = pi_cone.lineality()
-        sage: actual == n^2
-        True
-        sage: K = K.dual()
-        sage: K.is_full_space()
-        True
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K], lattice=L)
-        sage: pi_cone.lineality() == n^2
-        True
-        sage: K = Cone([(1,0),(0,1),(0,-1)])
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K], check=False)
-        sage: actual = pi_cone.lineality()
-        sage: actual == 2
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
+        sage: R = K.lattice().vector_space().base_ring()
+        sage: L = zero_matrix(R, K.lattice_dim())
+        sage: is_Z_on(L,K)
         True
 
         True
 
-    A cone is proper if and only if its cone of positive operators
-    is proper::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: K.is_proper() == pi_cone.is_proper()
-        True
+    TESTS:
 
 
-    The positive operators of a permuted cone can be obtained by
-    conjugation::
+    Everything in ``K.Z_operators_gens()`` should be a Z-operator
+    on ``K``::
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: p = SymmetricGroup(K.lattice_dim()).random_element().matrix()
-        sage: pK = Cone([ p*k for k in K ], K.lattice(), check=False)
-        sage: pi_of_pK = positive_operator_gens(pK)
-        sage: actual = Cone([t.list() for t in pi_of_pK],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: expected = Cone([(p*t*p.inverse()).list() for t in pi_of_K],
-        ....:                   lattice=L,
-        ....:                   check=False)
-        sage: actual.is_equivalent(expected)
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=6)
+        sage: all([ is_Z_on(L,K)
+        ....:       for L in K.Z_operators_gens() ])
         True
         True
-
-    A transformation is positive on a cone if and only if its adjoint is
-    positive on the dual of that cone::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: F = K.lattice().vector_space().base_field()
-        sage: n = K.lattice_dim()
-        sage: L = ToricLattice(n**2)
-        sage: W = VectorSpace(F, n**2)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: pi_of_K_star = positive_operator_gens(K.dual())
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: pi_star = Cone([p.list() for p in pi_of_K_star],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: M = MatrixSpace(F, n)
-        sage: L = M(pi_cone.random_element(ring=QQ).list())
-        sage: pi_star.contains(W(L.transpose().list()))
+        sage: all([ is_Z_on(L.change_ring(SR),K)
+        ....:       for L in K.Z_operators_gens() ])
         True
 
         True
 
-        sage: L = W.random_element()
-        sage: L_star = W(M(L.list()).transpose().list())
-        sage: pi_cone.contains(L) ==  pi_star.contains(L_star)
-        True
     """
     """
-    # Matrices are not vectors in Sage, so we have to convert them
-    # to vectors explicitly before we can find a basis. We need these
-    # two values to construct the appropriate "long vector" space.
-    F = K.lattice().base_field()
-    n = K.lattice_dim()
-
-    tensor_products = [ s.tensor_product(x) for x in K for s in K.dual() ]
+    return is_cross_positive_on(-L,K)
 
 
-    # Convert those tensor products to long vectors.
-    W = VectorSpace(F, n**2)
-    vectors = [ W(tp.list()) for tp in tensor_products ]
 
 
-    check = True
-    if K.is_proper():
-        # All of the generators involved are extreme vectors and
-        # therefore minimal [Tam]_. If this cone is neither solid nor
-        # strictly convex, then the tensor product of ``s`` and ``x``
-        # is the same as that of ``-s`` and ``-x``. However, as a
-        # /set/, ``tensor_products`` may still be minimal.
-        check = False
-
-    # Create the dual cone of the positive operators, expressed as
-    # long vectors.
-    pi_dual = Cone(vectors, ToricLattice(W.dimension()), check=check)
+def is_lyapunov_like_on(L,K):
+    r"""
+    Determine whether or not ``L`` is Lyapunov-like on ``K``.
 
 
-    # Now compute the desired cone from its dual...
-    pi_cone = pi_dual.dual()
+    We say that ``L`` is Lyapunov-like on ``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.
 
 
-    # And finally convert its rays back to matrix representations.
-    M = MatrixSpace(F, n)
-    return [ M(v.list()) for v in pi_cone ]
+    INPUT:
 
 
+    - ``L`` -- A linear transformation or matrix.
 
 
-def Z_transformation_gens(K):
-    r"""
-    Compute generators of the cone of Z-transformations on this cone.
+    - ``K`` -- A polyhedral closed convex cone.
 
     OUTPUT:
 
 
     OUTPUT:
 
-    A list of `n`-by-``n`` matrices where ``n == K.lattice_dim()``.
-    Each matrix ``L`` in the list should have the property that
-    ``(L*x).inner_product(s) <= 0`` whenever ``(x,s)`` is an element of
-    this cone's :meth:`discrete_complementarity_set`. Moreover, any
-    conic (nonnegative linear) combination of these matrices shares the
-    same property.
+    ``True`` if it can be proven that ``L`` is Lyapunov-like on ``K``,
+    and ``False`` otherwise.
 
 
-    REFERENCES:
+    .. WARNING::
 
 
-    M. Orlitzky.
-    Positive operators and Z-transformations on closed convex cones.
+        If this function returns ``True``, then ``L`` is Lyapunov-like
+        on ``K``. However, if ``False`` is returned, that could mean one
+        of two things. The first is that ``L`` is definitely not
+        Lyapunov-like on ``K``. The second is more of an "I don't know"
+        answer, returned (for example) if we cannot prove that an inner
+        product is zero.
 
     EXAMPLES:
 
 
     EXAMPLES:
 
-    Z-transformations on the nonnegative orthant are just Z-matrices.
-    That is, matrices whose off-diagonal elements are nonnegative::
-
-        sage: K = Cone([(1,0),(0,1)])
-        sage: Z_transformation_gens(K)
-        [
-        [ 0 -1]  [ 0  0]  [-1  0]  [1 0]  [ 0  0]  [0 0]
-        [ 0  0], [-1  0], [ 0  0], [0 0], [ 0 -1], [0 1]
-        ]
-        sage: K = Cone([(1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)])
-        sage: all([ z[i][j] <= 0 for z in Z_transformation_gens(K)
-        ....:                    for i in range(z.nrows())
-        ....:                    for j in range(z.ncols())
-        ....:                    if i != j ])
-        True
-
-    The trivial cone in a trivial space has no Z-transformations::
-
-        sage: K = Cone([], ToricLattice(0))
-        sage: Z_transformation_gens(K)
-        []
+    Lyapunov-like operators on the nonnegative orthant are diagonal
+    matrices::
 
 
-    Every operator is a Z-transformation on the ambient vector space::
-
-        sage: K = Cone([(1,),(-1,)])
-        sage: K.is_full_space()
-        True
-        sage: Z_transformation_gens(K)
-        [[-1], [1]]
-
-        sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
-        sage: K.is_full_space()
-        True
-        sage: Z_transformation_gens(K)
-        [
-        [-1  0]  [1 0]  [ 0 -1]  [0 1]  [ 0  0]  [0 0]  [ 0  0]  [0 0]
-        [ 0  0], [0 0], [ 0  0], [0 0], [-1  0], [1 0], [ 0 -1], [0 1]
-        ]
-
-    A non-obvious application is to find the Z-transformations on the
-    right half-plane::
-
-        sage: K = Cone([(1,0),(0,1),(0,-1)])
-        sage: Z_transformation_gens(K)
-        [
-        [-1  0]  [1 0]  [ 0  0]  [0 0]  [ 0  0]  [0 0]
-        [ 0  0], [0 0], [-1  0], [1 0], [ 0 -1], [0 1]
-        ]
-
-    Z-transformations on a subspace are Lyapunov-like and vice-versa::
-
-        sage: K = Cone([(1,0),(-1,0),(0,1),(0,-1)])
-        sage: K.is_full_space()
-        True
-        sage: lls = span([ vector(l.list()) for l in K.lyapunov_like_basis() ])
-        sage: zs  = span([ vector(z.list()) for z in Z_transformation_gens(K) ])
-        sage: zs == lls
+        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
 
     TESTS:
 
         True
 
     TESTS:
 
-    The Z-property is possessed by every Z-transformation::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: dcs = K.discrete_complementarity_set()
-        sage: all([(z*x).inner_product(s) <= 0 for z in Z_of_K
-        ....:                                  for (x,s) in dcs])
-        True
-
-    The lineality space of the cone of Z-transformations is the space of
-    Lyapunov-like transformations::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: Z_cone = Cone([ z.list() for z in Z_transformation_gens(K) ],
-        ....:               lattice=L,
-        ....:               check=False)
-        sage: ll_basis = [ vector(l.list()) for l in K.lyapunov_like_basis() ]
-        sage: lls = L.vector_space().span(ll_basis)
-        sage: Z_cone.linear_subspace() == lls
-        True
-
-    The lineality of the Z-transformations on a cone is the Lyapunov
-    rank of that cone::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: Z_cone  = Cone([ z.list() for z in Z_of_K ],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: Z_cone.lineality() == K.lyapunov_rank()
-        True
-
-    The lineality spaces of the duals of the positive operator and
-    Z-transformation cones are equal. From this it follows that the
-    dimensions of the Z-transformation cone and positive operator cone
-    are equal::
+    The identity is always Lyapunov-like in a nontrivial space::
 
         sage: set_random_seed()
 
         sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: pi_of_K = positive_operator_gens(K)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: pi_cone = Cone([p.list() for p in pi_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: Z_cone = Cone([ z.list() for z in Z_of_K],
-        ....:               lattice=L,
-        ....:               check=False)
-        sage: pi_cone.dim() == Z_cone.dim()
-        True
-        sage: pi_star = pi_cone.dual()
-        sage: z_star = Z_cone.dual()
-        sage: pi_star.linear_subspace() == z_star.linear_subspace()
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=8)
+        sage: L = identity_matrix(K.lattice_dim())
+        sage: is_lyapunov_like_on(L,K)
         True
 
         True
 
-    The trivial cone, full space, and half-plane all give rise to the
-    expected dimensions::
+    As is the "zero" transformation::
 
 
-        sage: n = ZZ.random_element().abs()
-        sage: K = Cone([[0] * n], ToricLattice(n))
-        sage: K.is_trivial()
-        True
-        sage: L = ToricLattice(n^2)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: Z_cone = Cone([z.list() for z in Z_of_K],
-        ....:               lattice=L,
-        ....:               check=False)
-        sage: actual = Z_cone.dim()
-        sage: actual == n^2
-        True
-        sage: K = K.dual()
-        sage: K.is_full_space()
-        True
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: Z_cone = Cone([z.list() for z in Z_of_K],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: actual = Z_cone.dim()
-        sage: actual == n^2
-        True
-        sage: K = Cone([(1,0),(0,1),(0,-1)])
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: Z_cone = Cone([z.list() for z in Z_of_K], check=False)
-        sage: Z_cone.dim() == 3
+        sage: K = random_cone(min_ambient_dim=1, 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
 
         True
 
-    The Z-transformations of a permuted cone can be obtained by
-    conjugation::
+    Everything in ``K.lyapunov_like_basis()`` should be Lyapunov-like
+    on ``K``::
 
 
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: L = ToricLattice(K.lattice_dim()**2)
-        sage: p = SymmetricGroup(K.lattice_dim()).random_element().matrix()
-        sage: pK = Cone([ p*k for k in K ], K.lattice(), check=False)
-        sage: Z_of_pK = Z_transformation_gens(pK)
-        sage: actual = Cone([t.list() for t in Z_of_pK],
-        ....:                lattice=L,
-        ....:                check=False)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: expected = Cone([(p*t*p.inverse()).list() for t in Z_of_K],
-        ....:                   lattice=L,
-        ....:                   check=False)
-        sage: actual.is_equivalent(expected)
+        sage: K = random_cone(min_ambient_dim=1, max_ambient_dim=6)
+        sage: all([ is_lyapunov_like_on(L,K)
+        ....:       for L in K.lyapunov_like_basis() ])
         True
         True
-
-    A transformation is a Z-transformation on a cone if and only if its
-    adjoint is a Z-transformation on the dual of that cone::
-
-        sage: set_random_seed()
-        sage: K = random_cone(max_ambient_dim=4)
-        sage: F = K.lattice().vector_space().base_field()
-        sage: n = K.lattice_dim()
-        sage: L = ToricLattice(n**2)
-        sage: W = VectorSpace(F, n**2)
-        sage: Z_of_K = Z_transformation_gens(K)
-        sage: Z_of_K_star = Z_transformation_gens(K.dual())
-        sage: Z_cone = Cone([p.list() for p in Z_of_K],
-        ....:               lattice=L,
-        ....:               check=False)
-        sage: Z_star = Cone([p.list() for p in Z_of_K_star],
-        ....:               lattice=L,
-        ....:               check=False)
-        sage: M = MatrixSpace(F, n)
-        sage: L = M(Z_cone.random_element(ring=QQ).list())
-        sage: Z_star.contains(W(L.transpose().list()))
+        sage: all([ is_lyapunov_like_on(L.change_ring(SR),K)
+        ....:       for L in K.lyapunov_like_basis() ])
         True
 
         True
 
-        sage: L = W.random_element()
-        sage: L_star = W(M(L.list()).transpose().list())
-        sage: Z_cone.contains(L) ==  Z_star.contains(L_star)
-        True
     """
     """
-    # Matrices are not vectors in Sage, so we have to convert them
-    # to vectors explicitly before we can find a basis. We need these
-    # two values to construct the appropriate "long vector" space.
-    F = K.lattice().base_field()
-    n = K.lattice_dim()
-
-    # These tensor products contain generators for the dual cone of
-    # the cross-positive transformations.
-    tensor_products = [ s.tensor_product(x)
-                        for (x,s) in K.discrete_complementarity_set() ]
-
-    # Turn our matrices into long vectors...
-    W = VectorSpace(F, n**2)
-    vectors = [ W(m.list()) for m in tensor_products ]
-
-    check = True
-    if K.is_proper():
-        # All of the generators involved are extreme vectors and
-        # therefore minimal. If this cone is neither solid nor
-        # strictly convex, then the tensor product of ``s`` and ``x``
-        # is the same as that of ``-s`` and ``-x``. However, as a
-        # /set/, ``tensor_products`` may still be minimal.
-        check = False
-
-    # Create the dual cone of the cross-positive operators,
-    # expressed as long vectors.
-    Sigma_dual = Cone(vectors, lattice=ToricLattice(W.dimension()), check=check)
-
-    # Now compute the desired cone from its dual...
-    Sigma_cone = Sigma_dual.dual()
-
-    # And finally convert its rays back to matrix representations.
-    # But first, make them negative, so we get Z-transformations and
-    # not cross-positive ones.
-    M = MatrixSpace(F, n)
-    return [ -M(v.list()) for v in Sigma_cone ]
+    if L.base_ring().is_exact() or L.base_ring() is SR:
+        # The "fast method" of creating a vector space based on a
+        # ``lyapunov_like_basis`` is actually slower than this.
+        return all([ s*(L*x) == 0
+                     for (x,s) in K.discrete_complementarity_set() ])
+    else:
+        # The only inexact ring that we're willing to work with is SR,
+        # since it can still be exact when working with symbolic
+        # constants like pi and e.
+        raise ValueError('base ring of operator L is neither SR nor exact')
+
+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):
 
 def Z_cone(K):
-    gens = Z_transformation_gens(K)
+    gens = K.Z_operators_gens()
     L = ToricLattice(K.lattice_dim()**2)
     return Cone([ g.list() for g in gens ], lattice=L, check=False)
 
     L = ToricLattice(K.lattice_dim()**2)
     return Cone([ g.list() for g in gens ], lattice=L, check=False)
 
-def pi_cone(K):
-    gens = positive_operator_gens(K)
-    L = ToricLattice(K.lattice_dim()**2)
+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)
     return Cone([ g.list() for g in gens ], lattice=L, check=False)