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
Fix for pretty printing of MatAdd symbols and numbers #23553
base: master
Are you sure you want to change the base?
Conversation
✅ Hi, I am the SymPy bot (v167). I'm here to help you write a release notes entry. Please read the guide on how to write release notes. Your release notes are in good order. Here is what the release notes will look like:
This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.11. Click here to see the pull request description that was parsed.
|
Maybe something like this can work: def _print_MatAdd(self, expr):
s = None
for item in expr.args:
pform = self._print(item)
if s is None:
s = pform # First element
else:
#In case we have a MatAdd of a non-matrix and a matrix
if hasattr(item, 'as_coeff_mmul'):
coeff = item.as_coeff_mmul()[0]
else:
coeff = item.as_coeff_mul()[0]
if S(coeff).could_extract_minus_sign():
s = prettyForm(*stringPict.next(s, ' '))
pform = self._print(item)
else:
s = prettyForm(*stringPict.next(s, ' + '))
s = prettyForm(*stringPict.next(s, pform))
return s |
e6c57f1
to
3a4b25d
Compare
Benchmark results from GitHub Actions Lower numbers are good, higher numbers are bad. A ratio less than 1 Significantly changed benchmark results (PR vs master) Significantly changed benchmark results (master vs previous release) before after ratio
[77f1d79c] [b1cf21e1]
<sympy-1.10.1^0>
+ 97.2±0.3ms 178±0.5ms 1.84 sum.TimeSum.time_doit
Full benchmark results can be found as artifacts in GitHub Actions |
@ThePauliPrinciple - thanks. I've refactored the function and extended the tests quite a bit. I do not understand why the code has to use the coefficients for I have revised the release notes by editing the intial PR comment - so I think this one is good to go. |
sympy/printing/pretty/pretty.py
Outdated
else: | ||
s = prettyForm(*stringPict.next(s, ' + ')) | ||
s = prettyForm(*stringPict.next(s, pform)) | ||
args = list(expr.args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only because I saw it lower down, and I didn't know for sure that it would be an indexable object. If it will, then can be omitted, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expr.args will always be an iterable, yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it always be indexable? So I can do expr.args[0]
and expr.args[1:]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
args is always a list (or possibly a tuple, I do not recall).
It may be of length 1 though, e.g. MatAdd(A)
will not evaluate to A
(which is probably a deficiency, but then there is still the possibility that someone does MatAdd(A, evaluate=False)
). Seems like MatAdd(evaluate=False)
does evaluate to GenericZeroMatrix
, so one can be sure that it is at least of length 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah - length 1 is OK:
[ins] In [1]: a = [1]
[ins] In [2]: a[1:]
Out[2]: []
It would be useful to see how these expressions print when |
3a4b25d
to
96f3f5a
Compare
@oscargus - thanks - I added tests for evaluated and not-evaluated - they give the same output. Not to do with this PR directly, because this behavior has not changed, but is the following really the In [4]: pprint(MatAdd(A, -B))
A -B |
I think the optional dependencies failure is not related. I think I have fixed the release notes now. Is there anything else I should do? |
I am a bit doubtful about this whole thing. It is not possible to add a This is also the reason why it prints badly. Doing the "correct" thing and subtract A and B will print in a slightly better way. |
Right - yes - I get the doubtful aspect. But the question is what to do about it. This fix seems to me better than what was there before. Before, and after, we have no problem forming the expression, but before, when you |
@@ -5190,17 +5195,14 @@ def test_pretty_prec(): | |||
] | |||
|
|||
|
|||
def pprint_out(expr, *args, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does pretty
lead to the same error? In that case, I'd prefer to simply use that instead of fetching the print output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, same error. Will push tests change to using pretty
.
item.as_coeff_mmul()[0] if hasattr(item, 'as_coeff_mmul') | ||
else item.as_coeff_mul()[0] if hasattr(item, 'as_coeff_mul') | ||
else item) | ||
joiner = ' ' if S(coeff).could_extract_minus_sign() else ' + ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If coeff
is not already sympified that is a bug elsewhere. So S
is not (should not be) required. Probably better to not have it as it will hide bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately that does not work because:
[nav] In [10]: A = sympy.MatrixSymbol('A', 1, 1)
[ins] In [11]: B = sympy.MatrixSymbol('B', 1, 1)
[ins] In [12]: (A + B).args[1]
Out[12]: B
[ins] In [13]: (A + B).args[1].as_coeff_mmul()
Out[13]: (1, B)
Therefore:
[ins] In [15]: coeff = (A + B).args[1].as_coeff_mmul()[0]
[ins] In [16]: coeff
Out[16]: 1
[ins] In [17]: coeff.could_extract_minus_sign()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Input In [17], in <cell line: 1>()
----> 1 coeff.could_extract_minus_sign()
AttributeError: 'int' object has no attribute 'could_extract_minus_sign'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, that is clearly a bug here:
sympy/sympy/matrices/expressions/matexpr.py
Lines 400 to 401 in 24afa46
def as_coeff_mmul(self): | |
return 1, MatMul(self) |
as it should not return 1, but S.One
I'll open a PR for that and changing check default to True.
We'll see what remains of the original issue then, but even though this may not be merged(?), it was clearly worthwhile to identify two issues in the code!
Apply versions of suggestions from sympy#23552, add tests.
96f3f5a
to
5343374
Compare
A = MatrixSymbol('A', 1, 1) | ||
b = Symbol('b') | ||
assert pretty(MatAdd(evaluate=False)) == '0' | ||
assert pprint_out(MatAdd(evaluate=False)) == '𝟘' | ||
assert pretty(MatAdd(A, evaluate=False)) == 'A' | ||
assert pretty(MatAdd(b, evaluate=False)) == 'b' | ||
assert pretty(MatAdd(1, evaluate=False)) == '1' | ||
assert pretty(MatAdd(1, b, evaluate=False)) == '1 + b' | ||
assert pretty(MatAdd(b, 1, evaluate=False)) == 'b + 1' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These things should not be in the test suite. This is an invalid use of MatAdd
and it should be disallowed. This test deliberately asserts that MatAdd
can be used in ways that break its internal invariants. This kind of thing just makes it hard in future to enforce or depend on those invariants in the codebase (at which point someone like me will come along and strip out all these tests as being invalid).
Whether or not this should raise an exception or emit a warning is a question that can reasonably be asked. We shouldn't add regression tests for invalid usage though.
Rather than trying to make this work can we please focus on how to better serve the usecase in a way that can reasonably be supported:
- Maybe there could be a
PrintAdd
class whose sole purpose is for printing - Maybe we should make it easier somehow to use
MatrixSymbol
. - Something else?
I don't see why so much effort is being expended here on making it possible to do something in a bad way when there is already a perfectly good way: use MatrixSymbol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I might say - I think the effort being expended is just because it's still not clear what the right thing to do is.
I see that these should in some sense not be allowed, but it is in fact possible to create these objects, and so you get a very obscure error from pprint
.
I really have no skin in this game - I can solve my problem another way. It just seemed to me that the current error in pprint
or pretty
was probably not the best possible outcome - and that the problem needed to be fixed elsewhere - possibly to prevent these objects being created. But seemed out of scope.
I just put the tests in because I wanted to test I'd fixed the undesirable behavior. Why not just leave them in, then remove the ability to create these objects, and then remove the tests. But - as I say - totally up to y'all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the proper fix is for MatAdd to error when given non matrices as arguments. It used to. I don't know why that was changed. I'm not sure (given that at least one person is apparently using this) whether there should be a deprecation for this even though it was never something that was "supported".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's totally fine by me - as I say. This current fix is only for the case where you haven't done that yet...
Just as a recap - the fix here is just to avoid the very confusing current situation, of an uninformative error from certain attempts to print these expressions, which are impossible to evaluate, but easy to generate. The potential for confusion may be best illustrated with: [ins] In [1]: import sympy
[ins] In [2]: A = sympy.MatrixSymbol('A', 1, 1)
[ins] In [3]: b = sympy.Symbol('b')
[ins] In [4]: expr = sympy.MatAdd(A, b)
[ins] In [5]: display(expr)
b + A
[ins] In [6]: sympy.init_printing()
[ins] In [7]: display(expr)
---------------------------------------------------------------------------
... [snip] ...
AttributeError Traceback (most recent call last)
File ~/Library/Python/3.8/lib/python/site-packages/sympy/printing/pretty/pretty.py:876, in PrettyPrinter._print_MatAdd(self, expr)
874 s = pform # First element
875 else:
--> 876 coeff = item.as_coeff_mmul()[0]
877 if S(coeff).could_extract_minus_sign():
878 s = prettyForm(*stringPict.next(s, ' '))
AttributeError: 'Symbol' object has no attribute 'as_coeff_mmul' |
My vote is for this: diff --git a/sympy/matrices/expressions/matadd.py b/sympy/matrices/expressions/matadd.py
index 4f8c2b6..fd17ab9 100644
--- a/sympy/matrices/expressions/matadd.py
+++ b/sympy/matrices/expressions/matadd.py
@@ -34,7 +34,7 @@ class MatAdd(MatrixExpr, Add):
identity = GenericZeroMatrix()
- def __new__(cls, *args, evaluate=False, check=False, _sympify=True):
+ def __new__(cls, *args, evaluate=False, check=True, _sympify=True):
if not args:
return cls.identity
Then you get: In [2]: import sympy
...: A = sympy.MatrixSymbol('A', 1, 1)
...: b = sympy.Symbol('b')
...: expr = sympy.MatAdd(A, b)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-57ac487b2ab3> in <module>
2 A = sympy.MatrixSymbol('A', 1, 1)
3 b = sympy.Symbol('b')
----> 4 expr = sympy.MatAdd(A, b)
~/current/sympy/sympy.git/sympy/matrices/expressions/matadd.py in __new__(cls, evaluate, check, _sympify, *args)
50 if not any(isinstance(i, MatrixExpr) for i in args):
51 return Add.fromiter(args)
---> 52 validate(*args)
53
54 if evaluate:
~/current/sympy/sympy.git/sympy/matrices/expressions/matadd.py in validate(*args)
104 def validate(*args):
105 if not all(arg.is_Matrix for arg in args):
--> 106 raise TypeError("Mix of Matrix and Scalar symbols")
107
108 A = args[0]
TypeError: Mix of Matrix and Scalar symbols We can of course debate the potential need for a warning to be emitted for a few releases rather than an error. |
We discussed this a bit above. What do you think should happen then for:
|
Garbage in, garbage out. The purpose of |
Well - but I think that leaves us in the same place as we were at the beginning - is this really the best way to indicate that you have a garbage expression? [ins] In [1]: import sympy
[ins] In [2]: A = sympy.MatrixSymbol('A', 1, 1)
[ins] In [3]: b = sympy.Symbol('b')
[ins] In [4]: expr = sympy.MatAdd(A, b, check=False)
[ins] In [5]: display(expr)
b + A
[ins] In [6]: sympy.init_printing()
[ins] In [7]: display(expr)
---------------------------------------------------------------------------
... [snip] ...
AttributeError Traceback (most recent call last)
File ~/Library/Python/3.8/lib/python/site-packages/sympy/printing/pretty/pretty.py:876, in PrettyPrinter._print_MatAdd(self, expr)
874 s = pform # First element
875 else:
--> 876 coeff = item.as_coeff_mmul()[0]
877 if S(coeff).could_extract_minus_sign():
878 s = prettyForm(*stringPict.next(s, ' '))
AttributeError: 'Symbol' object has no attribute 'as_coeff_mmul' I would argue no - that there is no benefit to this error specifically on printing - that is just makes the source of the error needlessly confusing. Put another way - yes, by all means raise an error, but not here - where it appears sometimes, and sometimes not. |
We should make it clear that |
I feel I should give up at this point. Feel free to close the PR. I continue to think that this printing error is the wrong thing to do, and confusing, but if it seems like the right thing to do - that's fine - your call. |
I agree that the printing error is not ideal. It arises because of the earlier problem: creating an invalid expression. Ideally the error would be raised at the point of error when the expression is created. That's better for users because they see the error in the right place. It's also better for the library because we only need to have one place that ensures the validity of expressions (and can then rightfully presume that that validity holds elsewhere). |
With the changes in #23671
Actually, there is no use of |
Well - I guess I'm just going to say it one more time - but is it the right thing to do, to have this error only come up for certain display options? Is this what y'all really want?
Again - I'd say no - that's very confusing. But your call. |
I agree that it is confusing. For reference, the LaTeX printer does not run into this problem, so I wonder what is different there? (I have not checked it carefully, but as far as I can tell it does not have a dedicated MatAdd printer.) I'm inclined to say that your modified code (without the S in S(coeff) after #23553 is merged) is my proposed way to go, at least until check is possibly removed. And that the test is a simple smoke test, just doing |
Apply suggestion from
#23552 (comment) and
add tests.
MatAdd
expressions.