]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
Add the random_psd() function.
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 4 Nov 2014 14:57:53 +0000 (09:57 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 4 Nov 2014 14:57:53 +0000 (09:57 -0500)
mjo/cone/symmetric_psd.py

index c9f2a4df4ac87ffca9e44a956516af794a934ee7..bb46fc926ec8dc23588ef08ea49a974060a02841 100644 (file)
@@ -201,3 +201,120 @@ def factor_psd(A):
     Q = matrix(A.base_ring(), [ vec for (val,vec) in evs ]).transpose()
 
     return Q*root_D*Q.transpose()
+
+
+def random_psd(V, accept_zero=True, rank=None):
+    """
+    Generate a random symmetric positive-semidefinite matrix over the
+    vector space ``V``. That is, the returned matrix will be a linear
+    transformation on ``V``, with the same base ring as ``V``.
+
+    We take a very loose interpretation of "random," here. Otherwise we
+    would never (for example) choose a matrix on the boundary of the
+    cone (with a zero eigenvalue).
+
+    INPUT:
+
+    - ``V`` - The vector space on which the returned matrix will act.
+
+    - ``accept_zero`` - Do you want to accept the zero matrix (which
+                        is symmetric PSD? Defaults to ``True``.
+
+    - ``rank`` - Require the returned matrix to have the given rank
+                 (optional).
+
+    OUTPUT:
+
+    A random symmetric positive semidefinite matrix, i.e. a linear
+    transformation from ``V`` to itself.
+
+    ALGORITHM:
+
+    The matrix is constructed from some number of spectral projectors,
+    which in turn are created at "random" from the underlying vector
+    space ``V``.
+
+    If no particular ``rank`` is desired, we choose the number of
+    projectors at random. Otherwise, we keep adding new projectors until
+    the desired rank is achieved.
+
+    Finally, before returning, we check if the matrix is zero. If
+    ``accept_zero`` is ``False``, we restart the process from the
+    beginning.
+
+    EXAMPLES:
+
+    Well, it doesn't crash at least::
+
+        sage: V = VectorSpace(QQ, 2)
+        sage: A = random_psd(V)
+        sage: A.matrix_space()
+        Full MatrixSpace of 2 by 2 dense matrices over Rational Field
+        sage: is_symmetric_psd(A)
+        True
+
+    A matrix with the desired rank is returned::
+
+        sage: V = VectorSpace(QQ, 5)
+        sage: A = random_psd(V,False,1)
+        sage: A.rank()
+        1
+        sage: A = random_psd(V,False,2)
+        sage: A.rank()
+        2
+        sage: A = random_psd(V,False,3)
+        sage: A.rank()
+        3
+        sage: A = random_psd(V,False,4)
+        sage: A.rank()
+        4
+        sage: A = random_psd(V,False,5)
+        sage: A.rank()
+        5
+
+    If the user asks for a rank that's too high, we fail::
+
+        sage: V = VectorSpace(QQ, 2)
+        sage: A = random_psd(V,False,3)
+        Traceback (most recent call last):
+        ...
+        ValueError: The ``rank`` must be between 0 and the dimension of ``V``.
+
+    """
+
+    # We construct the matrix from its spectral projectors. Since
+    # there can be at most ``n`` of them, where ``n`` is the dimension
+    # of our vector space, we want to choose a random integer between
+    # ``0`` and ``n`` and then construct that many random elements of
+    # ``V``.
+    n = V.dimension()
+
+    rank_A = 0
+    if rank is None:
+        # Choose one randomly
+        rank_A = ZZ.random_element(n+1)
+    elif (rank < 0) or (rank > n):
+        # The rank of ``A`` can be at most ``n``.
+        msg  = 'The ``rank`` must be between 0 and the dimension of ``V``.'
+        raise ValueError(msg)
+    else:
+        # Use the one the user gave us.
+        rank_A = rank
+
+    # Begin with the zero matrix, and add projectors to it if we have
+    # any.
+    A = V.zero_element().column()*V.zero_element().row()
+
+    # Careful, begin at idx=1 so that we only generate a projector
+    # when rank_A is greater than zero.
+    while A.rank() < rank_A:
+        v = V.random_element()
+        A += v.column()*v.row()
+
+    if accept_zero or not A.is_zero():
+        # We either don't care what ``A`` is, or it's non-zero, so
+        # just return it.
+        return A
+    else:
+        # Uh oh, we need to generate a new one.
+        return random_psd(V, accept_zero, rank)