from .matexpr import MatrixExpr from sympy.core.function import FunctionClass, Lambda from sympy.core.symbol import Dummy from sympy.core.sympify import _sympify, sympify from sympy.matrices import Matrix from sympy.functions.elementary.complexes import re, im class FunctionMatrix(MatrixExpr): """Represents a matrix using a function (``Lambda``) which gives outputs according to the coordinates of each matrix entries. Parameters ========== rows : nonnegative integer. Can be symbolic. cols : nonnegative integer. Can be symbolic. lamda : Function, Lambda or str If it is a SymPy ``Function`` or ``Lambda`` instance, it should be able to accept two arguments which represents the matrix coordinates. If it is a pure string containing Python ``lambda`` semantics, it is interpreted by the SymPy parser and casted into a SymPy ``Lambda`` instance. Examples ======== Creating a ``FunctionMatrix`` from ``Lambda``: >>> from sympy import FunctionMatrix, symbols, Lambda, MatPow >>> i, j, n, m = symbols('i,j,n,m') >>> FunctionMatrix(n, m, Lambda((i, j), i + j)) FunctionMatrix(n, m, Lambda((i, j), i + j)) Creating a ``FunctionMatrix`` from a SymPy function: >>> from sympy import KroneckerDelta >>> X = FunctionMatrix(3, 3, KroneckerDelta) >>> X.as_explicit() Matrix([ [1, 0, 0], [0, 1, 0], [0, 0, 1]]) Creating a ``FunctionMatrix`` from a SymPy undefined function: >>> from sympy import Function >>> f = Function('f') >>> X = FunctionMatrix(3, 3, f) >>> X.as_explicit() Matrix([ [f(0, 0), f(0, 1), f(0, 2)], [f(1, 0), f(1, 1), f(1, 2)], [f(2, 0), f(2, 1), f(2, 2)]]) Creating a ``FunctionMatrix`` from Python ``lambda``: >>> FunctionMatrix(n, m, 'lambda i, j: i + j') FunctionMatrix(n, m, Lambda((i, j), i + j)) Example of lazy evaluation of matrix product: >>> Y = FunctionMatrix(1000, 1000, Lambda((i, j), i + j)) >>> isinstance(Y*Y, MatPow) # this is an expression object True >>> (Y**2)[10,10] # So this is evaluated lazily 342923500 Notes ===== This class provides an alternative way to represent an extremely dense matrix with entries in some form of a sequence, in a most sparse way. """ def __new__(cls, rows, cols, lamda): rows, cols = _sympify(rows), _sympify(cols) cls._check_dim(rows) cls._check_dim(cols) lamda = sympify(lamda) if not isinstance(lamda, (FunctionClass, Lambda)): raise ValueError( "{} should be compatible with SymPy function classes." .format(lamda)) if 2 not in lamda.nargs: raise ValueError( '{} should be able to accept 2 arguments.'.format(lamda)) if not isinstance(lamda, Lambda): i, j = Dummy('i'), Dummy('j') lamda = Lambda((i, j), lamda(i, j)) return super().__new__(cls, rows, cols, lamda) @property def shape(self): return self.args[0:2] @property def lamda(self): return self.args[2] def _entry(self, i, j, **kwargs): return self.lamda(i, j) def _eval_trace(self): from sympy.matrices.expressions.trace import Trace from sympy.concrete.summations import Sum return Trace(self).rewrite(Sum).doit() def as_real_imag(self): return (re(Matrix(self)), im(Matrix(self)))