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

Simplify and move Qblox modules settings #640

Merged
merged 18 commits into from
Nov 30, 2023

Conversation

PiergiorgioButtarini
Copy link
Contributor

@PiergiorgioButtarini PiergiorgioButtarini commented Oct 31, 2023

This modifications have reproduced the basic calibration routine but further testing is needed

To do:

  • Reproduce routine performance
  • General refactoring might be still needed in order to reduce as much as possible the number of new lines in the runcard yaml.
  • Dump of the module settings in the runcard
  • Test have to be adjusted

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.

@PiergiorgioButtarini PiergiorgioButtarini linked an issue Oct 31, 2023 that may be closed by this pull request
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 implementing this. I will have a look in more detail in the following days, just noting that you can also update the qblox platform/runcard in tests/dummy_qrc. This is required for tests to pass with the new approach and can also serve as a template for the new structure of qblox runcards.

@PiergiorgioButtarini PiergiorgioButtarini changed the title simplified and moved qblox modules settings Simplify and move Qblox modules settings Nov 3, 2023
@PiergiorgioButtarini PiergiorgioButtarini added enhancement New feature or request drivers labels Nov 3, 2023
@PiergiorgioButtarini PiergiorgioButtarini self-assigned this Nov 3, 2023
Copy link
Contributor

@andrea-pasquale andrea-pasquale 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 opening the PR @PiergiorgioButtarini.
Please find below some comments (mainly removing commented code and a few questions).
I did not test the code yet with qibocal.

"""Initialises the instance.

All class attributes are defined and initialised.
"""

super().__init__(name, address)
self.settings: ClusterQRM_RF_Settings = settings
# self.settings: ClusterQRM_RF_Settings = settings
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

Comment on lines 313 to 316
# self.connect()
# if not settings:
# log.warning(f'Called setup() without passing parameters for module {self.name}')
# return
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

Comment on lines 333 to 334
# else:
# raise Exception("The instrument cannot be set up, there is no connection")
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

self.ports["o1"].lo_enabled = True
self.ports["o1"].lo_frequency = self._settings["o1"]["lo_frequency"]
self.ports["o1"].gain = self._settings["o1"]["gain"]
# self.ports["o1"].hardware_mod_en = port_settings.hardware_mod_en
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

Comment on lines +439 to +453
# for sweeper in sweepers:
# if sweeper.parameter is Parameter.amplitude:
# # qblox cannot sweep amplitude in real time, but sweeping gain is quivalent
# for pulse in sweeper.pulses:
# pulse.amplitude = 1

# elif sweeper.parameter is Parameter.gain:
# for pulse in sweeper.pulses:
# # qblox has an external and an internal gains
# # when sweeping the internal, set the external to 1
# # TODO check if it needs to be restored after execution
# if pulse.type == PulseType.READOUT:
# qubits[pulse.qubit].readout.gain = 1
# elif pulse.type == PulseType.DRIVE:
# qubits[pulse.qubit].drive.gain = 1
Copy link
Contributor

Choose a reason for hiding this comment

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

I know that this was already commented but if it is not used I suggest to remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would like to speak with Alvaro before removing this.

Copy link
Member

Choose a reason for hiding this comment

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

I would also suggest to stick to one thing per PR so that it is easier to review and fix any introduced bugs. Since this PR is about the instrument settings, avoid touching the sweepers as much as possible, even if you see things that you don't like. If it's small fixes, fine, but if it's a big change it may introduce some bug which will be harder to solve the bigger the PR is (and the settings part is already big).

Args:
name(str): A unique name given to the instrument.
address: IP_address:module_number (the IP address of the cluster and module number)
cluster: the cluster to which the instrument belongs.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please add the correct type hint also in the docstring

Copy link
Member

Choose a reason for hiding this comment

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

I would actually suggest avoiding type hints in the docstrings, just because they are now redundant with the actual hints, and it's more surface to be kept up to date (Sphinx is also fetching the parameter hints, so they will always be displayed).

In general, given the transition to explicit types, we do not necessarily need structured docstrings everywhere.
If possible, just keep few parameters in each function, and you can adopt a more conversational style (like it happens in Python docs, below some examples).
https://docs.python.org/3/library/shutil.html?highlight=shutils#shutil.copy2
https://docs.python.org/3/library/pathlib.html#pathlib.PurePath
https://docs.python.org/3/library/subprocess.html#subprocess.run
(even though they do not have the explicit types :P)

Examples are always welcome :)

Copy link
Contributor Author

@PiergiorgioButtarini PiergiorgioButtarini Nov 16, 2023

Choose a reason for hiding this comment

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

In your opinion something like this can work?

 def __init__(self, name: str, address: str, cluster: Cluster = None):
        """
        Initialize a Qblox QCM baseband module.

        Parameters:
        - name: An arbitrary name to identify the module.
        - address: The network address of the instrument, specified as "cluster_IP:module_slot_idx".
        - cluster: The Cluster object to which the QCM baseband module is connected.

        Example:
        To create a ClusterQCM_BB instance named 'qcm_bb' connected to slot 2 of a Cluster at address '192.168.1.100':

        >>> cluster_instance = Cluster("cluster","192.168.1.100", settings)
        >>> qcm_module = ClusterQCM_BB(name="qcm_bb", address="192.168.1.100:2", cluster=cluster_instance)
        """

Comment on lines 274 to 278
# self.connect()
# if not settings:
# log.warning(f'Called setup() without passing parameters for module {self.name}')
# return

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

self.ports[port].gain = self._settings[port]["gain"]
self.ports[port].offset = self._settings[port]["offset"]
self.ports[port].qubit = self._settings[port]["qubit"]
# self.ports[port].hardware_mod_en = settings.hardware_mod_en
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

Comment on lines 297 to 299
# if not settings:
# log.warning(f'Called setup() without passing parameters for module {self.name}')
# return
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

self.ports[port].lo_frequency = self._settings[port]["lo_frequency"]
self.ports[port].attenuation = self._settings[port]["attenuation"]
self.ports[port].gain = self._settings[port]["gain"]
# selected_port.hardware_mod_en = port_settings["hardware_mod_en"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove dead code

Copy link

codecov bot commented Nov 7, 2023

Codecov Report

Attention: 225 lines in your changes are missing coverage. Please review.

Comparison is base (4546d40) 65.51% compared to head (5b1ae2a) 65.94%.
Report is 69 commits behind head on main.

Files Patch % Lines
src/qibolab/instruments/qblox/cluster_qcm_bb.py 16.88% 64 Missing ⚠️
src/qibolab/instruments/qblox/controller.py 1.75% 56 Missing ⚠️
src/qibolab/instruments/qblox/cluster_qcm_rf.py 20.58% 54 Missing ⚠️
src/qibolab/instruments/qblox/cluster_qrm_rf.py 24.07% 41 Missing ⚠️
src/qibolab/instruments/qblox/port.py 47.36% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #640      +/-   ##
==========================================
+ Coverage   65.51%   65.94%   +0.43%     
==========================================
  Files          57       57              
  Lines        7928     7817     -111     
==========================================
- Hits         5194     5155      -39     
+ Misses       2734     2662      -72     
Flag Coverage Δ
unittests 65.94% <18.18%> (+0.43%) ⬆️

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.

@scarrazza scarrazza added this to the Qibolab 0.1.4 milestone Nov 7, 2023
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.

Thank you for working on this @PiergiorgioButtarini. I did not check all the changes in detail, but there are a few general things I would like to point out. I have mainly looked at cluster_qrm_rf.py for this, which I believe is the most complicated module, but I am assuming these observations can be somewhat generalized/transfered to the other modules as well.

The general hierarchy I have in mind is module -> ports -> settings dataclasses. The ports also provide an interface to change settings directly from Python (this is already implemented with the getters/setters in port.py). The workflow is the following:

  1. In the platform create you load the runcard and you call module.setup (via load_instrument_settings). This parses the settings to the dataclasses under the ports. At this stage the settings are just cached as Python variables, not uploaded to the instrument as you are still not connected,
  2. when platform.connect() is called you connect to the instrument and then you should upload all the settings that are cached under the ports,
  3. if the users changes some setting using the port interface while you are connected, the change should be immediately uploaded to the instrument (I believe this is implemented already),
  4. platform.setup() should be useless at this stage, as it was already done during creation, and we will deprecate at some point.

That being said, starting from main (not this branch), I believe there are several attributes of ClusterQRM_RF that are related to the port/settings business and we can clean them up. These are:

  • self._input_port_keys: I believe it is nowhere used.
  • self._output_port_keys: This is used in a few places but it can easily be replaced by something like if isinstance(port, QbloxOutputPort) etc. Or easier, instead of having a self.ports dictionary with all the ports, you could split in two self._input_ports and self._output_ports and make a property
@property
def ports(self):
    return self._input_ports | self._output_ports
  • self._device_parameters: This is just caching the module settings in Python, but we already have the caching mechanism with ports/settings, so we don't need it twice. If there are parameters that exist in self._device_parameters but not in in the settings objects we should move them to settings. We can keep the same default values so that we don't have to set them in the runcard. It is just easier from code and documentation perspective to have all the parameters in a single place.
  • self.channels: I believe it is nowhere used and in the current qibolab design instruments do not need to hold references to channels because they are accessible through the Qubit objects.
  • self._channel_port_map: It is only used to create self.channels and in the get_if function where it can be replaced by using the Qubit objects.
  • self._port_channel_map it is only used to create self._channel_port_map and in process_pulse_sequence where it can be replaced by using the Qubit objects.
  • The methods _set_device_parameter and _erase_device_parameters can also be dropped if we remove self._device_parameters. If _set_device_parameters is useful for the port setters we can redefine it as a standalone utility function that can be reused among all modules.

These are quite high level suggestions, I have not really tested if we can safely drop all these without breaking something important. If you agree, I would suggest to also sit together for sometime and have a look if there are any issues.

Comment on lines +439 to +453
# for sweeper in sweepers:
# if sweeper.parameter is Parameter.amplitude:
# # qblox cannot sweep amplitude in real time, but sweeping gain is quivalent
# for pulse in sweeper.pulses:
# pulse.amplitude = 1

# elif sweeper.parameter is Parameter.gain:
# for pulse in sweeper.pulses:
# # qblox has an external and an internal gains
# # when sweeping the internal, set the external to 1
# # TODO check if it needs to be restored after execution
# if pulse.type == PulseType.READOUT:
# qubits[pulse.qubit].readout.gain = 1
# elif pulse.type == PulseType.DRIVE:
# qubits[pulse.qubit].drive.gain = 1
Copy link
Member

Choose a reason for hiding this comment

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

I would also suggest to stick to one thing per PR so that it is easier to review and fix any introduced bugs. Since this PR is about the instrument settings, avoid touching the sweepers as much as possible, even if you see things that you don't like. If it's small fixes, fine, but if it's a big change it may introduce some bug which will be harder to solve the bigger the PR is (and the settings part is already big).

if self.is_connected:
try:
if "o1" in self.settings:
self.ports["o1"].attenuation = self.settings["o1"]["attenuation"]
Copy link
Member

Choose a reason for hiding this comment

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

As in the previous comment, here there is also some duplication with self.settings. In principle you should have the same attenuation value cached under self.ports["o1"]._settings.attenuation (or something like that).

Comment on lines +87 to +91
def instantiate_channels(channel_name: str, module, module_port_name: str):
module.channels.append(channel_name)
module._port_channel_map[module_port_name] = channel_name
module._channel_port_map[channel_name] = module_port_name
return Channel(name=channel_name, port=module.ports[module_port_name])
Copy link
Member

Choose a reason for hiding this comment

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

The main reason I want to drop _port_channel_map and _channel_port_map is to get rid of this function. From the user perspective (the one that is writing the create method) it should be sufficient to define the channels and map them to ports, ie

channels["L3-15"] = Channel(name="L3-15", port=qcm_rf0.ports["o1"])

This contains all the information we need from the lab configuration. The rest are manipulations of the channel map that we can do internally and only when needed.

@PiergiorgioButtarini
Copy link
Contributor Author

Requirement

This branch works with the qblox runcard of PR qiboteam/qibolab_platforms_qrc#75.

Comments

This initial refactoring where the settings - tunable by some routine - of each qblox module are moved in the runcard yaml is working properly.
At the following link can be found the report with some routine performed with this branch: http://login.qrccluster.com:9000/U1tsuCh8Q2CiYrLn1IWXLA==

In order to facilitate the review and debugging process I will implement the further refactoring pointed out by @stavros11 in other sub-branches.

@@ -119,8 +119,11 @@ def dump_qubits(qubits: QubitMap, pairs: QubitPairMap, couplers: CouplerMap = No

def dump_instruments(instruments: InstrumentMap) -> dict:
"""Dump instrument settings to a dictionary following the runcard format."""
# Qblox modules settings are dictionaries and not dataclasses
Copy link
Member

Choose a reason for hiding this comment

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

Please, open an issue to trace the migration (unless you plan to migrate in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

About this I'm not sure I understood your comment. So for now I put this patch that works both for qblox and the other instruments, but you want that I open an issue in order to migrate in the future the settings of qblox from dictionaries to dataclasses?

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, that's exactly the request.

The idea is that we were migrating to dataclasses, and that is the interface we'd like to maintain. Fine to have a temporary patch, but (at least in the future) Qblox should implement the interface, instead of just giving up on the interface we established.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok perfect thanks. I will open an issue about that.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks

@alecandido
Copy link
Member

In order to facilitate the review and debugging process I will implement the further refactoring pointed out by @stavros11 in other sub-branches

Instead of making sub-branches, I would suggest attempting to merge this PR, and whatever is postponed will be added in further PRs (but, if possible, all pointing to main, to minimize the time you depart from it).

@PiergiorgioButtarini
Copy link
Contributor Author

PiergiorgioButtarini commented Nov 16, 2023

In order to facilitate the review and debugging process I will implement the further refactoring pointed out by @stavros11 in other sub-branches

Instead of making sub-branches, I would suggest attempting to merge this PR, and whatever is postponed will be added in further PRs (but, if possible, all pointing to main, to minimize the time you depart from it).

Thank you @alecandido for the review, I will implement your suggestion asap.
By the way I would like to keep the idea of creating a single sub-branch in order to address the channel refactoring suggested by @stavros11. This sub-branch will avoid changing multiple times, in a short period of time, the interface of the qblox runcards.
Moreover the modification needed to solve this problem should be only a few and are also linked to the modification already made in this branch.

@stavros11
Copy link
Member

Instead of making sub-branches, I would suggest attempting to merge this PR, and whatever is postponed will be added in further PRs (but, if possible, all pointing to main, to minimize the time you depart from it).

Generally I would agree with this, however for this particular case I suggested to hold (not merge) this PR even if it is working, because it requires changes to the qblox platforms (see qiboteam/qibolab_platforms_qrc#75). Implementing my suggestions would require further changes to the platforms (hopefully towards simplifying). And right now we have several (non-developer) users who using qibolab from main, so every time we merge something that requires changes to the platform creation, we need to properly communicate this. I would prefer to only communicate the final most simplified version, once we have it and we are confident with it.

I guess the main flaw here is that users who are not developing are using main (instead of releases) but until the releases are sufficiently stable it is hard to convince people to use them in practice.

The sub-branches idea is not strictly required, in principle we can push everything directly here until we are ready to merge, but it may help to keep the current status of this branch as a baseline (as it was already tested to work) and work the newer simplifications on top.

@alecandido
Copy link
Member

I wrote this reply before @stavros11's one, but I forgot to send before

Ok, if you wish to postpone the merge of this branch into main until you will have completed the next PR it is even fine (just to avoid them ending up in two different releases).

But try to push this PR at a stage in which you could consider frozen (of course it won't be, but just make it as final as possible), in such a way that when the other will be ready, you will be able to first merge this one, and then the new one.

We agree on the problems and the solution. The further bits are only the requests to:

  • try to finalize this as much as possible, before working too long on the sub-branch
  • merge first this one in main, and then the other one as well (the idea is always to validate the minimal set of changes as possible, and as much as possible in isolation)

@alecandido
Copy link
Member

I hope this problem is really a very temporary one: as soon as we stabilize the platform creation, we should be able to freely rework the internals without breaking the interface (or, at least, we can schedule the interface breaking, and make it as unfrequent as possible).

@PiergiorgioButtarini PiergiorgioButtarini marked this pull request as ready for review November 29, 2023 08:35
@PiergiorgioButtarini PiergiorgioButtarini merged commit 29fe6f1 into main Nov 30, 2023
55 of 56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
drivers enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Move qblox settings to runcard
5 participants