From eef60f312efb43096e8b4c8548a77a9ccdb9e4e7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 31 Aug 2024 13:23:33 +0400 Subject: [PATCH] refactor: Clean up --- .../two_qubit_interaction/cryoscope.py | 93 ++++++----- .../cryoscope_amplitude.py | 153 +++++------------- 2 files changed, 95 insertions(+), 151 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/cryoscope.py b/src/qibocal/protocols/two_qubit_interaction/cryoscope.py index 4c697522c..7faa5fb33 100644 --- a/src/qibocal/protocols/two_qubit_interaction/cryoscope.py +++ b/src/qibocal/protocols/two_qubit_interaction/cryoscope.py @@ -22,6 +22,37 @@ from .filters import exponential_decay, single_exponential_correction +def exponential_decay(x, a, t): + return 1 + a * np.exp(-x / t) + + +def single_exponential_correction( + A: float, + tau: float, +): + """ + Calculate the best FIR and IIR filter taps to correct for an exponential decay + (undershoot or overshoot) of the shape + `1 + A * exp(-t/tau)`. + Args: + A: The exponential decay pre-factor. + tau: The time constant for the exponential decay, given in ns. + Returns: + A tuple of two items. + The first is a list of 2 FIR (feedforward) taps starting at 0 and spaced `Ts` apart. + The second is a single IIR (feedback) tap. + """ + tau *= 1e-9 + Ts = 1e-9 # sampling rate + k1 = Ts + 2 * tau * (A + 1) + k2 = Ts - 2 * tau * (A + 1) + c1 = Ts + 2 * tau + c2 = Ts - 2 * tau + feedback_tap = [-k2 / k1] + feedforward_taps = list(np.array([c1, c2]) / k1) + return feedforward_taps, feedback_tap + + @dataclass class CryoscopeParameters(Parameters): """Cryoscope runcard inputs.""" @@ -34,13 +65,11 @@ class CryoscopeParameters(Parameters): """Flux pulse duration step.""" flux_pulse_amplitude: float """Flux pulse amplitude.""" - padding: int = 0 - """Time padding before and after flux pulse.""" + parabola_coefficients: list[float] + """Coefficients computed using FluxAmplitudeDetuning.""" nshots: Optional[int] = None """Number of shots per point.""" - unrolling: bool = False - # flux_pulse_shapes - # TODO support different shapes, for now only rectangular + padding: int = 0 @dataclass @@ -174,28 +203,19 @@ def _acquisition( averaging_mode=AveragingMode.CYCLIC, ) - if params.unrolling: - results_x = platform.execute_pulse_sequences(sequences_x, options) - results_y = platform.execute_pulse_sequences(sequences_y, options) - elif not params.unrolling: - results_x = [ - platform.execute_pulse_sequence(sequence, options) - for sequence in sequences_x - ] - results_y = [ - platform.execute_pulse_sequence(sequence, options) - for sequence in sequences_y - ] + results_x = [ + platform.execute_pulse_sequence(sequence, options) for sequence in sequences_x + ] + results_y = [ + platform.execute_pulse_sequence(sequence, options) for sequence in sequences_y + ] for ig, (duration, ro_pulses) in enumerate( zip(duration_range, sequences_x_ro_pulses) ): for qubit in targets: serial = ro_pulses.get_qubit_pulses(qubit)[0].serial - if params.unrolling: - result = results_x[serial][ig] - else: - result = results_x[ig][serial] + result = results_x[ig][serial] data.register_qubit( CryoscopeType, (qubit, "MX"), @@ -210,19 +230,16 @@ def _acquisition( ): for qubit in targets: serial = ro_pulses.get_qubit_pulses(qubit)[0].serial - if params.unrolling: - result = results_y[serial][ig] - else: - result = results_y[ig][serial] - data.register_qubit( - CryoscopeType, - (qubit, "MY"), - dict( - duration=np.array([duration]), - prob_0=result.probability(state=0), - prob_1=result.probability(state=1), - ), - ) + result = results_y[ig][serial] + data.register_qubit( + CryoscopeType, + (qubit, "MY"), + dict( + duration=np.array([duration]), + prob_0=result.probability(state=0), + prob_1=result.probability(state=1), + ), + ) return data @@ -279,8 +296,12 @@ def _plot(data: CryoscopeData, fit: CryoscopeResults, target: QubitId): # col=1, # ) - coeffs = [-9.10575082, -7.28208663e-3, -4.73157701e-5] # D2 - coeffs = [-7.76584706, 2.25726809e-3, -3.76982885e-4] # D1 + # coeffs = [-9.10575082, -7.28208663e-3, -4.73157701e-5] # D2 + coeffs = [[-9.09948820, 5.52686083e-3, -7.42079805e-5]] # D2 + # coeffs = [-7.76584706, 2.25726809e-3, -3.76982885e-4] # D1 + coeffs = [-7.76812980, 4.00605656e-02, -3.88473996e-4] # D1 + coeffs = [-8.99178536, 5.19796241e-3, -1.61507231e-4] # D3 + # coeffs = [-8.99334695, -6.70786688e-4, -2.15611619e-04] # D3 # coeffs = [-9.10575082e+00, -7.28208663e-03, -4.73157701e-05] # with filters detuning = scipy.signal.savgol_filter( phase / 2 / np.pi, diff --git a/src/qibocal/protocols/two_qubit_interaction/cryoscope_amplitude.py b/src/qibocal/protocols/two_qubit_interaction/cryoscope_amplitude.py index 1a91d064a..aeada6380 100644 --- a/src/qibocal/protocols/two_qubit_interaction/cryoscope_amplitude.py +++ b/src/qibocal/protocols/two_qubit_interaction/cryoscope_amplitude.py @@ -1,4 +1,4 @@ -"""Cryoscope experiment, corrects distortions.""" +"""FluxAmplitudeDetuning experiment, corrects distortions.""" from dataclasses import dataclass, field from typing import Optional @@ -21,8 +21,8 @@ @dataclass -class CryoscopeParameters(Parameters): - """Cryoscope runcard inputs.""" +class FluxAmplitudeDetuningParameters(Parameters): + """FluxAmplitudeDetuning runcard inputs.""" amplitude_min: float """Minimum flux pulse amplitude.""" @@ -32,89 +32,39 @@ class CryoscopeParameters(Parameters): """Flux pulse amplitude step.""" flux_pulse_amplitude: float """Flux pulse duration.""" - - # duration_min: int - # """Minimum flux pulse duration.""" - # duration_max: int - # """Maximum flux duration start.""" - # duration_step: int - # """Flux pulse duration step.""" - flux_pulse_duration: float """Flux pulse duration.""" - padding: int = 20 - """Time padding before and after flux pulse.""" - dt: int = 0 - """Time delay between flux pulse and basis rotation.""" nshots: Optional[int] = None """Number of shots per point.""" - # flux_pulse_shapes - # TODO support different shapes, for now only rectangular - @dataclass -class CryoscopeResults(Results): - """Cryoscope outputs.""" +class FluxAmplitudeDetuningResults(Results): + """FluxAmplitudeDetuning outputs.""" - pass + flux_coefficients: dict[QubitId, float] = field(default_factory=dict) -# TODO: use probabilities -# CryoscopeType = np.dtype( -# [("amp", np.float64), ("duration", np.float64), ("prob", np.float64)] -# ) -CryoscopeType = np.dtype( - [("amplitude", float), ("prob_0", np.float64), ("prob_1", np.float64)] -) -"""Custom dtype for Cryoscope.""" +FluxAmplitudeDetuningType = np.dtype([("amplitude", float), ("prob", np.float64)]) +"""Custom dtype for FluxAmplitudeDetuning.""" @dataclass -class CryoscopeData(Data): - """Cryoscope acquisition outputs.""" +class FluxAmplitudeDetuningData(Data): + """FluxAmplitudeDetuning acquisition outputs.""" flux_pulse_duration: int - data: dict[tuple[QubitId, str], npt.NDArray[CryoscopeType]] = field( + data: dict[tuple[QubitId, str], npt.NDArray[FluxAmplitudeDetuningType]] = field( default_factory=dict ) - def register_qubit( - self, - qubit: QubitId, - tag: str, - amps: npt.NDArray[np.int32], - prob_0: npt.NDArray[np.float64], - prob_1: npt.NDArray[np.float64], - ): - """Store output for a single qubit.""" - # size = len(amps) * len(durs) - # amplitudes, durations = np.meshgrid(amps, durs) - - size = len(amps) - # durations = amps - - ar = np.empty(size, dtype=CryoscopeType) - ar["amplitude"] = amps.ravel() - ar["prob_0"] = prob_0.ravel() - ar["prob_1"] = prob_1.ravel() - - self.data[(qubit, tag)] = np.rec.array(ar) - - # def __getitem__(self, qubit): - # return { - # index: value - # for index, value in self.data.items() - # if set(qubit).issubset(index) - # } - def _acquisition( - params: CryoscopeParameters, + params: FluxAmplitudeDetuningParameters, platform: Platform, targets: list[QubitId], -) -> CryoscopeData: - # define sequences of pulses to be executed +) -> FluxAmplitudeDetuningData: + sequence_x = PulseSequence() sequence_y = PulseSequence() @@ -129,40 +79,25 @@ def _acquisition( initial_pulses[qubit] = platform.create_RX90_pulse( qubit, start=0, relative_phase=np.pi / 2 ) - - # TODO add support for flux pulse shapes - # if params.flux_pulse_shapes and len(params.flux_pulse_shapes) == len(qubits): - # flux_pulse_shape = eval(params.flux_pulse_shapes[qubit]) - # else: - # flux_pulse_shape = Rectangular() flux_pulse_shape = Rectangular() - flux_start = initial_pulses[qubit].finish + params.padding + flux_start = initial_pulses[qubit].finish # apply a detuning flux pulse flux_pulses[qubit] = FluxPulse( start=flux_start, duration=params.flux_pulse_duration, amplitude=params.flux_pulse_amplitude, shape=flux_pulse_shape, - channel=platform.qubits["D2"].flux.name, - qubit="D2", + channel=platform.qubits[qubit].flux.name, + qubit=qubit, ) - - # rotation_start = flux_start + params.duration_max + params.padding + params.dt - # rotation_start = flux_start + params.duration_max + params.padding + params.dt # rotate around the X axis RX(-pi/2) to measure Y component rx90_pulses[qubit] = platform.create_RX90_pulse( - qubit, - start=initial_pulses[qubit].finish - + flux_pulses[qubit].finish - + params.padding, - # relative_phase=np.pi, + qubit, start=initial_pulses[qubit].finish + flux_pulses[qubit].finish ) # rotate around the Y axis RX(-pi/2) to measure X component ry90_pulses[qubit] = platform.create_RX90_pulse( qubit, - start=initial_pulses[qubit].finish - + flux_pulses[qubit].finish - + params.padding, + start=initial_pulses[qubit].finish + flux_pulses[qubit].finish, relative_phase=np.pi / 2, ) @@ -184,13 +119,9 @@ def _acquisition( rx90_pulses[qubit], # rotate around X to measure Y CHECK ro_pulses[qubit], ) - print(sequence_x) amplitude_range = np.arange( params.amplitude_min, params.amplitude_max, params.amplitude_step ) - # duration_range = np.arange( - # params.duration_min, params.duration_max, params.duration_step - # ) amp_sweeper = Sweeper( Parameter.amplitude, @@ -199,45 +130,41 @@ def _acquisition( type=SweeperType.FACTOR, ) - # dur_sweeper = Sweeper( - # Parameter.duration, - # duration_range, - # pulses=list(flux_pulses.values()), - # type=SweeperType.ABSOLUTE, - # ) - options = ExecutionParameters( nshots=params.nshots, acquisition_type=AcquisitionType.DISCRIMINATION, averaging_mode=AveragingMode.CYCLIC, ) - data = CryoscopeData(flux_pulse_duration=params.flux_pulse_duration) + data = FluxAmplitudeDetuningData(flux_pulse_duration=params.flux_pulse_duration) for sequence, tag in [(sequence_x, "MX"), (sequence_y, "MY")]: - # results = platform.sweep(sequence, options, amp_sweeper, dur_sweeper) results = platform.sweep(sequence, options, amp_sweeper) for qubit in targets: result = results[ro_pulses[qubit].serial] data.register_qubit( - qubit, - tag, - amplitude_range * params.flux_pulse_amplitude, - result.probability(state=0), - result.probability(state=1), + FluxAmplitudeDetuningType, + (qubit, tag), + dict( + amplitude=amplitude_range * params.flux_pulse_amplitude, + prob=result.probability(state=1), + ), ) return data -def _fit(data: CryoscopeData) -> CryoscopeResults: - return CryoscopeResults() +def _fit(data: FluxAmplitudeDetuningData) -> FluxAmplitudeDetuningResults: + + return FluxAmplitudeDetuningResults() -def _plot(data: CryoscopeData, fit: CryoscopeResults, target: QubitId): - """Cryoscope plots.""" +def _plot( + data: FluxAmplitudeDetuningData, fit: FluxAmplitudeDetuningResults, target: QubitId +): + """FluxAmplitudeDetuning plots.""" figures = [] - fitting_report = f"Cryoscope of qubit {target}" + fitting_report = f"FluxAmplitudeDetuning of qubit {target}" fig = make_subplots( rows=3, @@ -252,7 +179,7 @@ def _plot(data: CryoscopeData, fit: CryoscopeResults, target: QubitId): fig.add_trace( go.Scatter( x=qubit_X_data.amplitude, - y=qubit_X_data.prob_1, + y=qubit_X_data.prob, name="X", legendgroup="X", ), @@ -263,18 +190,15 @@ def _plot(data: CryoscopeData, fit: CryoscopeResults, target: QubitId): fig.add_trace( go.Scatter( x=qubit_Y_data.amplitude, - y=qubit_Y_data.prob_1, + y=qubit_Y_data.prob, name="Y", legendgroup="Y", ), row=1, col=1, ) - - # minus sign for X_exp becuase I get -cos phase - X_exp = qubit_X_data.prob_1 - qubit_X_data.prob_0 - Y_exp = qubit_Y_data.prob_0 - qubit_Y_data.prob_1 - + X_exp = 2 * qubit_X_data.prob - 1 + Y_exp = 1 - 2 * qubit_Y_data.prob phase = np.angle(X_exp + 1.0j * Y_exp) fig.add_trace( go.Scatter( @@ -285,7 +209,6 @@ def _plot(data: CryoscopeData, fit: CryoscopeResults, target: QubitId): row=2, col=1, ) - # scipy.signal.savgol_filter(phase / 2 / np.pi, 5, 3, deriv=1, delta=1) fig.add_trace( go.Scatter( x=qubit_X_data.amplitude,