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

Initial implementation for Keysight QCS #944

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open

Conversation

sorewachigauyo
Copy link
Collaborator

@sorewachigauyo sorewachigauyo commented Jul 31, 2024

Trial implementation of the core functions. Testing is rough on my side since I do not currently have access to the instrument library on my development machine, so I have to test the code on the embedded controller.

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.
  • Prepare simple runcard.
  • Test pulse execution.
  • Test sweeper execution.
  • Improve documentation

Copy link

codecov bot commented Jul 31, 2024

Codecov Report

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

Project coverage is 47.37%. Comparing base (d16f03f) to head (3a29ffa).
Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
src/qibolab/_core/instruments/keysight/qcs.py 0.00% 62 Missing ⚠️
src/qibolab/_core/instruments/keysight/pulse.py 0.00% 55 Missing ⚠️
src/qibolab/_core/instruments/keysight/sweep.py 0.00% 35 Missing ⚠️
src/qibolab/_core/instruments/keysight/results.py 0.00% 20 Missing ⚠️
...b/_core/instruments/keysight/components/configs.py 0.00% 8 Missing ⚠️
src/qibolab/_core/instruments/keysight/__init__.py 0.00% 6 Missing ⚠️
.../_core/instruments/keysight/components/__init__.py 0.00% 4 Missing ⚠️
src/qibolab/instruments/keysight_qcs.py 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #944      +/-   ##
==========================================
- Coverage   50.55%   47.37%   -3.19%     
==========================================
  Files          63       71       +8     
  Lines        2886     3080     +194     
==========================================
  Hits         1459     1459              
- Misses       1427     1621     +194     
Flag Coverage Δ
unittests 47.37% <0.00%> (-3.19%) ⬇️

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.

@alecandido
Copy link
Member

alecandido commented Aug 5, 2024

@sorewachigauyo I previously rebased https://github.com/qiboteam/qibolab/tree/0.2 on current https://github.com/qiboteam/qibolab/tree/main, thus I also rebased your PR consequently.

The Rust workflow is failing for unknown reasons (nothing changed on our side, so the reason should be in some dependency, but it seems non-reproducible locally).
I already blocked the workflow run on push in #885. As soon as we'll merge, I will update this branch as well. For the time being, just ignore it, please.

P.S.: same for QuTiP, which is making the other workflows to fail as well (but we should merge the PR very soon)

Base automatically changed from 0.2 to main September 20, 2024 12:05
@sorewachigauyo
Copy link
Collaborator Author

sorewachigauyo commented Oct 1, 2024

Initial testing with readout spectroscopy using runcard https://github.com/qiboteam/qibolab_platforms_nqch/tree/qibolab-0.2/iqm5q

import numpy as np
import matplotlib.pyplot as plt
from qibolab import (
    AcquisitionType,
    AveragingMode,
    Parameter,
    Sweeper,
    create_platform,
)

platform = create_platform("iqm5q")
platform.connect()

qubit = platform.qubits[4]
natives = platform.natives.single_qubit[4]
sequence = natives.MZ.create_sequence()

f0 = platform.config(qubit.probe).frequency  # center frequency
sweeper = Sweeper(
    parameter=Parameter.frequency,
    range=(f0 - 20e6, f0 + 100e6, 1e6),
    channels=[qubit.probe],
)

results = platform.execute([sequence],
                           [[sweeper]],
                           nshots=100,
                           relaxation_time=100e3,
                           averaging_mode=AveragingMode.SEQUENTIAL,
                           acquisition_type=AcquisitionType.INTEGRATION)
_, acq = next(iter(sequence.acquisitions))

# plot the results
signal = results[acq.id]
amplitudes = np.abs(signal[..., 0] + 1j * signal[..., 1])
frequencies = sweeper.values

plt.title("Resonator Spectroscopy")
plt.xlabel("Frequencies [GHz]")
plt.ylabel("Amplitudes [a.u.]")
plt.plot(frequencies / 1e9, amplitudes * 1e6)
plt.savefig("test.png")

image

