Skip to content

Fix sympy error #5930

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

Merged
merged 9 commits into from
Oct 30, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cirq-core/cirq/study/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Resolves ParameterValues to assigned values."""
import numbers
import warnings
from typing import Any, cast, Dict, Iterator, Mapping, Optional, TYPE_CHECKING, Union

import numpy as np
Expand Down Expand Up @@ -179,7 +180,11 @@ def value_of(
if not recursive:
# Resolves one step at a time. For example:
# a.subs({a: b, b: c}) == b
v = value.subs(self.param_dict, simultaneous=True)
try:
v = value.subs(self.param_dict, simultaneous=True)
except sympy.SympifyError:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "not parsable"? An actual syntax error? If so, then this is probably a user mistake and it's better to let the exception propagate out of here. Then the PR could catch it in resolver_test.py rather than in resolver.py. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual sympy bug is here:
sympy/sympy#23887

Their definition is "sympifiable" whatever that means. I think that it is either a sympy object or a basic python type, possibly a few other cases. I think it is talking about objects foreign to the sympy library. In our case, that is the symbol 'NotImplemented'.

I am hesitant to change the existing behavior. The test currently asserts that resolving {'b': NotImplemented} would just leave sympy.Symbol('b') alone. I am loathe to change this behavior right now, since there is a lot of special handling around NotImplemented.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we should preserve existing behavior.

I have two conerns:

  • the fact that if one of the values is unsympifiable then none of the substitutions are made,
  • the fact that we're failing to communicate a potential mistake to the user as soon as we discover it (we could be passing an unsympifiable object as a result of a simple typo in the initialization of the ParamDict).

Is NotImplemented the only offender? If so, could we just filter them out by saying something like

v = value.subs({k: w for k, w in self.param_dict.values() if w is not NotImplemented}, simultaneous=True)

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to raise a ValueError instead, explaining what the problem substitution is.

We cannot do your suggestion, because the test case is a function that defines resolved_value as NotImplemented. This is indistinguishable from something like Sympy.Symbol('b')+Sympy.Symbol('c') which also has an unimplemented resolved_value.

This does change the behavior of the function slightly, but, as you pointed out, the previous PR would also change the value as some additional variables would not be substituted.

I think this is the best solution we can do, since this preserves the behavior 'the variable could not be substituted' but makes it explicit as a ValueError rather than silently refusing to substitute it. It's also quite a buried case, because it involves the user specifying something that was unresolvable in the first place.

warnings.warn(f'Could not resolve parameter {value}')
return value
if v.free_symbols:
return v
elif sympy.im(v):
Expand Down