diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index fa496a05de8..b96aace55bd 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -326,21 +326,27 @@ cdef class MatrixArgs: sage: from sage.matrix.args import MatrixArgs sage: MatrixArgs().finalized() - + sage: MatrixArgs(1).finalized() - + sage: MatrixArgs(1, 1, 3).finalized() - + sage: MatrixArgs(1, 1, 1, 1).finalized() Traceback (most recent call last): ... TypeError: too many arguments in matrix constructor sage: MatrixArgs(3, nrows=1, ncols=1).finalized() - + sage: MatrixArgs(3, nrows=1).finalized() - + sage: MatrixArgs(3, ncols=1).finalized() - + """ if "ring" in kwds.keys(): deprecation_cython(issue_number=33380, message="ring is deprecated (keyword will be removed in the future). Use base_ring instead", stacklevel=3) @@ -646,8 +652,8 @@ cdef class MatrixArgs: INPUT: - - ``convert`` -- if True, the matrix is guaranteed to have - the correct parent matrix space. If False, the input matrix + - ``convert`` -- if ``True``, the matrix is guaranteed to have + the correct parent matrix space. If ``False``, the input matrix may be returned even if it lies in the wrong space. .. NOTE:: @@ -737,7 +743,7 @@ cdef class MatrixArgs: INPUT: - - ``convert`` -- If True, the entries are converted to the base + - ``convert`` -- If ``True``, the entries are converted to the base ring. Otherwise, the entries are returned as given. .. NOTE:: @@ -797,11 +803,12 @@ cdef class MatrixArgs: cpdef dict dict(self, bint convert=True): """ - Return the entries of the matrix as a dict. The keys of this - dict are the non-zero positions ``(i,j)``. The corresponding - value is the entry at that position. Zero values are skipped. + Return the entries of the matrix as a :class:`dict`. - If ``convert`` is True, the entries are converted to the base + The keys of this :class:`dict` are the non-zero positions ``(i,j)``. The + corresponding value is the entry at that position. Zero values are skipped. + + If ``convert`` is ``True``, the entries are converted to the base ring. Otherwise, the entries are returned as given. EXAMPLES:: diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index c35c84468d7..7f070ded8b4 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -38,7 +38,7 @@ def matrix(*args, **kwds): INPUT: - The matrix command takes the entries of a matrix, optionally + The :func:`matrix` command takes the entries of a matrix, optionally preceded by a ring and the dimensions of the matrix, and returns a matrix. @@ -50,11 +50,11 @@ def matrix(*args, **kwds): columns. You can create a matrix of zeros by passing an empty list or the integer zero for the entries. To construct a multiple of the identity (`cI`), you can specify square dimensions and pass in - `c`. Calling matrix() with a Sage object may return something that - makes sense. Calling matrix() with a NumPy array will convert the + `c`. Calling :func:`matrix` with a Sage object may return something that + makes sense. Calling :func:`matrix` with a NumPy array will convert the array to a matrix. - All arguments (even the positional) are optional. + All arguments (even the positional ones) are optional. Positional and keyword arguments: @@ -136,8 +136,8 @@ def matrix(*args, **kwds): :: - sage: v1=vector((1,2,3)) - sage: v2=vector((4,5,6)) + sage: v1 = vector((1,2,3)) + sage: v2 = vector((4,5,6)) sage: m = matrix([v1,v2]); m; m.parent() [1 2 3] [4 5 6] @@ -145,28 +145,28 @@ def matrix(*args, **kwds): :: - sage: m = matrix(QQ,2,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 2, [1,2,3,4,5,6]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field :: - sage: m = matrix(QQ,2,3,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 2, 3, [1,2,3,4,5,6]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field :: - sage: m = matrix({(0,1): 2, (1,1):2/5}); m; m.parent() + sage: m = matrix({(0,1): 2, (1,1): 2/5}); m; m.parent() [ 0 2] [ 0 2/5] Full MatrixSpace of 2 by 2 sparse matrices over Rational Field :: - sage: m = matrix(QQ,2,3,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 2, 3, {(1,1): 2}); m; m.parent() [0 0 0] [0 2 0] Full MatrixSpace of 2 by 3 sparse matrices over Rational Field @@ -235,7 +235,7 @@ def matrix(*args, **kwds): :: - sage: M = Matrix([[1,2,3],[4,5,6],[7,8,9]], immutable=True) + sage: M = Matrix([[1,2,3], [4,5,6], [7,8,9]], immutable=True) sage: M[0] = [9,9,9] Traceback (most recent call last): ... diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 377afa9a5ee..7ecdeeadd0f 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -435,9 +435,73 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): class MatrixSpace(UniqueRepresentation, Parent): """ - The space of matrices of given size and base ring + The space of matrices of given size and base ring. - EXAMPLES: + INPUT: + + - ``base_ring`` -- a ring + + - ``nrows`` or ``row_keys`` -- (nonnegative integer) the number of rows, or + a finite family of arbitrary objects that index the rows of the matrix + + - ``ncols`` or ``column_keys`` -- (nonnegative integer, default ``nrows``) + the number of columns, or a finite family of arbitrary objects that index + the columns of the matrix + + - ``sparse`` -- (boolean, default ``False``) whether or not matrices + are given a sparse representation + + - ``implementation`` -- (optional, a string or a matrix class) a possible + implementation. Depending on the base ring, the string can be + + - ``'generic'`` -- on any base rings + + - ``'flint'`` -- for integers and rationals + + - ``'meataxe'`` -- finite fields using the optional package :ref:`spkg_meataxe` + + - ``'m4ri'`` -- for characteristic 2 using the :ref:`spkg_m4ri` library + + - ``'linbox-float'`` -- for integer mod rings up to `2^8 = 256` + + - ``'linbox-double'`` -- for integer mod rings up to + `floor(2^26*sqrt(2) + 1/2) = 94906266` + + - ``'numpy'`` -- for real and complex floating point numbers + + OUTPUT: a matrix space or, more generally, a homspace between free modules. + + This factory function creates instances of various specialized classes + depending on the input. Not all combinations of options are + implemented. + + - If the parameters ``row_keys`` or ``column_keys`` are provided, they + must be finite families of objects. In this case, instances of + :class:`CombinatorialFreeModule` are created via the factory function + :func:`FreeModule`. Then the homspace between these modules is returned. + + EXAMPLES:: + + sage: MatrixSpace(QQ, 2) + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + sage: MatrixSpace(ZZ, 3, 2) + Full MatrixSpace of 3 by 2 dense matrices over Integer Ring + sage: MatrixSpace(ZZ, 3, sparse=False) + Full MatrixSpace of 3 by 3 dense matrices over Integer Ring + + sage: MatrixSpace(ZZ, 10, 5) + Full MatrixSpace of 10 by 5 dense matrices over Integer Ring + sage: MatrixSpace(ZZ, 10, 5).category() + Category of infinite enumerated finite dimensional modules with basis over + (Dedekind domains and euclidean domains + and infinite enumerated sets and metric spaces) + sage: MatrixSpace(ZZ, 10, 10).category() + Category of infinite enumerated finite dimensional algebras with basis over + (Dedekind domains and euclidean domains + and infinite enumerated sets and metric spaces) + sage: MatrixSpace(QQ, 10).category() + Category of infinite finite dimensional algebras with basis over + (number fields and quotient fields and metric spaces) Some examples of square 2 by 2 rational matrices:: @@ -464,8 +528,7 @@ class MatrixSpace(UniqueRepresentation, Parent): sage: B[1,1] [0 0] [0 1] - sage: A = MS.matrix([1,2,3,4]) - sage: A + sage: A = MS.matrix([1,2,3,4]); A [1 2] [3 4] @@ -477,19 +540,27 @@ class MatrixSpace(UniqueRepresentation, Parent): [ 9 12 15] [19 26 33] + Using ``row_keys`` and ``column_keys``:: + + sage: MS = MatrixSpace(ZZ, ['u', 'v'], ['a', 'b', 'c']); MS + Set of Morphisms + from Free module generated by {'a', 'b', 'c'} over Integer Ring + to Free module generated by {'u', 'v'} over Integer Ring + in Category of finite dimensional modules with basis over Integer Ring + Check categories:: - sage: MatrixSpace(ZZ,10,5) + sage: MatrixSpace(ZZ, 10, 5) Full MatrixSpace of 10 by 5 dense matrices over Integer Ring - sage: MatrixSpace(ZZ,10,5).category() + sage: MatrixSpace(ZZ, 10, 5).category() Category of infinite enumerated finite dimensional modules with basis over (Dedekind domains and euclidean domains and infinite enumerated sets and metric spaces) - sage: MatrixSpace(ZZ,10,10).category() + sage: MatrixSpace(ZZ, 10, 10).category() Category of infinite enumerated finite dimensional algebras with basis over (Dedekind domains and euclidean domains and infinite enumerated sets and metric spaces) - sage: MatrixSpace(QQ,10).category() + sage: MatrixSpace(QQ, 10).category() Category of infinite finite dimensional algebras with basis over (number fields and quotient fields and metric spaces) @@ -537,14 +608,72 @@ class MatrixSpace(UniqueRepresentation, Parent): sage: M1 = MatrixSpace(GF(2), 5) sage: M1(m * m) == M1(m) * M1(m) True + + Check various combinations of dimensions and row/column keys:: + + sage: M_ab_4 = MatrixSpace(QQ, ['a','b'], 4); M_ab_4 + Set of Morphisms (Linear Transformations) + from Vector space of dimension 4 over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + sage: TestSuite(M_ab_4).run() # known bug + sage: M_4_ab = MatrixSpace(QQ, 4, ['a','b']); M_4_ab + Set of Morphisms + from Free module generated by {'a', 'b'} over Rational Field + to Vector space of dimension 4 over Rational Field + in Category of finite dimensional vector spaces with basis + over (number fields and quotient fields and metric spaces) + sage: TestSuite(M_4_ab).run() # known bug + sage: M_ab_xy = MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2); M_ab_xy + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: TestSuite(M_ab_xy).run() # known bug + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of rows: + should be cardinality of ['a', 'b'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], ncols=2) + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2, ncols=2) + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2, ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=4, ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, 4, ['a','b'], nrows=4, ncols=2) + Traceback (most recent call last): + ... + ValueError: duplicate values for nrows """ @staticmethod - def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementation=None, **kwds): + def __classcall__(cls, base_ring, + nrows_or_row_keys=None, ncols_or_column_keys=None, + sparse=False, implementation=None, *, + nrows=None, ncols=None, + row_keys=None, column_keys=None, + **kwds): """ - Normalize the arguments to call the ``__init__`` constructor. - - See the documentation in ``__init__``. + Normalize the arguments to call the ``__init__`` constructor or delegate to another class. TESTS:: @@ -589,13 +718,53 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio """ if base_ring not in _Rings: raise TypeError("base_ring (=%s) must be a ring" % base_ring) - nrows = int(nrows) - if ncols is None: + + if ncols_or_column_keys is not None: + try: + n = int(ncols_or_column_keys) + except (TypeError, ValueError): + if column_keys is not None: + raise ValueError("duplicate values for column_keys") + column_keys = ncols_or_column_keys + else: + if ncols is not None: + raise ValueError("duplicate values for ncols") + ncols = n + if column_keys is not None and ncols is not None and ncols != len(column_keys): + raise ValueError(f"inconsistent number of columns: should be cardinality of {column_keys} " + f"but got {ncols}") + + if nrows_or_row_keys is not None: + try: + n = int(nrows_or_row_keys) + except (TypeError, ValueError): + if row_keys is not None: + raise ValueError("duplicate values for row_keys") + row_keys = nrows_or_row_keys + else: + if nrows is not None: + raise ValueError("duplicate values for nrows") + nrows = n + if row_keys is not None and nrows is not None and nrows != len(row_keys): + raise ValueError(f"inconsistent number of rows: should be cardinality of {row_keys} " + f"but got {nrows}") + + if ncols is None and column_keys is None: ncols = nrows - else: - ncols = int(ncols) + column_keys = row_keys + sparse = bool(sparse) + if row_keys is not None or column_keys is not None: + from sage.categories.homset import Hom + from sage.modules.free_module import FreeModule + + domain = FreeModule(base_ring, rank=ncols, basis_keys=column_keys, + sparse=sparse, **kwds) + codomain = FreeModule(base_ring, rank=nrows, basis_keys=row_keys, + sparse=sparse, **kwds) + return Hom(domain, codomain) + if nrows < 0: raise ArithmeticError("nrows must be nonnegative") if ncols < 0: @@ -609,59 +778,6 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio def __init__(self, base_ring, nrows, ncols, sparse, implementation): r""" - INPUT: - - - ``base_ring`` - - - ``nrows`` - (positive integer) the number of rows - - - ``ncols`` - (positive integer, default nrows) the number of - columns - - - ``sparse`` - (boolean, default false) whether or not matrices - are given a sparse representation - - - ``implementation`` -- (optional, a string or a matrix class) a possible - implementation. Depending on the base ring the string can be - - - ``'generic'`` - on any base rings - - - ``'flint'`` - for integers and rationals - - - ``'meataxe'`` - finite fields, needs to install the optional package meataxe - - - ``m4ri`` - for characteristic 2 using M4RI library - - - ``linbox-float`` - for integer mod rings up to `2^8 = 256` - - - ``linbox-double`` - for integer mod rings up to - `floor(2^26*sqrt(2) + 1/2) = 94906266` - - - ``numpy`` - for real and complex floating point numbers - - EXAMPLES:: - - sage: MatrixSpace(QQ, 2) - Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: MatrixSpace(ZZ, 3, 2) - Full MatrixSpace of 3 by 2 dense matrices over Integer Ring - sage: MatrixSpace(ZZ, 3, sparse=False) - Full MatrixSpace of 3 by 3 dense matrices over Integer Ring - - sage: MatrixSpace(ZZ,10,5) - Full MatrixSpace of 10 by 5 dense matrices over Integer Ring - sage: MatrixSpace(ZZ,10,5).category() - Category of infinite enumerated finite dimensional modules with basis over - (Dedekind domains and euclidean domains - and infinite enumerated sets and metric spaces) - sage: MatrixSpace(ZZ,10,10).category() - Category of infinite enumerated finite dimensional algebras with basis over - (Dedekind domains and euclidean domains - and infinite enumerated sets and metric spaces) - sage: MatrixSpace(QQ,10).category() - Category of infinite finite dimensional algebras with basis over - (number fields and quotient fields and metric spaces) - TESTS: We test that in the real or complex double dense case, diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index c0e709829e1..61a448ebbab 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -515,12 +515,32 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m Traceback (most recent call last): ... NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got [1, 3, 5] + + sage: FreeModule(QQ, ['a', 'b'], rank=2) + Free module generated by {'a', 'b'} over Rational Field + sage: FreeModule(QQ, 2, basis_keys=['a', 'b']) + Free module generated by {'a', 'b'} over Rational Field + sage: FreeModule(QQ, ['a', 'b'], rank=3) + Traceback (most recent call last): + ... + ValueError: inconsistent rank: should be cardinality of ['a', 'b'] but got 3 """ if rank_or_basis_keys is not None: try: - rank = sage.rings.integer_ring.ZZ(rank_or_basis_keys) + n = sage.rings.integer_ring.ZZ(rank_or_basis_keys) except (TypeError, ValueError): + if basis_keys is not None: + raise ValueError("duplicate values for basis_keys") basis_keys = rank_or_basis_keys + else: + if rank is not None: + raise ValueError("duplicate values for rank") + rank = n + + if rank is not None and basis_keys is not None and rank != len(basis_keys): + raise ValueError(f"inconsistent rank: should be cardinality of {basis_keys} " + f"but got {rank}") + if not with_basis: if inner_product_matrix is not None: raise NotImplementedError @@ -540,7 +560,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m return FiniteRankFreeModule(base_ring, rank, start_index=start_index, **args) return FiniteRankFreeModule(base_ring, rank, **args) elif with_basis == 'standard': - if rank is not None: + if basis_keys is None: return FreeModuleFactory_with_standard_basis(base_ring, rank, sparse, inner_product_matrix, **args) else: