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
core/containers: no longer create Tuple's with lists as arguments. #22551
core/containers: no longer create Tuple's with lists as arguments. #22551
Conversation
✅ Hi, I am the SymPy bot (v162). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.
Click here to see the pull request description that was parsed.
|
sympy/core/containers.py
Outdated
@@ -50,6 +50,12 @@ class Tuple(Basic): | |||
def __new__(cls, *args, **kwargs): | |||
if kwargs.get('sympify', True): | |||
args = (sympify(arg) for arg in args) | |||
#if kwargs.get('denest_lists', False): |
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.
This is to be removed before merging, but shows that currently there are no longer any Tuple
with list
args being created during the testing.
sympy/core/function.py
Outdated
@@ -1270,19 +1270,20 @@ def __new__(cls, expr, *variables, **kwargs): | |||
must be supplied to differentiate %s''' % expr)) | |||
|
|||
# Standardize the variables by sympifying them: | |||
variables = list(sympify(variables)) | |||
#variables = sympify(list(variables)) |
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.
This is actually not needed here since all variables are eventually stored in Tuple
(or Array
which again stores it in Tuple
) where sympify
is also used.
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.
Maybe this should just be deleted.
sympy/printing/tests/test_julia.py
Outdated
@@ -212,7 +212,8 @@ def test_containers(): | |||
assert julia_code(Tuple(*[1, 2, 3])) == "(1, 2, 3)" | |||
assert julia_code((1, x*y, (3, x**2))) == "(1, x.*y, (3, x.^2))" | |||
# scalar, matrix, empty matrix and empty list | |||
assert julia_code((1, eye(3), Matrix(0, 0, []), [])) == "(1, [1 0 0;\n0 1 0;\n0 0 1], zeros(0, 0), Any[])" | |||
from sympy.codegen.pynodes import List |
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.
Should there be sympy/codegen/{julia,maple,octave}nodes.py files defining List
or perhaps this means that List
should be moved to a more general file in sympy/codegen. Obviously the import should be made top-level but I put it here for convenience of review.
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'm not sure that List has the same meaning in all languages or if this should be considered an abstract "list" that would have different implementations in different languages.
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 each code printer has its own way to print a list
, but since sympify
is used on the input to CodePrinter
s, this line causes the creation of a Tuple
, which then has a list
in it. Changing this to a List
seems to avoid this problem, I'm not sure there is a better way. List
is actually just a wrapper around Tuple
which gets printed differently (if I understand correctly, it defaults to printing the same as list
.
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 you know more about this than I do so I can't really advise but I trust your judgement.
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.
@bjodah do you have any preference for how this is dealt with?
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'm fine with moving it. I put it in pynodes to signal equivalence with Python lists.
@@ -584,8 +584,10 @@ def test_cse_list(): | |||
assert _cse(x) == ([], x) | |||
assert _cse('x') == ([], 'x') | |||
it = [x] | |||
for c in (list, tuple, set, Tuple): | |||
for c in (list, tuple, set): |
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.
Tuple
doesn't do typecasting, the equivalent of tuple(x)
is Tuple(*x)
.
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
[907895ac] [4489b4c7]
- 4.23±0.03s 323±20ms 0.08 polygon.PolygonArbitraryPoint.time_bench01
+ 3.45±0.2ms 5.78±0.3ms 1.67 solve.TimeMatrixOperations.time_det(4, 2)
+ 3.22±0.2ms 6.02±0.06ms 1.87 solve.TimeMatrixOperations.time_det_bareiss(4, 2)
+ 37.5±1ms 56.5±2ms 1.51 solve.TimeMatrixSolvePyDySlow.time_linsolve(1)
+ 38.4±0.9ms 57.7±3ms 1.50 solve.TimeMatrixSolvePyDySlow.time_solve(1)
Full benchmark results can be found as artifacts in GitHub Actions |
sympy/core/containers.py
Outdated
@@ -50,6 +50,12 @@ class Tuple(Basic): | |||
def __new__(cls, *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.
Probably this should use an explicit keyword argument:
def __new__(cls, *args, sympify=False):
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.
Would it be confusing if this gets changed in this PR? Since it solves a different issue. Note also that the current default value is sympify=True
.
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.
Turns out that this is not trival. Dict
assumes Tuple
uses sympify
and not _sympify
(to convert str
to Symbols
).
So if you use sympify
as a keyword argument, you don't have access to it anymore
sympy/core/containers.py
Outdated
args = tuple(args) | ||
for arg in args: | ||
if isinstance(arg, list): | ||
warnings.warn(DeprecationWarning('Tuple',arg)) |
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.
Deprecation warnings in SymPy are usually given like this:
Lines 453 to 460 in ed0bfd1
SymPyDeprecationWarning( | |
feature="String fallback in sympify", | |
useinstead= \ | |
'sympify(str(obj)) or ' + \ | |
'sympy.core.sympify.converter or obj._sympy_', | |
issue=18066, | |
deprecated_since_version='1.6' | |
).warn() |
The SymPyDeprecationWarning class is handled specially by the test runner so that it is an error if it is emitted in the test suite.
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.
As far as I am concerned, this warning will be removed before the merge (see also the first review comment by me). If such a warning is to be added, it should be added to Basic
when all such issues are resolved. This is for demonstration purposes. (that there are no longer such violations)
🟠Hi, I am the SymPy bot (v162). I've noticed that some of your commits add or delete files. Since this is sometimes done unintentionally, I wanted to alert you about it. This is an experimental feature of SymPy Bot. If you have any feedback on it, please comment at sympy/sympy-bot#75. The following commits add new files:
If these files were added/deleted on purpose, you can ignore this message. |
Is this finished now? |
@bjodah Do you have an opinion about the new location of I think it is possible to have Is |
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'm fine with putting the class in .abstract_nodes
.
sympy/printing/tests/test_julia.py
Outdated
@@ -212,7 +213,7 @@ def test_containers(): | |||
assert julia_code(Tuple(*[1, 2, 3])) == "(1, 2, 3)" | |||
assert julia_code((1, x*y, (3, x**2))) == "(1, x.*y, (3, x.^2))" | |||
# scalar, matrix, empty matrix and empty list | |||
assert julia_code((1, eye(3), Matrix(0, 0, []), [])) == "(1, [1 0 0;\n0 1 0;\n0 0 1], zeros(0, 0), Any[])" |
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.
this looks like it introduces backwards incompatibility? If so, I'm afraid it needs a deprecation cycle.
sympy/printing/tests/test_maple.py
Outdated
@@ -233,8 +234,7 @@ def test_containers(): | |||
assert maple_code(Tuple(*[1, 2, 3])) == "[1, 2, 3]" | |||
assert maple_code((1, x * y, (3, x ** 2))) == "[1, x*y, [3, x^2]]" | |||
# scalar, matrix, empty matrix and empty list | |||
|
|||
assert maple_code((1, eye(3), Matrix(0, 0, []), [])) == \ | |||
assert maple_code((1, eye(3), Matrix(0, 0, []), List(*[]))) == \ |
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.
same thing here and for the octave test too.
sympy/printing/codeprinter.py
Outdated
#for codeprinting, all lists should be converted to abstract_nodes.Lists. | ||
from sympy.codegen.abstract_nodes import List | ||
store_list_converter=converter.get(list, None) | ||
converter[list]=lambda x: List(*x) |
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'm adding list->List
conversion to sympify
for the purposes of CodePrinting
, this will allow current tuple/list nesting without creating list
s in Tuple
s but not change anything for the user. Is there a better way to do this?
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.
This kind of coding is fragile in my humble opinion: importing a mutable object, modifying it in-place, calling a function using it, and then reverting the change. If the converter
in .sympify
is intended to be modifiable, then I'd rather see a context manager class in .sympify
allowing to override the default converters for a region of code, would you agree?
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.
This is also not thread-safe. The converters are only really supposed to be registered during start up.
Probably somewhere there should explicit code that checks for list
and converts to List
rather than depending on sympify
to do this implicitly.
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.
Point taken, I'll try to see of sympy.strategies can be of any help.
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 this has been solved.
sympy/printing/codeprinter.py
Outdated
del converter[list] | ||
else: | ||
converter[list] = store_list_converter | ||
|
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.
Restore sympify
to normal behaviour. Does it make sense to add this as an option to symplify
?
expr = _handle_assign_to(expr, assign_to) | ||
expr = _convert_python_lists(expr) |
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.
This is where the explicit conversion was done previously. (Note that this was added only for backwards compatibility, I would have rather done without it, but it was already being called with []
as can be found in the tests).
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.
It needs to happen before _handle_assign_to
(since it will gladly convert ([],)
to Tuple([])
in sympify
which should not be allowed).
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.
can we simply move the call up?
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.
Currently _convert_python_lists
only goes through list
s (so it would stop as soon as it sees the tuple
). I could try to change it to loop through tuple
too.
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 updated _convert_python_lists
to recurse through tuple
and moved it forward. I believe this solves all issues.
Thanks! The changes regarding List now looks good to me 🙂👍 |
|
||
class List(Tuple): | ||
"""Represents a (frozen) (Python) list (for code printing purposes).""" | ||
def __eq__(self, other): |
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.
Is the __eq__
method needed for this class to function or is it just for the tests?
In general it's better if Basic subclass don't override __eq__
.
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.
from sympy import *
class List(Tuple):
pass
print([1] == List(1))
class List(Tuple):
"""Represents a (frozen) (Python) list (for code printing purposes)."""
def __eq__(self, other):
if isinstance(other, list):
return self == List(*other)
else:
return self.args == other
print([1] == List(1))
Shows that the first case is False
while the second case is True
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, but why does that matter? Does the List class actually need to be compared (with ==
) to the list class in order to function properly in actual use?
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'm not sure, that would have to be tested. Note that I only moved this class. It already had this functionality, so I assume there was a reason (perhaps a bit naive). It was previously in pynodes.py, which should indicate that this is specific to python printers, which it actually is not since it was already being used for all CodePrinter
s. The 'bug' related to this class was actually that it had to be used earlier in CodePrinter.doprint
to avoid converting Tuple
s with list
s as arguments.
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.
Note that I only moved this class
Oh, okay. Let's just leave it for now then.
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 we can remove the __eq__
method and the test if we want. I didn't reflect over implications of overriding __eq__
in a Basic subclass.
Looks good. Thanks! |
References to other Issues or PRs
Fixes #22550
Brief description of what is fixed or changed
Several classes in SymPy use
sympify
on their arguments. In some cases, this leads to the creation ofTuple
objects that havelists
as their arguments. This PR solves those cases as far as they show up in the tests, by explicitly looping over iterables when a mix oftuple
andlist
can be used for the input arguments.Other comments
Release Notes
NO ENTRY