| | 196 | def inverse(self): |
| | 197 | r""" |
| | 198 | Returns the inverse of this matrix morphism, if the inverse exists. |
| | 199 | |
| | 200 | Raises a ``ZeroDivisionError`` if the inverse does not exist. |
| | 201 | |
| | 202 | EXAMPLES: |
| | 203 | |
| | 204 | An invertible morphism created as a restriction of |
| | 205 | a non-invertible morphism, and which has an unequal |
| | 206 | domain and codomain. :: |
| | 207 | |
| | 208 | sage: V = QQ^4 |
| | 209 | sage: W = QQ^3 |
| | 210 | sage: m = matrix(QQ, [[2, 0, 3], [-6, 1, 4], [1, 2, -4], [1, 0, 1]]) |
| | 211 | sage: phi = V.hom(m, W) |
| | 212 | sage: rho = phi.restrict_domain(V.span([V.0, V.3])) |
| | 213 | sage: zeta = rho.restrict_codomain(W.span([W.0, W.2])) |
| | 214 | sage: x = vector(QQ, [2, 0, 0, -7]) |
| | 215 | sage: y = zeta(x); y |
| | 216 | (-3, 0, -1) |
| | 217 | sage: inv = zeta.inverse(); inv |
| | 218 | Free module morphism defined by the matrix |
| | 219 | [-1 3] |
| | 220 | [ 1 -2] |
| | 221 | Domain: Vector space of degree 3 and dimension 2 over Rational Field |
| | 222 | Basis ... |
| | 223 | Codomain: Vector space of degree 4 and dimension 2 over Rational Field |
| | 224 | Basis ... |
| | 225 | sage: inv(y) == x |
| | 226 | True |
| | 227 | |
| | 228 | An example of an invertible morphism between modules, |
| | 229 | (rather than between vector spaces). :: |
| | 230 | |
| | 231 | sage: M = ZZ^4 |
| | 232 | sage: p = matrix(ZZ, [[ 0, -1, 1, -2], |
| | 233 | ... [ 1, -3, 2, -3], |
| | 234 | ... [ 0, 4, -3, 4], |
| | 235 | ... [-2, 8, -4, 3]]) |
| | 236 | sage: phi = M.hom(p, M) |
| | 237 | sage: x = vector(ZZ, [1, -3, 5, -2]) |
| | 238 | sage: y = phi(x); y |
| | 239 | (1, 12, -12, 21) |
| | 240 | sage: rho = phi.inverse(); rho |
| | 241 | Free module morphism defined by the matrix |
| | 242 | [ -5 3 -1 1] |
| | 243 | [ -9 4 -3 2] |
| | 244 | [-20 8 -7 4] |
| | 245 | [ -6 2 -2 1] |
| | 246 | Domain: Ambient free module of rank 4 over the principal ideal domain ... |
| | 247 | Codomain: Ambient free module of rank 4 over the principal ideal domain ... |
| | 248 | sage: rho(y) == x |
| | 249 | True |
| | 250 | |
| | 251 | A non-invertible morphism, despite having an appropriate |
| | 252 | domain and codomain. :: |
| | 253 | |
| | 254 | sage: V = QQ^2 |
| | 255 | sage: m = matrix(QQ, [[1, 2], [20, 40]]) |
| | 256 | sage: phi = V.hom(m, V) |
| | 257 | sage: phi.is_bijective() |
| | 258 | False |
| | 259 | sage: phi.inverse() |
| | 260 | Traceback (most recent call last): |
| | 261 | ... |
| | 262 | ZeroDivisionError: matrix morphism not invertible |
| | 263 | |
| | 264 | The matrix representation of this morphism is invertible |
| | 265 | over the rationals, but not over the integers, thus the |
| | 266 | morphism is not invertible as a map between modules. |
| | 267 | It is easy to notice from the definition that every |
| | 268 | vector of the image will have a second entry that |
| | 269 | is an even integer. :: |
| | 270 | |
| | 271 | sage: V = ZZ^2 |
| | 272 | sage: q = matrix(ZZ, [[1, 2], [3, 4]]) |
| | 273 | sage: phi = V.hom(q, V) |
| | 274 | sage: phi.matrix().change_ring(QQ).inverse() |
| | 275 | [ -2 1] |
| | 276 | [ 3/2 -1/2] |
| | 277 | sage: phi.is_bijective() |
| | 278 | False |
| | 279 | sage: phi.image() |
| | 280 | Free module of degree 2 and rank 2 over Integer Ring |
| | 281 | Echelon basis matrix: |
| | 282 | [1 0] |
| | 283 | [0 2] |
| | 284 | sage: phi.lift(vector(ZZ, [1, 1])) |
| | 285 | Traceback (most recent call last): |
| | 286 | ... |
| | 287 | ValueError: element is not in the image |
| | 288 | sage: phi.inverse() |
| | 289 | Traceback (most recent call last): |
| | 290 | ... |
| | 291 | ZeroDivisionError: matrix morphism not invertible |
| | 292 | |
| | 293 | The unary invert operator (~, tilde, "wiggle") is synonymous |
| | 294 | with the ``inverse()`` method (and a lot easier to type). :: |
| | 295 | |
| | 296 | sage: V = QQ^2 |
| | 297 | sage: r = matrix(QQ, [[4, 3], [-2, 5]]) |
| | 298 | sage: phi = V.hom(r, V) |
| | 299 | sage: rho = phi.inverse() |
| | 300 | sage: zeta = ~phi |
| | 301 | sage: rho == zeta |
| | 302 | True |
| | 303 | |
| | 304 | TESTS:: |
| | 305 | |
| | 306 | sage: V = QQ^2 |
| | 307 | sage: W = QQ^3 |
| | 308 | sage: U = W.span([W.0, W.1]) |
| | 309 | sage: m = matrix(QQ, [[2, 1], [3, 4]]) |
| | 310 | sage: phi = V.hom(m, U) |
| | 311 | sage: inv = phi.inverse() |
| | 312 | sage: (inv*phi).is_identity() |
| | 313 | True |
| | 314 | sage: (phi*inv).is_identity() |
| | 315 | True |
| | 316 | """ |
| | 317 | return self.__invert__() |
| | 318 | |
| | 719 | def nullity(self): |
| | 720 | r""" |
| | 721 | Returns the nullity of the matrix representing this morphism. |
| | 722 | |
| | 723 | EXAMPLES:: |
| | 724 | |
| | 725 | sage: V = ZZ^2; phi = V.hom(V.basis()) |
| | 726 | sage: phi.nullity() |
| | 727 | 0 |
| | 728 | sage: V = ZZ^2; phi = V.hom([V.0, V.0]) |
| | 729 | sage: phi.nullity() |
| | 730 | 1 |
| | 731 | """ |
| | 732 | return self.matrix().left_nullity() |
| | 733 | |
| | 734 | def is_injective(self): |
| | 735 | r""" |
| | 736 | Tell whether ``self`` is injective. |
| | 737 | |
| | 738 | EXAMPLE:: |
| | 739 | |
| | 740 | sage: V1 = QQ^2 |
| | 741 | sage: V2 = QQ^3 |
| | 742 | sage: phi = V1.hom(Matrix([[1,2],[3,4],[5,6]]),V2) |
| | 743 | sage: phi.is_injective() |
| | 744 | True |
| | 745 | sage: psi = V2.hom(Matrix([[1,2,3],[4,5,6]]),V1) |
| | 746 | sage: psi.is_injective() |
| | 747 | False |
| | 748 | |
| | 749 | AUTHOR: |
| | 750 | |
| | 751 | -- Simon King (2010-05) |
| | 752 | """ |
| | 753 | return self._matrix.kernel().dimension() == 0 |
| | 754 | |
| | 755 | def is_surjective(self): |
| | 756 | r""" |
| | 757 | Tell whether ``self`` is surjective. |
| | 758 | |
| | 759 | EXAMPLES:: |
| | 760 | |
| | 761 | sage: V1 = QQ^2 |
| | 762 | sage: V2 = QQ^3 |
| | 763 | sage: phi = V1.hom(Matrix([[1,2],[3,4],[5,6]]), V2) |
| | 764 | sage: phi.is_surjective() |
| | 765 | False |
| | 766 | sage: psi = V2.hom(Matrix([[1,2,3],[4,5,6]]), V1) |
| | 767 | sage: psi.is_surjective() |
| | 768 | True |
| | 769 | |
| | 770 | An example over a PID that is not `\ZZ`. :: |
| | 771 | |
| | 772 | sage: R = PolynomialRing(QQ, 'x') |
| | 773 | sage: A = R^2 |
| | 774 | sage: B = R^2 |
| | 775 | sage: H = A.hom([B([x^2-1, 1]), B([x^2, 1])]) |
| | 776 | sage: H.image() |
| | 777 | Free module of degree 2 and rank 2 over Univariate Polynomial Ring in x over Rational Field |
| | 778 | Echelon basis matrix: |
| | 779 | [ 1 0] |
| | 780 | [ 0 -1] |
| | 781 | sage: H.is_surjective() |
| | 782 | True |
| | 783 | |
| | 784 | This tests if Trac #11552 is fixed. :: |
| | 785 | |
| | 786 | sage: V = ZZ^2 |
| | 787 | sage: m = matrix(ZZ, [[1,2],[0,2]]) |
| | 788 | sage: phi = V.hom(m, V) |
| | 789 | sage: phi.lift(vector(ZZ, [0, 1])) |
| | 790 | Traceback (most recent call last): |
| | 791 | ... |
| | 792 | ValueError: element is not in the image |
| | 793 | sage: phi.is_surjective() |
| | 794 | False |
| | 795 | |
| | 796 | AUTHORS: |
| | 797 | |
| | 798 | - Simon King (2010-05) |
| | 799 | - Rob Beezer (2011-06-28) |
| | 800 | """ |
| | 801 | # Testing equality of free modules over PIDs is unreliable |
| | 802 | # see Trac #11579 for explanation and status |
| | 803 | # We test if image equals codomain with two inclusions |
| | 804 | # reverse inclusion of below is trivially true |
| | 805 | return self.codomain().is_submodule(self.image()) |
| | 806 | |
| | 807 | def is_bijective(self): |
| | 808 | r""" |
| | 809 | Tell whether ``self`` is bijective. |
| | 810 | |
| | 811 | EXAMPLES: |
| | 812 | |
| | 813 | Two morphisms that are obviously not bijective, simply on |
| | 814 | considerations of the dimensions. However, each fullfills |
| | 815 | half of the requirements to be a bijection. :: |
| | 816 | |
| | 817 | sage: V1 = QQ^2 |
| | 818 | sage: V2 = QQ^3 |
| | 819 | sage: m = matrix(QQ, [[1,2],[3,4],[5,6]]) |
| | 820 | sage: phi = V1.hom(m, V2) |
| | 821 | sage: phi.is_injective() |
| | 822 | True |
| | 823 | sage: phi.is_bijective() |
| | 824 | False |
| | 825 | sage: rho = V2.hom(m.transpose(), V1) |
| | 826 | sage: rho.is_surjective() |
| | 827 | True |
| | 828 | sage: rho.is_bijective() |
| | 829 | False |
| | 830 | |
| | 831 | We construct a simple bijection between two one-dimensional |
| | 832 | vector spaces. :: |
| | 833 | |
| | 834 | sage: V1 = QQ^3 |
| | 835 | sage: V2 = QQ^2 |
| | 836 | sage: phi = V1.hom(matrix(QQ, [[1, 2, 3], [4, 5, 6]]), V2) |
| | 837 | sage: x = vector(QQ, [1, -1, 4]) |
| | 838 | sage: y = phi(x); y |
| | 839 | (18, 22) |
| | 840 | sage: rho = phi.restrict_domain(V1.span([x])) |
| | 841 | sage: zeta = rho.restrict_codomain(V2.span([y])) |
| | 842 | sage: zeta.is_bijective() |
| | 843 | True |
| | 844 | |
| | 845 | AUTHOR: |
| | 846 | |
| | 847 | - Rob Beezer (2011-06-28) |
| | 848 | """ |
| | 849 | return self.is_injective() and self.is_surjective() |
| | 850 | |
| | 851 | def is_identity(self): |
| | 852 | r""" |
| | 853 | Determines if this morphism is an identity function or not. |
| | 854 | |
| | 855 | EXAMPLES: |
| | 856 | |
| | 857 | A homomorphism that cannot possibly be the identity |
| | 858 | due to an unequal domain and codomain. :: |
| | 859 | |
| | 860 | sage: V = QQ^3 |
| | 861 | sage: W = QQ^2 |
| | 862 | sage: m = matrix(QQ, [[1, 2], [3, 4], [5, 6]]) |
| | 863 | sage: phi = V.hom(m, W) |
| | 864 | sage: phi.is_identity() |
| | 865 | False |
| | 866 | |
| | 867 | A bijection, but not the identity. :: |
| | 868 | |
| | 869 | sage: V = QQ^3 |
| | 870 | sage: n = matrix(QQ, [[3, 1, -8], [5, -4, 6], [1, 1, -5]]) |
| | 871 | sage: phi = V.hom(n, V) |
| | 872 | sage: phi.is_bijective() |
| | 873 | True |
| | 874 | sage: phi.is_identity() |
| | 875 | False |
| | 876 | |
| | 877 | A restriction that is the identity. :: |
| | 878 | |
| | 879 | sage: V = QQ^3 |
| | 880 | sage: p = matrix(QQ, [[1, 0, 0], [5, 8, 3], [0, 0, 1]]) |
| | 881 | sage: phi = V.hom(p, V) |
| | 882 | sage: rho = phi.restrict(V.span([V.0, V.2])) |
| | 883 | sage: rho.is_identity() |
| | 884 | True |
| | 885 | |
| | 886 | An identity linear transformation that is defined with a |
| | 887 | domain and codomain with wildly different bases, so that the |
| | 888 | matrix representation is not simply the identity matrix. :: |
| | 889 | |
| | 890 | sage: A = matrix(QQ, [[1, 1, 0], [2, 3, -4], [2, 4, -7]]) |
| | 891 | sage: B = matrix(QQ, [[2, 7, -2], [-1, -3, 1], [-1, -6, 2]]) |
| | 892 | sage: U = (QQ^3).subspace_with_basis(A.rows()) |
| | 893 | sage: V = (QQ^3).subspace_with_basis(B.rows()) |
| | 894 | sage: H = Hom(U, V) |
| | 895 | sage: id = lambda x: x |
| | 896 | sage: phi = H(id) |
| | 897 | sage: phi([203, -179, 34]) |
| | 898 | (203, -179, 34) |
| | 899 | sage: phi.matrix() |
| | 900 | [ 1 0 1] |
| | 901 | [ -9 -18 -2] |
| | 902 | [-17 -31 -5] |
| | 903 | sage: phi.is_identity() |
| | 904 | True |
| | 905 | |
| | 906 | TEST:: |
| | 907 | |
| | 908 | sage: V = QQ^10 |
| | 909 | sage: H = Hom(V, V) |
| | 910 | sage: id = H.identity() |
| | 911 | sage: id.is_identity() |
| | 912 | True |
| | 913 | |
| | 914 | AUTHOR: |
| | 915 | |
| | 916 | - Rob Beezer (2011-06-28) |
| | 917 | """ |
| | 918 | if self.domain() != self.codomain(): |
| | 919 | return False |
| | 920 | # testing for the identity matrix will only work for |
| | 921 | # endomorphisms which have the same basis for domain and codomain |
| | 922 | # so we test equality on a basis, which is sufficient |
| | 923 | return all( self(u) == u for u in self.domain().basis() ) |
| | 924 | |
| | 925 | def is_zero(self): |
| | 926 | r""" |
| | 927 | Determines if this morphism is a zero function or not. |
| | 928 | |
| | 929 | EXAMPLES: |
| | 930 | |
| | 931 | A zero morphism created from a function. :: |
| | 932 | |
| | 933 | sage: V = ZZ^5 |
| | 934 | sage: W = ZZ^3 |
| | 935 | sage: z = lambda x: zero_vector(ZZ, 3) |
| | 936 | sage: phi = V.hom(z, W) |
| | 937 | sage: phi.is_zero() |
| | 938 | True |
| | 939 | |
| | 940 | An image list that just barely makes a non-zero morphism. :: |
| | 941 | |
| | 942 | sage: V = ZZ^4 |
| | 943 | sage: W = ZZ^6 |
| | 944 | sage: z = zero_vector(ZZ, 6) |
| | 945 | sage: images = [z, z, W.5, z] |
| | 946 | sage: phi = V.hom(images, W) |
| | 947 | sage: phi.is_zero() |
| | 948 | False |
| | 949 | |
| | 950 | TEST:: |
| | 951 | |
| | 952 | sage: V = QQ^10 |
| | 953 | sage: W = QQ^3 |
| | 954 | sage: H = Hom(V, W) |
| | 955 | sage: rho = H.zero() |
| | 956 | sage: rho.is_zero() |
| | 957 | True |
| | 958 | |
| | 959 | AUTHOR: |
| | 960 | |
| | 961 | - Rob Beezer (2011-07-15) |
| | 962 | """ |
| | 963 | # any nonzero entry in any matrix representation |
| | 964 | # disqualifies the morphism as having totally zero outputs |
| | 965 | return self.matrix().is_zero() |
| | 966 | |
| | 967 | def is_equal_function(self, other): |
| | 968 | r""" |
| | 969 | Determines if two morphisms are equal functions. |
| | 970 | |
| | 971 | INPUT: |
| | 972 | |
| | 973 | - ``other`` - a morphism to compare with ``self`` |
| | 974 | |
| | 975 | OUTPUT: |
| | 976 | |
| | 977 | Returns ``True`` precisely when the two morphisms have |
| | 978 | equal domains and codomains (as sets) and produce identical |
| | 979 | output when given the same input. Otherwise returns ``False``. |
| | 980 | |
| | 981 | This is useful when ``self`` and ``other`` may have different |
| | 982 | representations. |
| | 983 | |
| | 984 | Sage's default comparison of matrix morphisms requires the |
| | 985 | domains to have the same bases and the codomains to have the |
| | 986 | same bases, and then compares the matrix representations. |
| | 987 | This notion of equality is more permissive (it will |
| | 988 | return ``True`` "more often"), but is more correct |
| | 989 | mathematically. |
| | 990 | |
| | 991 | EXAMPLES: |
| | 992 | |
| | 993 | Three morphisms defined by combinations of different |
| | 994 | bases for the domain and codomain and different functions. |
| | 995 | Two are equal, the third is different from both of the others. :: |
| | 996 | |
| | 997 | sage: B = matrix(QQ, [[-3, 5, -4, 2], |
| | 998 | ... [-1, 2, -1, 4], |
| | 999 | ... [ 4, -6, 5, -1], |
| | 1000 | ... [-5, 7, -6, 1]]) |
| | 1001 | sage: U = (QQ^4).subspace_with_basis(B.rows()) |
| | 1002 | sage: C = matrix(QQ, [[-1, -6, -4], |
| | 1003 | ... [ 3, -5, 6], |
| | 1004 | ... [ 1, 2, 3]]) |
| | 1005 | sage: V = (QQ^3).subspace_with_basis(C.rows()) |
| | 1006 | sage: H = Hom(U, V) |
| | 1007 | |
| | 1008 | sage: D = matrix(QQ, [[-7, -2, -5, 2], |
| | 1009 | ... [-5, 1, -4, -8], |
| | 1010 | ... [ 1, -1, 1, 4], |
| | 1011 | ... [-4, -1, -3, 1]]) |
| | 1012 | sage: X = (QQ^4).subspace_with_basis(D.rows()) |
| | 1013 | sage: E = matrix(QQ, [[ 4, -1, 4], |
| | 1014 | ... [ 5, -4, -5], |
| | 1015 | ... [-1, 0, -2]]) |
| | 1016 | sage: Y = (QQ^3).subspace_with_basis(E.rows()) |
| | 1017 | sage: K = Hom(X, Y) |
| | 1018 | |
| | 1019 | sage: f = lambda x: vector(QQ, [x[0]+x[1], 2*x[1]-4*x[2], 5*x[3]]) |
| | 1020 | sage: g = lambda x: vector(QQ, [x[0]-x[2], 2*x[1]-4*x[2], 5*x[3]]) |
| | 1021 | |
| | 1022 | sage: rho = H(f) |
| | 1023 | sage: phi = K(f) |
| | 1024 | sage: zeta = H(g) |
| | 1025 | |
| | 1026 | sage: rho.is_equal_function(phi) |
| | 1027 | True |
| | 1028 | sage: phi.is_equal_function(rho) |
| | 1029 | True |
| | 1030 | sage: zeta.is_equal_function(rho) |
| | 1031 | False |
| | 1032 | sage: phi.is_equal_function(zeta) |
| | 1033 | False |
| | 1034 | |
| | 1035 | TEST:: |
| | 1036 | |
| | 1037 | sage: H = Hom(ZZ^2, ZZ^2) |
| | 1038 | sage: phi = H(matrix(ZZ, 2, range(4))) |
| | 1039 | sage: phi.is_equal_function('junk') |
| | 1040 | Traceback (most recent call last): |
| | 1041 | ... |
| | 1042 | TypeError: can only compare to a matrix morphism, not junk |
| | 1043 | |
| | 1044 | AUTHOR: |
| | 1045 | |
| | 1046 | - Rob Beezer (2011-07-15) |
| | 1047 | """ |
| | 1048 | if not is_MatrixMorphism(other): |
| | 1049 | msg = 'can only compare to a matrix morphism, not {0}' |
| | 1050 | raise TypeError(msg.format(other)) |
| | 1051 | if self.domain() != other.domain(): |
| | 1052 | return False |
| | 1053 | if self.codomain() != other.codomain(): |
| | 1054 | return False |
| | 1055 | # check agreement on any basis of the domain |
| | 1056 | return all( self(u) == other(u) for u in self.domain().basis() ) |
| | 1057 | |
| 801 | | def is_injective(self): |
| 802 | | """ |
| 803 | | Tell whether ``self`` is injective. |
| 804 | | |
| 805 | | EXAMPLE:: |
| 806 | | |
| 807 | | sage: V1 = QQ^2 |
| 808 | | sage: V2 = QQ^3 |
| 809 | | sage: phi = V1.hom(Matrix([[1,2],[3,4],[5,6]]),V2) |
| 810 | | sage: phi.is_injective() |
| 811 | | True |
| 812 | | sage: psi = V2.hom(Matrix([[1,2,3],[4,5,6]]),V1) |
| 813 | | sage: psi.is_injective() |
| 814 | | False |
| 815 | | |
| 816 | | AUTHOR: |
| 817 | | |
| 818 | | -- Simon King (2010-05) |
| 819 | | """ |
| 820 | | return self._matrix.kernel().dimension() == 0 |
| 821 | | |
| 822 | | def is_surjective(self): |
| 823 | | r""" |
| 824 | | Tell whether ``self`` is surjective. |
| 825 | | |
| 826 | | EXAMPLES:: |
| 827 | | |
| 828 | | sage: V1 = QQ^2 |
| 829 | | sage: V2 = QQ^3 |
| 830 | | sage: phi = V1.hom(Matrix([[1,2],[3,4],[5,6]]), V2) |
| 831 | | sage: phi.is_surjective() |
| 832 | | False |
| 833 | | sage: psi = V2.hom(Matrix([[1,2,3],[4,5,6]]), V1) |
| 834 | | sage: psi.is_surjective() |
| 835 | | True |
| 836 | | |
| 837 | | An example over a PID that is not `\ZZ`. :: |
| 838 | | |
| 839 | | sage: R = PolynomialRing(QQ, 'x') |
| 840 | | sage: A = R^2 |
| 841 | | sage: B = R^2 |
| 842 | | sage: H = A.hom([B([x^2-1, 1]), B([x^2, 1])]) |
| 843 | | sage: H.image() |
| 844 | | Free module of degree 2 and rank 2 over Univariate Polynomial Ring in x over Rational Field |
| 845 | | Echelon basis matrix: |
| 846 | | [ 1 0] |
| 847 | | [ 0 -1] |
| 848 | | sage: H.is_surjective() |
| 849 | | True |
| 850 | | |
| 851 | | This tests if Trac #11552 is fixed. :: |
| 852 | | |
| 853 | | sage: V = ZZ^2 |
| 854 | | sage: m = matrix(ZZ, [[1,2],[0,2]]) |
| 855 | | sage: phi = V.hom(m, V) |
| 856 | | sage: phi.lift(vector(ZZ, [0, 1])) |
| 857 | | Traceback (most recent call last): |
| 858 | | ... |
| 859 | | ValueError: element is not in the image |
| 860 | | sage: phi.is_surjective() |
| 861 | | False |
| 862 | | |
| 863 | | AUTHORS: |
| 864 | | |
| 865 | | - Simon King (2010-05) |
| 866 | | - Rob Beezer (2011-06-28) |
| 867 | | """ |
| 868 | | # Testing equality of free modules over PIDs is unreliable |
| 869 | | # see Trac #11579 for explanation and status |
| 870 | | # We test if image equals codomain with two inclusions |
| 871 | | # reverse inclusion of below is trivially true |
| 872 | | return self.codomain().is_submodule(self.image()) |