-
Notifications
You must be signed in to change notification settings - Fork 6
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
RX90 calibration implementation #1044
base: 0.2
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 @@
## 0.2 #1044 +/- ##
==========================================
+ Coverage 97.04% 97.05% +0.01%
==========================================
Files 98 98
Lines 7893 7958 +65
==========================================
+ Hits 7660 7724 +64
- Misses 233 234 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
I attach here some reports where I tested the code:
|
Thanks @ElStabilini, the results seem to make sense since for both amplitude and duration there is a factor 2. I think that rather than changing the protocol completly we could consider adding a flag to specify whether we want to calibrate the RX or RX90 pulse. We should also check the corresponding update function in the protocols to be consistent. |
@andrea-pasquale I see this only now, I should have done this by adding the pihalf_pulse (soon rx90) option, correct? |
for more information, see https://pre-commit.ci
Merge branch 'pi_half' of github.com:qiboteam/qibocal into pi_half
We changed the default runcard option to False (by default will calibrate pi pulse)
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 for the updates @ElStabilini.
At this point I would rename everywhere pihalf_pulse
to rx90
.
Everything else looks good to me.
src/qibocal/protocols/rabi/ef.py
Outdated
update.drive_12_amplitude( | ||
results.amplitude[target], results.pihalf_pulse, platform, target |
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.
Here we should force the user to calibrate just with RX, given that we have no RX1290 pulse.
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 should have fixed this too, in this commit 349e312.
If the user sets rx90: True it will raise an error
doc/source/protocols/rabi/rabi.rst
Outdated
relaxation_time: 50000 | ||
RX90: 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 line needs to be fixed.
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 should have fixed it and also added the figure
Merge branch 'pi_half' of github.com:qiboteam/qibocal into pi_half
Just to avoid too many things at the same time I suggest to focus first on rabi experiments. In another PR we can propagate these changes to the other protocols. |
Someone noticed a certain streak of pretty close commit messages (once in a while, I was not the first one - I won't take this merit :P). Commit messages are extremely useful for debugging, to navigate the commits in case something goes wrong. So, ideally, they should describe in the most succinct and expressive way the commits' changes. Of course, sometimes you need to push to debug the workflow. But this is better done with a local reproduction as much as possible. And, if really needed, possibly even squashing some failed attempts afterward, to leave a better history for later :) |
for more information, see https://pre-commit.ci
Merge branch 'pi_half' of github.com:qiboteam/qibocal into pi_half
Merge branch 'pi_half' of github.com:qiboteam/qibocal into pi_half
if data.rx90: | ||
pulse_name = "Pi-half pulse" | ||
else: | ||
pulse_name = "Pi pulse" |
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 these cases, it is especially convenient to use the ternary if
operator (exact same of the ... ? ... : ...
construct in C)
if data.rx90: | |
pulse_name = "Pi-half pulse" | |
else: | |
pulse_name = "Pi pulse" | |
pulse_name = "Pi-half pulse" if data.rx90 else "Pi pulse" |
@ElStabilini just to provide you some context about #1044 (comment), the clear advantage of the proposed syntax is neither saving characters nor lines. The most prominent difference between the equivalent expressions is that one is imperative, the other functional. The functional approach makes the computation flow clearer, since it is somehow more rigid (you're not even thinking about operations, and possibly memory, but just values manipulation), and makes the analysis simpler and more effective. In languages which are actually functional, this may empower the compiler to act more aggressively. In Python, it makes the linters more effective[*], leading to fewer errors and more helpful messages :) [*]: since it optimizes the simplicity of the code, it makes it also more human-readable in general |
It definitely makes sense, thank you! |
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.
Generally good, and pretty much ready to go.
The main suggestions are about the docs. The other ones are very minor refactor requests, which could even be neglected.
- id: flipping | ||
operation: flipping | ||
parameters: | ||
delta_amplitude: 0.05 | ||
nflips_max: 30 | ||
nflips_step: 1 | ||
rx90: 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.
Try to indent with spaces, not tabs (you can set your editor to do that)
pulse_length: 40 | ||
nshots: 1024 | ||
relaxation_time: 50000 | ||
rx90: 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.
Another tab here ^^
In all the previous examples we run Rabi experiments for calibrating the amplitude (duration) of the drive pulse | ||
to excite the qubit from the ground state up to state :math:`\ket{1}`. | ||
All these example runcards can be modified to calibrate the amplitude (duration) of the drive pulse | ||
to excite the qubit from the ground state up to state :math:`\frac{\ket{0}-i\ket{1}}{\sqrt{2}}` by simply setting the `rx90` parameter to `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.
Instead of mentioning the experiment in the general section, and describe it only in the example, you may move this description up, and just cite here you're performing the pi/2 described above.
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.
Moreover, a description of the sequence (i.e. "same, but replace each pi with two pi/2") and a brief motivation may be appreciated. Then, you may cite this section in the flipping, instead of repeating it.
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.
Even a reference to the literature you consulted may be appreciated as well :)
if rx90: | ||
sequence |= natives.RX90() | ||
|
||
qd_channel, rx_pulse = natives.RX()[0] | ||
for _ in range(flips): | ||
qd_channel, qd_pulse = natives.RX90()[0] | ||
|
||
rx_detuned = update.replace( | ||
rx_pulse, amplitude=rx_pulse.amplitude + delta_amplitude | ||
) | ||
sequence.append((qd_channel, rx_detuned)) | ||
sequence.append((qd_channel, rx_detuned)) | ||
qd_detuned = update.replace( | ||
qd_pulse, delta_amplitude=qd_pulse.amplitude + delta_amplitude | ||
) | ||
|
||
sequence.append((qd_channel, qd_detuned)) | ||
sequence.append((qd_channel, qd_detuned)) | ||
sequence.append((qd_channel, qd_detuned)) | ||
sequence.append((qd_channel, qd_detuned)) | ||
|
||
sequence |= natives.MZ() | ||
|
||
else: | ||
sequence |= natives.R(theta=np.pi / 2) | ||
|
||
sequence |= natives.MZ() | ||
for _ in range(flips): | ||
qd_channel, qd_pulse = natives.RX()[0] | ||
|
||
qd_detuned = update.replace( | ||
qd_pulse, amplitude=qd_pulse.amplitude + delta_amplitude | ||
) | ||
sequence.append((qd_channel, qd_detuned)) | ||
sequence.append((qd_channel, qd_detuned)) | ||
|
||
sequence |= natives.MZ() |
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.
A good chunk of code is duplicated between the two branches. You may try to scope further the if
within the sequence definition, to minimize duplication.
(In general, there is no exact solution about how to make code "minimal" or "simplest" - but here there is some room for improvements ^^)
@@ -222,10 +254,14 @@ def _fit(data: FlippingData) -> FlippingResults: | |||
perr = np.sqrt(np.diag(perr)).tolist() | |||
popt = popt.tolist() | |||
correction = popt[2] / 2 | |||
|
|||
if data.rx90: | |||
correction = correction / 2 |
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 some other contexts, there is a difference between redefinition and in-place mutation, and the distinction may be very relevant.
However, in this case it's just about a single scalar, so it makes no difference (to the point that a compiler may optimize the operation anyhow). So, better to use the idiomatic shortcut ;)
correction = correction / 2 | |
correction /= 2 |
qubit: ( | ||
platform.natives.single_qubit[qubit].RX90[0][1].amplitude | ||
if params.rx90 | ||
else platform.natives.single_qubit[qubit].RX[0][1].amplitude | ||
) |
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.
Ok, this is just for fun, and at the level of code golf. But just for you to know that such an option exists
qubit: ( | |
platform.natives.single_qubit[qubit].RX90[0][1].amplitude | |
if params.rx90 | |
else platform.natives.single_qubit[qubit].RX[0][1].amplitude | |
) | |
qubit: getattr(platform.natives.single_qubit[qubit], "RX90" if params.rx90 else "RX")[0][1].amplitude |
if rx90: | ||
qd_channel, qd_pulse = natives.RX90()[0] | ||
else: | ||
qd_channel, qd_pulse = natives.RX()[0] |
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 case you like code golf as well, you may try to deduplicate even this one (but it's absolutely not needed)
extract_rabi(RabiAmplitudeEFData) | ||
|
||
|
||
def test_extract_rabi(): |
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 redefining the former, thus shadowing it.
You have to use two different names in order to be able to access both, and thus also to run both in Pytest.
While a redefinition is not an error, it is unlikely that you want to define a function that already exists. Consequently, your editor may have warned you about this (or better, Ruff should have done it).
Checklist:
master
main
main