-
Notifications
You must be signed in to change notification settings - Fork 61
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
Updating GST to accept parameterised gates #1534
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #1534 +/- ##
=======================================
Coverage 99.68% 99.68%
=======================================
Files 80 80
Lines 11606 11612 +6
=======================================
+ Hits 11570 11576 +6
Misses 36 36
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
I am no expert of tomography, but exactly what do you mean by "tomography of a parametrized gate"? I mean, I presume you are thinking of: fixing the set of parameters and performing the tomography for that specific parameter choice. So it's not really a parametrized gate anymore. |
@BrunoLiegiBastonLiegi the problem I believe @mho291 is trying to address should not be theoretical, but rather technical. Cf. #1502 So, you're right that once the parameters are fixed, the gate is completely analogous to any other fixed one. But from the point of view of Qibo is still holding some parameters in an attribute. Thus, it's parameterized in that sense... |
Yeah, that I understand. This is a limitation imposed by the design choice (which was mine) of having only the class reference of gates passed rather than the actual instance. Hence: gate_set = [gates.X, gates.Y, gates.Z] rather than: gate_set = [gates.X(0), gates.Y(0), gates.Z(0)] The rationale behind this choice is that I honestly find misleading making the user pass a gate built on a qubit that is completely ignored by the GST in practice. Therefore, I know that this choice for instance limits the applicability of GST to parametrized gates, but is that really limiting some relevant set up? (This I am actually asking because I am not sure) In any case, apart from the philosophical motivation, I would rather suggest to have a separate GST(gate_set=[gates.RX, gates.Y, gates.U3], ..., gate_parameters=[rx_param, u3_params], ...) Or, alternatively, to pass the parametrized gates as a tuple:
|
Thanks @BrunoLiegiBastonLiegi and @alecandido. I guess the most straightforward reason to passing in a gate with parameters is so that the user does not need to transpile it to the standard gates. Will it be odd if we passed the entire gate but not use the qubit parameter, for e.g. Following the GST wishlist here, we might want to consider adding a feature that allows the user to specify which qubit to use and gates to use for state preparation & measurements, so that GST can be used on a device that supports verbatim compilation, like the Amazon Braket devices. It's more complicated than adding I've been doing GST with verbatim compilation on IQM Garnet recently so I have some gates that we can use for state preparation and measurements. There, I extracted the qubit information and used it for state preparation and measurements. But maybe let's leave that for a later task? |
In case, I would explore the option of using some clearly invalid value as a placeholder. More in details, not to check for this value (and just ignore it, as you would do anyhow), but rather to systematically use that to document the GST usage. The best one would be This is just to make it clear that this value is completely irrelevant (which makes explicit that the interface would be better without, but hiding it is just worse).
Definitely, let's keep for a separate task. The one in this PR is a patch, to allow working with something that before was not accepted (unexpectedly, perhaps). |
Thanks @alecandido . To carry on the discussion further, what might be the best way forward? I see two possible routes we can take:
Thanks a lot! Enjoy the weekends! |
@mho291 in case of doubt, do less (per PR). So, 2. is definitely better than 1.. @BrunoLiegiBastonLiegi's proposal was to do even less, not more (i.e. to avoid instances, in order to avoid the qubit specification). Concerning the alternative of doing more or less, the answer is less - and then move to further PRs.
Sorry for the late reply. I actually enjoyed it :P |
Thanks @alecandido , let me do it like how @BrunoLiegiBastonLiegi suggested, to incorporate the gate parameters: Glad you enjoyed the weekends! ;) |
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.
In general I still struggle to find a strong motivation for this, as it would make more sense to me to decompose your parametrized (fixed) gate in actual non parametrized ones, which will compose your gate set for GST then.
In any case, even though the (gate, params)
approach is probably not the cleanest, it should be fine for the moment.
@@ -2242,7 +2242,8 @@ Let's first define the set of gates we want to estimate: | |||
|
|||
from qibo import gates | |||
|
|||
gate_set = {gates.X(0), gates.H(0), gates.CZ(0, 1)} | |||
target_gates = [gates.RX(0, np.pi/3), gates.Z(0), gates.PRX(0, np.pi/2, np.pi/3), gates.GPI(0, np.pi/7), gates.CNOT(0,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.
target_gates = [gates.RX(0, np.pi/3), gates.Z(0), gates.PRX(0, np.pi/2, np.pi/3), gates.GPI(0, np.pi/7), gates.CNOT(0,1)] |
not sure whether target_gates
is used somewhere else, but it's not needed to run GST in principle
gate_set (tuple or set or list): set of :class:`qibo.gates.Gate` and parameters to run | ||
GST on. | ||
E.g. target_gates = [gates.RX(0, np.pi/3), gates.Z(0), gates.PRX(0, np.pi/2, np.pi/3), | ||
gates.GPI(0, np.pi/7), gates.CNOT(0,1)] | ||
gate_set = [(g.__class__, [g.parameters[i] for i in range(len(g.parameters))]) | ||
if g.parameters else (g.__class__, []) for g in target_gates] |
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.
gate_set (tuple or set or list): set of :class:`qibo.gates.Gate` and parameters to run | |
GST on. | |
E.g. target_gates = [gates.RX(0, np.pi/3), gates.Z(0), gates.PRX(0, np.pi/2, np.pi/3), | |
gates.GPI(0, np.pi/7), gates.CNOT(0,1)] | |
gate_set = [(g.__class__, [g.parameters[i] for i in range(len(g.parameters))]) | |
if g.parameters else (g.__class__, []) for g in target_gates] | |
gate_set (tuple or set or list): set of :class:`qibo.gates.Gate` to run | |
GST on. In case a gate is parametrized, it has to be passed as a tuple ``(gate, params)`` | |
E.g. ``gate_set = [(gates.RX, np.pi/3), gates.Z, (gates.PRX, [np.pi/2, np.pi/3])]`` |
init_args = signature(gate[0]).parameters | ||
params = gate[1] | ||
|
||
angle_names = [name for name in init_args if name in {"theta", "phi"}] | ||
|
||
angle_values = {} | ||
for name, value in zip(angle_names, params): # Zip ensures correct order | ||
angle_values[name] = value |
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.
init_args = signature(gate[0]).parameters | |
params = gate[1] | |
angle_names = [name for name in init_args if name in {"theta", "phi"}] | |
angle_values = {} | |
for name, value in zip(angle_names, params): # Zip ensures correct order | |
angle_values[name] = value | |
params = {} | |
if isinstance(gate, tuple): | |
params.update({ | |
angle: param | |
for angle, param in zip(["theta", "phi"][:len(gate[1])], gate[1]) | |
}) | |
gate = gate[0] |
if we clearly document how you are supposed to pass the parameters, it is safe to assume that each input tuple is identifying a parametrized gate
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.
Thanks Andrea! I realise that if we do
for angle, param in zip(["theta", "phi"][:len(gate[1])], gate[1])
it won't work for gates like gates.GPI(0, np.pi)
because GPI
gate doesn't have theta
parameter. I run into this error message:
---> 46 gate = gate(*range(nqubits), **params)
TypeError: GPI.__init__() got an unexpected keyword argument 'theta'
Hence my train of thought is that if we search for theta
or phi
or both, then we just extract that angle_name out and assign the parameter directly (Lines 295 to 302)
angle_names = [name for name in init_args if name in {"theta", "phi"}]
angle_values = {}
for name, value in zip(angle_names, params): # Zip ensures correct order
angle_values[name] = value
raise_error( | ||
RuntimeError, | ||
f"Gate {gate} is not supported for `GST`, only 1- and 2-qubits gates are supported.", | ||
) | ||
gate = gate[0](*range(nqubits), **angle_values) |
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.
with the change above
gate = gate[0](*range(nqubits), **angle_values) | |
gate = gate(*range(nqubits), **params) |
gate_set = [ | ||
( | ||
(g.__class__, [g.parameters[i] for i in range(len(g.parameters))]) | ||
if g.parameters | ||
else (g.__class__, []) | ||
) | ||
for g in target_gates | ||
] |
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.
gate_set = [ | |
( | |
(g.__class__, [g.parameters[i] for i in range(len(g.parameters))]) | |
if g.parameters | |
else (g.__class__, []) | |
) | |
for g in target_gates | |
] | |
gate_set = [ | |
( | |
(g.__class__, g.parameters) | |
if g.parameters | |
else g.__class__ | |
) | |
for g in target_gates | |
] |
isn't this working?
gate_set = [ | ||
( | ||
(g.__class__, [g.parameters[i] for i in range(len(g.parameters))]) | ||
if g.parameters | ||
else (g.__class__, []) | ||
) | ||
for g in target_gates | ||
] |
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 as above
Thanks @BrunoLiegiBastonLiegi for the comments.
True, I do see your point of view. However, if I wanted to quickly do GST for parameterised gates without the decomposition, then we'll probably need this small functionality. Correct me if I'm wrong: I have the impression that since Anyway, back to the PR, when I was constructing the From an end-user perspective, the most convenient way is to dump the entire gate and its parameters, including qubit specifications, into the |
Yeah, fair enough, it's surely faster to just allow for passing the parametrized gate here, rather than manually decomposing
Yes and now, because if I remember correctly our
I understand your wish of standardizing the inputs, but personally I am not a fan of making the user specify a non parametrized gate as (gate, []) everytime. Say for instance, that I want to run GST on GST(gate_set=[(X, []), (Y, []), (Z, [])]) not the best in my opinion. We could actually leverage the asimettry, instead, and infer from the presence of a tuple the presence of a parametrized gate. |
Checklist: