-
Notifications
You must be signed in to change notification settings - Fork 344
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
Proposal - feat: The Quil constant pi can now be represented directly with the new Pi class #1697
base: master
Are you sure you want to change the base?
Conversation
@bramathon curious what you think about this proposal. Are the limitations workable, or would you need a solution for them before this could really be adopted? |
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 uncontroversial to me! Interested to hear the user perspective as well.
def __array__(self, dtype: Optional[type] = None): | ||
return np.asarray(float(self), dtype=dtype) |
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 this necessary? This feels like we might regret having to maintain it
if expr.is_pi(): | ||
return np.pi |
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.
Worth noting, quil-rs
should do this itself, but I can see wanting the extra assurance here
def _simplify(self) -> quil_rs_expr.Expression: | ||
rs_expression = _convert_to_rs_expression(self) | ||
rs_expression.simplify() | ||
return rs_expression |
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.
Not changes requested, but for your awareness in case it comes up in the near future - expression simplification can be expensive.
In pyquil v3, the constant pi was a float. Specifically, the program constructed like so, import numpy as np
from pyquil.quil import Program
from pyquil.gates import RX
program = Program()
program += RX(np.pi/2, 0)
program.instructions[0].params[0] would yield a parameter value of 1.5707963267948966, however it would be printed as:
With pyquil 4, the identical construction led to a program string of:
while parsing the Quil
produces a gate with a scalar float of 1.5707963267948966, but the program retains the string representation using the symbol pi. With this proposal, I understand that the gate parameter would become an expression
I believe this change would cause more problems than it would solve. Pyquil expressions are not typically convenient objects to work with, particular for vectorized code. It would be preferable to me to simply maintain the pyquil 3 behaviour, where scalar values near fractions of pi are represented as so, but if that is not possible then they should always be floats. |
It seems like between the issue and this comment, the concern moves between two different things:
It seems to me like this MR implementation meets your targets for both, right?
The only catch is that as a user you have to ask for the behavior you want. As the vendors of the library, that seems like the right choice to keep different users happy with different preferences. Right?
This, to me, was always hacky / "magical" in that we were trying to infer the user's intent, but only in some cases, and only for the convenience of readability. That is, maybe we do |
Changing pi to be an expression will cause a tremendous number of bugs, because to date we have assumed that expressions are parametric while fixed values are scalars, and a lot of code relies on this being true. From my perspective, printing decimal values as fractions of pi is a nice-to-have. It makes the quil much more readable and since What is more important is that:
Sure, why not? import numpy as np
from fractions import Fraction
def to_pi_frac(x: float) -> str:
"""Convert a float to a fraction of pi with a denominator <64."""
frac = Fraction(x / np.pi).limit_denominator(64)
if not np.isclose(float(frac)*np.pi, x, atol=1e-6):
return x
return ("𝜋" if frac.numerator == 1 else f"{frac.numerator}𝜋") + ("" if frac.denominator == 1 else f"/{frac.denominator}") |
My first reaction was that a symbolic @bramathon there's two flaws in your argument: (1) The statement that the change "will cause a tremendous number of bugs, because to date we have assumed that expressions are parametric while fixed values are scalars, and a lot of code relies on this being true" is by definition not a bug in (2) Your code example that determines a I agree with the round-trip requirements but I assume they would hold; if we wrote using |
Yes, sorry the introduction of the
I understand the goal of the The crux of the issue is that as a user, my preferences are in this order:
One thing I do not rely on is the round-trip requirement, although it could be important elsewhere. |
Description
This is an attempt to mitigate the issues in #1664 where parsing a program containing the string
pi
would result in a program containing the Quil constantpi
but programatically building a program from PyQuil classes containing a float likenp.pi
would result in expressions containing the literal float3.14...
. This results in what are effectively equivalent programs not being evaluated as such.My proposal here is to add a new
Pi
class to the expression module that can be used to represent the Quil constantpi
. As a descendant of theExpression
class, it can be used as part of most kinds of Python expressions easily.One limitation is that Python expressions between
Expression
objects result in anExpression
. This can be problematic in situations where an atomic type is needed. In order to support that, I've added support for casting anExpression
to anint
,float
, orcomplex
value is supported. This leans onquil
to simplify the expression, and will fail if the expression contains any unbound parameters.Adding casting makes it easier to use
Expression
s in numpy array's, but note that once used inside of an array, the expression will be cast to an atomic value and you will lose information about the expression (e.g. if it is a "pi" constant).Checklist
master
branch