From 765a1a31b5adf714b0e99d1ed11e46a19ea5c8e4 Mon Sep 17 00:00:00 2001 From: Stavros Efthymiou <35475381+stavros11@users.noreply.github.com> Date: Sat, 20 Apr 2024 23:33:39 +0400 Subject: [PATCH] fix: single shot classification works --- src/qibolab/instruments/qm/config.py | 20 +++++++++--- src/qibolab/instruments/qm/controller.py | 25 ++++++++------- src/qibolab/instruments/qm/program.py | 15 ++------- src/qibolab/instruments/qm/sweepers.py | 3 +- src/qibolab/pulses/pulse.py | 39 ++++++++++++------------ 5 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/qibolab/instruments/qm/config.py b/src/qibolab/instruments/qm/config.py index 243a5c2c9..1bb60a86f 100644 --- a/src/qibolab/instruments/qm/config.py +++ b/src/qibolab/instruments/qm/config.py @@ -19,6 +19,16 @@ """ +def operation(pulse): + """Generate operation name in QM ``config`` for the given pulse.""" + return str(hash(pulse)) + + +def element(pulse): + """Generate element name in QM ``config`` for the given pulse.""" + return pulse.channel + + def float_serial(x): """Convert float to string to use in config keys.""" return format(x, ".6f").rstrip("0").rstrip(".") @@ -256,7 +266,7 @@ def register_element(self, qubit, pulse, time_of_flight=0, smearing=0): element = self.register_flux_element(qubit, pulse.frequency) return element - def register_pulse(self, pulse, operation, element, qubit): + def register_pulse(self, pulse, qubit): """Registers pulse, waveforms and integration weights in QM config. Args: @@ -350,7 +360,7 @@ def register_waveform(self, pulse, mode="i"): phase = (pulse.relative_phase % (2 * np.pi)) / (2 * np.pi) amplitude = float_serial(pulse.amplitude) phase_str = float_serial(phase) - if isinstance(pulse.shape, Rectangular): + if isinstance(pulse.envelope, Rectangular): serial = f"constant_wf({amplitude}, {phase_str})" if serial not in self.waveforms: if mode == "i": @@ -359,10 +369,10 @@ def register_waveform(self, pulse, mode="i"): sample = pulse.amplitude * np.sin(phase) self.waveforms[serial] = {"type": "constant", "sample": sample} else: - serial = f"{mode}({pulse.duration}, {amplitude}, {phase_str}, {str(pulse.shape)})" + serial = f"{hash(pulse)}_{mode}" if serial not in self.waveforms: - samples_i = pulse.envelope_waveform_i(SAMPLING_RATE).data - samples_q = pulse.envelope_waveform_q(SAMPLING_RATE).data + samples_i = pulse.i(SAMPLING_RATE) + samples_q = pulse.q(SAMPLING_RATE) if mode == "i": samples = samples_i * np.cos(phase) - samples_q * np.sin(phase) else: diff --git a/src/qibolab/instruments/qm/controller.py b/src/qibolab/instruments/qm/controller.py index 1d4462452..26a6146e7 100644 --- a/src/qibolab/instruments/qm/controller.py +++ b/src/qibolab/instruments/qm/controller.py @@ -10,15 +10,15 @@ from qibolab import AveragingMode from qibolab.instruments.abstract import Controller -from qibolab.pulses import PulseType +from qibolab.pulses import Delay, PulseType from qibolab.sweeper import Parameter from qibolab.unrolling import Bounds from .acquisition import create_acquisition, fetch_results -from .config import SAMPLING_RATE, QMConfig +from .config import SAMPLING_RATE, QMConfig, element, operation from .devices import Octave, OPXplus from .ports import OPXIQ -from .program import Parameters, element, operation +from .program import Parameters from .sweepers import sweep OCTAVE_ADDRESS_OFFSET = 11000 @@ -292,6 +292,9 @@ def register_pulses(self, qubits, sequence, options): acquisitions = {} parameters = defaultdict(Parameters) for pulse in sequence: + if isinstance(pulse, Delay): + continue + qubit = qubits[pulse.qubit] self.config.register_port(getattr(qubit, pulse.type.name.lower()).port) if pulse.type is PulseType.READOUT: @@ -314,17 +317,14 @@ def register_pulses(self, qubits, sequence, options): self.config.register_pulse(qubit, qmpulse) qmsequence.add(qmpulse) - op = operation(hash(pulse)) - el = element(pulse) - if op not in self.config.pulses: - self.config.register_pulse(pulse, op, el, qubit) - + op = self.config.register_pulse(pulse, qubit) if pulse.type is PulseType.READOUT: if op not in acquisitions: + el = element(pulse) acquisitions[op] = create_acquisition( - op, el, options, qubit.threshold, qubit.iq_angle + op, el, pulse.qubit, options, qubit.threshold, qubit.iq_angle ) - parameters[op].acquisition = acquisitions[op] + parameters[op].acquisition = acquisitions[op] acquisitions[op].keys.append(pulse.id) return acquisitions, parameters @@ -369,8 +369,11 @@ def sweep(self, qubits, couplers, sequence, options, *sweepers): acquisition.download(*buffer_dims) if self.script_file_name is not None: + script = generate_qua_script(experiment, self.config.__dict__) + for pulse in sequence: + script = script.replace(operation(pulse), str(pulse)) with open(self.script_file_name, "w") as file: - file.write(generate_qua_script(experiment, self.config.__dict__)) + file.write(script) if self.simulation_duration is not None: result = self.simulate_program(experiment) diff --git a/src/qibolab/instruments/qm/program.py b/src/qibolab/instruments/qm/program.py index bb46ad90a..a95f9832e 100644 --- a/src/qibolab/instruments/qm/program.py +++ b/src/qibolab/instruments/qm/program.py @@ -6,16 +6,7 @@ from qibolab.pulses import Delay, PulseType from .acquisition import Acquisition - - -def operation(pulse): - """Generate operation name in QM ``config`` for the given pulse.""" - return str(hash(pulse)) - - -def element(pulse): - """Generate element name in QM ``config`` for the given pulse.""" - return pulse.channel +from .config import element, operation @dataclass @@ -30,7 +21,7 @@ class Parameters: def _delay(pulse): # TODO: How to play delays on multiple elements? - qua.wait(pulse.duration, element(pulse)) + qua.wait(pulse.duration // 4 + 1, element(pulse)) def _play(pulse, parameters): @@ -51,7 +42,7 @@ def _play(pulse, parameters): qua.reset_frame(el) -def play(self, sequence, parameters, relaxation_time=0): +def play(sequence, parameters, relaxation_time=0): """Part of QUA program that plays an arbitrary pulse sequence. Should be used inside a ``program()`` context. diff --git a/src/qibolab/instruments/qm/sweepers.py b/src/qibolab/instruments/qm/sweepers.py index d1bdee6c2..c49dacef5 100644 --- a/src/qibolab/instruments/qm/sweepers.py +++ b/src/qibolab/instruments/qm/sweepers.py @@ -9,7 +9,8 @@ from qibolab.channels import check_max_offset from qibolab.pulses import PulseType -from .program import element, operation, play +from .config import element, operation +from .program import play def maximum_sweep_value(values, value0): diff --git a/src/qibolab/pulses/pulse.py b/src/qibolab/pulses/pulse.py index 48fd3b9ae..da05446a3 100644 --- a/src/qibolab/pulses/pulse.py +++ b/src/qibolab/pulses/pulse.py @@ -1,6 +1,5 @@ """Pulse class.""" -from dataclasses import fields from enum import Enum from typing import Optional, Union @@ -117,25 +116,25 @@ def modulated_waveforms(self, sampling_rate): # -> tuple[Waveform, Waveform]: def __hash__(self): """Hash the content. - .. warning:: - - unhashable attributes are not taken into account, so there will be more - clashes than those usually expected with a regular hash - - .. todo:: - - This method should be eventually dropped, and be provided automatically by - freezing the dataclass (i.e. setting ``frozen=true`` in the decorator). - However, at the moment is not possible nor desired, because it contains - unhashable attributes and because some instances are mutated inside Qibolab. - """ - return hash( - tuple( - getattr(self, f.name) - for f in fields(self) - if f.name not in ("type", "shape") - ) - ) + # .. warning:: + + # unhashable attributes are not taken into account, so there will be more + # clashes than those usually expected with a regular hash + + # .. todo:: + + # This method should be eventually dropped, and be provided automatically by + # freezing the dataclass (i.e. setting ``frozen=true`` in the decorator). + # However, at the moment is not possible nor desired, because it contains + # unhashable attributes and because some instances are mutated inside Qibolab. + # """ + # return hash(self) + # # tuple( + # # getattr(self, f.name) + # # for f in fields(self) + # # if f.name not in ("type", "shape") + # # ) + # #) def __add__(self, other): if isinstance(other, Pulse):