| | 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 | |