Ticket #9754: trac_9754-random-subspace-unimodular-matrix-v2.patch

File trac_9754-random-subspace-unimodular-matrix-v2.patch, 15.5 KB (added by bwonderly, 3 years ago)

revised to fit generalization of random_matrix constructor. Apply v4 from #9720 and v2 from #9803 and go from there.

  • sage/matrix/constructor.py

    # HG changeset patch
    # User Billy Wonderly <bwonderly@ups.edu>
    # Date 1282951283 25200
    # Node ID 98eb0fc79e75a774d86d4cd6179388b89108bf98
    # Parent  3b7bc4feac06ca113c8f501d2ad29337d3d79959
    Trac 9754: random subspaces and unimodular  matrices
    
    diff -r 3b7bc4feac06 -r 98eb0fc79e75 sage/matrix/constructor.py
    a b  
    808808 
    809809       -  ``echelon_form`` - creates a matrix in echelon form 
    810810 
    811        -  ``echelonizable`` - creates a matrix that has a predictable echelon form 
     811       -  ``echelonizable`` - creates a matrix that has a predictable 
     812          echelon form 
     813 
     814       - ``subspaces`` - creates a matrix whose four subspaces, when 
     815         explored, have reasonably sized, integral valued, entries. 
     816 
     817       - ``unimodular`` - creates a matrix of determinant 1. 
    812818 
    813819    -  ``*args, **kwds`` - arguments and keywords to describe additional properties. 
    814820       See more detailed documentation below. 
     
    10041010    matrix also has integer entries.  Other exact rings may be also 
    10051011    specified, but there is no notion of controlling the size.  Square matrices 
    10061012    of full rank generated by this function always have determinant one, and 
    1007     can be constructed with the upcoming ``unimodular`` keyword. :: 
     1013    can be constructed with the ``unimodular`` keyword. :: 
    10081014 
    10091015        sage: A=random_matrix(QQ, 4, 8, algorithm='echelonizable', rank=3, upper_bound=60); A # random 
    10101016        sage: A.base_ring() 
     
    10261032 
    10271033        from sage.matrix.constructor import random_echelonizable_matrix 
    10281034 
     1035    Random matrices with predictable subspaces.  The ``algorithm='subspaces'`` 
     1036    keyword, along with an optional nullity (``nullity``) will return  
     1037    a matrix whose natural basis vectors for its four fundamental subspaces, if computed as 
     1038    described in the documentation of the :func:`~sage.matrix.constructor.random_subspaces_matrix` 
     1039    contain only integer entries.  If ``nullity``, is not set, the  
     1040    nullity of the matrix will be generated randomly. :: 
     1041 
     1042        sage: B=random_matrix(QQ, 5, 6, algorithm='subspaces', nullity=2); B #random 
     1043        sage: B_expanded=B.augment(identity_matrix(5)).rref() 
     1044        sage: all([x in ZZ for x in B_expanded.list()]) 
     1045        True 
     1046        sage: C=B_expanded.submatrix(0,0,B.nrows()-B.nullity(),B.ncols()) 
     1047        sage: L=B_expanded.submatrix(B.nrows()-B.nullity(),B.ncols()) 
     1048        sage: B.right_kernel()==C.right_kernel() 
     1049        True 
     1050        sage: B.row_space()==C.row_space() 
     1051        True 
     1052        sage: B.column_space()==L.right_kernel() 
     1053        True 
     1054        sage: B.left_kernel()==L.row_space() 
     1055        True 
     1056 
     1057    For more, see the documentation of the :func:`~sage.matrix.constructor.random_subspaces_matrix` 
     1058    function.  In the notebook or at the Sage command-line, first execute the following to make 
     1059    this further documentation available:: 
     1060 
     1061        from sage.matrix.constructor import random_subspaces_matrix 
     1062 
     1063    Random unimodular matrices.  The ``algorithm='unimodular'`` 
     1064    keyword, along with an optional entry size control (``upper_bound``) 
     1065    will return a matrix of determinant 1. When the base ring is ``ZZ`` 
     1066    or ``QQ`` the result has integer entries, whose magnitudes 
     1067    can be limited by the value of ``upper_bound``. :: 
     1068 
     1069        sage: C=random_matrix(QQ, 5, algorithm='unimodular', upper_bound=70); C # random 
     1070        sage: det(C) 
     1071        1 
     1072        sage: C.base_ring() 
     1073        Rational Field 
     1074        sage: (C.nrows(), C.ncols()) 
     1075        (5, 5) 
     1076        sage: all([abs(x)<70 for x in C.list()]) 
     1077        True 
     1078 
     1079    For more, see the documentation of the :func:`~sage.matrix.constructor.random_unimodular_matrix` 
     1080    function.  In the notebook or at the Sage command-line, first execute the following to make 
     1081    this further documentation available:: 
     1082 
     1083        from sage.matrix.constructor import random_unimodular_matrix 
     1084 
    10291085    TESTS: 
    10301086 
    10311087    We return an error for a bogus value of ``algorithm``:: 
     
    10591115        return random_rref_matrix(parent, *args, **kwds) 
    10601116    elif algorithm == 'echelonizable': 
    10611117        return random_echelonizable_matrix(parent, *args, **kwds) 
     1118    elif algorithm == 'subspaces': 
     1119        return random_subspaces_matrix(parent, *args, **kwds) 
     1120    elif algorithm == 'unimodular': 
     1121        return random_unimodular_matrix(parent, *args, **kwds) 
    10621122    else: 
    10631123        raise ValueError('random matrix algorithm "%s" is not recognized' % algorithm) 
    10641124 
     
    17091769        sage: B.rank() 
    17101770        4 
    17111771 
    1712     Square matrices over ZZ or QQ with full rank are unimodular. :: 
     1772    Square matrices over ZZ or QQ with full rank are always unimodular. :: 
    17131773 
    17141774        sage: E=random_matrix(QQ, 7, 7, algorithm='echelonizable', rank=7); E # random 
    17151775        [  1  -1   5  12 -24 -41  47] 
     
    17671827                row_index=0 
    17681828                while row_index<rows: 
    17691829                    # To each row in a pivot column add a scalar multiple of the pivot row. 
     1830                    # for full rank, square matrices, using only this row operation preserves the determinant of 1. 
    17701831                    if pivots!=row_index: 
    17711832                    # To ensure a leading one is not removed by the addition of the pivot row by its 
    17721833                    # additive inverse. 
     
    18111872        if rows>1: 
    18121873            matrix.add_multiple_of_row(0,randint(1,rows-1),ring.random_element()) 
    18131874    return matrix 
     1875 
     1876def random_subspaces_matrix(parent, nullity=None): 
     1877    """ 
     1878    Create a matrix of the designated size and nullity whose right and 
     1879    left null spaces, column space, and row space have desirable 
     1880    properties that simplify the subspaces.  
     1881 
     1882    INPUT: 
     1883 
     1884    - ``parent`` - A matrix space specifying the base ring, dimensions, and 
     1885      representation (dense/sparse) for the result.  The base ring must be exact. 
     1886 
     1887    - ``nullity`` - The desired nullity of the return matrix. (Default: None) 
     1888 
     1889    OUTPUT: 
     1890 
     1891    A matrix whose natrual basis vectors for its four subspaces, when  
     1892    computed, have reasonably sized, integral valued, entries. 
     1893 
     1894    .. note:: 
     1895 
     1896        It is easiest to use this function via a call to the 
     1897        :func:`~sage.matrix.constructor.random_matrix` 
     1898        function with the ``algorithm='subspaces'`` keyword.  We provide 
     1899        one example accessing this function directly, while the remainder will 
     1900        use this more general function. 
     1901 
     1902    EXAMPLES: 
     1903 
     1904    A 6x8 matrix with designated nullity of 3.  The four subspaces are 
     1905    determined using one simple routine in which we augment the 
     1906    original matrix with the equal row dimension identity matrix.  The 
     1907    resulting matrix is then put in reduced row-echelon form and the 
     1908    subspaces can then be determined by analyzing subdivisions of this 
     1909    matrix. See the four subspaces routine in [BEEZER]_ for more. :: 
     1910 
     1911        sage: from sage.matrix.constructor import random_subspaces_matrix 
     1912        sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 6, 8) 
     1913        sage: B=random_subspaces_matrix(matrix_space, nullity=3); B # random 
     1914        [ 113  339  -46  218 -243  641 -269 -306] 
     1915        [ -33  -99   13  -63   69 -185   77   90] 
     1916        [  35  105  -14   67  -74  197  -82  -95] 
     1917        [ -18  -54    7  -34   37 -100   41   49] 
     1918        [ -26  -78   10  -49   53 -144   59   71] 
     1919        [  -8  -24    3  -15   16  -44   18   22] 
     1920        sage: B.nullity() 
     1921        3 
     1922        sage: B_expanded=B.augment(identity_matrix(6)).rref() 
     1923        sage: B_expanded # random 
     1924        [  1   3   0   0   1   1   3  -2   0   0  -3   0  -9  16] 
     1925        [  0   0   1   0   3  -2  -1  -3   0   0   2   0  11 -27] 
     1926        [  0   0   0   1  -1   2  -3  -1   0   0   2   0   7 -14] 
     1927        [  0   0   0   0   0   0   0   0   1   0  -5   0  -3   2] 
     1928        [  0   0   0   0   0   0   0   0   0   1   1   0   1  -3] 
     1929        [  0   0   0   0   0   0   0   0   0   0   0   1  -1   1] 
     1930        sage: B_expanded.subdivide(B.nrows()-B.nullity(),B.ncols());B_expanded # random 
     1931        [  1   3   0   0   1   1   3  -2|  0   0  -3   0  -9  16] 
     1932        [  0   0   1   0   3  -2  -1  -3|  0   0   2   0  11 -27] 
     1933        [  0   0   0   1  -1   2  -3  -1|  0   0   2   0   7 -14] 
     1934        [-------------------------------+-----------------------] 
     1935        [  0   0   0   0   0   0   0   0|  1   0  -5   0  -3   2] 
     1936        [  0   0   0   0   0   0   0   0|  0   1   1   0   1  -3] 
     1937        [  0   0   0   0   0   0   0   0|  0   0   0   1  -1   1] 
     1938        sage: C=B_expanded.subdivision(0,0) 
     1939        sage: C # random 
     1940        [ 1  3  0  0  1  1  3 -2] 
     1941        [ 0  0  1  0  3 -2 -1 -3] 
     1942        [ 0  0  0  1 -1  2 -3 -1] 
     1943        sage: L=B_expanded.subdivision(1,1) 
     1944        sage: L # random 
     1945        [ 1  0 -5  0 -3  2] 
     1946        [ 0  1  1  0  1 -3] 
     1947        [ 0  0  0  1 -1  1] 
     1948        sage: B.right_kernel()==C.right_kernel() 
     1949        True 
     1950        sage: B.row_space()==C.row_space() 
     1951        True 
     1952        sage: B.column_space()==L.right_kernel() 
     1953        True 
     1954        sage: B.left_kernel()==L.row_space() 
     1955        True 
     1956 
     1957    A matrix to show that the null space of the L matrix is the column space of the starting matrix. :: 
     1958 
     1959        sage: A=random_matrix(QQ, 5, 7, algorithm='subspaces', nullity=None); A # random 
     1960        [-31  12  -9 -27  21   2 -15] 
     1961        [105 -24   6 103 -30 -34  79] 
     1962        [ 29  -9   5  26 -14  -5  17] 
     1963        [233 -55  16 228 -71 -73 173] 
     1964        [-42  10  -3 -41  13  13 -31] 
     1965        sage: A.nullity() # random 
     1966        1 
     1967        sage: A_expanded=A.augment(identity_matrix(5)).rref() 
     1968        sage: A_expanded # random 
     1969        [  1   0   0   0   0   1   0   0   3   7  25 151] 
     1970        [  0   1   0   0   1   2   1   0   5  21  84 493] 
     1971        [  0   0   1   0  -1   2   0   0   2  13  53 308] 
     1972        [  0   0   0   1   0  -1   1   0  -2  -3  -9 -57] 
     1973        [  0   0   0   0   0   0   0   1  -3   1   1  -2] 
     1974        sage: C=A_expanded.submatrix(0,0,A.nrows()-A.nullity(),A.ncols()) 
     1975        sage: L=A_expanded.submatrix(A.nrows()-A.nullity(),A.ncols()) 
     1976        sage: A.right_kernel()==C.right_kernel() 
     1977        True 
     1978        sage: A.row_space()==C.row_space() 
     1979        True 
     1980        sage: A.column_space()==L.right_kernel() 
     1981        True 
     1982        sage: A.left_kernel()==L.row_space() 
     1983        True 
     1984 
     1985    TESTS: 
     1986 
     1987    The designated rank of the L matrix cannot be greater than the number of desired rows. :: 
     1988 
     1989        sage: random_matrix(QQ, 19, 20, algorithm='subspaces', nullity=21) 
     1990        Traceback (most recent call last): 
     1991        ... 
     1992        ValueError: nullity cannot exceed the number of rows or columns. 
     1993 
     1994    REFERENCES: 
     1995 
     1996        .. [BEEZER] `A First Course in Linear Algebra <http://linear.ups.edu/>`_. 
     1997           Robert A. Beezer, accessed 15 July 2010. 
     1998 
     1999    AUTHOR: 
     2000 
     2001    Billy Wonderly (2010-07) 
     2002    """ 
     2003 
     2004    import sage.gsl.probability_distribution as pd 
     2005 
     2006    ring = parent.base_ring() 
     2007    rows = parent.nrows() 
     2008    columns = parent.nrows() 
     2009 
     2010    if nullity>rows: 
     2011        raise ValueError("nullity cannot exceed the number of rows or columns.") 
     2012    # If nullity is not designated, generate using probability distribution skewing to smaller numbers, always at least 1. 
     2013    if nullity==None: 
     2014        nullity_generator=pd.RealDistribution("beta",[1.4,5.5]) 
     2015        nullity=int(nullity_generator.get_random_element()*(rows-1)+1) 
     2016    else: 
     2017        nullity=nullity 
     2018    rank=rows-nullity 
     2019    B=random_matrix(ring, rows, columns, algorithm='echelon_form', num_pivots=rank) 
     2020    # Create a nonsingular matrix whose columns will be used to stack a matrix over the L matrix, forming a nonsingular matrix. 
     2021    K_nonzero_columns=random_matrix(ring, rank, rank, algorithm='echelonizable', rank=rank)  
     2022    K=matrix(QQ,rank,rows) 
     2023    L=random_matrix(ring, nullity, rows, algorithm='echelon_form', num_pivots=nullity) 
     2024    for column in range(len(L.nonpivots())): 
     2025        for entry in range(rank): 
     2026            K[entry,L.nonpivots()[column]]=K_nonzero_columns[entry,column] 
     2027    J=K.stack(L) 
     2028    J_inverse=J.inverse() 
     2029    N=B.augment(J) 
     2030    M=J_inverse*N # Entries remain whole values as J is unimodular. 
     2031    return_matrix=M.submatrix(0,0,M.nrows(),M.ncols()-M.nrows()) 
     2032    return return_matrix 
     2033 
     2034def random_unimodular_matrix(parent, upper_bound=None): 
     2035    """ 
     2036    Generate a random unimodular (determinant 1) matrix of a desired size over a desired ring. 
     2037 
     2038    INPUT: 
     2039 
     2040    - ``parent`` - A matrix space specifying the base ring, dimensions 
     2041      and representation (dense/sparse) for the result.  The base ring 
     2042      must be exact. 
     2043 
     2044    - ``upper_bound`` - For large matrices over QQ or ZZ, 
     2045      ``upper_bound`` is the largest value matrix entries can achieve. 
     2046 
     2047    OUTPUT: 
     2048 
     2049    An invertible matrix with the desired properties and determinant 1. 
     2050 
     2051    .. note:: 
     2052 
     2053        It is easiest to use this function via a call to the 
     2054        :func:`~sage.matrix.constructor.random_matrix` 
     2055        function with the ``algorithm='unimodular'`` keyword.  We provide 
     2056        one example accessing this function directly, while the remainder will 
     2057        use this more general function. 
     2058 
     2059    EXAMPLES: 
     2060 
     2061    A matrix size 5 over QQ. :: 
     2062 
     2063        sage: from sage.matrix.constructor import random_unimodular_matrix 
     2064        sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 5) 
     2065        sage: A=random_unimodular_matrix(matrix_space); A # random 
     2066        [  -8   31   85  148 -419] 
     2067        [   2   -9  -25  -45  127] 
     2068        [  -1   10   30   65 -176] 
     2069        [  -3   12   33   58 -164] 
     2070        [   5  -21  -59 -109  304] 
     2071        sage: det(A) 
     2072        1 
     2073 
     2074    A matrix size 6 with entries no larger than 50. :: 
     2075 
     2076        sage: B=random_matrix(ZZ, 7, algorithm='unimodular', upper_bound=50);B # random 
     2077        [ -1   0   3   1  -2   2   9] 
     2078        [  1   2  -5   0  14  19 -49] 
     2079        [ -3  -2  12   5  -6  -4  24] 
     2080        [  1   2  -9  -3   3   4  -7] 
     2081        [ -2  -1   7   2  -8  -5  31] 
     2082        [  2   2  -6  -3   8  16 -32] 
     2083        [  1   2  -9  -2   5   6 -12] 
     2084        sage: det(B) 
     2085        1 
     2086 
     2087    A matrix over the number Field in `y` with defining polynomial `y^2-2y-2`. :: 
     2088 
     2089        sage: y = var('y') 
     2090        sage: K=NumberField(y^2-2*y-2,'y') 
     2091        sage: C=random_matrix(K, 3, algorithm='unimodular');C # random 
     2092        [   2*y - 33 681*y - 787   31*y - 37] 
     2093        [      y + 6 -155*y + 83    -7*y + 4] 
     2094        [         -y   24*y + 51       y + 3] 
     2095        sage: det(C) 
     2096        1 
     2097 
     2098    TESTS: 
     2099 
     2100    Unimodular matrices are square. :: 
     2101 
     2102        sage: random_matrix(QQ, 5, 6, algorithm='unimodular') 
     2103        Traceback (most recent call last): 
     2104        ... 
     2105        TypeError: a unimodular matrix must be square. 
     2106 
     2107    Only matrices over ZZ and QQ can have size control. :: 
     2108 
     2109        sage: F.<a>=GF(5^7) 
     2110        sage: random_matrix(F, 5, algorithm='unimodular', upper_bound=20) 
     2111        Traceback (most recent call last): 
     2112        ... 
     2113        TypeError: only matrices over ZZ or QQ can have size control. 
     2114 
     2115    AUTHOR: 
     2116 
     2117    Billy Wonderly (2010-07) 
     2118    """ 
     2119 
     2120    ring=parent.base_ring() 
     2121    size=parent.nrows() 
     2122    if parent.nrows()!=parent.ncols(): 
     2123        raise TypeError("a unimodular matrix must be square.") 
     2124    if upper_bound!=None and (ring!=ZZ and ring!=QQ): 
     2125        raise TypeError("only matrices over ZZ or QQ can have size control.") 
     2126    if upper_bound==None: 
     2127        # random_echelonizable_matrix() always returns a determinant one matrix if given full rank. 
     2128        return random_matrix(ring, size, algorithm='echelonizable', rank=size) 
     2129    elif upper_bound!=None and (ring==ZZ or ring==QQ): 
     2130        return random_matrix(ring, size,algorithm='echelonizable',rank=size, upper_bound=upper_bound) 
     2131