Ticket #10737: trac_10737-extended-echelon-form.patch

File trac_10737-extended-echelon-form.patch, 9.1 KB (added by rbeezer, 2 years ago)
  • sage/matrix/matrix2.pyx

    # HG changeset patch
    # User Rob Beezer <beezer@ups.edu>
    # Date 1296756395 28800
    # Node ID b83c2c3a8a8409e265d3904483059ec934cfcab2
    # Parent  ca99b574cd0fc987138a9b741e6e9b747642bcfa
    10737: extended echelon form of a matrix
    
    diff -r ca99b574cd0f -r b83c2c3a8a84 sage/matrix/matrix2.pyx
    a b  
    46364636        self.cache('echelon_form', self) 
    46374637        verbose('done with gauss echelon form', tm) 
    46384638 
     4639    def extended_echelon_form(self, subdivide=False, **kwds): 
     4640        r""" 
     4641        Returns the echelon form of ``self`` augmented with an identity matrix. 
     4642 
     4643        INPUT: 
     4644 
     4645        - ``subdivide`` - default: ``False`` - determines if the 
     4646          returned matrix is subdivided.  See the description of the 
     4647          (output) below for details. 
     4648        - ``kwds`` - additional keywords that can be passed to 
     4649          the method that computes the echelon form. 
     4650 
     4651        OUTPUT: 
     4652 
     4653        If `A` is an `m\times n` matrix, add the `m` columns of an 
     4654        `m\times m` identity matrix to the right of ``self``.  Then 
     4655        row-reduce this `m\times(n+m)` matrix.  This matrix is returned 
     4656        as an immutable matrix. 
     4657 
     4658        If ``subdivide`` is ``True`` then the returned matrix has a single 
     4659        division among the columns and a single division among the rows. 
     4660        The column subdivision has `n` columns to the left and `m` 
     4661        columns to the right.  The row division separates the non-zero rows 
     4662        from the zero rows, when restricted to the first `n` columns. 
     4663 
     4664        For a nonsingular matrix the final `m` columns of the extended 
     4665        echelon form are the inverse of ``self``.  For a matrix of 
     4666        any size, the final `m` columns provide a matrix that transforms 
     4667        ``self`` to echelon form when it multiplies ``self`` from the left. 
     4668        When the base ring is a field, the uniqueness of reduced row-echelon 
     4669        form implies that this transformation matrix can be taken as a 
     4670        canonical set of linear combinations of the rows of ``self`` that 
     4671        yield reduced row-echelon form. 
     4672 
     4673        When subdivided as described above, and again over a field, the 
     4674        parts of the subdivision in the upper-left corner and lower-right 
     4675        corner satisfy several interesting relationships with the row space, 
     4676        column space, left kernel and right kernel of ``self``.  See the 
     4677        examples below. 
     4678 
     4679        .. note:: 
     4680 
     4681            This method returns an echelon form.  If the base ring 
     4682            is not a field, no atttempt is made to move to the fraction field. 
     4683            See an example below where the base ring is changed manually. 
     4684 
     4685        EXAMPLES: 
     4686 
     4687        The four relationships at the end of this example hold in general. :: 
     4688 
     4689            sage: A = matrix(QQ, [[2, -1, 7, -1, 0, -3], 
     4690            ...                   [-1, 1, -5, 3, 4, 4], 
     4691            ...                   [2, -1, 7, 0, 2, -2], 
     4692            ...                   [2, 0, 4, 3, 6, 1], 
     4693            ...                   [2, -1, 7, 0, 2, -2]]) 
     4694            sage: E = A.extended_echelon_form(subdivide=True); E 
     4695            [   1    0    2    0    0   -1|   0   -1    0    1   -1] 
     4696            [   0    1   -3    0   -2    0|   0   -2    0    2   -3] 
     4697            [   0    0    0    1    2    1|   0  2/3    0 -1/3  2/3] 
     4698            [-----------------------------+------------------------] 
     4699            [   0    0    0    0    0    0|   1  2/3    0 -1/3 -1/3] 
     4700            [   0    0    0    0    0    0|   0    0    1    0   -1] 
     4701            sage: J = E.matrix_from_columns(range(6,11)); J 
     4702            [   0   -1    0    1   -1] 
     4703            [   0   -2    0    2   -3] 
     4704            [   0  2/3    0 -1/3  2/3] 
     4705            [   1  2/3    0 -1/3 -1/3] 
     4706            [   0    0    1    0   -1] 
     4707            sage: J*A == A.rref() 
     4708            True 
     4709            sage: C = E.subdivision(0,0); C 
     4710            [ 1  0  2  0  0 -1] 
     4711            [ 0  1 -3  0 -2  0] 
     4712            [ 0  0  0  1  2  1] 
     4713            sage: L = E.subdivision(1,1); L 
     4714            [   1  2/3    0 -1/3 -1/3] 
     4715            [   0    0    1    0   -1] 
     4716            sage: A.right_kernel() == C.right_kernel() 
     4717            True 
     4718            sage: A.row_space() == C.row_space() 
     4719            True 
     4720            sage: A.column_space() == L.right_kernel() 
     4721            True 
     4722            sage: A.left_kernel() == L.row_space() 
     4723            True 
     4724 
     4725        For a nonsingular matrix, the right half of the extended 
     4726        echelon form is the inverse matrix. :: 
     4727 
     4728            sage: B = matrix(QQ, [[1,3,4], [1,4,4], [0,-2,-1]]) 
     4729            sage: E = B.extended_echelon_form() 
     4730            sage: J = E.matrix_from_columns(range(3,6)); J 
     4731            [-4  5  4] 
     4732            [-1  1  0] 
     4733            [ 2 -2 -1] 
     4734            sage: J == B.inverse() 
     4735            True 
     4736 
     4737        The result is in echelon form, so if the base ring is 
     4738        not a field, the leading entry of each row may not be 1. 
     4739        But you can easily change to the fraction field if necessary.  :: 
     4740 
     4741            sage: A = matrix(ZZ, [[16, 20, 4, 5, -4, 13, 5], 
     4742            ...                   [10, 13, 3, -3, 7, 11, 6], 
     4743            ...                   [-12, -15, -3, -3, 2, -10, -4], 
     4744            ...                   [10, 13, 3, 3, -1, 9, 4], 
     4745            ...                   [4, 5, 1, 8, -10, 1, -1]]) 
     4746            sage: E = A.extended_echelon_form(subdivide=True); E 
     4747            [ 2  0 -2  2 -9 -3 -4| 0  4 -3 -9  4] 
     4748            [ 0  1  1  2  0  1  1| 0  1  2  1  1] 
     4749            [ 0  0  0  3 -4 -1 -1| 0  3  1 -3  3] 
     4750            [--------------------+--------------] 
     4751            [ 0  0  0  0  0  0  0| 1  6  3 -6  5] 
     4752            [ 0  0  0  0  0  0  0| 0  7  2 -7  6] 
     4753            sage: J = E.matrix_from_columns(range(7,12)); J 
     4754            [ 0  4 -3 -9  4] 
     4755            [ 0  1  2  1  1] 
     4756            [ 0  3  1 -3  3] 
     4757            [ 1  6  3 -6  5] 
     4758            [ 0  7  2 -7  6] 
     4759            sage: J*A == A.echelon_form() 
     4760            True 
     4761            sage: B = A.change_ring(QQ) 
     4762            sage: B.extended_echelon_form(subdivide=True) 
     4763            [     1      0     -1      0  -19/6   -7/6   -5/3|     0      0 -89/42   -5/2    1/7] 
     4764            [     0      1      1      0    8/3    5/3    5/3|     0      0  34/21      2   -1/7] 
     4765            [     0      0      0      1   -4/3   -1/3   -1/3|     0      0   1/21      0    1/7] 
     4766            [------------------------------------------------+----------------------------------] 
     4767            [     0      0      0      0      0      0      0|     1      0    9/7      0   -1/7] 
     4768            [     0      0      0      0      0      0      0|     0      1    2/7     -1    6/7] 
     4769 
     4770        Subdivided, or not, the result is immutable, so make a 
     4771        copy if you want to make changes.  :: 
     4772 
     4773            sage: A = matrix(FiniteField(7), [[2,0,3], [5,5,3], [5,6,5]]) 
     4774            sage: E = A.extended_echelon_form() 
     4775            sage: E.is_mutable() 
     4776            False 
     4777            sage: F = A.extended_echelon_form(subdivide=True) 
     4778            sage: F 
     4779            [1 0 0|0 4 6] 
     4780            [0 1 0|4 2 2] 
     4781            [0 0 1|5 2 3] 
     4782            [-----+-----] 
     4783            sage: F.is_mutable() 
     4784            False 
     4785            sage: G = copy(F) 
     4786            sage: G.subdivide([],[]); G 
     4787            [1 0 0 0 4 6] 
     4788            [0 1 0 4 2 2] 
     4789            [0 0 1 5 2 3] 
     4790 
     4791        If you want to determine exactly which algorithm is 
     4792        used to compute the echelon form, you can add additional 
     4793        keywords to pass on to the ``echelon_form()`` routine 
     4794        employed on the augmented matrix.  Sending the flag 
     4795        ``include_zero_rows`` is a bit silly, since the 
     4796        extended echelon form will never have any zero rows. :: 
     4797 
     4798            sage: A = matrix(ZZ, [[1,2], [5,0], [5,9]]) 
     4799            sage: E = A.extended_echelon_form(algorithm='padic', include_zero_rows=False) 
     4800            sage: E 
     4801            [  1   0  36   1  -8] 
     4802            [  0   1   5   0  -1] 
     4803            [  0   0  45   1 -10] 
     4804 
     4805        TESTS: 
     4806 
     4807        The ``subdivide`` keyword is checked. :: 
     4808 
     4809            sage: A = matrix(QQ, 2, range(4)) 
     4810            sage: A.extended_echelon_form(subdivide='junk') 
     4811            Traceback (most recent call last): 
     4812            ... 
     4813            TypeError: subdivide must be True or False, not junk 
     4814 
     4815        AUTHOR: 
     4816 
     4817        - Rob Beezer (2011-02-02) 
     4818        """ 
     4819        if not subdivide in [True, False]: 
     4820            raise TypeError("subdivide must be True or False, not %s" % subdivide) 
     4821        R = self.base_ring() 
     4822        import constructor 
     4823        ident = constructor.identity_matrix(R, self.nrows()) 
     4824        E = self.augment(ident) 
     4825        extended = E.echelon_form(**kwds) 
     4826        if subdivide: 
     4827            from copy import copy 
     4828            extended = copy(extended) 
     4829            # pivots of E are cached from echelon form computation 
     4830            rank = len([c for c in E.pivots() if c < self.ncols()]) 
     4831            extended.subdivide(rank, self.ncols()) 
     4832            extended.set_immutable() 
     4833        return extended 
     4834 
    46394835    def weak_popov_form(self, ascend=True): 
    46404836        """ 
    46414837        This function computes a weak Popov form of a matrix over a rational