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

Apply phases using frame_rotation in QM #1116

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft

Apply phases using frame_rotation in QM #1116

wants to merge 3 commits into from

Conversation

stavros11
Copy link
Member

In 0.2 pulse phases was applied in the uploaded waveform samples. Here I am switching this back to the approach used in 0.1, based on frame_rotation, because this is how I managed to make the CHSH routine work.

I am not sure which solution of the two is better. The one implemented here may suffer from the second note in https://docs.quantum-machines.co/latest/docs/API_references/qua/dsl_main/?h=frame_#qm.qua._dsl.frame_rotation_2pi, so I am leaving it as draft for now.

@stavros11 stavros11 changed the base branch from main to interpolated December 7, 2024 18:54
Copy link

codecov bot commented Dec 7, 2024

Codecov Report

Attention: Patch coverage is 0% with 6 lines in your changes missing coverage. Please review.

Project coverage is 50.76%. Comparing base (577b91f) to head (f6caa22).

Files with missing lines Patch % Lines
...bolab/_core/instruments/qm/program/instructions.py 0.00% 4 Missing ⚠️
src/qibolab/_core/instruments/qm/config/pulses.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1116      +/-   ##
==========================================
+ Coverage   50.74%   50.76%   +0.01%     
==========================================
  Files          63       63              
  Lines        3019     3018       -1     
==========================================
  Hits         1532     1532              
+ Misses       1487     1486       -1     
Flag Coverage Δ
unittests 50.76% <0.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@alecandido alecandido left a comment

Choose a reason for hiding this comment

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

I'd like to understand why the results are different in the two cases (before and after this PR).

However, the current approach (after this PR) may not be that bad, since it would deduplicate waveforms that are different just because of the relative_phase, saving memory and networking.

Moreover, that second note is talking about an error of ~1e-5, which may be relevant at some point, but individually would be compensated by the calibration, and the accumulated one could be counteracted exactly in the way they are describing (which is adding some complexity to the program generation, but if it's efficient in other respects we could accept it - and with our current experiments we should not be that sensitive...).

@@ -21,7 +21,7 @@ def _delay(pulse: Delay, element: str, parameters: Parameters):
duration = max(int(pulse.duration) // 4 + 1, 4)
qua.wait(duration, element)
elif parameters.interpolated:
duration = parameters.duration + 1
Copy link
Member

Choose a reason for hiding this comment

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

What was the +1 for?

@@ -73,7 +73,7 @@ def _play(
_play_single_waveform(op, element, parameters, acquisition)

if parameters.phase is not None:
qua.reset_frame(element)
Copy link
Member

Choose a reason for hiding this comment

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

If the phase was already accounted for in the waveform, why were you resetting the frame?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe, if a frame reset was needed, it could have always been needed, even when the parameters.phase is None. Since None should be a relative zero, I guess (in Pulse.relative_phase we actually use 0.0 as default, and never None - not sure where the None is generated).
If instead is not needed, because you're somehow accumulating the relative phases, then it may be never needed.

I'm trying to understand the whole flow, so, I'm still at the level of collecting hints to reconstruct the full picture...

Copy link
Member Author

Choose a reason for hiding this comment

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

If the phase was already accounted for in the waveform, why were you resetting the frame?

Because we were still using frame_rotation_2pi when sweeping phases, and in that case we may need to reset_frame. For example doing the following with sweeper:

for theta in sweeper.values:
    RX(0, theta=theta)
    RX(0, theta=0)

there will be a frame rotation to apply the first gate and we need to reset the frame before applying the second.

Maybe, if a frame reset was needed, it could have always been needed, even when the parameters.phase is None.

That's true, I think you can add reset_frame in the program even without doing any rotation and it should have no effect. However, I think reset_frame, if not used properly, will screw up VirtualZ "pulses", that's why I completely removed it from this PR. For example in

RZ(0, theta=np.pi / 4)
RX(0, theta=np.pi / 2)
RX(0, theta=0)

the first np.pi/4 is a virtual-Z and should be maintained throughout the circuit, so if the second RX causes a reset_frame in the driver, it won't be correct.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's exactly what I had in mind. The only way I see to make reset_frame work should be the one described in #1116 (comment), i.e.

  • preserving a counter of the accumulated virtual phase in parallel
  • restoring the virtual phase after every reset_frame

However, for the individual RX it seems simpler to just rotate forward before the gate, and backward right after the play() instruction (which I expect is what you're now doing). This would accumulate a negligible error on the individual instruction, and we only have to pay attention for long sequences, applying a reset strategy.

@stavros11
Copy link
Member Author

stavros11 commented Dec 8, 2024

I'd like to understand why the results are different in the two cases (before and after this PR).

I haven't understood that myself yet, that's why it's draft. I just switched to the original approach as I was debugging the CHSH and since I managed to make it work I opened this PR mainly as support of qiboteam/qibocal#1049. The idea is that the qibocal PR should be good as it is and we can decide internally in the driver how to do the phases.

However, the current approach (after this PR) may not be that bad, since it would deduplicate waveforms that are different just because of the relative_phase, saving memory and networking.

Indeed, however this may not happen in practice right now, because the deduplication is handled by using hash(pulse) when uploading and if two pulses have different phases, they'll have different hash. But I need to investigate this more, as I am not sure.

Moreover, that second note is talking about an error of ~1e-5, which may be relevant at some point, but individually would be compensated by the calibration, and the accumulated one could be counteracted exactly in the way they are describing

I am not sure if this can be solved in calibration. For example, take a single qubit and apply a sequence of random unitaries/U3s. In single qubit calibration we never deal with phases, we only calibrate an RX (or RX90) with zero phase. Then the sequence of random unitaries is compiled to RX90s with random phases, so in principle if there are many gates, this error should affect if we never use reset_frame (if I understand it correctly).

@alecandido
Copy link
Member

Indeed, however this may not happen in practice right now, because the deduplication is handled by using hash(pulse) when uploading and if two pulses have different phases, they'll have different hash. But I need to investigate this more, as I am not sure.

I'm also considering hashing the generated samples for deduplication, instead of hashing the parameters.
However, it may be more convenient for to skip samples generation, if possible, and thus applying the current strategy of hashing the pulse, but with the correct information. We may consider to have a .hash() (or a better-named equivalent) that ignores by default the relative_phase, or it does as an option.

I am not sure if this can be solved in calibration. For example, take a single qubit and apply a sequence of random unitaries/U3s. In single qubit calibration we never deal with phases, we only calibrate an RX (or RX90) with zero phase. Then the sequence of random unitaries is compiled to RX90s with random phases, so in principle if there are many gates, this error should affect if we never use reset_frame (if I understand it correctly).

Ok, my point was that we don't need this to make the individual gate optimal. Then, I agree we should still place reset_frame every now and then, but we can do something like that as a final post-processing, by somehow keeping the information about the virtual-zs somewhere else than in the QUA commands, as a full double precision float, and issuing a reset_frame followed by a single frame_rotation of the accumulated virtual phase, after a certain number of frame_rotations.

Base automatically changed from interpolated to main December 16, 2024 13:09
@stavros11
Copy link
Member Author

I haven't understood that myself yet, that's why it's draft. I just switched to the original approach as I was debugging the CHSH and since I managed to make it work I opened this PR mainly as support of qiboteam/qibocal#1049. The idea is that the qibocal PR should be good as it is and we can decide internally in the driver how to do the phases.

I was testing this PR with the QM simulator and I do not see any difference in the final output compared to the current main.

phases

Here "QM simulation" is the raw waveform output of the simulator while for "qibolab" I am using the

function. For the two to agree, the start time of the "qibolab" waveforms needs to be adjusted because in QM there is always some initial overhead, so the simulated phase is calculated as:

2 * np.pi * freq * (pulse.start + start_offset) + pulse.relative_phase

where freq is the IF frequency, pulse.start the time the pulse starts to play (calculated from what comes before in the same channel, since this is no longer a property of the pulse in 0.2) and start_offset the QM overhead.

I have tried this experiment for R(theta=np.pi / 2, phi=0) and R(theta=np.pi / 2, phi=np.pi / 2) pulses and I get agreement in both cases. I also get agreement with both this branch and main, the only difference being that start_offset is slightly higher for this branch, presumably due to the overhead added by the frame_rotation_2pi instructions.

The open questions are still:

  1. Difference in amplitude in plots above, despite the phase agreeing,
  2. Why CHSH worked better with this branch compared to main: for this I will try to investigate the sequences used in that experiment.

@alecandido
Copy link
Member

  1. Difference in amplitude in plots above, despite the phase agreeing,

@stavros11 I don't know what is happening with the amplitude, which is for sure interesting to investigate (I assume you're comparing like with like, and that the plot is coming from the output of the QM simulator in both cases, at least at some stage).
modulate should not change the maximum for a square envelope. The example above is not a square envelope, but the envelopes and carriers seem to be synced, since the two shapes seems just to differ only by a constant factor...
Maybe you could even just start by confirming it is only a scaling, by extracting the ratio.

  1. Why CHSH worked better with this branch compared to main: for this I will try to investigate the sequences used in that experiment.

For this it may be not sufficient to test with a single pulse, since you can feel the difference between a correct and wrong usage of reset_frame() (as discussed above) only when multiple gates are present (since you need to keep track of virtual phases).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants