After the lengthy discussions surrounding sympy.rules
and sympy.unify
, I've come to the conclusion that the only clean way for them to work is to avoid any form of auto-evaluation in their constructors.
For now, I've only dealt with Adjoint and Transpose. The rest will follow.
NB: this depends on #1667.
git pull https://github.com/rlamy/sympy MatExpr
Or view, comment on, or merge it at:
https://github.com/sympy/sympy/pull/1670
—
Reply to this email directly or view it on GitHub.
Great. I look forward to seeing the result.
SymPy Bot Summary: Passed after merging rlamy/MatExpr (fa1d10d) into master (4ce940a).
Python 2.5.0-final-0: pass
Python 2.6.6-final-0: pass
Python 2.7.2-final-0: pass
Python 2.6.8-final-0: pass
Python 2.7.3-final-0: pass
PyPy 1.9.1-dev-0; 2.7.3-final-42: pass
Python 3.2.2-final-0: pass
Python 3.3.0-final-0: pass
Python 3.2.3-final-0: pass
Python 3.3.0-final-0: pass
Python 3.3.0-final-0: pass
**Sphinx 1.1.3:** pass
Looks good to me. I suppose we might also want trace
to evaluate and Trace
would remain unevaluated.
Yes, that's part of the idea, but trace is a bit of a mess. Quantum code uses sympy.core.trace
and AFAIK there's actually no trace()
function anywhere. I started some work on it, but I put it on hold. I'll come back to it later.
Yeah, the Tr
/Trace
issue isn't great. I'm of the opinion that we shouldn't try to merge them though. A general trace
function that branches between the two based on the input might be useful.
This establishes a very nice distinction between lower-case dothing
and upper-case Thing
. I started adding more tests, that I will submit as a PR to you. How far did you get on reworking trace/Trace/Tr
?
Thanks for the tests. I didn't actually do much about the trace - I tried to make the obvious changes in Trace.__new__
but didn't fix the resulting issues.
This should be ready for merging now. In addition to the auto-evaluation thing, I have also tried to ensure that obj.__class__(*obj.args) == obj
holds for all MatrixExprs, but I didn't deal with the trace.
In sympy/matrices/expressions/inverse.py:
> B^-1*A^-1 > + >>> (A*B).inverse() == Inverse(A*B) > + False
While this is important to understand I think that this line will be confusing to non-experts. I think this sort of thing belongs in documentation but not in docstrings. If you really think it should be here then I think that this issue should be explained.
In sympy/matrices/expressions/adjoint.py:
> @@ -1,33 +1,36 @@ > -from matexpr import MatrixExpr > -from sympy import Basic > -from sympy.functions.elementary.complexes import conjugate > +from sympy.core import Basic > +from sympy.functions import adjoint, conjugate, transpose
These inherit from Expr - does this make sense?
Are you sure that canonicalization runs deep?
In [21]: X = MatrixSymbol('X', 3, 3)
In [22]: transpose(X+X)
Out[22]: X'⋅2
In sympy/matrices/expressions/blockmatrix.py:
> > - def inv(self, expand=False):
This was inv
to match the Matrix API. I'm fine with the new version but some users might find it disconcerting.
In sympy/matrices/expressions/inverse.py:
> > >>> from sympy import MatrixSymbol, Inverse > >>> A = MatrixSymbol('A', 3, 3) > >>> B = MatrixSymbol('B', 3, 3) > >>> Inverse(A) > A^-1 > - >>> A.I
I prefer the A.I
syntax. It keeps to the numpy standard
In sympy/matrices/expressions/tests/test_matrix_exprs.py:
> @@ -341,3 +177,14 @@ def test_matmul_simplify(): > A = MatrixSymbol('A', 1, 1) > assert simplify(MatMul(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \ > MatMul(A, ImmutableMatrix([[1]])) > + > +def test_invariants(): > + A = MatrixSymbol('A', n, m) > + B = MatrixSymbol('B', m, l) > + X = MatrixSymbol('X', n, n) > + objs = [Identity(n), ZeroMatrix(m, n), A, MatMul(A, B), MatAdd(A, A), > + Transpose(A), Adjoint(A), Inverse(X), MatPow(X, 2), MatPow(X, -1), > + MatPow(X, 0)] > + for obj in objs: > + print obj
In sympy/matrices/expressions/tests/test_hadamard.py:
> assert isinstance(expr2, MatrixSymbol) > + > +def test_hadamard(): > + m, n, p = symbols('m, n, p', integer=True) > + A = MatrixSymbol('A', m, n) > + B = MatrixSymbol('B', m, n) > + C = MatrixSymbol('C', m, p) > + with raises(TypeError): > + hadamard_product() > + assert hadamard_product(A) == A > + assert isinstance(hadamard_product(A, B), HadamardProduct) > + assert hadamard_product(A, B).doit() == hadamard_product(A, B) > + assert hadamard_product(A, B) == hadamard_product(B, A) > + with raises(ShapeError): > + hadamard_product(A, C)
Neat.
In sympy/matrices/expressions/matadd.py:
> @@ -18,21 +19,12 @@ class MatAdd(MatrixExpr): > is_MatAdd = True > > def __new__(cls, *args, **kwargs): > - evaluate = kwargs.get('evaluate', True) > - check = kwargs.get('check' , True) > - > - # TODO: This is a kludge > - # We still use Matrix + 0 in a few places. This removes it > - # In particular see matrix_multiply > - args = [x for x in args if not x == 0]
Did you run across any issues when removing this? It was actually necessary at some point because (if I recall correctly) BlockMatrix used Matrix routines which assumed that they could sum elements onto scalar 0.
doit
doesn't seem quite appropriate here. Often doit
doesn't do anything. I wonder if there isn't a better choice of method name.
In sympy/matrices/expressions/inverse.py:
> B^-1*A^-1 > + >>> (A*B).inverse() == Inverse(A*B) > + False
Well, non-experts should not look at Inverse
in the first place, but just use .I
/.inverse()
. If they do look at it, this is really the most important thing they should know. What do you want me to add to the paragraph above?
In sympy/matrices/expressions/inverse.py:
> > >>> from sympy import MatrixSymbol, Inverse > >>> A = MatrixSymbol('A', 3, 3) > >>> B = MatrixSymbol('B', 3, 3) > >>> Inverse(A) > A^-1 > - >>> A.I
The problem with A.I
is that it uses attribute syntax but can trigger expensive computations, so it doesn't follow the recommendation that properties should perform simple and fast operations. I didn't remove it but I think that we should not encourage its use.
In sympy/matrices/expressions/matadd.py:
> @@ -18,21 +19,12 @@ class MatAdd(MatrixExpr): > is_MatAdd = True > > def __new__(cls, *args, **kwargs): > - evaluate = kwargs.get('evaluate', True) > - check = kwargs.get('check' , True) > - > - # TODO: This is a kludge > - # We still use Matrix + 0 in a few places. This removes it > - # In particular see matrix_multiply > - args = [x for x in args if not x == 0]
Yes, that was the problem, but fixing it was just a matter of handling empty matrices separately. Cf. df17dac.
In sympy/matrices/expressions/adjoint.py:
> @@ -1,33 +1,36 @@ > -from matexpr import MatrixExpr > -from sympy import Basic > -from sympy.functions.elementary.complexes import conjugate > +from sympy.core import Basic > +from sympy.functions import adjoint, conjugate, transpose
Well, they are only used as functions (in the Python sense), and I think they should actually be turned into functions, some day.
In sympy/matrices/expressions/blockmatrix.py:
> > - def inv(self, expand=False):
Generic MatrixExprs don't have .inv()
so having it here wasn't really consistent. Also the distinction between .inv()
and .inverse()
was confusing.
In sympy/matrices/expressions/adjoint.py:
> > - Represents the Adjoint of a matrix expression. > +class Adjoint(MatrixExpr): > + """ > + The Hermitian adjoint of a matrix expression.
Could you please add the same "warning" as in the Inverse
and Transpose
docstrings i.e. that this is unevaluated.
In sympy/matrices/expressions/hadamard.py:
> class HadamardProduct(MatrixExpr): > - """Elementwise Product of Matrix Expressions > + """ > + Elementwise product of matrix expressions
Another unevaluated "warning".
In sympy/matrices/expressions/inverse.py:
> B^-1*A^-1 > + >>> (A*B).inverse() == Inverse(A*B) > + False
More useful might be to actually show how they differ.
>>> Inverse(A*B)
(A*B)^-1
>>> (A*B).inverse()
B^-1*A^-1
What do you mean with your canonicalization comment?
Current behavior is
In [21]: X = MatrixSymbol('X', 3, 3)
In [22]: transpose(X+X)
Out[22]: X'⋅2
Ideal behavior is
In [21]: X = MatrixSymbol('X', 3, 3)
In [22]: transpose(X+X)
Out[22]: 2⋅X'
Current behavior in master is
>>> X = MatrixSymbol('X', 3, 3)
>>> transpose(X+X)
2*X'
In sympy/matrices/expressions/matmul.py:
> > def _eval_transpose(self): > - from transpose import Transpose > - return MatMul(*[Transpose(arg) for arg in self.args[::-1]]) > + return MatMul(*[transpose(arg) for arg in self.args[::-1]])
This is the line that causes the behavior
>>> transpose(2*X)
X'*2
something better could be done, maybe using as_coeff_matrices
.
In sympy/matrices/expressions/matmul.py:
> > def _eval_transpose(self): > - from transpose import Transpose > - return MatMul(*[Transpose(arg) for arg in self.args[::-1]]) > + return MatMul(*[transpose(arg) for arg in self.args[::-1]])
SymPy Bot Summary: Passed after merging rlamy/MatExpr (748db07) into master (501534b).
PyPy 2.0.0-beta-1; 2.7.3-final-42: pass
Python 2.7.2-final-0: pass
Python 3.2.1-final-0: pass
Sphinx 1.1.3: pass
Docs build command: make clean && make html-errors && make latex && cd _build/latex && xelatex sympy-*.tex
SymPy Bot Summary: Passed after merging rlamy/MatExpr (e339511) into master (d503614).
PyPy 2.0.0-beta-1; 2.7.3-final-42: pass
Python 2.7.2-final-0: pass
Python 3.2.1-final-0: pass
Sphinx 1.1.3: pass
Docs build command: make clean && make html-errors && make latex && cd _build/latex && xelatex sympy-*.tex
In sympy/matrices/expressions/inverse.py:
> B^-1*A^-1 > + >>> (A*B).inverse() == Inverse(A*B) > + False
done
In sympy/matrices/expressions/adjoint.py:
> > - Represents the Adjoint of a matrix expression. > +class Adjoint(MatrixExpr): > + """ > + The Hermitian adjoint of a matrix expression.
In sympy/matrices/expressions/hadamard.py:
> class HadamardProduct(MatrixExpr): > - """Elementwise Product of Matrix Expressions > + """ > + Elementwise product of matrix expressions
Are there any other comments? I think this is ready now.
Seems good to me. If you wait on merging until the weekend I can give it another look. I'd be fine merging now though.
Thanks! I'd rather merge now, so if you see anything to change, please send another PR.
Merged #1670.