@sorewachigauyo sorewachigauyo marked this pull request as ready for review October 16, 2024 15:32
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.

Sorry for the lengthy review 😅

src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
Comment on lines 298 to 302
for channel_id, input_op in acquisitions:
channel = self.virtual_channel_map[channel_id]
if channel not in acquisition_map:
acquisition_map[channel] = []
acquisition_map[channel].append(input_op)
Copy link
Member

Choose a reason for hiding this comment

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

In principle, we could even lift the construction of such a mapping to the PulseSequence itself (do you think it may be useful elsewhere? @stavros11)

Of course, it would be a ChannelId -> [InputOps] mapping, that you could supplement with your ChannelId -> qcs.Channels mapping to obtain the desired result.

src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/qcs.py Outdated Show resolved Hide resolved
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.

Thanks @sorewachigauyo, now it seems extremely cleaner.

I'm sorry that a bunch of functions right now require a lot of arguments. We could try to find a better way to split some parts, partially evaluate tasks, or better package the arguments.
But this is really the kind of refactor that can be postponed (almost forever...).

Most of the comments are on the sweepers, which is frequently the most critical part. All the others seem pretty nice (though I may spend a bit more time later on proposing a small refactor about the array manipulations in the result - but nothing that serious, it's already pretty succinct).

src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
Comment on lines 50 to 59
for idx2, sweeper in enumerate(parallel_sweeper):
qcs_variable = qcs.Scalar(
name=f"V{idx}_{idx2}", value=sweeper.values[0], dtype=float
)

if sweeper.parameter is Parameter.frequency:
sweeper_channel_map.update(
{channel_id: qcs_variable for channel_id in sweeper.channels}
)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry @sorewachigauyo, this is not about this PR, but rather Qibolab's overall design. I'm writing this here because it is an optimal example to discuss, and you're invited to provide your opinion as well. I may lift this immediately as a separate issue, I'm just undecided.

With @stavros11, we wondered at some point whether to strip away the option of having multiple channels and pulses swept by a single sweeper (not a parallel one). In fact, this could be now replaced by an equivalent set of parallel sweepers.
We didn't do that because of the specific implementation of parallel sweepers in QM, which requires the usage of a for_each_ context manager, which had some disadvantages with respect to a plain for_.

It now appears to me that quite redundant, since here you have explicitly three nested iterations:

  • idx about the sweepers' group
  • idx2 within the parallel sweepers
  • and sweeper.channels (or sweeper.pulses down below - exact analogue)

These could have been decreased by one level by unraveling the .channels in the parallel group.

However, it is true that, thanks to the presence of multiple .channels, you are able to use a single qcs_variable, and this could be an advantage, and a reason to keep the current behavior (which we would keep anyhow, but we may consider deprecating at some point - and finally removing in 0.3).

What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't have a strong opinion of the subject as I can go either way and have been hacking the driver implementation to fit the ongoing design.

Though, since Qibolab 0.3 is targeting program-level control, it may be possible to pass that program directly to a FPGA without much conversion/parsing layers in the driver. Also, unraveling the channel/pulse arrays could help in maybe a Qibolab FPGA implementation by reducing the complexity required to implement sweepers.

Copy link
Member

@alecandido alecandido Oct 29, 2024

Choose a reason for hiding this comment

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

Just to clarify: Qibolab itself is not planning to become an FPGA design, but the idea is to have a high-level language to be compiled to the individual ones. But at the same time, I'd like to keep it as close as possible to the existing languages to minimize the conversion (i.e. compilation) efforts.

If then you're willing to contribute to the language design, and you may want to use it as the basis to design an FPGA-based processor (at which point, it would require essentially no conversion), you're definitely more than welcome :P

src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/sweep.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/results.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/results.py Outdated Show resolved Hide resolved
@alecandido
Copy link
Member

Personally, I prefer stricter separation, like in C++ namespaces and anonymous namespaces.

I like that as well, e.g. the strictly modular structure in Rust, in which you need to declare any visibility beyond the current namespace.

However, that's not Python style, and even __all__ is not saving you from there, since you can always directly import (__all__ has only an effect on the from mymod import * statements, and nothing else).
That's why I added the _core, and separately re-exporting top-level

The Python philosophy is to leave everything public, since

we're all consenting adults here

(not able to find a proper reference, sorry, but it is a GvR quote)

The only way to go beyond that are naming conventions, and I'd suggest avoiding double leading underscores, unless you're extremely confident (but it's unfamiliar and a bit confusing...).

So, whatever is inside qibolab._core is already internal and not exposed to the end user.
If you want identifiers to be scoped within a module, prepend them with a leading underscore. If you want an entire module to be scoped within a subpackage, prepend its name with a leading underscore.

@sorewachigauyo
Copy link
Collaborator Author

Tested some 2D scans, so sweepers should be good to go
image

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.

Another tiny proposal for the results handling, but other than that it looks good to me.

I'll wait for @stavros11 review, but it may be good to go :)

Thanks @sorewachigauyo for your effort!

src/qibolab/_core/instruments/keysight/results.py Outdated Show resolved Hide resolved
Copy link
Member

@stavros11 stavros11 left a comment

Choose a reason for hiding this comment

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

Thanks @sorewachigauyo for this. I left some comments below, most are not very important and mainly cosmetics, I would approve immediately if it weren't for the last two comments (mainly the last one), which I consider the most important. The main issue with these is that they are interface facing, and it would be good to address here in order to avoid breaking later.

In any case, as I mentioned also in the comments, I am have never used these instruments in practice, so if you prefer to keep the existing approach it is also fine with me. I am more concerned with avoid breaking in the future, than the exact approach used.

Also, I have not read all resolved comments from the previous reviews as I was not following last week, so I am sorry if something was already discussed before.

Comment on lines +20 to +21
elif isinstance(shape, (Gaussian, Drag)):
return qcs.GaussianEnvelope(shape.rel_sigma)
Copy link
Member

Choose a reason for hiding this comment

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

What is the motivation to include Drag here? It seems that this always returns a Gaussian, completely ignoring the DRAG beta parameter. If DRAG is not available then doesn't it make more sense to just raise an error, like for all other shapes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So the DRAG is implemented here at https://github.com/qiboteam/qibolab/pull/944/files#diff-e3c18ab9440f0e68561a15f5e9497097a405ef090fd2612f4f3a362c71c83502R126-R127
and I need the gaussian as the base envelope for the summation of the base and derivative envelope.
(https://quantumbenchmark.com/docs/qcs/api/channels.html#rj-drag)

I could implement the DRAG based on Qibolab's implementation, but I would not like to create a static envelope especially when duration sweepers can change the envelope size.

Copy link
Member

Choose a reason for hiding this comment

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

@sorewachigauyo I understand your concern, and now that you explained I'm even fine with your solution.

Not to enforce the change request, but as a general note, we suspect that most of the APIs are actually implementing duration sweepers in the same inefficient way you would do using Qibolab's envelopes (i.e. evaluating on software all the required arrays and uploading them).
This is not happening for QM, but there is a dedicated feature linked to that, and we're now exposing it as a separate sweeper (where the envelope is not uploaded multiple times, but interpolated on the device).

duration_interpolated = (auto(), _PULSE)

If you suspect Keysight to be able to interpolate as well, I'd recommend you to verify it (as much as you can), and expose it through that sweeper, instead of the bare duration one (there will be a fallback at some level, but they may have different caveats, related to timing and approximation).

src/qibolab/_core/instruments/keysight/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/results.py Outdated Show resolved Hide resolved
Comment on lines +64 to +66
# Duration is not supported with hardware sweeping
if sweeper.parameter is Parameter.duration:
hardware_sweeping = False
Copy link
Member

Choose a reason for hiding this comment

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

I'd guess that it should be possible to support this by uploading multiple waveforms, which is the default solution we are using for QM and I believe we will also use in Qblox and others.

It is fine to not do this in this PR. If you are interested in doing it some time in the future, you can open an issue for it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am told duration sweeping will eventually be supported, so I think this is fine for now to make the backend layer handle it in software (which I assume is exactly the implementation you are describing right now, just not explicit).

Copy link
Member

Choose a reason for hiding this comment

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

Are you sure?
The implementation @stavros11 is talking about is inefficient, since it requires multiple arrays uploads, but not as inefficient as what we usually call "a software sweeper", since the multiple arrays will be uploaded on the device, and they will be executed without any networking, nor any memory exchange on the device itself. While a software sweeper (for us) is just a software loop, repeating many separate experiments and downloading the results at each iteration

Comment on lines +48 to +49
# Currently nested hardware sweeping is not supported
hardware_sweeping = len(hardware_sweepers + software_sweepers) == 0
Copy link
Member

Choose a reason for hiding this comment

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

This can also be an issue, as it may be useful for various routines, in terms of performance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am told its on the roadmap.

src/qibolab/_core/instruments/keysight/qcs.py Outdated Show resolved Hide resolved
src/qibolab/_core/instruments/keysight/qcs.py Outdated Show resolved Hide resolved
# Map of QCS virtual channels to QCS physical channels
qcs_channel_map: qcs.ChannelMapper
# Map of Qibolab channel IDs to QCS virtual channels
virtual_channel_map: dict[ChannelId, qcs.Channels]
Copy link
Member

Choose a reason for hiding this comment

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

Looking at the example platform here(*): https://github.com/qiboteam/qibolab_platforms_nqch/blob/qibolab-0.2/iqm5q/platform.py, I have the impression that this dictionary can be eliminated by using qibolab's channel.path, which I believe is currently not used. I am not sure if types can be worked out though, because our channel.path should be a str. If qcs.ChannelMapper provides string keys, you may be able to retain the information using just qcs_channel_map and channel.path, otherwise it may be tricky.

In any case, I have no experience with these particular instruments, so if you find it easier to use it this way, it is fine with me.

(*) Unrelated to the above, it would be useful to also have an example (not necessarily real/working) platform here, but we can postpone this for later, when we have better testing or even better documentation for how to use each driver.

Copy link
Collaborator Author

@sorewachigauyo sorewachigauyo Nov 1, 2024

Choose a reason for hiding this comment

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

I prefer to have a direct mapping because other approaches would involve some type casting or eval to get the necessary parameters from the channel object and start to warp the model.

The qcs.ChannelMapper does not refer to channels by string keys but offers an array view to the virtual channel bundles.

src/qibolab/_core/instruments/keysight/qcs.py Outdated Show resolved Hide resolved
Copy link
Member

@stavros11 stavros11 left a 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 and responses @sorewachigauyo. Other than the unresolved discussions above, mainly about the duration sweep and generally software vs hardware sweeps, which I guess that we all agree we can postpone for later, this looks good to me for merging.

It would be good if you can test on the actual device before merging this, as we cannot do that on our side. My main concern is the last change adding the QcsAcquisitionConfig, which looks good in terms for code, however I saw that the platform in the other repo has not been updated to use it and we should probably verify if it works.

@alecandido
Copy link
Member

Thanks @stavros11 for the review!

It would be good if you can test on the actual device before merging this, as we cannot do that on our side. My main concern is the last change adding the QcsAcquisitionConfig, which looks good in terms for code, however I saw that the platform in the other repo has not been updated to use it and we should probably verify if it works.

@sorewachigauyo, I guess it would be ideal if you can just repeat a whatever scan, or even publish a report with a few routines from qiboteam/qibocal#990

Other than that, I would aim to merge asap, to get further traction for 0.2.

@sorewachigauyo
Copy link
Collaborator Author

It would be good if you can test on the actual device before merging this, as we cannot do that on our side. My main concern is the last change adding the QcsAcquisitionConfig, which looks good in terms for code, however I saw that the platform in the other repo has not been updated to use it and we should probably verify if it works.

I have tested this on my machine.

@sorewachigauyo, I guess it would be ideal if you can just repeat a whatever scan, or even publish a report with a few routines from qiboteam/qibocal#990

Unfortunately, it would be next week earliest as we are currently installing the full control electronics package and other maintenance works on the fridge.

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.

3 participants