diff --git a/src/qibocal/protocols/characterization/__init__.py b/src/qibocal/protocols/characterization/__init__.py index 53e81224f..e4664477a 100644 --- a/src/qibocal/protocols/characterization/__init__.py +++ b/src/qibocal/protocols/characterization/__init__.py @@ -23,7 +23,9 @@ resonator_flux, ) from .qubit_spectroscopy import qubit_spectroscopy +from .qubit_spectroscopy_ef import qubit_spectroscopy_ef from .rabi.amplitude import rabi_amplitude +from .rabi.ef import rabi_amplitude_ef from .rabi.length import rabi_length from .rabi.length_sequences import rabi_length_sequences from .ramsey import ramsey @@ -88,4 +90,6 @@ class Operation(Enum): readout_mitigation_matrix = readout_mitigation_matrix twpa_frequency = twpa_frequency twpa_power = twpa_power + rabi_amplitude_ef = rabi_amplitude_ef + qubit_spectroscopy_ef = qubit_spectroscopy_ef resonator_amplitude = resonator_amplitude diff --git a/src/qibocal/protocols/characterization/allxy/allxy.py b/src/qibocal/protocols/characterization/allxy/allxy.py index a27de5962..59abd46a6 100644 --- a/src/qibocal/protocols/characterization/allxy/allxy.py +++ b/src/qibocal/protocols/characterization/allxy/allxy.py @@ -102,7 +102,9 @@ def _acquisition( z_proj = 2 * results[ro_pulses[qubit].serial].probability(0) - 1 # store the results gate = "-".join(gates) - data.register_qubit(AllXYType, (qubit), dict(prob=z_proj, gate=gate)) + data.register_qubit( + AllXYType, (qubit), dict(prob=np.array([z_proj]), gate=np.array([gate])) + ) # finally, save the remaining data return data diff --git a/src/qibocal/protocols/characterization/qubit_spectroscopy_ef.py b/src/qibocal/protocols/characterization/qubit_spectroscopy_ef.py new file mode 100644 index 000000000..f45d3c101 --- /dev/null +++ b/src/qibocal/protocols/characterization/qubit_spectroscopy_ef.py @@ -0,0 +1,181 @@ +from dataclasses import asdict, dataclass, field + +import numpy as np +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.platform import Platform +from qibolab.pulses import PulseSequence +from qibolab.qubits import QubitId +from qibolab.sweeper import Parameter, Sweeper, SweeperType + +from qibocal import update +from qibocal.auto.operation import Qubits, Routine + +from .qubit_spectroscopy import ( + QubitSpectroscopyData, + QubitSpectroscopyParameters, + QubitSpectroscopyResults, + _fit, +) +from .resonator_spectroscopy import ResSpecType +from .utils import GHZ_TO_HZ, HZ_TO_GHZ, spectroscopy_plot, table_dict, table_html + +DEFAULT_ANHARMONICITY = 300e6 +"""Initial guess for anharmonicity.""" + + +@dataclass +class QubitSpectroscopyEFParameters(QubitSpectroscopyParameters): + """QubitSpectroscopyEF runcard inputs.""" + + +@dataclass +class QubitSpectroscopyEFResults(QubitSpectroscopyResults): + """QubitSpectroscopyEF outputs.""" + + anharmonicity: dict[QubitId, float] = field(default_factory=dict) + + +@dataclass +class QubitSpectroscopyEFData(QubitSpectroscopyData): + """QubitSpectroscopy acquisition outputs.""" + + drive_frequencies: dict[QubitId, float] = field(default_factory=dict) + + +def _fit_ef(data: QubitSpectroscopyEFData) -> QubitSpectroscopyEFResults: + results = _fit(data) + anharmoncities = { + qubit: data.drive_frequencies[qubit] * HZ_TO_GHZ - results.frequency[qubit] + for qubit in data.qubits + } + params = asdict(results) + params.update({"anharmonicity": anharmoncities}) + + return QubitSpectroscopyEFResults(**params) + + +def _acquisition( + params: QubitSpectroscopyEFParameters, platform: Platform, qubits: Qubits +) -> QubitSpectroscopyEFData: + """Data acquisition for qubit spectroscopy ef protocol. + + Similar to a qubit spectroscopy with the difference that the qubit is first + excited to the state 1. This protocols aims at finding the transition frequency between + state 1 and the state 2. The anharmonicity is also computed. + + If the RX12 frequency is not present in the runcard the sweep is performed around the + qubit drive frequency shifted by DEFAULT_ANHARMONICITY, an hardcoded parameter editable + in this file. + + """ + # create a sequence of pulses for the experiment: + # long drive probing pulse - MZ + + # taking advantage of multiplexing, apply the same set of gates to all qubits in parallel + sequence = PulseSequence() + ro_pulses = {} + qd_pulses = {} + rx_pulses = {} + amplitudes = {} + drive_frequencies = {} + for qubit in qubits: + rx_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) + drive_frequencies[qubit] = rx_pulses[qubit].frequency + qd_pulses[qubit] = platform.create_qubit_drive_pulse( + qubit, start=rx_pulses[qubit].finish, duration=params.drive_duration + ) + if platform.qubits[qubit].native_gates.RX12.frequency is None: + qd_pulses[qubit].frequency = ( + rx_pulses[qubit].frequency - DEFAULT_ANHARMONICITY + ) + else: + qd_pulses[qubit].frequency = platform.qubits[ + qubit + ].native_gates.RX12.frequency + + if params.drive_amplitude is not None: + qd_pulses[qubit].amplitude = params.drive_amplitude + + amplitudes[qubit] = qd_pulses[qubit].amplitude + + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) + sequence.add(rx_pulses[qubit]) + sequence.add(qd_pulses[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + # sweep only before qubit frequency + delta_frequency_range = np.arange( + -params.freq_width, params.freq_width, params.freq_step + ) + sweeper = Sweeper( + Parameter.frequency, + delta_frequency_range, + pulses=[qd_pulses[qubit] for qubit in qubits], + type=SweeperType.OFFSET, + ) + + # Create data structure for data acquisition. + data = QubitSpectroscopyEFData( + resonator_type=platform.resonator_type, + amplitudes=amplitudes, + drive_frequencies=drive_frequencies, + ) + + results = platform.sweep( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + sweeper, + ) + + # retrieve the results for every qubit + for qubit, ro_pulse in ro_pulses.items(): + # average msr, phase, i and q over the number of shots defined in the runcard + result = results[ro_pulse.serial] + # store the results + data.register_qubit( + ResSpecType, + (qubit), + dict( + msr=result.magnitude, + phase=result.phase, + freq=delta_frequency_range + qd_pulses[qubit].frequency, + ), + ) + return data + + +def _plot(data: QubitSpectroscopyEFData, qubit, fit: QubitSpectroscopyEFResults): + """Plotting function for QubitSpectroscopy.""" + figures, report = spectroscopy_plot(data, qubit, fit) + if fit is not None: + report = table_html( + table_dict( + qubit, + ["Frequency 1->2", "Amplitude", "Anharmonicity"], + [ + np.round(fit.frequency[qubit] * GHZ_TO_HZ, 0), + fit.amplitude[qubit], + np.round(fit.anharmonicity[qubit] * GHZ_TO_HZ, 0), + ], + ) + ) + + return figures, report + + +def _update(results: QubitSpectroscopyEFResults, platform: Platform, qubit: QubitId): + """Update w12 frequency""" + update.frequency_12_transition(results.frequency[qubit], platform, qubit) + update.anharmonicity(results.anharmonicity[qubit], platform, qubit) + + +qubit_spectroscopy_ef = Routine(_acquisition, _fit_ef, _plot, _update) +"""QubitSpectroscopyEF Routine object.""" diff --git a/src/qibocal/protocols/characterization/rabi/ef.py b/src/qibocal/protocols/characterization/rabi/ef.py new file mode 100644 index 000000000..bff13b562 --- /dev/null +++ b/src/qibocal/protocols/characterization/rabi/ef.py @@ -0,0 +1,124 @@ +from dataclasses import dataclass + +import numpy as np +from qibolab import AcquisitionType, AveragingMode, ExecutionParameters +from qibolab.platform import Platform +from qibolab.pulses import PulseSequence +from qibolab.qubits import QubitId +from qibolab.sweeper import Parameter, Sweeper, SweeperType + +from qibocal import update +from qibocal.auto.operation import Qubits, Routine + +from . import amplitude, utils + + +@dataclass +class RabiAmplitudeEFParameters(amplitude.RabiAmplitudeParameters): + """RabiAmplitudeEF runcard inputs.""" + + +@dataclass +class RabiAmplitudeEFResults(amplitude.RabiAmplitudeResults): + """RabiAmplitudeEF outputs.""" + + +@dataclass +class RabiAmplitudeEFData(amplitude.RabiAmplitudeData): + """RabiAmplitude data acquisition.""" + + +def _acquisition( + params: RabiAmplitudeEFParameters, platform: Platform, qubits: Qubits +) -> RabiAmplitudeEFData: + r""" + Data acquisition for Rabi EF experiment sweeping amplitude. + + The rabi protocol is performed after exciting the qubit to state 1. + This protocol allows to compute the amplitude of the RX12 pulse to excite + the qubit to state 2 starting from state 1. + + """ + + # create a sequence of pulses for the experiment + sequence = PulseSequence() + qd_pulses = {} + ro_pulses = {} + rx_pulses = {} + durations = {} + for qubit in qubits: + rx_pulses[qubit] = platform.create_RX_pulse(qubit, start=0) + qd_pulses[qubit] = platform.create_RX_pulse( + qubit, start=rx_pulses[qubit].finish + ) + if params.pulse_length is not None: + qd_pulses[qubit].duration = params.pulse_length + + durations[qubit] = qd_pulses[qubit].duration + ro_pulses[qubit] = platform.create_qubit_readout_pulse( + qubit, start=qd_pulses[qubit].finish + ) + sequence.add(rx_pulses[qubit]) + sequence.add(qd_pulses[qubit]) + sequence.add(ro_pulses[qubit]) + + # define the parameter to sweep and its range: + # qubit drive pulse amplitude + qd_pulse_amplitude_range = np.arange( + params.min_amp_factor, + params.max_amp_factor, + params.step_amp_factor, + ) + sweeper = Sweeper( + Parameter.amplitude, + qd_pulse_amplitude_range, + [qd_pulses[qubit] for qubit in qubits], + type=SweeperType.FACTOR, + ) + + # create a DataUnits object to store the results, + # DataUnits stores by default MSR, phase, i, q + # additionally include qubit drive pulse amplitude + data = RabiAmplitudeEFData(durations=durations) + + # sweep the parameter + results = platform.sweep( + sequence, + ExecutionParameters( + nshots=params.nshots, + relaxation_time=params.relaxation_time, + acquisition_type=AcquisitionType.INTEGRATION, + averaging_mode=AveragingMode.CYCLIC, + ), + sweeper, + ) + for qubit in qubits: + # average msr, phase, i and q over the number of shots defined in the runcard + result = results[ro_pulses[qubit].serial] + data.register_qubit( + amplitude.RabiAmpType, + (qubit), + dict( + amp=qd_pulses[qubit].amplitude * qd_pulse_amplitude_range, + msr=result.magnitude, + phase=result.phase, + ), + ) + return data + + +def _plot(data: RabiAmplitudeEFData, qubit, fit: RabiAmplitudeEFResults = None): + """Plotting function for RabiAmplitude.""" + figures, report = utils.plot(data, qubit, fit) + if report is not None: + report = report.replace("Pi pulse", "Pi pulse 12") + return figures, report + + +def _update(results: RabiAmplitudeEFResults, platform: Platform, qubit: QubitId): + """Update RX2 amplitude""" + update.drive_12_amplitude(results.amplitude[qubit], platform, qubit) + + +rabi_amplitude_ef = Routine(_acquisition, amplitude._fit, _plot, _update) +"""RabiAmplitudeEF Routine object.""" diff --git a/src/qibocal/protocols/characterization/rabi/utils.py b/src/qibocal/protocols/characterization/rabi/utils.py index 177b16162..84b1bc6ca 100644 --- a/src/qibocal/protocols/characterization/rabi/utils.py +++ b/src/qibocal/protocols/characterization/rabi/utils.py @@ -26,7 +26,7 @@ def rabi_length_fit(x, p0, p1, p2, p3, p4): def plot(data, qubit, fit): - if data.__class__.__name__ == "RabiAmplitudeData": + if "RabiAmplitude" in data.__class__.__name__: quantity = "amp" title = "Amplitude (dimensionless)" fitting = rabi_amplitude_fit @@ -44,7 +44,7 @@ def plot(data, qubit, fit): horizontal_spacing=0.1, vertical_spacing=0.1, subplot_titles=( - "MSR (V)", + "MSR (uV)", "phase (rad)", ), ) diff --git a/src/qibocal/protocols/characterization/two_qubit_interaction/cz_virtualz.py b/src/qibocal/protocols/characterization/two_qubit_interaction/cz_virtualz.py index 4b080a5b4..aac273781 100644 --- a/src/qibocal/protocols/characterization/two_qubit_interaction/cz_virtualz.py +++ b/src/qibocal/protocols/characterization/two_qubit_interaction/cz_virtualz.py @@ -395,8 +395,8 @@ def _plot(data: CZVirtualZData, fit: CZVirtualZResults, qubit): def _update(results: CZVirtualZResults, platform: Platform, qubit_pair: QubitPairId): - if qubit_pair[0] > qubit_pair[1]: - qubit_pair = (qubit_pair[1], qubit_pair[0]) + # FIXME: quick fix for qubit order + qubit_pair = tuple(sorted(qubit_pair)) update.virtual_phases(results.virtual_phase[qubit_pair], platform, qubit_pair) diff --git a/src/qibocal/update.py b/src/qibocal/update.py index 34845cf82..aae29d3ef 100644 --- a/src/qibocal/update.py +++ b/src/qibocal/update.py @@ -169,8 +169,14 @@ def drag_pulse_beta(beta: float, platform: Platform, qubit: QubitId): def sweetspot(sweetspot: float, platform: Platform, qubit: QubitId): platform.qubits[qubit].sweetspot = float(sweetspot) - if platform.qubits[qubit].flux is not None: - platform.qubits[qubit].flux.offset = sweetspot + + +def frequency_12_transition(frequency: int, platform: Platform, qubit: QubitId): + platform.qubits[qubit].native_gates.RX12.frequency = int(frequency * GHZ_TO_HZ) + + +def drive_12_amplitude(amplitude: float, platform: Platform, qubit: QubitId): + platform.qubits[qubit].native_gates.RX12.amplitude = float(amplitude) def twpa_frequency(frequency: int, platform: Platform, qubit: QubitId): @@ -179,3 +185,7 @@ def twpa_frequency(frequency: int, platform: Platform, qubit: QubitId): def twpa_power(power: float, platform: Platform, qubit: QubitId): platform.qubits[qubit].twpa.local_oscillator.power = float(power) + + +def anharmonicity(anharmonicity: float, platform: Platform, qubit: QubitId): + platform.qubits[qubit].anharmonicity = int(anharmonicity * GHZ_TO_HZ) diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index 1be398aff..c390063b5 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -93,6 +93,18 @@ actions: nshots: 10 + - id: qubit spectroscopy ef + priority: 0 + operation: qubit_spectroscopy_ef + #FIXME: add RX12 for qubit 4 + qubits: [0, 1, 2, 3] + parameters: + drive_amplitude: 0.001 + drive_duration: 1000 + freq_width: 2_000_000 + freq_step: 500_000 + nshots: 10 + - id: resonator flux dependence priority: 0 @@ -174,6 +186,17 @@ actions: pulse_length: 30 nshots: 1024 + - id: rabi_ef + priority: 0 + operation: rabi_amplitude_ef + #FIXME: add RX12 for qubit 4 + qubits: [0, 1, 2, 3] + parameters: + min_amp_factor: 0.0 + max_amp_factor: 1.0 + step_amp_factor: 0.1 + pulse_length: 30 + nshots: 1024 - id: rabi length priority: 0 diff --git a/tests/test_update.py b/tests/test_update.py index c9f7a921b..d3701045d 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -150,6 +150,18 @@ def test_sweetspot_update(qubit): assert qubit.sweetspot == RANDOM_FLOAT +# FIXME: missing qubit 4 RX12 +@pytest.mark.parametrize("qubit", QUBITS[:-1]) +def test_12_transition_update(qubit): + update.drive_12_amplitude(RANDOM_FLOAT, PLATFORM, qubit.name) + update.frequency_12_transition(FREQUENCIES_GHZ, PLATFORM, qubit.name) + update.anharmonicity(FREQUENCIES_GHZ, PLATFORM, qubit.name) + + assert qubit.native_gates.RX12.amplitude == RANDOM_FLOAT + assert qubit.native_gates.RX12.frequency == FREQUENCIES_HZ + assert qubit.anharmonicity == FREQUENCIES_HZ + + @pytest.mark.parametrize("qubit", QUBITS) def test_twpa_update(qubit): update.twpa_frequency(RANDOM_INT, PLATFORM, qubit.name)