From 44d4171ee49b5e3a952eacb3b139f9a6b413aaa7 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:02:38 +0400 Subject: [PATCH 1/6] get rid of parameters flux_pulse_amplitude and flux_pulse_duration --- .../two_qubit_interaction/virtual_z_phases.py | 48 +------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py index c4659b660..73f1797f1 100644 --- a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py +++ b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py @@ -38,10 +38,6 @@ class VirtualZPhasesParameters(Parameters): iSWAP and CZ are the possible options. """ - flux_pulse_amplitude: Optional[float] = None - """Amplitude of flux pulse implementing CZ.""" - flux_pulse_duration: Optional[float] = None - """Duration of flux pulse implementing CZ.""" dt: Optional[float] = 20 """Time delay between flux pulses and readout.""" parking: bool = True @@ -62,10 +58,6 @@ class VirtualZPhasesResults(Results): """Virtual Z phase correction.""" leakage: dict[QubitPairId, dict[QubitId, float]] """Leakage on control qubit for pair.""" - flux_pulse_amplitude: dict[QubitPairId, float] - """Amplitude of flux pulse implementing CZ.""" - flux_pulse_duration: dict[QubitPairId, int] - """Duration of flux pulse implementing CZ.""" def __contains__(self, key: QubitPairId): """Check if key is in class. @@ -88,8 +80,6 @@ class VirtualZPhasesData(Data): data: dict[tuple, npt.NDArray[VirtualZPhasesType]] = field(default_factory=dict) native: str = "CZ" thetas: list = field(default_factory=list) - amplitudes: dict[tuple[QubitId, QubitId], float] = field(default_factory=dict) - durations: dict[tuple[QubitId, QubitId], float] = field(default_factory=dict) def __getitem__(self, pair): return { @@ -113,9 +103,6 @@ def create_sequence( ) -> tuple[ PulseSequence, dict[QubitId, Pulse], - dict[QubitId, Pulse], - dict[QubitId, Pulse], - dict[QubitId, Pulse], ]: """Create the experiment PulseSequence.""" @@ -173,12 +160,8 @@ def create_sequence( if pulse.qubit not in ordered_pair: pulse.duration = theta_pulse.finish sequence.add(pulse) - return ( - sequence, - theta_pulse, - flux_sequence.get_qubit_pulses(ordered_pair[1])[0].amplitude, - flux_sequence.get_qubit_pulses(ordered_pair[1])[0].duration, - ) + + return sequence, theta_pulse def _acquisition( @@ -217,8 +200,6 @@ def _acquisition( ( sequence, theta_pulse, - data.amplitudes[ord_pair], - data.durations[ord_pair], ) = create_sequence( platform, setup, @@ -228,7 +209,6 @@ def _acquisition( params.native, params.dt, params.parking, - params.flux_pulse_amplitude, ) theta = np.arange( params.theta_start, @@ -342,8 +322,6 @@ def _fit( pass # exception covered above return VirtualZPhasesResults( native=data.native, - flux_pulse_amplitude=data.amplitudes, - flux_pulse_duration=data.durations, angle=angle, virtual_phase=virtual_phase, fitted_parameters=fitted_parameters, @@ -436,21 +414,6 @@ def _plot(data: VirtualZPhasesData, fit: VirtualZPhasesResults, target: QubitPai ) ) ) - fitting_report.add( - table_html( - table_dict( - [qubits[1], qubits[1]], - [ - "Flux pulse amplitude [a.u.]", - "Flux pulse duration [ns]", - ], - [ - np.round(data.amplitudes[qubits], 4), - np.round(data.durations[qubits], 4), - ], - ) - ) - ) fig1.update_layout( title_text=f"Phase correction Qubit {qubits[0]}", @@ -473,17 +436,10 @@ def _plot(data: VirtualZPhasesData, fit: VirtualZPhasesResults, target: QubitPai def _update(results: VirtualZPhasesResults, platform: Platform, target: QubitPairId): # FIXME: quick fix for qubit order - qubit_pair = tuple(sorted(target)) target = tuple(sorted(target)) update.virtual_phases( results.virtual_phase[target], results.native, platform, target ) - getattr(update, f"{results.native}_duration")( - results.flux_pulse_duration[target], platform, target - ) - getattr(update, f"{results.native}_amplitude")( - results.flux_pulse_amplitude[target], platform, target - ) correct_virtual_z_phases = Routine( From 1e307e1add635251d268a467fcadb83c62205c1a Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:16:08 +0400 Subject: [PATCH 2/6] get rid of argument amplitude, and rename argument duration to flux_pulse_max_duration --- .../two_qubit_interaction/virtual_z_phases.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py index 73f1797f1..2cac98d2d 100644 --- a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py +++ b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py @@ -98,8 +98,7 @@ def create_sequence( native: str, parking: bool, dt: float, - amplitude: float = None, - duration: float = None, + flux_pulse_max_duration: float = None, ) -> tuple[ PulseSequence, dict[QubitId, Pulse], @@ -118,11 +117,10 @@ def create_sequence( start=max(Y90_pulse.finish, RX_pulse_start.finish), ) - if amplitude is not None: - flux_sequence.get_qubit_pulses(ordered_pair[1])[0].amplitude = amplitude - - if duration is not None: - flux_sequence.get_qubit_pulses(ordered_pair[1])[0].duration = duration + if flux_pulse_max_duration is not None: + flux_sequence.get_qubit_pulses(ordered_pair[1])[ + 0 + ].duration = flux_pulse_max_duration theta_pulse = platform.create_RX90_pulse( target_qubit, start=flux_sequence.finish + dt, From 361f05931e3ae272e30d951ca7fe555184f2b91a Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:10:03 +0400 Subject: [PATCH 3/6] unify fitting code, and normalize phase difference calculation --- .../two_qubit_interaction/optimize.py | 42 ++++------- .../two_qubit_interaction/virtual_z_phases.py | 69 +++++++++++-------- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/optimize.py b/src/qibocal/protocols/two_qubit_interaction/optimize.py index cf2ce3216..ef80979de 100644 --- a/src/qibocal/protocols/two_qubit_interaction/optimize.py +++ b/src/qibocal/protocols/two_qubit_interaction/optimize.py @@ -11,7 +11,6 @@ from qibolab.platform import Platform from qibolab.qubits import QubitId, QubitPairId from qibolab.sweeper import Parameter, Sweeper, SweeperType -from scipy.optimize import curve_fit from qibocal import update from qibocal.auto.operation import Data, Parameters, Results, Routine @@ -19,7 +18,7 @@ from qibocal.protocols.utils import table_dict, table_html from .utils import order_pair -from .virtual_z_phases import create_sequence, fit_function +from .virtual_z_phases import create_sequence, fit_sinusoid, phase_diff @dataclass @@ -164,9 +163,8 @@ def _acquisition( for setup in ("I", "X"): ( sequence, + flux_pulse, theta_pulse, - amplitude, - data.durations[ord_pair], ) = create_sequence( platform, setup, @@ -176,7 +174,7 @@ def _acquisition( params.native, params.dt, params.parking, - params.flux_pulse_amplitude_min, + flux_pulse_max_duration=params.duration_max, ) theta = np.arange( params.theta_start, @@ -211,15 +209,15 @@ def _acquisition( sweeper_amplitude = Sweeper( Parameter.amplitude, - amplitude_range / amplitude, - pulses=[sequence.qf_pulses[0]], + amplitude_range / flux_pulse.amplitude, + pulses=[flux_pulse], type=SweeperType.FACTOR, ) sweeper_duration = Sweeper( Parameter.duration, duration_range, - pulses=[sequence.qf_pulses[0]], + pulses=[flux_pulse], type=SweeperType.ABSOLUTE, ) @@ -280,28 +278,12 @@ def _fit( ) ) ] - pguess = [ - np.max(target_data) - np.min(target_data), - np.mean(target_data), - np.pi, - ] try: - popt, _ = curve_fit( - fit_function, - np.array(data.thetas), - target_data, - p0=pguess, - bounds=( - (0, -np.max(target_data), 0), - (np.max(target_data), np.max(target_data), 2 * np.pi), - ), - ) - + params = fit_sinusoid(np.array(data.thetas), target_data) fitted_parameters[ target, control, setup, amplitude, duration - ] = popt.tolist() - + ] = params except Exception as e: log.warning( f"Fit failed for pair ({target, control}) due to {e}." @@ -312,13 +294,13 @@ def _fit( pair, list(pair)[::-1], ): - angles[target_q, control_q, amplitude, duration] = abs( + angles[target_q, control_q, amplitude, duration] = phase_diff( fitted_parameters[ target_q, control_q, "X", amplitude, duration - ][2] - - fitted_parameters[ + ][2], + fitted_parameters[ target_q, control_q, "I", amplitude, duration - ][2] + ][2], ) virtual_phases[ord_pair[0], ord_pair[1], amplitude, duration][ target_q diff --git a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py index 2cac98d2d..d450f6068 100644 --- a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py +++ b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases.py @@ -101,7 +101,8 @@ def create_sequence( flux_pulse_max_duration: float = None, ) -> tuple[ PulseSequence, - dict[QubitId, Pulse], + Pulse, + Pulse, ]: """Create the experiment PulseSequence.""" @@ -117,10 +118,10 @@ def create_sequence( start=max(Y90_pulse.finish, RX_pulse_start.finish), ) + flux_pulse = flux_sequence.get_qubit_pulses(ordered_pair[1])[0] if flux_pulse_max_duration is not None: - flux_sequence.get_qubit_pulses(ordered_pair[1])[ - 0 - ].duration = flux_pulse_max_duration + flux_pulse.duration = flux_pulse_max_duration + theta_pulse = platform.create_RX90_pulse( target_qubit, start=flux_sequence.finish + dt, @@ -159,7 +160,7 @@ def create_sequence( pulse.duration = theta_pulse.finish sequence.add(pulse) - return sequence, theta_pulse + return sequence, flux_pulse, theta_pulse def _acquisition( @@ -197,6 +198,7 @@ def _acquisition( for setup in ("I", "X"): ( sequence, + _, theta_pulse, ) = create_sequence( platform, @@ -245,12 +247,38 @@ def _acquisition( return data -def fit_function(x, amplitude, offset, phase): +def sinusoid(x, amplitude, offset, phase): """Sinusoidal fit function.""" - # return p0 + p1 * np.sin(2*np.pi*p2 * x + p3) return np.sin(x + phase) * amplitude + offset +def phase_diff(phase_1, phase_2): + """Return the phase difference of two sinusoids, normalized in the range [0, pi].""" + return np.arccos(np.cos(phase_1 - phase_2)) + + +def fit_sinusoid(thetas, data): + """Fit sinusoid to the given data.""" + pguess = [ + np.max(data) - np.min(data), + np.mean(data), + np.pi, + ] + + popt, _ = curve_fit( + sinusoid, + thetas, + data, + p0=pguess, + bounds=( + (0, -np.max(data), 0), + (np.max(data), np.max(data), 2 * np.pi), + ), + ) + + return popt.tolist() + + def _fit( data: VirtualZPhasesData, ) -> VirtualZPhasesResults: @@ -272,23 +300,9 @@ def _fit( leakage[pair] = {} for target, control, setup in data[pair]: target_data = data[pair][target, control, setup].target - pguess = [ - np.max(target_data) - np.min(target_data), - np.mean(target_data), - np.pi, - ] try: - popt, _ = curve_fit( - fit_function, - np.array(data.thetas), - target_data, - p0=pguess, - bounds=( - (0, -np.max(target_data), 0), - (np.max(target_data), np.max(target_data), 2 * np.pi), - ), - ) - fitted_parameters[target, control, setup] = popt.tolist() + params = fit_sinusoid(np.array(data.thetas), target_data) + fitted_parameters[target, control, setup] = params except Exception as e: log.warning(f"CZ fit failed for pair ({target, control}) due to {e}.") @@ -298,9 +312,9 @@ def _fit( pair, list(pair)[::-1], ): - angle[target_q, control_q] = abs( - fitted_parameters[target_q, control_q, "X"][2] - - fitted_parameters[target_q, control_q, "I"][2] + angle[target_q, control_q] = phase_diff( + fitted_parameters[target_q, control_q, "X"][2], + fitted_parameters[target_q, control_q, "I"][2], ) virtual_phase[pair][target_q] = -fitted_parameters[ target_q, control_q, "I" @@ -327,7 +341,6 @@ def _fit( ) -# TODO: remove str def _plot(data: VirtualZPhasesData, fit: VirtualZPhasesResults, target: QubitPairId): """Plot routine for VirtualZPhases.""" pair_data = data[target] @@ -382,7 +395,7 @@ def _plot(data: VirtualZPhasesData, fit: VirtualZPhasesResults, target: QubitPai fig.add_trace( go.Scatter( x=angle_range, - y=fit_function( + y=sinusoid( angle_range, *fitted_parameters, ), From b989a23fa9faf836e0a9799920d1ae62e7f26903 Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:31:02 +0400 Subject: [PATCH 4/6] adjust the heatmap range --- src/qibocal/protocols/two_qubit_interaction/optimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/optimize.py b/src/qibocal/protocols/two_qubit_interaction/optimize.py index ef80979de..dcf4845b9 100644 --- a/src/qibocal/protocols/two_qubit_interaction/optimize.py +++ b/src/qibocal/protocols/two_qubit_interaction/optimize.py @@ -409,7 +409,7 @@ def _plot( y=amps, z=cz, zmin=np.pi / 2, - zmax=3 * np.pi / 2, + zmax=np.pi, name="{fit.native} angle", colorbar_x=-0.1, colorscale="RdBu", From 18dd9239be069af2678b5e5633eed826ec0cb2eb Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:39:42 +0400 Subject: [PATCH 5/6] Update the vz signal experiment accordingly as well --- .../two_qubit_interaction/virtual_z_phases_signal.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases_signal.py b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases_signal.py index d4f54ec46..733790440 100644 --- a/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases_signal.py +++ b/src/qibocal/protocols/two_qubit_interaction/virtual_z_phases_signal.py @@ -75,9 +75,8 @@ def _acquisition( for setup in ("I", "X"): ( sequence, + _, theta_pulse, - data.amplitudes[ord_pair], - data.durations[ord_pair], ) = create_sequence( platform, setup, @@ -87,7 +86,6 @@ def _acquisition( params.native, params.dt, params.parking, - params.flux_pulse_amplitude, ) theta = np.arange( params.theta_start, From 2026610607936689613c2afe4ec61e5ddc7ea7cd Mon Sep 17 00:00:00 2001 From: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:59:08 +0400 Subject: [PATCH 6/6] update runcard in tests --- tests/runcards/protocols.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index ac8ecc407..5fb92f1fa 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -796,8 +796,6 @@ actions: theta_start: 0 theta_end: 180 theta_step: 10 - flux_pulse_amplitude: 0.5 - flux_pulse_duration: 10 dt: 0 native: iSWAP parking: True @@ -809,8 +807,6 @@ actions: theta_start: 0 theta_end: 180 theta_step: 10 - flux_pulse_amplitude: 0.5 - flux_pulse_duration: 10 native: iSWAP dt: 0 parking: True @@ -822,8 +818,6 @@ actions: theta_start: 0 theta_end: 180 theta_step: 10 - flux_pulse_amplitude: 0.5 - flux_pulse_duration: 10 dt: 0 native: CZ parking: True @@ -835,8 +829,6 @@ actions: theta_start: 0 theta_end: 180 theta_step: 10 - flux_pulse_amplitude: 0.5 - flux_pulse_duration: 10 native: CZ dt: 0 parking: True