Skip to content
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

Allow custom definition for _sympy_pass_through #3921

Merged
merged 16 commits into from
Mar 17, 2021

Conversation

zchen088
Copy link
Collaborator

Fixes #3916

@zchen088 zchen088 requested review from maffoo and balopat March 17, 2021 02:36
@zchen088 zchen088 requested review from cduck, vtomole and a team as code owners March 17, 2021 02:36
@google-cla google-cla bot added the cla: yes Makes googlebot stop complaining. label Mar 17, 2021
Copy link
Contributor

@maffoo maffoo left a comment

Choose a reason for hiding this comment

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

This seems reasonable to me. Once we settle on the design, we should update the value_of docs (and probably the resolve_parameters protocol docs) to describe this mechanism and how it can be used on custom types that one might want to use when resolving parameters.

@@ -244,4 +244,9 @@ def _sympy_pass_through(val: Any) -> Optional[Any]:
return val.p / val.q
if val == sympy.pi:
return np.pi

getter = getattr(val, '_sympy_pass_through_', None)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it'd be helpful to change the name, because the point of defining a special method is to broaden this beyond the hard-coded sympy handling. In addition, the code above allows transforming values, not just passing them through (e.g. sympy integers become plain old integers). So maybe we could call the special method something like _resolver_value_ and if it exists we call it to get a value to pass through, which could be different from val itself. The method could return NotImplemented to indicate that the value should not be passed through. We could also change this function itself to use NotImplemented as the sentinel value instead of None if we want to allow things to resolve to None (the function is only called in value_of above, so that change wouldn't affect any other code).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could also change this function itself to use NotImplemented as the sentinel value instead of None if we want to allow things to resolve to None (the function is only called in value_of above, so that change wouldn't affect any other code).

I tried this but it turns out that we're relying on None as a sentinel in some subtle way that I couldn't quite unravel, probably because of this line: https://github.com/quantumlib/Cirq/blob/master/cirq/study/resolver.py#L175

Copy link
Collaborator

Choose a reason for hiding this comment

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

That None was me :p A valid workaround would be to replace None with a file-local class used only for flagging potential loops in recursive parameter evaluation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm...I guess replacing wouldn't resolve this entirely, since None is checked for earlier in value_of.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@95-martin-orion thanks for the hint, got all the tests to pass now, check if this is what you had in mind?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd put a leading underscore on RecursionFlag just to make it abundantly clear that it's a non-public object, but otherwise this looks right.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to leading underscore. I'd also suggest adding a comment that this is used to mark values that are being resolved recursively to detect loops.


class Bar:
def _sympy_pass_through_(self):
return False
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add a case here that has _resolver_value_ method that returns NotImplemented?

@95-martin-orion
Copy link
Collaborator

This seems reasonable to me. Once we settle on the design, we should update the value_of docs (and probably the resolve_parameters protocol docs) to describe this mechanism {...}

Our requirement for "magic methods" (i.e. _functions_like_this_) is that they have an associated protocol. For _resolve_parameters_, this protocol is SupportsParameterization:

class SupportsParameterization(Protocol):

To follow the same rule for _resolver_value_, we should add a protocol (in resolve_parameters.py is fine) for that method.

@maffoo
Copy link
Contributor

maffoo commented Mar 17, 2021

@95-martin-orion, the problem with adding this in SupportsParametrization is that this new method _resolver_value_ is for values that we want the resolver to deal with, not parametrized objects themselves. We could add a new ResolvableValue protocol for this, but while we have top-level methods to invoke most protocols (like cirq.resolve_parameters for SupportsParametrization) I don't think it makes sense to add a top-level method in this case. It's really an internal detail of how the existing parameter resolution protocol works.

@95-martin-orion
Copy link
Collaborator

@95-martin-orion, the problem with adding this in SupportsParametrization is that this new method _resolver_value_ is for values that we want the resolver to deal with, not parametrized objects themselves. We could add a new ResolvableValue protocol for this, but while we have top-level methods to invoke most protocols (like cirq.resolve_parameters for SupportsParametrization) I don't think it makes sense to add a top-level method in this case. It's really an internal detail of how the existing parameter resolution protocol works.

Protocols do not require top-level methods - see SerializableByKey (although, due to imnplementation details, this protocol no longer has a "magic method" associated with it):

class SerializableByKey(SupportsJSON):

@@ -237,6 +237,16 @@ def _from_json_dict_(cls, param_dict, **kwargs):
return cls(dict(param_dict))


class ResolvableValue(Protocol):
@doc_private
def _resolver_value_(self) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should call it _resolved_value_ to match what's in the docstring below (I think that explanation is pretty clear). Also, I'd suggest moving this protocol into resolve_parameters.py alongside the other protocols that are related to parameter resolution, so we can document the whole process there.

further parsing like we do with Sympy symbols.
"""


def _sympy_pass_through(val: Any) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename this _resolve_value?

@zchen088 zchen088 requested a review from maffoo March 17, 2021 16:16
Copy link
Collaborator

@95-martin-orion 95-martin-orion left a comment

Choose a reason for hiding this comment

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

One remaining nit, then this looks good to go.

@@ -230,6 +230,32 @@ def test_resolve_unknown_type():
assert r.value_of(cirq.X) == cirq.X


def test_custom_sympy_pass_through():
Copy link
Collaborator

Choose a reason for hiding this comment

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

test_custom_sympy_pass_through -> test_custom_resolved_value

Copy link
Contributor

@maffoo maffoo left a comment

Choose a reason for hiding this comment

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

LGTM

@zchen088 zchen088 merged commit 38cb2f2 into quantumlib:master Mar 17, 2021
@zchen088 zchen088 deleted the 3916-sympy-pass-through branch March 17, 2021 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Makes googlebot stop complaining.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a way to mark types that should be passed-through parameter resolution.
3 participants