Source code for qilisdk.experiments.experiment_functional

# Copyright 2025 Qilimanjaro Quantum Tech
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

from abc import ABC
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar

from qilisdk.experiments.experiment_result import (
    ExperimentResult,
    RabiExperimentResult,
    T1ExperimentResult,
    T1SoftSaturationHWLExperimentResult,
    T2ExperimentResult,
    TwoTonesAtFixedFluxBiasExperimentResult,
    TwoTonesFrequencyVsFluxQdacRampCWExperimentResult,
    TwoTonesPulsedSoftExperimentResult,
    TwoTonesVsFluxBiasExperimentResult,
)
from qilisdk.functionals.functional import Functional
from qilisdk.yaml import yaml

if TYPE_CHECKING:
    import numpy as np

[docs] TResult_co = TypeVar("TResult_co", bound=ExperimentResult, covariant=True)
@yaml.register_class
[docs] class ExperimentFunctional(Functional, ABC, Generic[TResult_co]): """Abstract base class for single-qubit experiment functionals. This class serves as a generic interface for defining quantum characterization experiments such as Rabi or T1. Each subclass specifies a concrete `ExperimentResult` type and the corresponding sweep parameters. """ def __init__(self, qubit: int, averages: int) -> None: """Initialize the experiment functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. """ self._qubit = qubit self._averages = averages @property
[docs] def qubit(self) -> int: """The physical qubit index on which the experiment is performed. Returns: int: Index of the qubit. """ return self._qubit
@property
[docs] def averages(self) -> int: """ Number of averages to acquire for the experiment. Returns: int: Number of averages. """ return self._averages
@yaml.register_class
[docs] class RabiExperiment(ExperimentFunctional[RabiExperimentResult]): """Rabi experiment functional for a single qubit. This functional defines a standard Rabi oscillation experiment where the drive pulse duration is swept to measure the oscillatory response of the qubit under continuous driving. """
[docs] result_type: ClassVar[type[RabiExperimentResult]] = RabiExperimentResult
"""Result type returned by this functional.""" def __init__(self, qubit: int, averages: int, drive_duration_values: np.ndarray) -> None: """Initialize a Rabi experiment functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. drive_duration_values (np.ndarray): Array of drive pulse durations (in nanoseconds) used to sweep the experiment. """ super().__init__(qubit=qubit, averages=averages) self._drive_duration_values = drive_duration_values @property
[docs] def drive_duration_values(self) -> np.ndarray: """Drive pulse duration sweep values. Returns: np.ndarray: The set of drive durations (in nanoseconds) used in the Rabi experiment. """ return self._drive_duration_values
@yaml.register_class
[docs] class T1Experiment(ExperimentFunctional[T1ExperimentResult]): """T1 relaxation experiment functional for a single qubit. This functional defines a standard T1 (energy relaxation) experiment, where the delay between excitation and measurement is varied to extract the relaxation time constant of the qubit. """
[docs] result_type: ClassVar[type[T1ExperimentResult]] = T1ExperimentResult
"""Result type returned by this functional.""" def __init__(self, qubit: int, averages: int, wait_duration_values: np.ndarray) -> None: """Initialize a T1 experiment functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. wait_duration_values (np.ndarray): Array of waiting times (in nanoseconds) between excitation and measurement. """ super().__init__(qubit=qubit, averages=averages) self._wait_duration_values: np.ndarray = wait_duration_values @property
[docs] def wait_duration_values(self) -> np.ndarray: """Waiting time sweep values. Returns: np.ndarray: The set of delay durations (in nanoseconds) used in the T1 experiment. """ return self._wait_duration_values
@yaml.register_class
[docs] class T2Experiment(ExperimentFunctional[T2ExperimentResult]): """T2 dephasing experiment functional for a single qubit. This functional defines a Ramsey/spin-echo style T2 experiment, where the free-evolution delay between phase-sensitive pulses is swept to extract the qubit coherence time. """
[docs] result_type: ClassVar[type[T2ExperimentResult]] = T2ExperimentResult
"""Result type returned by this functional.""" def __init__(self, qubit: int, averages: int, wait_duration_values: np.ndarray) -> None: """Initialize a T2 dephasing experiment functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. wait_duration_values (np.ndarray): Array of free-evolution delays (in nanoseconds) between the phase-sensitive pulses. """ super().__init__(qubit=qubit, averages=averages) self._wait_duration_values: np.ndarray = wait_duration_values @property
[docs] def wait_duration_values(self) -> np.ndarray: """Free-evolution delay sweep values. Returns: np.ndarray: The set of delay durations (in nanoseconds) used to estimate T2. """ return self._wait_duration_values
@yaml.register_class
[docs] class TwoTonesAtFixedFluxBiasExperiment(ExperimentFunctional[TwoTonesAtFixedFluxBiasExperimentResult]): """Two-tone spectroscopy functional for a single qubit. Sweeps a drive tone frequency while monitoring the readout tone to identify the qubit transition frequency. """
[docs] result_type: ClassVar[type[TwoTonesAtFixedFluxBiasExperimentResult]] = TwoTonesAtFixedFluxBiasExperimentResult
"""Result type returned by this functional.""" def __init__( self, qubit: int, averages: int, frequency_start: float, frequency_stop: float, frequency_step: float, ) -> None: """Initialize a two-tone spectroscopy functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. frequency_start (float): Starting frequency of the swept drive tone (in Hz). frequency_stop (float): Ending frequency of the swept drive tone (in Hz). frequency_step (float): Frequency increment between sweep points (in Hz). """ super().__init__(qubit=qubit, averages=averages) self._frequency_start: float = frequency_start self._frequency_stop: float = frequency_stop self._frequency_step: float = frequency_step @property
[docs] def frequency_start(self) -> float: """Start frequency for the drive tone sweep. Returns: float: Starting frequency of the drive tone (in Hz). """ return self._frequency_start
@property
[docs] def frequency_stop(self) -> float: """Stop frequency for the drive tone sweep. Returns: float: Ending frequency of the drive tone (in Hz). """ return self._frequency_stop
@property
[docs] def frequency_step(self) -> float: """Step size for the drive tone sweep. Returns: float: Frequency increment between sweep points (in Hz). """ return self._frequency_step
@yaml.register_class
[docs] class TwoTonesVsFluxBiasExperiment(ExperimentFunctional[TwoTonesVsFluxBiasExperimentResult]): """Two-tone spectroscopy functional for a single qubit, swept vs flux bias. Sweeps a drive tone frequency while monitoring the readout tone to identify the qubit transition frequency as a function of flux bias. """
[docs] result_type: ClassVar[type[TwoTonesVsFluxBiasExperimentResult]] = TwoTonesVsFluxBiasExperimentResult
"""Result type returned by this functional.""" def __init__( self, qubit: int, averages: int, frequency_start: float, frequency_stop: float, frequency_step: float, flux_start: float, flux_stop: float, flux_step: float, ) -> None: """Initialize a two-tone spectroscopy functional, swept vs flux bias. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. frequency_start (float): Starting frequency of the swept drive tone (in Hz). frequency_stop (float): Ending frequency of the swept drive tone (in Hz). frequency_step (float): Frequency increment between sweep points (in Hz). flux_start (float): Starting value of the flux bias sweep (in units of flux quantum). flux_stop (float): Ending value of the flux bias sweep (in units of flux quantum). flux_step (float): Increment between flux bias sweep points (in units of flux quantum). """ super().__init__(qubit=qubit, averages=averages) self._frequency_start: float = frequency_start self._frequency_stop: float = frequency_stop self._frequency_step: float = frequency_step self._flux_start: float = flux_start self._flux_stop: float = flux_stop self._flux_step: float = flux_step @property
[docs] def frequency_start(self) -> float: """Start frequency for the drive tone sweep. Returns: float: Starting frequency of the drive tone (in Hz). """ return self._frequency_start
@property
[docs] def frequency_stop(self) -> float: """Stop frequency for the drive tone sweep. Returns: float: Ending frequency of the drive tone (in Hz). """ return self._frequency_stop
@property
[docs] def frequency_step(self) -> float: """Step size for the drive tone sweep. Returns: float: Frequency increment between sweep points (in Hz). """ return self._frequency_step
@property
[docs] def flux_start(self) -> float: """Start value for the flux bias sweep. Returns: float: Starting value of the flux bias (in units of flux quantum). """ return self._flux_start
@property
[docs] def flux_stop(self) -> float: """Stop value for the flux bias sweep. Returns: float: Ending value of the flux bias (in units of flux quantum). """ return self._flux_stop
@property
[docs] def flux_step(self) -> float: """Step size for the flux bias sweep. Returns: float: Increment between flux bias sweep points (in units of flux quantum). """ return self._flux_step
@yaml.register_class
[docs] class TwoTonesFrequencyVsFluxQdacRampCWExperiment( ExperimentFunctional[TwoTonesFrequencyVsFluxQdacRampCWExperimentResult] ): """Two-tone spectroscopy functional for a single qubit, swept vs flux bias using ramping through the QDAC and continous wave. Sweeps a drive tone frequency while monitoring the readout tone to identify the qubit transition frequency as a function of flux bias. """
[docs] result_type: ClassVar[type[TwoTonesFrequencyVsFluxQdacRampCWExperimentResult]] = ( TwoTonesFrequencyVsFluxQdacRampCWExperimentResult )
"""Result type returned by this functional.""" def __init__( self, qubit: int, averages: int, frequency_start: float, frequency_stop: float, frequency_step: float, flux_start: float, flux_stop: float, flux_step: float, ) -> None: """Initialize a two-tone spectroscopy functional, swept vs flux bias, using the Qdac to ramp. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. frequency_start (float): Starting frequency of the swept drive tone (in Hz). frequency_stop (float): Ending frequency of the swept drive tone (in Hz). frequency_step (float): Frequency increment between sweep points (in Hz). flux_start (float): Starting value of the flux bias sweep (in units of flux quantum). flux_stop (float): Ending value of the flux bias sweep (in units of flux quantum). flux_step (float): Increment between flux bias sweep points (in units of flux quantum). """ super().__init__(qubit=qubit, averages=averages) self._frequency_start: float = frequency_start self._frequency_stop: float = frequency_stop self._frequency_step: float = frequency_step self._flux_start: float = flux_start self._flux_stop: float = flux_stop self._flux_step: float = flux_step @property
[docs] def frequency_start(self) -> float: """Start frequency for the drive tone sweep. Returns: float: Starting frequency of the drive tone (in Hz). """ return self._frequency_start
@property
[docs] def frequency_stop(self) -> float: """Stop frequency for the drive tone sweep. Returns: float: Ending frequency of the drive tone (in Hz). """ return self._frequency_stop
@property
[docs] def frequency_step(self) -> float: """Step size for the drive tone sweep. Returns: float: Frequency increment between sweep points (in Hz). """ return self._frequency_step
@property
[docs] def flux_start(self) -> float: """Start value for the flux bias sweep. Returns: float: Starting value of the flux bias (in units of flux quantum). """ return self._flux_start
@property
[docs] def flux_stop(self) -> float: """Stop value for the flux bias sweep. Returns: float: Ending value of the flux bias (in units of flux quantum). """ return self._flux_stop
@property
[docs] def flux_step(self) -> float: """Step size for the flux bias sweep. Returns: float: Increment between flux bias sweep points (in units of flux quantum). """ return self._flux_step
@yaml.register_class
[docs] class TwoTonesPulsedSoftExperiment(ExperimentFunctional[TwoTonesPulsedSoftExperimentResult]): """Two-tone pulsed spectroscopy functional using a soft Gaussian ring-up drive. Sweeps the drive tone IF frequency while applying a smooth saturation pulse to identify the qubit transition frequency at a fixed flux bias point. """
[docs] result_type: ClassVar[type[TwoTonesPulsedSoftExperimentResult]] = TwoTonesPulsedSoftExperimentResult
"""Result type returned by this functional.""" def __init__( self, qubit: int, averages: int, frequency_start: float, frequency_stop: float, frequency_step: float, ) -> None: """Initialize a two-tone pulsed soft spectroscopy functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. frequency_start (float): Starting IF frequency of the swept drive tone (in Hz). frequency_stop (float): Ending IF frequency of the swept drive tone (in Hz). frequency_step (float): Frequency increment between sweep points (in Hz). """ super().__init__(qubit=qubit, averages=averages) self._frequency_start: float = frequency_start self._frequency_stop: float = frequency_stop self._frequency_step: float = frequency_step @property
[docs] def frequency_start(self) -> float: """Start IF frequency for the drive tone sweep. Returns: float: Starting IF frequency of the drive tone (in Hz). """ return self._frequency_start
@property
[docs] def frequency_stop(self) -> float: """Stop IF frequency for the drive tone sweep. Returns: float: Ending IF frequency of the drive tone (in Hz). """ return self._frequency_stop
@property
[docs] def frequency_step(self) -> float: """Step size for the drive tone IF frequency sweep. Returns: float: Frequency increment between sweep points (in Hz). """ return self._frequency_step
@yaml.register_class
[docs] class T1SoftSaturationHWLExperiment(ExperimentFunctional[T1SoftSaturationHWLExperimentResult]): """T1 relaxation experiment using a soft Gaussian ring-up saturation drive with hardware loop. Applies a smooth saturation pulse to excite the qubit and sweeps the delay before readout to extract the relaxation time constant. All idle durations are unrolled into a single QProgram for efficient hardware execution. """
[docs] result_type: ClassVar[type[T1SoftSaturationHWLExperimentResult]] = T1SoftSaturationHWLExperimentResult
"""Result type returned by this functional.""" def __init__(self, qubit: int, averages: int, wait_duration_values: np.ndarray) -> None: """Initialize a T1 soft saturation hardware-loop experiment functional. Args: qubit (int): The physical qubit index on which the experiment is performed. averages (int): Number of averages to acquire for the experiment. wait_duration_values (np.ndarray): Array of waiting times (in nanoseconds) between excitation and measurement. """ super().__init__(qubit=qubit, averages=averages) self._wait_duration_values: np.ndarray = wait_duration_values @property
[docs] def wait_duration_values(self) -> np.ndarray: """Waiting time sweep values. Returns: np.ndarray: The set of delay durations (in nanoseconds) used in the experiment. """ return self._wait_duration_values