Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecating 'cls' in special matrix constructors #16187

Open
sylee957 opened this issue Mar 7, 2019 · 2 comments
Open

Deprecating 'cls' in special matrix constructors #16187

sylee957 opened this issue Mar 7, 2019 · 2 comments
Labels

Comments

@sylee957
Copy link
Member

sylee957 commented Mar 7, 2019

@classmethod
def diag(kls, *args, **kwargs):
"""Returns a matrix with the specified diagonal.
If matrices are passed, a block-diagonal matrix
is created.
kwargs
======
rows : rows of the resulting matrix; computed if
not given.
cols : columns of the resulting matrix; computed if
not given.
cls : class for the resulting matrix
Examples
========
>>> from sympy.matrices import Matrix
>>> Matrix.diag(1, 2, 3)
Matrix([
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
>>> Matrix.diag([1, 2, 3])
Matrix([
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
The diagonal elements can be matrices; diagonal filling will
continue on the diagonal from the last element of the matrix:
>>> from sympy.abc import x, y, z
>>> a = Matrix([x, y, z])
>>> b = Matrix([[1, 2], [3, 4]])
>>> c = Matrix([[5, 6]])
>>> Matrix.diag(a, 7, b, c)
Matrix([
[x, 0, 0, 0, 0, 0],
[y, 0, 0, 0, 0, 0],
[z, 0, 0, 0, 0, 0],
[0, 7, 0, 0, 0, 0],
[0, 0, 1, 2, 0, 0],
[0, 0, 3, 4, 0, 0],
[0, 0, 0, 0, 5, 6]])
A given band off the diagonal can be made by padding with a
vertical or horizontal "kerning" vector:
>>> hpad = Matrix(0, 2, [])
>>> vpad = Matrix(2, 0, [])
>>> Matrix.diag(vpad, 1, 2, 3, hpad) + Matrix.diag(hpad, 4, 5, 6, vpad)
Matrix([
[0, 0, 4, 0, 0],
[0, 0, 0, 5, 0],
[1, 0, 0, 0, 6],
[0, 2, 0, 0, 0],
[0, 0, 3, 0, 0]])
The type of the resulting matrix can be affected with the ``cls``
keyword.
>>> type(Matrix.diag(1))
<class 'sympy.matrices.dense.MutableDenseMatrix'>
>>> from sympy.matrices import ImmutableMatrix
>>> type(Matrix.diag(1, cls=ImmutableMatrix))
<class 'sympy.matrices.immutable.ImmutableDenseMatrix'>
"""
klass = kwargs.get('cls', kls)
# allow a sequence to be passed in as the only argument
if len(args) == 1 and is_sequence(args[0]) and not getattr(args[0], 'is_Matrix', False):
args = args[0]
def size(m):
"""Compute the size of the diagonal block"""
if hasattr(m, 'rows'):
return m.rows, m.cols
return 1, 1
diag_rows = sum(size(m)[0] for m in args)
diag_cols = sum(size(m)[1] for m in args)
rows = kwargs.get('rows', diag_rows)
cols = kwargs.get('cols', diag_cols)
if rows < diag_rows or cols < diag_cols:
raise ValueError("A {} x {} diagnal matrix cannot accommodate a"
"diagonal of size at least {} x {}.".format(rows, cols,
diag_rows, diag_cols))
# fill a default dict with the diagonal entries
diag_entries = defaultdict(lambda: S.Zero)
row_pos, col_pos = 0, 0
for m in args:
if hasattr(m, 'rows'):
# in this case, we're a matrix
for i in range(m.rows):
for j in range(m.cols):
diag_entries[(i + row_pos, j + col_pos)] = m[i, j]
row_pos += m.rows
col_pos += m.cols
else:
# in this case, we're a single value
diag_entries[(row_pos, col_pos)] = m
row_pos += 1
col_pos += 1
return klass._eval_diag(rows, cols, diag_entries)
@classmethod
def eye(kls, rows, cols=None, **kwargs):
"""Returns an identity matrix.
Args
====
rows : rows of the matrix
cols : cols of the matrix (if None, cols=rows)
kwargs
======
cls : class of the returned matrix
"""
if cols is None:
cols = rows
klass = kwargs.get('cls', kls)
rows, cols = as_int(rows), as_int(cols)
return klass._eval_eye(rows, cols)
@classmethod
def jordan_block(kls, *args, **kwargs):
"""Returns a Jordan block with the specified size
and eigenvalue. You may call `jordan_block` with
two args (size, eigenvalue) or with keyword arguments.
kwargs
======
size : rows and columns of the matrix
rows : rows of the matrix (if None, rows=size)
cols : cols of the matrix (if None, cols=size)
eigenvalue : value on the diagonal of the matrix
band : position of off-diagonal 1s. May be 'upper' or
'lower'. (Default: 'upper')
cls : class of the returned matrix
Examples
========
>>> from sympy import Matrix
>>> from sympy.abc import x
>>> Matrix.jordan_block(4, x)
Matrix([
[x, 1, 0, 0],
[0, x, 1, 0],
[0, 0, x, 1],
[0, 0, 0, x]])
>>> Matrix.jordan_block(4, x, band='lower')
Matrix([
[x, 0, 0, 0],
[1, x, 0, 0],
[0, 1, x, 0],
[0, 0, 1, x]])
>>> Matrix.jordan_block(size=4, eigenvalue=x)
Matrix([
[x, 1, 0, 0],
[0, x, 1, 0],
[0, 0, x, 1],
[0, 0, 0, x]])
"""
klass = kwargs.get('cls', kls)
size, eigenvalue = None, None
if len(args) == 2:
size, eigenvalue = args
elif len(args) == 1:
size = args[0]
elif len(args) != 0:
raise ValueError("'jordan_block' accepts 0, 1, or 2 arguments, not {}".format(len(args)))
rows, cols = kwargs.get('rows', None), kwargs.get('cols', None)
size = kwargs.get('size', size)
band = kwargs.get('band', 'upper')
# allow for a shortened form of `eigenvalue`
eigenvalue = kwargs.get('eigenval', eigenvalue)
eigenvalue = kwargs.get('eigenvalue', eigenvalue)
if eigenvalue is None:
raise ValueError("Must supply an eigenvalue")
if (size, rows, cols) == (None, None, None):
raise ValueError("Must supply a matrix size")
if size is not None:
rows, cols = size, size
elif rows is not None and cols is None:
cols = rows
elif cols is not None and rows is None:
rows = cols
rows, cols = as_int(rows), as_int(cols)
return klass._eval_jordan_block(rows, cols, eigenvalue, band)
@classmethod
def ones(kls, rows, cols=None, **kwargs):
"""Returns a matrix of ones.
Args
====
rows : rows of the matrix
cols : cols of the matrix (if None, cols=rows)
kwargs
======
cls : class of the returned matrix
"""
if cols is None:
cols = rows
klass = kwargs.get('cls', kls)
rows, cols = as_int(rows), as_int(cols)
return klass._eval_ones(rows, cols)
@classmethod
def zeros(kls, rows, cols=None, **kwargs):
"""Returns a matrix of zeros.
Args
====
rows : rows of the matrix
cols : cols of the matrix (if None, cols=rows)
kwargs
======
cls : class of the returned matrix
"""
if cols is None:
cols = rows
klass = kwargs.get('cls', kls)
rows, cols = as_int(rows), as_int(cols)
return klass._eval_zeros(rows, cols)

From #16102

Also cls seems really awkward. When was it introduced? It seems like if someone wants a certain class, they should do something like TargetClass.jordon_block(size, eigenvalue) instead of passing in TargetClass to the cls kwarg.

(And I agree about the cls option. I'm not sure what merit there is in passing this rather than using it as the calling class -- either way you have to type it.)

There were some discussions about the keyword argument, and I also think that the keyword would not be any useful at all.

@asmeurer
Copy link
Member

asmeurer commented Mar 7, 2019

Here is the original comment for reference #16102 (comment)

@asmeurer
Copy link
Member

asmeurer commented Mar 7, 2019

I agree. The cls argument (the first argument of the classmethod) should be the class that is used. That way follows standard Python inheritance patterns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants