Source code for qiskit_experiments.library.driven_freq_tuning.p1_spect

# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""P1 experiment at various qubit frequencies."""

from __future__ import annotations

from collections.abc import Sequence

import numpy as np
from qiskit import pulse
from qiskit.circuit import QuantumCircuit, Gate, Parameter, ParameterExpression
from qiskit.providers.backend import Backend
from qiskit.utils import optionals as _optional

from qiskit_experiments.framework import BackendTiming, BaseExperiment, Options
from .p1_spect_analysis import StarkP1SpectAnalysis

from .coefficients import (
    StarkCoefficients,
    retrieve_coefficients_from_backend,
)

if _optional.HAS_SYMENGINE:
    import symengine as sym
else:
    import sympy as sym


[docs] class StarkP1Spectroscopy(BaseExperiment): """P1 spectroscopy experiment with Stark tone. # section: overview This experiment measures a probability of the excitation state of the qubit with a certain delay after excitation. A Stark tone is applied during this delay to move the qubit frequency to conduct a spectroscopy of qubit relaxation quantity. .. parsed-literal:: ┌───┐┌──────────────────┐┌─┐ q: ┤ X ├┤ Stark(stark_amp) ├┤M├ └───┘└──────────────────┘└╥┘ c: 1/══════════════════════════╩═ 0 Since the qubit relaxation rate may depend on the qubit frequency due to the coupling to nearby energy levels, this experiment is useful to find out lossy operation frequency that might be harmful to the gate fidelity [1]. # section: analysis_ref :class:`qiskit_experiments.library.driven_freq_tuning.StarkP1SpectAnalysis` # section: reference .. ref_arxiv:: 1 2105.15201 # section: see_also :class:`qiskit_experiments.library.driven_freq_tuning.ramsey.StarkRamseyXY` :class:`qiskit_experiments.library.driven_freq_tuning.ramsey_amp_scan.StarkRamseyXYAmpScan` # section: manual :doc:`/manuals/characterization/stark_experiment` """ def __init__( self, physical_qubits: Sequence[int], backend: Backend | None = None, **experiment_options, ): """ Initialize new experiment class. Args: physical_qubits: Sequence with the index of the physical qubit. backend: Optional, the backend to run the experiment on. experiment_options: Experiment options. See the class documentation or ``self._default_experiment_options`` for descriptions. """ self._timing = None super().__init__( physical_qubits=physical_qubits, analysis=StarkP1SpectAnalysis(), backend=backend, ) self.set_experiment_options(**experiment_options) @classmethod def _default_experiment_options(cls) -> Options: """Default experiment options. Experiment Options: t1_delay (float): The T1 delay time after excitation pulse. The delay must be sufficiently greater than the edge duration determined by the stark_sigma. stark_channel (PulseChannel): Pulse channel to apply Stark tones. If not provided, the same channel with the qubit drive is used. stark_freq_offset (float): Offset of Stark tone frequency from the qubit frequency. This must be greater than zero not to apply Rabi drive. stark_sigma (float): Gaussian sigma of the rising and falling edges of the Stark tone, in seconds. stark_risefall (float): Ratio of sigma to the duration of the rising and falling edges of the Stark tone. min_xval (float): Minimum x value. max_xval (float): Maximum x value. num_xvals (int): Number of x-values to scan. xval_type (str): Type of x-value. Either ``amplitude`` or ``frequency``. Setting to frequency requires pre-calibration of Stark shift coefficients. spacing (str): A policy for the spacing to create an amplitude list from ``min_stark_amp`` to ``max_stark_amp``. Either ``linear`` or ``quadratic`` must be specified. xvals (list[float]): The list of x-values that will be scanned in the experiment. If not set, then ``num_xvals`` parameters spaced according to the ``spacing`` policy between ``min_xval`` and ``max_xval`` are used. If ``xvals`` is set, these parameters are ignored. stark_coefficients (StarkCoefficients): Calibrated Stark shift coefficients. This value is necessary when xval_type is "frequency". When this value is None, a search for the "stark_coefficients" in the result database is run. This requires having the experiment service available in the backend set for the experiment. """ options = super()._default_experiment_options() options.update_options( t1_delay=20e-6, stark_channel=None, stark_freq_offset=80e6, stark_sigma=15e-9, stark_risefall=2, min_xval=-1.0, max_xval=1.0, num_xvals=201, xval_type="amplitude", spacing="quadratic", xvals=None, stark_coefficients=None, ) options.set_validator("spacing", ["linear", "quadratic"]) options.set_validator("xval_type", ["amplitude", "frequency"]) options.set_validator("stark_freq_offset", (0, np.inf)) options.set_validator("stark_channel", pulse.channels.PulseChannel) options.set_validator("stark_coefficients", StarkCoefficients) return options def _set_backend(self, backend: Backend): super()._set_backend(backend) self._timing = BackendTiming(backend)
[docs] def parameters(self) -> np.ndarray: """Stark tone amplitudes to use in circuits. Returns: The list of amplitudes to use for the different circuits based on the experiment options. Raises: ValueError: When invalid xval spacing is specified. """ opt = self.experiment_options # alias if opt.xvals is None: if opt.spacing == "linear": params = np.linspace(opt.min_xval, opt.max_xval, opt.num_xvals) elif opt.spacing == "quadratic": min_sqrt = np.sign(opt.min_xval) * np.sqrt(np.abs(opt.min_xval)) max_sqrt = np.sign(opt.max_xval) * np.sqrt(np.abs(opt.max_xval)) lin_params = np.linspace(min_sqrt, max_sqrt, opt.num_xvals) params = np.sign(lin_params) * lin_params**2 else: raise ValueError(f"Spacing option {opt.spacing} is not valid.") else: params = np.asarray(opt.xvals, dtype=float) if opt.xval_type == "frequency": coeffs = opt.stark_coefficients if coeffs is None: coeffs = retrieve_coefficients_from_backend( backend=self.backend, qubit=self.physical_qubits[0], ) return coeffs.convert_freq_to_amp(freqs=params) return params
[docs] def parameterized_circuits(self) -> tuple[QuantumCircuit, ...]: """Create circuits with parameters for P1 experiment with Stark shift. Returns: Quantum template circuit for P1 experiment. """ opt = self.experiment_options # alias param = Parameter("stark_amp") sym_param = param._symbol_expr # Pulse gates stark = Gate("Stark", 1, [param]) # Note that Stark tone yields negative (positive) frequency shift # when the Stark tone frequency is higher (lower) than qubit f01 frequency. # This choice gives positive frequency shift with positive Stark amplitude. qubit_f01 = self._backend_data.drive_freqs[self.physical_qubits[0]] neg_sign_of_amp = ParameterExpression( symbol_map={param: sym_param}, expr=-sym.sign(sym_param), ) abs_of_amp = ParameterExpression( symbol_map={param: sym_param}, expr=sym.Abs(sym_param), ) stark_freq = qubit_f01 + neg_sign_of_amp * opt.stark_freq_offset stark_channel = opt.stark_channel or pulse.DriveChannel(self.physical_qubits[0]) sigma_dt = opt.stark_sigma / self._backend_data.dt delay_dt = self._timing.round_pulse(time=opt.t1_delay) with pulse.build() as stark_schedule: pulse.set_frequency(stark_freq, stark_channel) pulse.play( pulse.GaussianSquare( duration=delay_dt, amp=abs_of_amp, sigma=sigma_dt, risefall_sigma_ratio=opt.stark_risefall, ), stark_channel, ) temp_t1 = QuantumCircuit(1, 1) temp_t1.x(0) temp_t1.append(stark, [0]) temp_t1.measure(0, 0) temp_t1.add_calibration( gate=stark, qubits=self.physical_qubits, schedule=stark_schedule, ) return (temp_t1,)
[docs] def circuits(self) -> list[QuantumCircuit]: """Create circuits. Returns: A list of P1 circuits with a variable Stark tone amplitudes. """ (t1_circ,) = self.parameterized_circuits() param = next(iter(t1_circ.parameters)) circs = [] for amp in self.parameters(): t1_assigned = t1_circ.assign_parameters({param: amp}, inplace=False) t1_assigned.metadata = {"xval": amp} circs.append(t1_assigned) return circs
def _metadata(self) -> dict[str, any]: """Return experiment metadata for ExperimentData.""" metadata = super()._metadata() metadata["stark_freq_offset"] = self.experiment_options.stark_freq_offset return metadata