Skip to content

Commit

Permalink
Merge pull request #565 from qiboteam/coupler_spec
Browse files Browse the repository at this point in the history
Couplers Resonator Spec
  • Loading branch information
andrea-pasquale authored Nov 2, 2023
2 parents 158e7e6 + ea04864 commit 20fb7d0
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 20 deletions.
4 changes: 4 additions & 0 deletions src/qibocal/protocols/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from .coherence.t2_sequences import t2_sequences
from .coherence.zeno import zeno
from .coherence.zeno_msr import zeno_msr
from .couplers.coupler_qubit_spectroscopy import coupler_qubit_spectroscopy
from .couplers.coupler_resonator_spectroscopy import coupler_resonator_spectroscopy
from .dispersive_shift import dispersive_shift
from .dispersive_shift_qutrit import dispersive_shift_qutrit
from .fast_reset.fast_reset import fast_reset
Expand Down Expand Up @@ -103,3 +105,5 @@ class Operation(Enum):
qutrit_classification = qutrit_classification
resonator_amplitude = resonator_amplitude
dispersive_shift_qutrit = dispersive_shift_qutrit
coupler_resonator_spectroscopy = coupler_resonator_spectroscopy
coupler_qubit_spectroscopy = coupler_qubit_spectroscopy
Empty file.
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."""
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."""
73 changes: 73 additions & 0 deletions src/qibocal/protocols/characterization/couplers/utils.py
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
)
Loading

0 comments on commit 20fb7d0

Please sign in to comment.