From 0c8514132e1341a090450d1bb1ef895b51058f8b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 6 Nov 2024 16:58:06 +0400 Subject: [PATCH 1/5] copy files --- .../_core/instruments/rfsoc/__init__.py | 3 + .../_core/instruments/rfsoc/constants.py | 6 + .../_core/instruments/rfsoc/convert.py | 201 ++++++ src/qibolab/_core/instruments/rfsoc/driver.py | 610 ++++++++++++++++++ 4 files changed, 820 insertions(+) create mode 100644 src/qibolab/_core/instruments/rfsoc/__init__.py create mode 100644 src/qibolab/_core/instruments/rfsoc/constants.py create mode 100644 src/qibolab/_core/instruments/rfsoc/convert.py create mode 100644 src/qibolab/_core/instruments/rfsoc/driver.py diff --git a/src/qibolab/_core/instruments/rfsoc/__init__.py b/src/qibolab/_core/instruments/rfsoc/__init__.py new file mode 100644 index 0000000000..4b2ba2f1b3 --- /dev/null +++ b/src/qibolab/_core/instruments/rfsoc/__init__.py @@ -0,0 +1,3 @@ +"""RFSoC module driver for qibosoq.""" + +from .driver import RFSoC diff --git a/src/qibolab/_core/instruments/rfsoc/constants.py b/src/qibolab/_core/instruments/rfsoc/constants.py new file mode 100644 index 0000000000..b697081285 --- /dev/null +++ b/src/qibolab/_core/instruments/rfsoc/constants.py @@ -0,0 +1,6 @@ +"""Shared constants.""" + +SAMPLING_RATE = 2 +NANO_TO_SECONDS = 1e-9 +HZ_TO_MHZ = 1e-6 +NS_TO_US = 1e-3 diff --git a/src/qibolab/_core/instruments/rfsoc/convert.py b/src/qibolab/_core/instruments/rfsoc/convert.py new file mode 100644 index 0000000000..1e036c95c7 --- /dev/null +++ b/src/qibolab/_core/instruments/rfsoc/convert.py @@ -0,0 +1,201 @@ +"""Convert helper functions for rfsoc driver.""" + +from copy import deepcopy +from dataclasses import asdict +from functools import singledispatch + +import numpy as np +import qibosoq.components.base as rfsoc +import qibosoq.components.pulses as rfsoc_pulses + +from qibolab._core.pulses import Pulse +from qibolab._core.qubits import Qubit +from qibolab._core.sequence import PulseSequence +from qibolab._core.sweeper import BIAS, DURATION, START, Parameter, Sweeper + +HZ_TO_MHZ = 1e-6 +NS_TO_US = 1e-3 + + +def replace_pulse_shape( + rfsoc_pulse: rfsoc_pulses.Pulse, shape: PulseShape, sampling_rate: float +) -> rfsoc_pulses.Pulse: + """Set pulse shape parameters in rfsoc_pulses pulse object.""" + if shape.name not in {"Gaussian", "Drag", "Rectangular", "Exponential"}: + new_pulse = rfsoc_pulses.Arbitrary( + **asdict(rfsoc_pulse), + i_values=shape.envelope_waveform_i(sampling_rate), + q_values=shape.envelope_waveform_q(sampling_rate), + ) + return new_pulse + new_pulse_cls = getattr(rfsoc_pulses, shape.name) + if shape.name == "Rectangular": + return new_pulse_cls(**asdict(rfsoc_pulse)) + if shape.name == "Gaussian": + return new_pulse_cls(**asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma) + if shape.name == "Drag": + return new_pulse_cls( + **asdict(rfsoc_pulse), rel_sigma=shape.rel_sigma, beta=shape.beta + ) + if shape.name == "Exponential": + return new_pulse_cls( + **asdict(rfsoc_pulse), tau=shape.tau, upsilon=shape.upsilon, weight=shape.g + ) + + +def pulse_lo_frequency(pulse: Pulse, qubits: dict[int, Qubit]) -> int: + """Return local_oscillator frequency (HZ) of a pulse.""" + pulse_type = pulse.type.name.lower() + try: + lo_frequency = getattr( + qubits[pulse.qubit], pulse_type + ).local_oscillator.frequency + except AttributeError: + lo_frequency = 0 + return lo_frequency + + +def convert_units_sweeper( + sweeper: rfsoc.Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit] +) -> rfsoc.Sweeper: + """Convert units for `qibosoq.abstract.Sweeper` considering also LOs.""" + sweeper = deepcopy(sweeper) + for idx, jdx in enumerate(sweeper.indexes): + parameter = sweeper.parameters[idx] + if parameter is rfsoc.Parameter.FREQUENCY: + pulse = sequence[jdx] + lo_frequency = pulse_lo_frequency(pulse, qubits) + sweeper.starts[idx] = (sweeper.starts[idx] - lo_frequency) * HZ_TO_MHZ + sweeper.stops[idx] = (sweeper.stops[idx] - lo_frequency) * HZ_TO_MHZ + elif parameter is rfsoc.Parameter.DELAY: + sweeper.starts[idx] *= NS_TO_US + sweeper.stops[idx] *= NS_TO_US + elif parameter is rfsoc.Parameter.RELATIVE_PHASE: + sweeper.starts[idx] = np.degrees(sweeper.starts[idx]) + sweeper.stops[idx] = np.degrees(sweeper.stops[idx]) + return sweeper + + +@singledispatch +def convert(*args): + """Convert from qibolab obj to qibosoq obj, overloaded.""" + raise ValueError(f"Convert function received bad parameters ({type(args[0])}).") + + +@convert.register +def _(qubit: Qubit) -> rfsoc.Qubit: + """Convert `qibolab.platforms.abstract.Qubit` to + `qibosoq.abstract.Qubit`.""" + if qubit.flux: + return rfsoc.Qubit(qubit.flux.offset, qubit.flux.port.name) + return rfsoc.Qubit(0.0, None) + + +@convert.register +def _( + sequence: PulseSequence, qubits: dict[int, Qubit], sampling_rate: float +) -> list[rfsoc_pulses.Pulse]: + """Convert PulseSequence to list of rfosc pulses with relative time.""" + last_pulse_start = 0 + list_sequence = [] + for pulse in sorted(sequence.pulses, key=lambda item: item.start): + start_delay = (pulse.start - last_pulse_start) * NS_TO_US + pulse_dict = asdict(convert(pulse, qubits, start_delay, sampling_rate)) + list_sequence.append(pulse_dict) + + last_pulse_start = pulse.start + return list_sequence + + +@convert.register +def _( + pulse: Pulse, qubits: dict[int, Qubit], start_delay: float, sampling_rate: float +) -> rfsoc_pulses.Pulse: + """Convert `qibolab.pulses.pulse` to `qibosoq.abstract.Pulse`.""" + pulse_type = pulse.type.name.lower() + dac = getattr(qubits[pulse.qubit], pulse_type).port.name + adc = qubits[pulse.qubit].feedback.port.name if pulse_type == "readout" else None + lo_frequency = pulse_lo_frequency(pulse, qubits) + + rfsoc_pulse = rfsoc_pulses.Pulse( + frequency=(pulse.frequency - lo_frequency) * HZ_TO_MHZ, + amplitude=pulse.amplitude, + relative_phase=np.degrees(pulse.relative_phase), + start_delay=start_delay, + duration=pulse.duration * NS_TO_US, + dac=dac, + adc=adc, + name=pulse.serial, + type=pulse_type, + ) + return replace_pulse_shape(rfsoc_pulse, pulse.shape, sampling_rate) + + +@convert.register +def _(par: Parameter) -> rfsoc.Parameter: + """Convert a qibolab sweeper.Parameter into a qibosoq.Parameter.""" + return getattr(rfsoc.Parameter, par.name.upper()) + + +@convert.register +def _( + sweeper: Sweeper, sequence: PulseSequence, qubits: dict[int, Qubit] +) -> rfsoc.Sweeper: + """Convert `qibolab.sweeper.Sweeper` to `qibosoq.abstract.Sweeper`. + + Note that any unit conversion is not done in this function (to avoid + to do it multiple times). Conversion will be done in + `convert_units_sweeper`. + """ + parameters = [] + starts = [] + stops = [] + indexes = [] + + if sweeper.parameter is BIAS: + for qubit in sweeper.qubits: + parameters.append(rfsoc.Parameter.BIAS) + indexes.append(list(qubits.values()).index(qubit)) + base_value = qubit.flux.offset + values = sweeper.get_values(base_value) + starts.append(values[0]) + stops.append(values[-1]) + + if max(np.abs(starts)) > 1 or max(np.abs(stops)) > 1: + raise ValueError("Sweeper amplitude is set to reach values higher than 1") + else: + for pulse in sweeper.pulses: + idx_sweep = sequence.index(pulse) + indexes.append(idx_sweep) + base_value = getattr(pulse, sweeper.parameter.name) + if idx_sweep != 0 and sweeper.parameter is START: + # do the conversion from start to delay + base_value = base_value - sequence[idx_sweep - 1].start + values = sweeper.get_values(base_value) + starts.append(values[0]) + stops.append(values[-1]) + + if sweeper.parameter is START: + parameters.append(rfsoc.Parameter.DELAY) + elif sweeper.parameter is DURATION: + parameters.append(rfsoc.Parameter.DURATION) + delta_start = values[0] - base_value + delta_stop = values[-1] - base_value + + if len(sequence) > idx_sweep + 1: + # if duration-swept pulse is not last + indexes.append(idx_sweep + 1) + t_start = sequence[idx_sweep + 1].start - sequence[idx_sweep].start + parameters.append(rfsoc.Parameter.DELAY) + starts.append(t_start + delta_start) + stops.append(t_start + delta_stop) + else: + parameters.append(convert(sweeper.parameter)) + + return rfsoc.Sweeper( + parameters=parameters, + indexes=indexes, + starts=starts, + stops=stops, + expts=len(sweeper.values), + ) diff --git a/src/qibolab/_core/instruments/rfsoc/driver.py b/src/qibolab/_core/instruments/rfsoc/driver.py new file mode 100644 index 0000000000..bb8ae34420 --- /dev/null +++ b/src/qibolab/_core/instruments/rfsoc/driver.py @@ -0,0 +1,610 @@ +"""RFSoC FPGA driver.""" + +import re +from dataclasses import asdict, dataclass +from typing import Union + +import numpy as np +import numpy.typing as npt +import qibosoq.components.base as rfsoc +from qibo.config import log +from qibosoq import client + +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.couplers import Coupler +from qibolab.instruments.abstract import Controller +from qibolab.instruments.port import Port +from qibolab.pulses import PulseSequence, PulseType +from qibolab.qubits import Qubit +from qibolab.result import AveragedSampleResults, IntegratedResults, SampleResults +from qibolab.sweeper import BIAS, Sweeper + +from .convert import convert, convert_units_sweeper + +HZ_TO_MHZ = 1e-6 +NS_TO_US = 1e-3 + + +@dataclass +class RFSoCPort(Port): + """Port object of the RFSoC.""" + + name: int + """DAC number.""" + offset: float = 0.0 + """Amplitude factor for biasing.""" + + +class RFSoC(Controller): + """Instrument object for controlling RFSoC FPGAs. + + The two way of executing pulses are with ``play`` (for arbitrary + qibolab ``PulseSequence``) or with ``sweep`` that execute a + ``PulseSequence`` object with one or more ``Sweeper``. + + Attributes: + cfg (rfsoc.Config): Configuration dictionary required for pulse execution. + """ + + PortType = RFSoCPort + + def __init__(self, name: str, address: str, port: int, sampling_rate: float = 1.0): + """Set server information and base configuration. + + Args: + name (str): Name of the instrument instance. + address (str): IP and port of the server (ex. 192.168.0.10) + port (int): Port of the server (ex.6000) + """ + super().__init__(name, address=address) + self.host = address + self.port = port + self.cfg = rfsoc.Config() + self._sampling_rate = sampling_rate + + @property + def sampling_rate(self): + return self._sampling_rate + + def connect(self): + """Empty method to comply with Instrument interface.""" + + def disconnect(self): + """Empty method to comply with Instrument interface.""" + + @staticmethod + def _try_to_execute(server_commands, host, port): + try: + return client.connect(server_commands, host, port) + except RuntimeError as e: + if "exception in readout loop" in str(e): + log.warning( + "%s %s", + "Exception in readout loop. Attempting again", + "You may want to increase the relaxation time.", + ) + return client.connect(server_commands, host, port) + buffer_overflow = r"buffer length must be \d+ samples or less" + if re.search(buffer_overflow, str(e)) is not None: + log.warning("Buffer full! Use shorter pulses.") + raise e + + @staticmethod + def convert_and_discriminate_samples(discriminated_shots, execution_parameters): + if execution_parameters.averaging_mode is AveragingMode.CYCLIC: + _, counts = np.unique(discriminated_shots, return_counts=True, axis=0) + freqs = counts / discriminated_shots.shape[0] + result = execution_parameters.results_type(freqs, discriminated_shots) + else: + result = execution_parameters.results_type(discriminated_shots) + return result + + @staticmethod + def validate_input_command( + sequence: PulseSequence, execution_parameters: ExecutionParameters, sweep: bool + ): + """Check if sequence and execution_parameters are supported.""" + if execution_parameters.acquisition_type is AcquisitionType.RAW: + if sweep: + raise NotImplementedError( + "Raw data acquisition is not compatible with sweepers" + ) + if len(sequence.ro_pulses) != 1: + raise NotImplementedError( + "Raw data acquisition is compatible only with a single readout" + ) + if execution_parameters.averaging_mode is not AveragingMode.CYCLIC: + raise NotImplementedError("Raw data acquisition can only be averaged") + if execution_parameters.fast_reset: + raise NotImplementedError("Fast reset is not supported") + + @staticmethod + def merge_sweep_results( + dict_a: dict[str, Union[IntegratedResults, SampleResults]], + dict_b: dict[str, Union[IntegratedResults, SampleResults]], + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Merge two dictionary mapping pulse serial to Results object. + + If dict_b has a key (serial) that dict_a does not have, simply add it, + otherwise sum the two results + + Args: + dict_a (dict): dict mapping ro pulses serial to qibolab res objects + dict_b (dict): dict mapping ro pulses serial to qibolab res objects + Returns: + A dict mapping the readout pulses serial to qibolab results objects + """ + for serial in dict_b: + if serial in dict_a: + data = lambda res: ( + res.voltage if isinstance(res, IntegratedResults) else res.samples + ) + dict_a[serial] = type(dict_a[serial])( + np.append(data(dict_a[serial]), data(dict_b[serial])) + ) + else: + dict_a[serial] = dict_b[serial] + return dict_a + + @staticmethod + def reshape_sweep_results(results, sweepers, execution_parameters): + shape = [len(sweeper.values) for sweeper in sweepers] + if execution_parameters.averaging_mode is not AveragingMode.CYCLIC: + shape.insert(0, execution_parameters.nshots) + + def data(value): + if isinstance(value, IntegratedResults): + data = value.voltage + elif isinstance(value, AveragedSampleResults): + data = value.statistical_frequency + else: + data = value.samples + return type(value)(data.reshape(shape)) + + return {key: data(value) for key, value in results.items()} + + def _execute_pulse_sequence( + self, + sequence: PulseSequence, + qubits: dict[int, Qubit], + opcode: rfsoc.OperationCode, + ) -> tuple[list, list]: + """Prepare the commands dictionary to send to the qibosoq server. + + Args: + sequence (`qibolab.pulses.PulseSequence`): arbitrary PulseSequence object to execute + qubits: list of qubits (`qibolab.platforms.abstract.Qubit`) of the platform in the form of a dictionary + opcode: can be `rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE` or `rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE_RAW` + Returns: + Lists of I and Q value measured + """ + server_commands = { + "operation_code": opcode, + "cfg": asdict(self.cfg), + "sequence": convert(sequence, qubits, self.sampling_rate), + "qubits": [asdict(convert(qubits[idx])) for idx in qubits], + } + return self._try_to_execute(server_commands, self.host, self.port) + + def _execute_sweeps( + self, + sequence: PulseSequence, + qubits: dict[int, Qubit], + sweepers: list[rfsoc.Sweeper], + ) -> tuple[list, list]: + """Prepare the commands dictionary to send to the qibosoq server. + + Args: + sequence (`qibolab.pulses.PulseSequence`): arbitrary PulseSequence object to execute + qubits: list of qubits (`qibolab.platforms.abstract.Qubit`) of the platform in the form of a dictionary + sweepers: list of `qibosoq.abstract.Sweeper` objects + Returns: + Lists of I and Q value measured + """ + converted_sweepers = [ + convert_units_sweeper(sweeper, sequence, qubits) for sweeper in sweepers + ] + server_commands = { + "operation_code": rfsoc.OperationCode.EXECUTE_SWEEPS, + "cfg": asdict(self.cfg), + "sequence": convert(sequence, qubits, self.sampling_rate), + "qubits": [asdict(convert(qubits[idx])) for idx in qubits], + "sweepers": [sweeper.serialized for sweeper in converted_sweepers], + } + return self._try_to_execute(server_commands, self.host, self.port) + + def play( + self, + qubits: dict[int, Qubit], + couplers: dict[int, Coupler], + sequence: PulseSequence, + execution_parameters: ExecutionParameters, + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Execute the sequence of instructions and retrieves readout results. + + Each readout pulse generates a separate acquisition. + The relaxation_time and the number of shots have default values. + + Args: + qubits (dict): List of `qibolab.platforms.utils.Qubit` objects + passed from the platform. + execution_parameters (`qibolab.ExecutionParameters`): Parameters (nshots, + relaxation_time, + fast_reset, + acquisition_type, + averaging_mode) + sequence (`qibolab.pulses.PulseSequence`): Pulse sequence to play. + Returns: + A dictionary mapping the readout pulses serial and respective qubits to + qibolab results objects + """ + if couplers != {}: + raise NotImplementedError( + "The RFSoC driver currently does not support couplers." + ) + + self.validate_input_command(sequence, execution_parameters, sweep=False) + self.update_cfg(execution_parameters) + + if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: + self.cfg.average = False + else: + self.cfg.average = ( + execution_parameters.averaging_mode is AveragingMode.CYCLIC + ) + + if execution_parameters.acquisition_type is AcquisitionType.RAW: + opcode = rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE_RAW + else: + opcode = rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE + toti, totq = self._execute_pulse_sequence(sequence, qubits, opcode) + + results = {} + probed_qubits = np.unique([p.qubit for p in sequence.ro_pulses]) + + for j, qubit in enumerate(probed_qubits): + for i, ro_pulse in enumerate(sequence.ro_pulses.get_qubit_pulses(qubit)): + i_pulse = np.array(toti[j][i]) + q_pulse = np.array(totq[j][i]) + + if ( + execution_parameters.acquisition_type + is AcquisitionType.DISCRIMINATION + ): + discriminated_shots = self.classify_shots( + i_pulse, q_pulse, qubits[ro_pulse.qubit] + ) + result = self.convert_and_discriminate_samples( + discriminated_shots, execution_parameters + ) + else: + result = execution_parameters.results_type(i_pulse + 1j * q_pulse) + results[ro_pulse.qubit] = results[ro_pulse.serial] = result + + return results + + def update_cfg(self, execution_parameters: ExecutionParameters): + """Update rfsoc.Config object with new parameters.""" + if execution_parameters.nshots is not None: + self.cfg.reps = execution_parameters.nshots + if execution_parameters.relaxation_time is not None: + self.cfg.relaxation_time = execution_parameters.relaxation_time * NS_TO_US + + def classify_shots( + self, + i_values: npt.NDArray[np.float64], + q_values: npt.NDArray[np.float64], + qubit: Qubit, + ) -> npt.NDArray[np.float64]: + """Classify IQ values using qubit threshold and rotation_angle if + available in runcard.""" + if qubit.iq_angle is None or qubit.threshold is None: + raise ValueError("Classification parameters were not provided") + angle = qubit.iq_angle + threshold = qubit.threshold + + rotated = np.cos(angle) * np.array(i_values) - np.sin(angle) * np.array( + q_values + ) + shots = np.heaviside(np.array(rotated) - threshold, 0) + if isinstance(shots, float): + return np.array([shots]) + return shots + + def play_sequence_in_sweep_recursion( + self, + qubits: dict[int, Qubit], + couplers: dict[int, Coupler], + sequence: PulseSequence, + or_sequence: PulseSequence, + execution_parameters: ExecutionParameters, + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Last recursion layer, if no sweeps are present. + + After playing the sequence, the resulting dictionary keys need + to be converted to the correct values. Even indexes correspond + to qubit number and are not changed. Odd indexes correspond to + readout pulses serials and are convert to match the original + sequence (of the sweep) and not the one just executed. + """ + res = self.play(qubits, couplers, sequence, execution_parameters) + newres = {} + serials = [pulse.serial for pulse in or_sequence.ro_pulses] + for idx, key in enumerate(res): + if idx % 2 == 1: + newres[serials[idx // 2]] = res[key] + else: + newres[key] = res[key] + + return newres + + def recursive_python_sweep( + self, + qubits: dict[int, Qubit], + couplers: dict[int, Coupler], + sequence: PulseSequence, + or_sequence: PulseSequence, + *sweepers: rfsoc.Sweeper, + execution_parameters: ExecutionParameters, + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Execute a sweep of an arbitrary number of Sweepers via recursion. + + Args: + qubits (list): List of `qibolab.platforms.utils.Qubit` objects + passed from the platform. + sequence (`qibolab.pulses.PulseSequence`): Pulse sequence to play. + This object is a deep copy of the original + sequence and gets modified. + or_sequence (`qibolab.pulses.PulseSequence`): Reference to original + sequence to not modify. + *sweepers (`qibolab.Sweeper`): Sweeper objects. + execution_parameters (`qibolab.ExecutionParameters`): Parameters (nshots, + relaxation_time, + fast_reset, + acquisition_type, + averaging_mode) + Returns: + A dictionary mapping the readout pulses serial and respective qubits to + results objects + """ + # If there are no sweepers run ExecutePulseSequence acquisition. + # Last layer for recursion. + + if len(sweepers) == 0: + return self.play_sequence_in_sweep_recursion( + qubits, couplers, sequence, or_sequence, execution_parameters + ) + + if not self.get_if_python_sweep(sequence, *sweepers): + toti, totq = self._execute_sweeps(sequence, qubits, sweepers) + res = self.convert_sweep_results( + or_sequence, qubits, toti, totq, execution_parameters + ) + return res + + sweeper = sweepers[0] + values = [] + for idx, _ in enumerate(sweeper.indexes): + val = np.linspace(sweeper.starts[idx], sweeper.stops[idx], sweeper.expts) + if sweeper.parameters[idx] in rfsoc.Parameter.variants( + {"duration", "delay"} + ): + val = val.astype(int) + values.append(val) + + results: dict[str, Union[IntegratedResults, SampleResults]] = {} + for idx in range(sweeper.expts): + # update values + for jdx, kdx in enumerate(sweeper.indexes): + sweeper_parameter = sweeper.parameters[jdx] + if sweeper_parameter is rfsoc.Parameter.BIAS: + qubits[list(qubits)[kdx]].flux.offset = values[jdx][idx] + elif sweeper_parameter in rfsoc.Parameter.variants( + { + "amplitude", + "frequency", + "relative_phase", + "duration", + } + ): + setattr( + sequence[kdx], sweeper_parameter.name.lower(), values[jdx][idx] + ) + if sweeper_parameter is rfsoc.Parameter.DURATION: + for pulse_idx in range( + kdx + 1, + len(sequence.get_qubit_pulses(sequence[kdx].qubit)), + ): + # TODO: this is a patch and works just for simple experiments + sequence[pulse_idx].start = sequence[pulse_idx - 1].finish + elif sweeper_parameter is rfsoc.Parameter.DELAY: + sequence[kdx].start_delay = values[jdx][idx] + + res = self.recursive_python_sweep( + qubits, + couplers, + sequence, + or_sequence, + *sweepers[1:], + execution_parameters=execution_parameters, + ) + results = self.merge_sweep_results(results, res) + return results + + def get_if_python_sweep( + self, sequence: PulseSequence, *sweepers: rfsoc.Sweeper + ) -> bool: + """Check if a sweeper must be run with python loop or on hardware. + + To be run on qick internal loop a sweep must: + * not be on the readout frequency + * not be a duration sweeper + * only one pulse per channel supported + * flux pulses are not compatible with sweepers + + Args: + sequence (`qibolab.pulses.PulseSequence`). Pulse sequence to play. + *sweepers (`qibosoq.abstract.Sweeper`): Sweeper objects. + Returns: + A boolean value true if the sweeper must be executed by python + loop, false otherwise + """ + if any(pulse.type is PulseType.FLUX for pulse in sequence): + return True + for sweeper in sweepers: + if all( + parameter is rfsoc.Parameter.BIAS for parameter in sweeper.parameters + ): + continue + if all( + parameter is rfsoc.Parameter.DELAY for parameter in sweeper.parameters + ): + continue + if any( + parameter is rfsoc.Parameter.DURATION + for parameter in sweeper.parameters + ): + return True + + for sweep_idx, parameter in enumerate(sweeper.parameters): + is_freq = parameter is rfsoc.Parameter.FREQUENCY + is_ro = sequence[sweeper.indexes[sweep_idx]].type == PulseType.READOUT + # if it's a sweep on the readout freq do a python sweep + if is_freq and is_ro: + return True + + for idx in sweeper.indexes: + sweep_pulse = sequence[idx] + channel = sweep_pulse.channel + ch_pulses = sequence.get_channel_pulses(channel) + if len(ch_pulses) > 1: + return True + # if all passed, do a firmware sweep + return False + + def convert_sweep_results( + self, + original_ro: PulseSequence, + qubits: dict[int, Qubit], + toti: list[list[list[float]]], + totq: list[list[list[float]]], + execution_parameters: ExecutionParameters, + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Convert sweep res to qibolab dict res. + + Args: + original_ro (`qibolab.pulses.PulseSequence`): Original PulseSequence + qubits (list): List of `qibolab.platforms.utils.Qubit` objects + passed from the platform. + toti (list): i values + totq (list): q values + results_type: qibolab results object + execution_parameters (`qibolab.ExecutionParameters`): Parameters (nshots, + relaxation_time, + fast_reset, + acquisition_type, + averaging_mode) + Returns: + A dict mapping the readout pulses serial to qibolab results objects + """ + results = {} + + adcs = np.unique([qubits[p.qubit].feedback.port.name for p in original_ro]) + for k, k_val in enumerate(adcs): + adc_ro = [ + pulse + for pulse in original_ro + if qubits[pulse.qubit].feedback.port.name == k_val + ] + for i, ro_pulse in enumerate(adc_ro): + i_vals = np.array(toti[k][i]) + q_vals = np.array(totq[k][i]) + + if not self.cfg.average: + i_vals = np.reshape(i_vals, (self.cfg.reps, *i_vals.shape[:-1])) + q_vals = np.reshape(q_vals, (self.cfg.reps, *q_vals.shape[:-1])) + + if ( + execution_parameters.acquisition_type + is AcquisitionType.DISCRIMINATION + ): + qubit = qubits[ro_pulse.qubit] + discriminated_shots = self.classify_shots(i_vals, q_vals, qubit) + result = self.convert_and_discriminate_samples( + discriminated_shots, execution_parameters + ) + + else: + result = execution_parameters.results_type(i_vals + 1j * q_vals) + + results[ro_pulse.qubit] = results[ro_pulse.serial] = result + return results + + def sweep( + self, + qubits: dict[int, Qubit], + couplers: dict[int, Coupler], + sequence: PulseSequence, + execution_parameters: ExecutionParameters, + *sweepers: Sweeper, + ) -> dict[str, Union[IntegratedResults, SampleResults]]: + """Execute the sweep and retrieves the readout results. + + Each readout pulse generates a separate acquisition. + The relaxation_time and the number of shots have default values. + + Args: + qubits (list): List of `qibolab.platforms.utils.Qubit` objects + passed from the platform. + execution_parameters (`qibolab.ExecutionParameters`): Parameters (nshots, + relaxation_time, + fast_reset, + acquisition_type, + averaging_mode) + sequence (`qibolab.pulses.PulseSequence`). Pulse sequence to play. + *sweepers (`qibolab.Sweeper`): Sweeper objects. + Returns: + A dictionary mapping the readout pulses serial and respective qubits to + results objects + """ + if couplers != {}: + raise NotImplementedError( + "The RFSoC driver currently does not support couplers." + ) + + self.validate_input_command(sequence, execution_parameters, sweep=True) + self.update_cfg(execution_parameters) + + if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: + self.cfg.average = False + else: + self.cfg.average = ( + execution_parameters.averaging_mode is AveragingMode.CYCLIC + ) + + rfsoc_sweepers = [convert(sweep, sequence, qubits) for sweep in sweepers] + + sweepsequence = sequence.copy() + + bias_change = any(sweep.parameter is BIAS for sweep in sweepers) + if bias_change: + initial_biases = [ + qubits[idx].flux.offset if qubits[idx].flux is not None else None + for idx in qubits + ] + + results = self.recursive_python_sweep( + qubits, + couplers, + sweepsequence, + sequence.ro_pulses, + *rfsoc_sweepers, + execution_parameters=execution_parameters, + ) + + if bias_change: + for idx, qubit in enumerate(qubits.values()): + if qubit.flux is not None: + qubit.flux.offset = initial_biases[idx] + + return self.reshape_sweep_results(results, sweepers, execution_parameters) From a97ab4ccfff1781fb27bfec4983d4834f990e85b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 6 Nov 2024 17:11:27 +0400 Subject: [PATCH 2/5] fix: update init / validate_input_command --- src/qibolab/_core/instruments/rfsoc/driver.py | 95 +++++++++++-------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/src/qibolab/_core/instruments/rfsoc/driver.py b/src/qibolab/_core/instruments/rfsoc/driver.py index bb8ae34420..d021c89184 100644 --- a/src/qibolab/_core/instruments/rfsoc/driver.py +++ b/src/qibolab/_core/instruments/rfsoc/driver.py @@ -1,7 +1,7 @@ """RFSoC FPGA driver.""" import re -from dataclasses import asdict, dataclass +from dataclasses import asdict from typing import Union import numpy as np @@ -10,29 +10,21 @@ from qibo.config import log from qibosoq import client -from qibolab import AcquisitionType, AveragingMode, ExecutionParameters -from qibolab.couplers import Coupler -from qibolab.instruments.abstract import Controller -from qibolab.instruments.port import Port -from qibolab.pulses import PulseSequence, PulseType -from qibolab.qubits import Qubit -from qibolab.result import AveragedSampleResults, IntegratedResults, SampleResults -from qibolab.sweeper import BIAS, Sweeper - +from qibolab._core import AcquisitionType, AveragingMode +from qibolab._core.couplers import Coupler +from qibolab._core.execution_parameters import ExecutionParameters +from qibolab._core.instruments.abstract import Controller +from qibolab._core.pulses import PulseType +from qibolab._core.qubits import Qubit +from qibolab._core.result import AveragedSampleResults, IntegratedResults, SampleResults +from qibolab._core.sequence import PulseSequence +from qibolab._core.sweeper import BIAS, Sweeper + +from .constants import NS_TO_US, SAMPLING_RATE from .convert import convert, convert_units_sweeper -HZ_TO_MHZ = 1e-6 -NS_TO_US = 1e-3 - - -@dataclass -class RFSoCPort(Port): - """Port object of the RFSoC.""" - - name: int - """DAC number.""" - offset: float = 0.0 - """Amplitude factor for biasing.""" +# update docstring +# self.host -> self.address class RFSoC(Controller): @@ -46,25 +38,30 @@ class RFSoC(Controller): cfg (rfsoc.Config): Configuration dictionary required for pulse execution. """ - PortType = RFSoCPort - - def __init__(self, name: str, address: str, port: int, sampling_rate: float = 1.0): - """Set server information and base configuration. - - Args: - name (str): Name of the instrument instance. - address (str): IP and port of the server (ex. 192.168.0.10) - port (int): Port of the server (ex.6000) - """ - super().__init__(name, address=address) - self.host = address - self.port = port - self.cfg = rfsoc.Config() - self._sampling_rate = sampling_rate + ### 1 + # Instrument + # name: str -> deprecated + # address: str + # settings: Optional[InstrumentSettings] = None + # Controller + # bounds: str + # channels: dict[ChannelId, Channel] = Field(default_factory=dict) + # RFSoC + # cfg: rfsoc.Config + # host: str -> remove / use self.address + # port: int + # sampling_rate: float = SAMPLING_RATE + + port: int + """Port of the server (ex.6000)""" + cfg: rfsoc.Config = rfsoc.Config() + """Configuration dataclass required for pulse execution.""" + bounds: str = "rfsoc/bounds" + """Maximum bounds used for batching in sequence unrolling.""" @property def sampling_rate(self): - return self._sampling_rate + return SAMPLING_RATE def connect(self): """Empty method to comply with Instrument interface.""" @@ -72,6 +69,8 @@ def connect(self): def disconnect(self): """Empty method to comply with Instrument interface.""" + # ======================================================================== + @staticmethod def _try_to_execute(server_commands, host, port): try: @@ -99,6 +98,8 @@ def convert_and_discriminate_samples(discriminated_shots, execution_parameters): result = execution_parameters.results_type(discriminated_shots) return result + ### 1 + # sequence.ro_pulses -> sequence.acquisitions @staticmethod def validate_input_command( sequence: PulseSequence, execution_parameters: ExecutionParameters, sweep: bool @@ -109,7 +110,7 @@ def validate_input_command( raise NotImplementedError( "Raw data acquisition is not compatible with sweepers" ) - if len(sequence.ro_pulses) != 1: + if len(sequence.acquisitions) != 1: raise NotImplementedError( "Raw data acquisition is compatible only with a single readout" ) @@ -184,7 +185,7 @@ def _execute_pulse_sequence( "sequence": convert(sequence, qubits, self.sampling_rate), "qubits": [asdict(convert(qubits[idx])) for idx in qubits], } - return self._try_to_execute(server_commands, self.host, self.port) + return self._try_to_execute(server_commands, self.address, self.port) def _execute_sweeps( self, @@ -211,8 +212,18 @@ def _execute_sweeps( "qubits": [asdict(convert(qubits[idx])) for idx in qubits], "sweepers": [sweeper.serialized for sweeper in converted_sweepers], } - return self._try_to_execute(server_commands, self.host, self.port) - + return self._try_to_execute(server_commands, self.address, self.port) + + ### + # def play( + # self, + # configs: dict[str, Config], + # sequences: list[PulseSequence], + # options: ExecutionParameters, + # sweepers: list[ParallelSweepers], + # ) -> dict[int, Result]: + # pass + # execution_parameters -> options def play( self, qubits: dict[int, Qubit], From 0723cba12ce8bca057ac59ab2b12557e5cad0b7b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 6 Nov 2024 17:19:48 +0400 Subject: [PATCH 3/5] fix: validate_input_command --- src/qibolab/_core/instruments/rfsoc/driver.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/qibolab/_core/instruments/rfsoc/driver.py b/src/qibolab/_core/instruments/rfsoc/driver.py index d021c89184..3bf779d53a 100644 --- a/src/qibolab/_core/instruments/rfsoc/driver.py +++ b/src/qibolab/_core/instruments/rfsoc/driver.py @@ -11,14 +11,16 @@ from qibosoq import client from qibolab._core import AcquisitionType, AveragingMode +from qibolab._core.components import Config from qibolab._core.couplers import Coupler from qibolab._core.execution_parameters import ExecutionParameters +from qibolab._core.identifier import Result from qibolab._core.instruments.abstract import Controller from qibolab._core.pulses import PulseType from qibolab._core.qubits import Qubit from qibolab._core.result import AveragedSampleResults, IntegratedResults, SampleResults from qibolab._core.sequence import PulseSequence -from qibolab._core.sweeper import BIAS, Sweeper +from qibolab._core.sweeper import BIAS, ParallelSweepers, Sweeper from .constants import NS_TO_US, SAMPLING_RATE from .convert import convert, convert_units_sweeper @@ -215,22 +217,21 @@ def _execute_sweeps( return self._try_to_execute(server_commands, self.address, self.port) ### + # execution_parameters -> options # def play( # self, - # configs: dict[str, Config], - # sequences: list[PulseSequence], - # options: ExecutionParameters, - # sweepers: list[ParallelSweepers], - # ) -> dict[int, Result]: - # pass - # execution_parameters -> options + # qubits: dict[int, Qubit], + # couplers: dict[int, Coupler], + # sequence: PulseSequence, + # execution_parameters: ExecutionParameters, + # ) -> dict[str, Union[IntegratedResults, SampleResults]]: def play( self, - qubits: dict[int, Qubit], - couplers: dict[int, Coupler], - sequence: PulseSequence, - execution_parameters: ExecutionParameters, - ) -> dict[str, Union[IntegratedResults, SampleResults]]: + configs: dict[str, Config], + sequences: list[PulseSequence], + options: ExecutionParameters, + sweepers: list[ParallelSweepers], + ) -> dict[int, Result]: """Execute the sequence of instructions and retrieves readout results. Each readout pulse generates a separate acquisition. @@ -254,7 +255,7 @@ def play( "The RFSoC driver currently does not support couplers." ) - self.validate_input_command(sequence, execution_parameters, sweep=False) + self.validate_input_command(sequences, options, sweep=(len(sweepers) != 0)) self.update_cfg(execution_parameters) if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: From 7d7aab36b8830d025458c43a9214b0b5324966d2 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 12 Nov 2024 12:42:18 +0400 Subject: [PATCH 4/5] fix: add dummy_qrc/platform.py & fix import --- .../_core/instruments/rfsoc/__init__.py | 6 +- .../_core/instruments/rfsoc/constants.py | 6 -- src/qibolab/_core/instruments/rfsoc/driver.py | 11 +++- src/qibolab/instruments/rfsoc.py | 7 +++ tests/dummy_qrc/rfsoc/platform.py | 59 +++++++++++++++++++ 5 files changed, 80 insertions(+), 9 deletions(-) delete mode 100644 src/qibolab/_core/instruments/rfsoc/constants.py create mode 100644 src/qibolab/instruments/rfsoc.py create mode 100644 tests/dummy_qrc/rfsoc/platform.py diff --git a/src/qibolab/_core/instruments/rfsoc/__init__.py b/src/qibolab/_core/instruments/rfsoc/__init__.py index 4b2ba2f1b3..dd63cf3815 100644 --- a/src/qibolab/_core/instruments/rfsoc/__init__.py +++ b/src/qibolab/_core/instruments/rfsoc/__init__.py @@ -1,3 +1,7 @@ """RFSoC module driver for qibosoq.""" -from .driver import RFSoC +from . import driver +from .driver import * + +__all__ = [] +__all__ += driver.__all__ diff --git a/src/qibolab/_core/instruments/rfsoc/constants.py b/src/qibolab/_core/instruments/rfsoc/constants.py deleted file mode 100644 index b697081285..0000000000 --- a/src/qibolab/_core/instruments/rfsoc/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Shared constants.""" - -SAMPLING_RATE = 2 -NANO_TO_SECONDS = 1e-9 -HZ_TO_MHZ = 1e-6 -NS_TO_US = 1e-3 diff --git a/src/qibolab/_core/instruments/rfsoc/driver.py b/src/qibolab/_core/instruments/rfsoc/driver.py index 3bf779d53a..1cc6708013 100644 --- a/src/qibolab/_core/instruments/rfsoc/driver.py +++ b/src/qibolab/_core/instruments/rfsoc/driver.py @@ -22,11 +22,15 @@ from qibolab._core.sequence import PulseSequence from qibolab._core.sweeper import BIAS, ParallelSweepers, Sweeper -from .constants import NS_TO_US, SAMPLING_RATE from .convert import convert, convert_units_sweeper # update docstring -# self.host -> self.address + +__all__ = ["RFSoC"] +SAMPLING_RATE = 9.8304 +NANO_TO_SECONDS = 1e-9 +HZ_TO_MHZ = 1e-6 +NS_TO_US = 1e-3 class RFSoC(Controller): @@ -73,6 +77,9 @@ def disconnect(self): # ======================================================================== + # def configure_channel(self, channel, config): + # pass + @staticmethod def _try_to_execute(server_commands, host, port): try: diff --git a/src/qibolab/instruments/rfsoc.py b/src/qibolab/instruments/rfsoc.py new file mode 100644 index 0000000000..ae8c7e1949 --- /dev/null +++ b/src/qibolab/instruments/rfsoc.py @@ -0,0 +1,7 @@ +"""RFSoC module driver for qibosoq.""" + +from qibolab._core.instruments import rfsoc +from qibolab._core.instruments.rfsoc import * # noqa: F403 + +__all__ = [] +__all__ += rfsoc.__all__ diff --git a/tests/dummy_qrc/rfsoc/platform.py b/tests/dummy_qrc/rfsoc/platform.py new file mode 100644 index 0000000000..a6c793b826 --- /dev/null +++ b/tests/dummy_qrc/rfsoc/platform.py @@ -0,0 +1,59 @@ +import pathlib + +from qibolab import AcquisitionChannel, DcChannel, IqChannel, Qubit +from qibolab.instruments.era import ERASynth +from qibolab.instruments.rfsoc import RFSoC +from qibolab.instruments.rohde_schwarz import SGS100A +from qibolab.platform import Platform + +FOLDER = pathlib.Path(__file__).parent + + +def create(): + """Dummy platform using QICK project on the RFSoC4x2 board. + + Used in ``test_instruments_rfsoc.py``. + """ + + qubit = Qubit.default("q0") + + # offset? + + channels = {} + + # Readout + assert qubit.probe is not None + channels[qubit.probe] = IqChannel( + device="L3-18_ro", path="0", mixer=None, lo="ErasynthLO" + ) + + # Acquire (feedback) + assert qubit.acquisition is not None + channels[qubit.acquisition] = AcquisitionChannel( + device="L2-RO", + path="0", + probe=qubit.probe, # twpa_pump="twpa_a" ? + ) + + # Drive + assert qubit.drive is not None + channels[qubit.drive] = IqChannel( + device="L3-18_qd", + path="1", + mixer=None, # lo="ErasynthLO" ? + ) + + # Flux + assert qubit.flux is not None + channels[qubit.flux] = DcChannel(device="L2-22_qf", path="2") + + lo_twpa = SGS100A(address="192.168.0.32") + lo_era = ERASynth(address="192.168.0.212", ethernet=True) + controller = RFSoC( + address="0.0.0.0", + channels=channels, + port=0, + ) + + instruments = {"tii_rfsoc4x2": controller, "twpa_a": lo_twpa, "ErasynthLO": lo_era} + return Platform.load(path=FOLDER, instruments=instruments, qubits={"q0": qubit}) From 83e7a1930ecd93a3ba0194e3f2edfbf1f1adc5a6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 13 Nov 2024 11:57:52 +0400 Subject: [PATCH 5/5] fix: add parameters.json --- .../_core/instruments/rfsoc/constants.py | 4 + .../_core/instruments/rfsoc/convert.py | 10 +- src/qibolab/_core/instruments/rfsoc/driver.py | 55 ++++++----- tests/dummy_qrc/rfsoc/calibration.json | 24 +++++ tests/dummy_qrc/rfsoc/parameters.json | 96 +++++++++++++++++++ tests/dummy_qrc/rfsoc/platform.py | 38 ++++---- 6 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 src/qibolab/_core/instruments/rfsoc/constants.py create mode 100644 tests/dummy_qrc/rfsoc/calibration.json create mode 100644 tests/dummy_qrc/rfsoc/parameters.json diff --git a/src/qibolab/_core/instruments/rfsoc/constants.py b/src/qibolab/_core/instruments/rfsoc/constants.py new file mode 100644 index 0000000000..093774ac09 --- /dev/null +++ b/src/qibolab/_core/instruments/rfsoc/constants.py @@ -0,0 +1,4 @@ +SAMPLING_RATE = 9.8304 +NANO_TO_SECONDS = 1e-9 +HZ_TO_MHZ = 1e-6 +NS_TO_US = 1e-3 diff --git a/src/qibolab/_core/instruments/rfsoc/convert.py b/src/qibolab/_core/instruments/rfsoc/convert.py index 1e036c95c7..8e9c2f7542 100644 --- a/src/qibolab/_core/instruments/rfsoc/convert.py +++ b/src/qibolab/_core/instruments/rfsoc/convert.py @@ -11,10 +11,9 @@ from qibolab._core.pulses import Pulse from qibolab._core.qubits import Qubit from qibolab._core.sequence import PulseSequence -from qibolab._core.sweeper import BIAS, DURATION, START, Parameter, Sweeper +from qibolab._core.sweeper import Parameter, Sweeper # , BIAS, DURATION, START -HZ_TO_MHZ = 1e-6 -NS_TO_US = 1e-3 +from .constants import HZ_TO_MHZ, NS_TO_US def replace_pulse_shape( @@ -108,11 +107,10 @@ def _( @convert.register -def _( - pulse: Pulse, qubits: dict[int, Qubit], start_delay: float, sampling_rate: float -) -> rfsoc_pulses.Pulse: +def _(pulse: Pulse, start_delay: float, sampling_rate: float) -> rfsoc_pulses.Pulse: """Convert `qibolab.pulses.pulse` to `qibosoq.abstract.Pulse`.""" pulse_type = pulse.type.name.lower() + # remove qubit, use channel instead dac = getattr(qubits[pulse.qubit], pulse_type).port.name adc = qubits[pulse.qubit].feedback.port.name if pulse_type == "readout" else None lo_frequency = pulse_lo_frequency(pulse, qubits) diff --git a/src/qibolab/_core/instruments/rfsoc/driver.py b/src/qibolab/_core/instruments/rfsoc/driver.py index 1cc6708013..6107e93007 100644 --- a/src/qibolab/_core/instruments/rfsoc/driver.py +++ b/src/qibolab/_core/instruments/rfsoc/driver.py @@ -22,15 +22,12 @@ from qibolab._core.sequence import PulseSequence from qibolab._core.sweeper import BIAS, ParallelSweepers, Sweeper +from .constants import NS_TO_US, SAMPLING_RATE from .convert import convert, convert_units_sweeper # update docstring __all__ = ["RFSoC"] -SAMPLING_RATE = 9.8304 -NANO_TO_SECONDS = 1e-9 -HZ_TO_MHZ = 1e-6 -NS_TO_US = 1e-3 class RFSoC(Controller): @@ -188,6 +185,9 @@ def _execute_pulse_sequence( Returns: Lists of I and Q value measured """ + + # TODO: pulse conversion + server_commands = { "operation_code": opcode, "cfg": asdict(self.cfg), @@ -257,26 +257,30 @@ def play( A dictionary mapping the readout pulses serial and respective qubits to qibolab results objects """ - if couplers != {}: - raise NotImplementedError( - "The RFSoC driver currently does not support couplers." - ) + # coupler check? + # if couplers != {}: + # raise NotImplementedError( + # "The RFSoC driver currently does not support couplers." + # ) + + # sequence -> sequences + # execution_parameters -> options self.validate_input_command(sequences, options, sweep=(len(sweepers) != 0)) - self.update_cfg(execution_parameters) + self.update_cfg(options) - if execution_parameters.acquisition_type is AcquisitionType.DISCRIMINATION: + if options.acquisition_type is AcquisitionType.DISCRIMINATION: self.cfg.average = False else: - self.cfg.average = ( - execution_parameters.averaging_mode is AveragingMode.CYCLIC - ) + self.cfg.average = options.averaging_mode is AveragingMode.CYCLIC - if execution_parameters.acquisition_type is AcquisitionType.RAW: + if options.acquisition_type is AcquisitionType.RAW: opcode = rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE_RAW else: opcode = rfsoc.OperationCode.EXECUTE_PULSE_SEQUENCE - toti, totq = self._execute_pulse_sequence(sequence, qubits, opcode) + + # + toti, totq = self._execute_pulse_sequence(sequences, qubits, opcode) results = {} probed_qubits = np.unique([p.qubit for p in sequence.ro_pulses]) @@ -286,28 +290,27 @@ def play( i_pulse = np.array(toti[j][i]) q_pulse = np.array(totq[j][i]) - if ( - execution_parameters.acquisition_type - is AcquisitionType.DISCRIMINATION - ): + if options.acquisition_type is AcquisitionType.DISCRIMINATION: discriminated_shots = self.classify_shots( i_pulse, q_pulse, qubits[ro_pulse.qubit] ) result = self.convert_and_discriminate_samples( - discriminated_shots, execution_parameters + discriminated_shots, options ) else: - result = execution_parameters.results_type(i_pulse + 1j * q_pulse) + result = options.results_type(i_pulse + 1j * q_pulse) results[ro_pulse.qubit] = results[ro_pulse.serial] = result return results - def update_cfg(self, execution_parameters: ExecutionParameters): + ### 1 + # execution_parameters -> options + def update_cfg(self, options: ExecutionParameters): """Update rfsoc.Config object with new parameters.""" - if execution_parameters.nshots is not None: - self.cfg.reps = execution_parameters.nshots - if execution_parameters.relaxation_time is not None: - self.cfg.relaxation_time = execution_parameters.relaxation_time * NS_TO_US + if options.nshots is not None: + self.cfg.reps = options.nshots + if options.relaxation_time is not None: + self.cfg.relaxation_time = options.relaxation_time * NS_TO_US def classify_shots( self, diff --git a/tests/dummy_qrc/rfsoc/calibration.json b/tests/dummy_qrc/rfsoc/calibration.json new file mode 100644 index 0000000000..03e6fbb3e2 --- /dev/null +++ b/tests/dummy_qrc/rfsoc/calibration.json @@ -0,0 +1,24 @@ +{ + "characterization": { + "single_qubit": { + "0": { + "readout_frequency": 7371258599, + "drive_frequency": 5542341844, + "pi_pulse_amplitude": 0.05284168507293318, + "T1": 10441.64173639732, + "T2": 4083.4697338939845, + "threshold": -0.8981346462690887, + "iq_angle": -1.2621946150226666, + "mean_gnd_states": [ + -0.17994037940379404, + -2.4709365853658536 + ], + "mean_exc_states": [ + 0.6854460704607047, + 0.24369105691056914 + ], + "T2_spin_echo": 5425.5448969467925 + } + } + } +} diff --git a/tests/dummy_qrc/rfsoc/parameters.json b/tests/dummy_qrc/rfsoc/parameters.json new file mode 100644 index 0000000000..eb5c904dc8 --- /dev/null +++ b/tests/dummy_qrc/rfsoc/parameters.json @@ -0,0 +1,96 @@ +{ + "settings": { + "nshots": 1024, + "relaxation_time": 100000 + }, + + "configs": { + "tii_rfsoc4x2": { + "kind": "bounds", + "waveforms": 0, + "readout": 0, + "instructions": 0 + }, + "twpa_a": { + "kind": "oscillator", + "frequency": 6200000000, + "power": -1 + }, + "ErasynthLO": { + "kind": "oscillator", + "frequency": 0, + "power": 0 + }, + "0/L3-18_ro": { + "kind": "iq", + "frequency": 7371258599 + }, + "0/L2-RO": { + "kind": "acquisition", + "delay": 0, + "smearing": 0.0, + "threshold": -0.8981346462690887, + "iq_angle": -1.2621946150226666 + }, + "0/L3-18_qd": { + "kind": "iq", + "frequency": 5542341844 + } + }, + + "native_gates": { + "single_qubit": { + "0": { + "RX": [ + [ + "0/L3-18_qd", + { + "kind": "pulse", + "duration": 30, + "amplitude": 0.05284168507293318, + "envelope": { + "kind": "rectangular" + }, + "relative_phase": 0.0 + } + ] + ], + "RX12": [ + [ + "0/L3-18_qd", + { + "kind": "pulse", + "duration": 30, + "amplitude": 0.05284168507293318, + "envelope": { + "kind": "rectangular" + }, + "relative_phase": 0.0 + } + ] + ], + "MZ": [ + [ + "0/L2-RO", + { + "kind": "readout", + "acquisition": { + "kind": "acquisition", + "duration": 600 + }, + "probe": { + "kind": "pulse", + "duration": 600, + "amplitude": 0.03, + "envelope": { + "kind": "rectangular" + }, + "relative_phase": 0.0 + } + } + ] + ] + } + } + } +} diff --git a/tests/dummy_qrc/rfsoc/platform.py b/tests/dummy_qrc/rfsoc/platform.py index a6c793b826..714a04e561 100644 --- a/tests/dummy_qrc/rfsoc/platform.py +++ b/tests/dummy_qrc/rfsoc/platform.py @@ -1,10 +1,10 @@ import pathlib -from qibolab import AcquisitionChannel, DcChannel, IqChannel, Qubit +from qibolab import AcquisitionChannel, DcChannel, IqChannel, Platform, Qubit from qibolab.instruments.era import ERASynth -from qibolab.instruments.rfsoc import RFSoC + +# from qibolab.instruments.rfsoc import RFSoC from qibolab.instruments.rohde_schwarz import SGS100A -from qibolab.platform import Platform FOLDER = pathlib.Path(__file__).parent @@ -15,45 +15,49 @@ def create(): Used in ``test_instruments_rfsoc.py``. """ - qubit = Qubit.default("q0") + qubit = Qubit( + probe="0/L3-18_ro", acquisition="0/L2-RO", drive="0/L3-18_qd", flux="0/L2-22_qf" + ) # offset? channels = {} - # Readout + # Readout (probe) assert qubit.probe is not None channels[qubit.probe] = IqChannel( - device="L3-18_ro", path="0", mixer=None, lo="ErasynthLO" + device="0/L3-18_ro", path="0", mixer=None, lo="ErasynthLO" ) # Acquire (feedback) assert qubit.acquisition is not None channels[qubit.acquisition] = AcquisitionChannel( - device="L2-RO", + device="0/L2-RO", path="0", - probe=qubit.probe, # twpa_pump="twpa_a" ? + probe=qubit.probe, + twpa_pump=None, # "twpa_a" ? ) # Drive assert qubit.drive is not None channels[qubit.drive] = IqChannel( - device="L3-18_qd", + device="0/L3-18_qd", path="1", - mixer=None, # lo="ErasynthLO" ? + mixer=None, + lo=None, # "ErasynthLO" ? ) # Flux assert qubit.flux is not None - channels[qubit.flux] = DcChannel(device="L2-22_qf", path="2") + channels[qubit.flux] = DcChannel(device="0/L2-22_qf", path="2") lo_twpa = SGS100A(address="192.168.0.32") lo_era = ERASynth(address="192.168.0.212", ethernet=True) - controller = RFSoC( - address="0.0.0.0", - channels=channels, - port=0, - ) + controller = lo_era # RFSoC( + # address="0.0.0.0", + # channels=channels, + # port=0, + # ) instruments = {"tii_rfsoc4x2": controller, "twpa_a": lo_twpa, "ErasynthLO": lo_era} - return Platform.load(path=FOLDER, instruments=instruments, qubits={"q0": qubit}) + return Platform.load(path=FOLDER, instruments=instruments, qubits={0: qubit})