-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #565 from qiboteam/coupler_spec
Couplers Resonator Spec
- Loading branch information
Showing
7 changed files
with
446 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
123 changes: 123 additions & 0 deletions
123
src/qibocal/protocols/characterization/couplers/coupler_qubit_spectroscopy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from typing import Optional | ||
|
||
import numpy as np | ||
from qibolab import AcquisitionType, AveragingMode, ExecutionParameters | ||
from qibolab.platform import Platform | ||
from qibolab.pulses import PulseSequence | ||
from qibolab.sweeper import Parameter, Sweeper, SweeperType | ||
|
||
from qibocal.auto.operation import Qubits, Routine | ||
|
||
from ..two_qubit_interaction.utils import order_pair | ||
from .coupler_resonator_spectroscopy import _fit, _plot, _update | ||
from .utils import CouplerSpectroscopyData, CouplerSpectroscopyParameters | ||
|
||
|
||
class CouplerSpectroscopyParametersQubit(CouplerSpectroscopyParameters): | ||
drive_duration: Optional[int] = 2000 | ||
"""Drive pulse duration to excite the qubit before the measurement""" | ||
|
||
|
||
def _acquisition( | ||
params: CouplerSpectroscopyParametersQubit, platform: Platform, qubits: Qubits | ||
) -> CouplerSpectroscopyData: | ||
""" | ||
Data acquisition for CouplerQubit spectroscopy. | ||
This consist on a frequency sweep on the qubit frequency while we change the flux coupler pulse amplitude of | ||
the coupler pulse. We expect to enable the coupler during the amplitude sweep and detect an avoided crossing | ||
that will be followed by the frequency sweep. This needs the qubits at resonance, the routine assumes a sweetspot | ||
value for the higher frequency qubit that moves it to the lower frequency qubit instead of trying to calibrate both pulses at once. This should be run after | ||
qubit_spectroscopy to further adjust the coupler sweetspot if needed and get some information | ||
on the flux coupler pulse amplitude requiered to enable 2q interactions. | ||
""" | ||
|
||
# TODO: Do we want to measure both qubits on the pair ? | ||
# Different acquisition, for now only measure one and reduce possible crosstalk. | ||
|
||
# create a sequence of pulses for the experiment: | ||
# Coupler pulse while Drive pulse - MZ | ||
|
||
sequence = PulseSequence() | ||
ro_pulses = {} | ||
qd_pulses = {} | ||
couplers = [] | ||
for i, pair in enumerate(qubits): | ||
qubit = platform.qubits[params.measured_qubits[i]].name | ||
# TODO: Qubit pair patch | ||
ordered_pair = order_pair(pair, platform.qubits) | ||
couplers.append(platform.pairs[tuple(sorted(ordered_pair))].coupler) | ||
|
||
ro_pulses[qubit] = platform.create_qubit_readout_pulse( | ||
qubit, start=params.drive_duration | ||
) | ||
qd_pulses[qubit] = platform.create_qubit_drive_pulse( | ||
qubit, start=0, duration=params.drive_duration | ||
) | ||
if params.amplitude is not None: | ||
qd_pulses[qubit].amplitude = params.amplitude | ||
|
||
sequence.add(qd_pulses[qubit]) | ||
sequence.add(ro_pulses[qubit]) | ||
|
||
# define the parameter to sweep and its range: | ||
delta_frequency_range = np.arange( | ||
-params.freq_width // 2, params.freq_width // 2, params.freq_step | ||
) | ||
|
||
sweeper_freq = Sweeper( | ||
Parameter.frequency, | ||
delta_frequency_range, | ||
pulses=[qd_pulses[qubit] for qubit in params.measured_qubits], | ||
type=SweeperType.OFFSET, | ||
) | ||
|
||
# define the parameter to sweep and its range: | ||
delta_bias_range = np.arange( | ||
-params.bias_width / 2, params.bias_width / 2, params.bias_step | ||
) | ||
|
||
# This sweeper is implemented in the flux pulse amplitude and we need it to be that way. | ||
sweeper_bias = Sweeper( | ||
Parameter.bias, | ||
delta_bias_range, | ||
couplers=couplers, | ||
type=SweeperType.ABSOLUTE, | ||
) | ||
|
||
data = CouplerSpectroscopyData( | ||
resonator_type=platform.resonator_type, | ||
) | ||
|
||
results = platform.sweep( | ||
sequence, | ||
ExecutionParameters( | ||
nshots=params.nshots, | ||
relaxation_time=params.relaxation_time, | ||
acquisition_type=AcquisitionType.INTEGRATION, | ||
averaging_mode=AveragingMode.CYCLIC, | ||
), | ||
sweeper_bias, | ||
sweeper_freq, | ||
) | ||
|
||
# retrieve the results for every qubit | ||
for i, pair in enumerate(qubits): | ||
# TODO: May measure both qubits on the pair | ||
qubit = platform.qubits[params.measured_qubits[i]].name | ||
# average msr, phase, i and q over the number of shots defined in the runcard | ||
result = results[ro_pulses[qubit].serial] | ||
# store the results | ||
data.register_qubit( | ||
qubit, | ||
msr=result.magnitude, | ||
phase=result.phase, | ||
freq=delta_frequency_range + qd_pulses[qubit].frequency, | ||
bias=delta_bias_range, | ||
) | ||
return data | ||
|
||
|
||
coupler_qubit_spectroscopy = Routine(_acquisition, _fit, _plot, _update) | ||
"""CouplerQubitSpectroscopy Routine object.""" |
177 changes: 177 additions & 0 deletions
177
src/qibocal/protocols/characterization/couplers/coupler_resonator_spectroscopy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
from typing import Optional | ||
|
||
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.auto.operation import Qubits, Routine | ||
|
||
from ..flux_dependence.utils import flux_dependence_plot | ||
from ..two_qubit_interaction.utils import order_pair | ||
from .utils import ( | ||
CouplerSpectroscopyData, | ||
CouplerSpectroscopyParameters, | ||
CouplerSpectroscopyResults, | ||
) | ||
|
||
|
||
class CouplerSpectroscopyParametersResonator(CouplerSpectroscopyParameters): | ||
readout_delay: Optional[int] = 1000 | ||
"""Readout delay before the measurement is done to let the flux coupler pulse act""" | ||
|
||
|
||
def _acquisition( | ||
params: CouplerSpectroscopyParametersResonator, platform: Platform, qubits: Qubits | ||
) -> CouplerSpectroscopyData: | ||
""" | ||
Data acquisition for CouplerResonator spectroscopy. | ||
This consist on a frequency sweep on the readout frequency while we change the flux coupler pulse amplitude of | ||
the coupler pulse. We expect to enable the coupler during the amplitude sweep and detect an avoided crossing | ||
that will be followed by the frequency sweep. No need to have the qubits at resonance. This should be run after | ||
resonator_spectroscopy to detect couplers and adjust the coupler sweetspot if needed and get some information | ||
on the flux coupler pulse amplitude requiered to enable 2q interactions. | ||
""" | ||
|
||
# TODO: Do we want to measure both qubits on the pair ? | ||
# Different acquisition, for now only measure one and reduce possible crosstalk. | ||
|
||
# create a sequence of pulses for the experiment: | ||
# Coupler pulse while MZ | ||
|
||
# taking advantage of multiplexing, apply the same set of gates to all qubits in parallel | ||
sequence = PulseSequence() | ||
ro_pulses = {} | ||
fx_pulses = {} | ||
couplers = [] | ||
|
||
for i, pair in enumerate(qubits): | ||
qubit = platform.qubits[params.measured_qubits[i]].name | ||
# TODO: Qubit pair patch | ||
ordered_pair = order_pair(pair, platform.qubits) | ||
coupler = platform.pairs[tuple(sorted(ordered_pair))].coupler | ||
couplers.append(coupler) | ||
|
||
# TODO: May measure both qubits on the pair | ||
ro_pulses[qubit] = platform.create_qubit_readout_pulse( | ||
qubit, start=params.readout_delay | ||
) | ||
if params.amplitude is not None: | ||
ro_pulses[qubit].amplitude = params.amplitude | ||
|
||
sequence.add(ro_pulses[qubit]) | ||
|
||
# define the parameter to sweep and its range: | ||
delta_frequency_range = np.arange( | ||
-params.freq_width // 2, params.freq_width // 2, params.freq_step | ||
) | ||
|
||
sweeper_freq = Sweeper( | ||
Parameter.frequency, | ||
delta_frequency_range, | ||
pulses=[ro_pulses[qubit] for qubit in params.measured_qubits], | ||
type=SweeperType.OFFSET, | ||
) | ||
|
||
# define the parameter to sweep and its range: | ||
delta_bias_range = np.arange( | ||
-params.bias_width / 2, params.bias_width / 2, params.bias_step | ||
) | ||
|
||
# This sweeper is implemented in the flux pulse amplitude and we need it to be that way. | ||
sweeper_bias = Sweeper( | ||
Parameter.bias, | ||
delta_bias_range, | ||
couplers=couplers, | ||
type=SweeperType.ABSOLUTE, | ||
) | ||
|
||
data = CouplerSpectroscopyData( | ||
resonator_type=platform.resonator_type, | ||
) | ||
|
||
results = platform.sweep( | ||
sequence, | ||
ExecutionParameters( | ||
nshots=params.nshots, | ||
relaxation_time=params.relaxation_time, | ||
acquisition_type=AcquisitionType.INTEGRATION, | ||
averaging_mode=AveragingMode.CYCLIC, | ||
), | ||
sweeper_bias, | ||
sweeper_freq, | ||
) | ||
|
||
# retrieve the results for every qubit | ||
for i, pair in enumerate(qubits): | ||
# TODO: May measure both qubits on the pair | ||
qubit = platform.qubits[params.measured_qubits[i]].name | ||
# average msr, phase, i and q over the number of shots defined in the runcard | ||
result = results[ro_pulses[qubit].serial] | ||
# store the results | ||
data.register_qubit( | ||
qubit, | ||
msr=result.magnitude, | ||
phase=result.phase, | ||
freq=delta_frequency_range + ro_pulses[qubit].frequency, | ||
bias=delta_bias_range, | ||
) | ||
return data | ||
|
||
|
||
def _fit(data: CouplerSpectroscopyData) -> CouplerSpectroscopyResults: | ||
"""Post-processing function for CouplerResonatorSpectroscopy.""" | ||
qubits = data.qubits | ||
pulse_amp = {} | ||
sweetspot = {} | ||
fitted_parameters = {} | ||
|
||
for qubit in qubits: | ||
# TODO: Implement fit | ||
"""It should get two things: | ||
Coupler sweetspot: the value that makes both features centered and symmetric | ||
Pulse_amp: That turn on the feature taking into account the shift introduced by the coupler sweetspot | ||
Issues: Coupler sweetspot it measured in volts while pulse_amp is a pulse amplitude, this routine just sweeps pulse amplitude | ||
and relies on manual shifting of that sweetspot by repeated scans as current chips are already symmetric for this feature. | ||
Maybe another routine sweeping the bias in volts would be needed and that sweeper implement on Zurich driver. | ||
""" | ||
# spot, amp, fitted_params = coupler_fit(data[qubit]) | ||
|
||
sweetspot[qubit] = 0 | ||
pulse_amp[qubit] = 0 | ||
fitted_parameters[qubit] = {} | ||
|
||
return CouplerSpectroscopyResults( | ||
pulse_amp=pulse_amp, | ||
sweetspot=sweetspot, | ||
fitted_parameters=fitted_parameters, | ||
) | ||
|
||
|
||
def _plot( | ||
data: CouplerSpectroscopyData, | ||
qubit, | ||
fit: CouplerSpectroscopyResults, | ||
): | ||
""" | ||
We may want to measure both qubits on the pair, | ||
that will require a different plotting that takes both. | ||
""" | ||
qubit_pair = qubit # TODO: Patch for 2q gate routines | ||
|
||
for qubit in qubit_pair: | ||
if qubit in data.data.keys(): | ||
return flux_dependence_plot(data, fit, qubit) | ||
|
||
|
||
def _update(results: CouplerSpectroscopyResults, platform: Platform, qubit: QubitId): | ||
pass | ||
|
||
|
||
coupler_resonator_spectroscopy = Routine(_acquisition, _fit, _plot, _update) | ||
"""CouplerResonatorSpectroscopy Routine object.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from dataclasses import dataclass, field | ||
from typing import Optional | ||
|
||
import numpy as np | ||
import numpy.typing as npt | ||
from qibolab.qubits import QubitId | ||
|
||
from qibocal.auto.operation import Data, Parameters, Results | ||
|
||
from ..flux_dependence.utils import create_data_array | ||
|
||
|
||
@dataclass | ||
class CouplerSpectroscopyParameters(Parameters): | ||
"""CouplerResonatorSpectroscopy and CouplerQubitSpectroscopy runcard inputs.""" | ||
|
||
bias_width: int | ||
"""Width for bias (V).""" | ||
bias_step: int | ||
"""Frequency step for bias sweep (V).""" | ||
freq_width: int | ||
"""Width for frequency sweep relative to the readout frequency (Hz).""" | ||
freq_step: int | ||
"""Frequency step for frequency sweep (Hz).""" | ||
# TODO: It may be better not to use readout multiplex to avoid readout crosstalk | ||
measured_qubits: list[QubitId] | ||
"""Qubit to readout from the pair""" | ||
amplitude: Optional[float] = None | ||
"""Readout or qubit drive amplitude (optional). If defined, same amplitude will be used in all qubits. | ||
Otherwise the default amplitude defined on the platform runcard will be used""" | ||
nshots: Optional[int] = None | ||
"""Number of shots.""" | ||
relaxation_time: Optional[int] = None | ||
"""Relaxation time (ns).""" | ||
|
||
|
||
CouplerSpecType = np.dtype( | ||
[ | ||
("freq", np.float64), | ||
("bias", np.float64), | ||
("msr", np.float64), | ||
("phase", np.float64), | ||
] | ||
) | ||
"""Custom dtype for coupler resonator spectroscopy.""" | ||
|
||
|
||
@dataclass | ||
class CouplerSpectroscopyResults(Results): | ||
"""CouplerResonatorSpectroscopy or CouplerQubitSpectroscopy outputs.""" | ||
|
||
sweetspot: dict[QubitId, float] | ||
"""Sweetspot for each coupler.""" | ||
pulse_amp: dict[QubitId, float] | ||
"""Pulse amplitude for the coupler.""" | ||
fitted_parameters: dict[QubitId, dict[str, float]] | ||
"""Raw fitted parameters.""" | ||
|
||
|
||
@dataclass | ||
class CouplerSpectroscopyData(Data): | ||
"""Data structure for CouplerResonatorSpectroscopy or CouplerQubitSpectroscopy.""" | ||
|
||
resonator_type: str | ||
"""Resonator type.""" | ||
data: dict[QubitId, npt.NDArray[CouplerSpecType]] = field(default_factory=dict) | ||
"""Raw data acquired.""" | ||
|
||
def register_qubit(self, qubit, freq, bias, msr, phase): | ||
"""Store output for single qubit.""" | ||
self.data[qubit] = create_data_array( | ||
freq, bias, msr, phase, dtype=CouplerSpecType | ||
) |
Oops, something went wrong.