Skip to content

Commit

Permalink
Hackathon/hotfixes (#413)
Browse files Browse the repository at this point in the history
* fix save results

* empty programs hotfix

* clean sequenes

* circuit_to_pulses fix

* black formatting

* unittest for execute

* updated changelog

* black formatting

* changelog updated

* isort test

* cleaning unittest

* black reformat

* fixing unittest for mocking all functions called

* another fix

* add save_results flag

* fix

* fix tests

---------

Co-authored-by: AlbertMitjans <a.mitjanscoma@gmail.com>
  • Loading branch information
jjmartinezQT and AlbertMitjans authored Jun 9, 2023
1 parent 03fab45 commit c8e688d
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 68 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@

### New features since last release

- Added hotfixes for several issues encountered during the hackathon:
https://github.com/qilimanjaro-tech/qililab/pull/413

- Save experiments flag for experiments execute method

```python
# Option 1 (Default, save_experiment=True)
experiment = Experiment(
platform=platform, circuits=circuits, options=options
)
experiment.execute()

# Option 2 (Equivalent to option 1, save_experiment=False)
experiment = Experiment(
platform=platform, circuits=circuits, options=options
)
experiment.execute(save_experiment=False)
```

- Empty sequences to avoid creating repeated programs.
- Create empty programs for all qubit flux lines to activate calibrated offsets.
- Added method to get qubits from the chip

```python
qubits = ql.Chip.qubits()
```

- Added to the `draw()` method the option of specifying if you want:

- modulation or not,
Expand Down
6 changes: 3 additions & 3 deletions interrogate_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/qililab/chip/chip.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ def to_dict(self):
"nodes": [{RUNCARD.NAME: node.name.value} | asdict(node, dict_factory=dict_factory) for node in self.nodes],
}

@property
def qubits(self):
"""Chip `qubits` property.
Returns:
list[int]: List of integers containing the indices of the qubits inside the chip.
"""
return [node.qubit_index for node in self.nodes if isinstance(node, Qubit)]

@property
def num_qubits(self) -> int:
"""Chip 'num_qubits' property
Expand Down
2 changes: 1 addition & 1 deletion src/qililab/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ def execute(circuit: Circuit, runcard_name: str, nshots=1):
# create experiment with options
sample_experiment = ql.Experiment(platform=platform, circuits=[circuit], options=options)

return sample_experiment.execute(save_results=False)
return sample_experiment.execute(save_experiment=False, save_results=False)
6 changes: 3 additions & 3 deletions src/qililab/execution/bus_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ def compile(self, idx: int, nshots: int, repetition_duration: int, num_bins: int

def upload(self):
"""Uploads any previously compiled program into the instrument."""
self.system_control.upload()
self.system_control.upload(port=self.port)

def run(self):
"""Run the given pulse sequence."""
return self.system_control.run()
return self.system_control.run(port=self.port)

def setup(self):
"""Generates the sequence for each bus and uploads it to the sequencer"""
Expand All @@ -67,7 +67,7 @@ def acquire_result(self) -> Result:
f"The bus {self.bus.alias} needs a readout system control to acquire the results. This bus "
f"has a {self.system_control.name} instead."
)
return self.system_control.acquire_result() # type: ignore # pylint: disable=no-member
return self.system_control.acquire_result(port=self.port) # type: ignore # pylint: disable=no-member

def acquire_time(self, idx: int = 0) -> int:
"""BusExecution 'acquire_time' property.
Expand Down
22 changes: 12 additions & 10 deletions src/qililab/experiment/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def build_execution(self):
# Build ``ExecutionManager`` class
self.execution_manager = EXECUTION_BUILDER.build(platform=self.platform, pulse_schedules=self.pulse_schedules)

def run(self, save_results=True) -> Results:
def run(self, save_experiment=True, save_results=True) -> Results:
"""This method is responsible for:
* Creating the live plotting (if connection is provided).
* Preparing the `Results` class and the `results.yml` file.
Expand All @@ -93,7 +93,9 @@ def run(self, save_results=True) -> Results:
raise ValueError("Please build the execution_manager before running an experiment.")

# Prepares the results
self.results, self.results_path = self.prepare_results(save_results=save_results)
self.results, self.results_path = self.prepare_results(
save_experiment=save_experiment, save_results=save_results
)

data_queue: Queue = Queue() # queue used to store the experiment results
self._asynchronous_data_handling(queue=data_queue)
Expand Down Expand Up @@ -174,7 +176,7 @@ def disconnect(self):
"""Disconnects from the instruments and releases the device."""
self.platform.disconnect()

def execute(self, save_results=True) -> Results:
def execute(self, save_experiment=True, save_results=True) -> Results:
"""Runs the whole execution pipeline, which includes the following steps:
* Connect to the instruments.
Expand All @@ -194,7 +196,7 @@ def execute(self, save_results=True) -> Results:
self.initial_setup()
self.build_execution()
self.turn_on_instruments()
results = self.run(save_results=save_results)
results = self.run(save_experiment=save_experiment, save_results=save_results)
self.turn_off_instruments()
self.disconnect()
QcodesInstrument.close_all()
Expand Down Expand Up @@ -457,7 +459,7 @@ def repetition_duration(self):
"""
return self.options.settings.repetition_duration

def prepare_results(self, save_results=True) -> tuple[Results, Path | None]:
def prepare_results(self, save_experiment=True, save_results=True) -> tuple[Results, Path | None]:
"""Creates the ``Results`` class, creates the ``results.yml`` file where the results will be saved, and dumps
the experiment data into this file.
Expand All @@ -476,14 +478,14 @@ def prepare_results(self, save_results=True) -> tuple[Results, Path | None]:
loops=self.options.loops,
)

if save_results:
if save_results or save_experiment:
# Create the folders & files needed to save the results locally
results_path = self._path_to_results_folder()
self._create_results_file(results_path)

# Dump the experiment data into the created file
with open(file=results_path / EXPERIMENT_FILENAME, mode="w", encoding="utf-8") as experiment_file:
yaml.dump(data=self.to_dict(), stream=experiment_file, sort_keys=False)
if save_experiment:
# Dump the experiment data into the created file
with open(file=results_path / EXPERIMENT_FILENAME, mode="w", encoding="utf-8") as experiment_file:
yaml.dump(data=self.to_dict(), stream=experiment_file, sort_keys=False)
else:
results_path = None

Expand Down
6 changes: 5 additions & 1 deletion src/qililab/instruments/awg.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,13 @@ def compile(
"""

@abstractmethod
def run(self):
def run(self, port: int):
"""Run the uploaded program"""

@abstractmethod
def upload(self, port: int):
"""Upload compiled program."""

@property
def num_sequencers(self):
"""Number of sequencers in the AWG
Expand Down
16 changes: 10 additions & 6 deletions src/qililab/instruments/qblox/qblox_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def __init__(self, settings: dict):
def initial_setup(self):
"""Initial setup"""
self._map_outputs()
self.clear_cache()
for sequencer in self.awg_sequencers:
sequencer_id = sequencer.identifier
# Set `sync_en` flag to False (this value will be set to True if the sequencer is used in the execution)
Expand Down Expand Up @@ -162,9 +163,9 @@ def _compile(self, pulse_bus_schedule: PulseBusSchedule, sequencer: AWGQbloxSequ
self.sequences[sequencer.identifier] = (sequence, False)
return sequence

def run(self):
def run(self, port: int):
"""Run the uploaded program"""
self.start_sequencer()
self.start_sequencer(port=port)

def _translate_pulse_bus_schedule(self, pulse_bus_schedule: PulseBusSchedule, sequencer: AWGQbloxSequencer):
"""Translate a pulse sequence into a Q1ASM program and a waveform dictionary.
Expand Down Expand Up @@ -268,9 +269,10 @@ def _append_acquire_instruction(
):
"""Append an acquire instruction to the loop."""

def start_sequencer(self):
def start_sequencer(self, port: int):
"""Start sequencer and execute the uploaded instructions."""
for sequencer in self.awg_sequencers:
sequencers = self.get_sequencers_from_chip_port_id(chip_port_id=port)
for sequencer in sequencers:
if sequencer.identifier in self.sequences:
self.device.arm_sequencer(sequencer=sequencer.identifier)
self.device.start_sequencer(sequencer=sequencer.identifier)
Expand Down Expand Up @@ -486,20 +488,22 @@ def turn_on(self):
def clear_cache(self):
"""Empty cache."""
self._cache = {}
self.sequences = {}

@Instrument.CheckDeviceInitialized
def reset(self):
"""Reset instrument."""
self.clear_cache()
self.device.reset()

def upload(self):
def upload(self, port: int):
"""Upload all the previously compiled programs to its corresponding sequencers.
This method must be called after the method ``compile``."""
if self.nshots is None or self.repetition_duration is None:
raise ValueError("Please compile the circuit before uploading it to the device.")
for sequencer in self.awg_sequencers:
sequencers = self.get_sequencers_from_chip_port_id(chip_port_id=port)
for sequencer in sequencers:
if (seq_idx := sequencer.identifier) in self.sequences:
sequence, uploaded = self.sequences[seq_idx]
self.device.sequencers[seq_idx].sync_en(True)
Expand Down
14 changes: 0 additions & 14 deletions src/qililab/platform/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ def __init__(self, runcard_schema: RuncardSchema, connection: API | None = None)
self.schema = Schema(**asdict(runcard_schema.schema))
self.connection = connection
self._connected_to_instruments: bool = False
self._initial_setup_applied: bool = False
self._instruments_turned_on: bool = False

def connect(self, manual_override=False):
"""Blocks the given device and connects to the instruments.
Expand All @@ -53,29 +51,17 @@ def connect(self, manual_override=False):

def initial_setup(self):
"""Set the initial setup of the instruments"""
if self._initial_setup_applied:
logger.info("Initial setup already applied to the instruments")
return
self.instrument_controllers.initial_setup()
self._initial_setup_applied = True
logger.info("Initial setup applied to the instruments")

def turn_on_instruments(self):
"""Turn on the instruments"""
if self._instruments_turned_on:
logger.info("Instruments already turned on")
return
self.instrument_controllers.turn_on_instruments()
self._instruments_turned_on = True
logger.info("Instruments turned on")

def turn_off_instruments(self):
"""Turn off the instruments"""
if not self._instruments_turned_on:
logger.info("Instruments already turned off")
return
self.instrument_controllers.turn_off_instruments()
self._instruments_turned_on = False
logger.info("Instruments turned off")

def disconnect(self):
Expand Down
13 changes: 7 additions & 6 deletions src/qililab/pulse/circuit_to_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ def translate(self, circuits: list[Circuit]) -> list[PulseSchedule]:
self._update_time(time=time, qubit_idx=gate.control_qubits[0], pulse_time=pad_time, wait_time=0)
if pulse_event is not None: # this happens for the Identity gate
pulse_schedule.add_event(pulse_event=pulse_event, port=port)
with contextlib.suppress(ValueError):
# If we find a flux port, create empty schedule for that port
for qubit_idx in gate.qubits:
flux_port = chip.get_port_from_qubit_idx(idx=qubit_idx, line=Line.FLUX)
if flux_port is not None:
pulse_schedule.create_schedule(port=flux_port)

for qubit in chip.qubits:
with contextlib.suppress(ValueError):
# If we find a flux port, create empty schedule for that port
flux_port = chip.get_port_from_qubit_idx(idx=qubit, line=Line.FLUX)
if flux_port is not None:
pulse_schedule.create_schedule(port=flux_port)

pulse_schedule_list.append(pulse_schedule)

Expand Down
2 changes: 1 addition & 1 deletion src/qililab/system_control/readout_system_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ReadoutSystemControl(SystemControl):

name = SystemControlName.READOUT_SYSTEM_CONTROL

def acquire_result(self) -> Result:
def acquire_result(self, port: int) -> Result:
"""Read the result from the vector network analyzer instrument
Returns:
Expand Down
6 changes: 3 additions & 3 deletions src/qililab/system_control/simulated_system_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ def __str__(self):
"""String representation of a Simulated SystemControl class."""
return "--"

def run(self) -> None:
def run(self, port: int) -> None:
"""Run the program"""
self._evo.set_pulse_sequence(pulse_sequence=self.sequence, resolution=self.settings.resolution * 1e-9)
self._evo.evolve()

def acquire_result(self) -> SimulatorResult:
def acquire_result(self, port: int) -> SimulatorResult:
"""Read the result from the AWG instrument
Returns:
Expand Down Expand Up @@ -110,5 +110,5 @@ def compile(
self.sequence = [i_waveform]
return []

def upload(self):
def upload(self, port: int):
pass
8 changes: 4 additions & 4 deletions src/qililab/system_control/system_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,22 @@ def compile(
"sequence."
)

def upload(self):
def upload(self, port: int):
"""Uploads any previously compiled program into the instrument."""
for instrument in self.instruments:
if isinstance(instrument, AWG):
instrument.upload()
instrument.upload(port=port)
return

raise AttributeError(
f"The system control with alias {self.settings.alias} doesn't have any AWG to upload a program."
)

def run(self) -> None:
def run(self, port: int) -> None:
"""Runs any previously uploaded program into the instrument."""
for instrument in self.instruments:
if isinstance(instrument, AWG):
instrument.run()
instrument.run(port=port)
return

raise AttributeError(
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/instruments/qblox/test_qblox_qcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def test_inital_setup_method(self, qcm: QbloxQCM):

def test_start_sequencer_method(self, qcm: QbloxQCM):
"""Test start_sequencer method"""
qcm.start_sequencer()
qcm.start_sequencer(port=0)
qcm.device.arm_sequencer.assert_not_called()
qcm.device.start_sequencer.assert_not_called()

Expand Down Expand Up @@ -211,12 +211,12 @@ def test_compile(self, qcm, pulse_bus_schedule):
def test_upload_raises_error(self, qcm):
"""Test upload method raises error."""
with pytest.raises(ValueError, match="Please compile the circuit before uploading it to the device"):
qcm.upload()
qcm.upload(port=0)

def test_upload_method(self, qcm, pulse_bus_schedule):
"""Test upload method"""
qcm.compile(pulse_bus_schedule, nshots=1000, repetition_duration=100, num_bins=1)
qcm.upload()
qcm.upload(port=pulse_bus_schedule.port)
qcm.device.sequencer0.sequence.assert_called_once()
qcm.device.sequencer0.sync_en.assert_called_once_with(True)

Expand Down
Loading

0 comments on commit c8e688d

Please sign in to comment.