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 27 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 181 lines in your changes missing coverage. Please review.

Project coverage is 48.81%. Comparing base (6087ba8) to head (5e490c1).
Report is 20 commits behind head on main.

Files with missing lines Patch % Lines
src/qibolab/_core/instruments/keysight/qcs.py 0.00% 63 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 ⚠️
src/qibolab/_core/instruments/keysight/__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   51.95%   48.81%   -3.15%     
==========================================
  Files          63       69       +6     
  Lines        2808     2989     +181     
==========================================
  Hits         1459     1459              
- Misses       1349     1530     +181     
Flag Coverage Δ
unittests 48.81% <0.00%> (-3.15%) ⬇️

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)

@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 58
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?

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
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