Source code for qblox_scheduler.device_under_test.nv_element

# Repository: https://gitlab.com/qblox/packages/software/qblox-scheduler
# Licensed according to the LICENSE file on the main branch
#
# Copyright 2020-2025, Quantify Consortium
# Copyright 2025, Qblox B.V.
"""
Device elements for NV centers.

Currently only for the electronic qubit,
but could be extended for other qubits (eg. carbon qubit).
"""

from __future__ import annotations

import math
from collections.abc import Hashable
from typing import Literal

from qblox_scheduler.backends.graph_compilation import (
    DeviceCompilationConfig,
    OperationCompilationConfig,
)
from qblox_scheduler.device_under_test.device_element import DeviceElement
from qblox_scheduler.device_under_test.transmon_element import (
    PulseCompensationModule,  # noqa: TC001
)
from qblox_scheduler.enums import TimeRef, TimeSource
from qblox_scheduler.operations import (
    measurement_factories,
    pulse_factories,
    pulse_library,
)
from qblox_scheduler.structure.model import Numbers, Parameter, SchedulerSubmodule
from qblox_scheduler.structure.types import (
    Amplitude,
    Delay,
    Duration,
    Frequency,
)


[docs] class Ports(SchedulerSubmodule): """Submodule containing the ports."""
[docs] microwave: str = Parameter( label="Name of microwave port", initial_value="", )
"""Name of the element's microwave port."""
[docs] optical_control: str = Parameter( label="Name of optical control port", initial_value="", )
"""Port to control the device element with optical pulses."""
[docs] optical_readout: str = Parameter( label="Name of optical readout port", initial_value="", )
"""Port to readout photons from the device element."""
[docs] def _fill_defaults(self) -> None: if self.parent: if not self.microwave: self.microwave = f"{self.parent.name}:mw" if not self.optical_control: self.optical_control = f"{self.parent.name}:optical_control" if not self.optical_readout: self.optical_readout = f"{self.parent.name}:optical_readout"
[docs] class ClockFrequencies(SchedulerSubmodule): """Submodule with clock frequencies specifying the transitions to address."""
[docs] f01: Frequency = Parameter( label="Microwave frequency in resonance with transition between 0 and 1", unit="Hz", initial_value=math.nan, )
"""Microwave frequency to resonantly drive the electron spin state of a negatively charged diamond NV center from the 0-state to 1-state :cite:t:`DOHERTY20131`."""
[docs] spec: Frequency = Parameter( label="Spectroscopy frequency", unit="Hz", initial_value=math.nan, )
"""Parameter that is swept for a spectroscopy measurement. It does not track properties of the device element."""
[docs] ge0: Frequency = Parameter( label="f_{ge0}", unit="Hz", initial_value=math.nan, )
"""Transition frequency from the m_s=0 state to the E_x,y state."""
[docs] ge1: Frequency = Parameter( label="f_{ge1}", unit="Hz", initial_value=math.nan, )
"""Transition frequency from the m_s=+-1 state to any of the A_1, A_2, or E_1,2 states."""
[docs] ionization: Frequency = Parameter( label="Frequency of ionization laser", unit="Hz", initial_value=math.nan, )
"""Frequency of the green ionization laser for manipulation of the NVs charge state."""
[docs] class SpectroscopyOperationNV(SchedulerSubmodule): """ Convert the SpectroscopyOperation into a hermite, square, or gaussian microwave pulse. This class contains parameters with a certain amplitude and duration for spin-state manipulation. The modulation frequency of the pulse is determined by the clock ``spec`` in :class:`~.ClockFrequencies`. """
[docs] amplitude: Amplitude = Parameter( label="Amplitude of spectroscopy pulse", initial_value=math.nan, )
"""Amplitude of spectroscopy pulse."""
[docs] duration: Duration = Parameter( label="Duration of spectroscopy pulse", initial_value=8e-6, unit="s", )
"""Duration of the MW pulse."""
[docs] pulse_shape: Literal["SquarePulse", "SkewedHermitePulse", "GaussPulse"] = Parameter( label="Shape of the pulse", initial_value="SquarePulse", )
"""Shape of the MW pulse."""
[docs] class ResetSpinpump(SchedulerSubmodule): r""" Submodule containing parameters to run the spinpump laser with a square pulse. This should reset the NV to the :math:`|0\rangle` state. """
[docs] amplitude: Amplitude = Parameter( initial_value=math.nan, )
"""Amplitude of reset pulse."""
[docs] duration: Duration = Parameter( initial_value=50e-6, unit="s", )
"""Duration of reset pulse."""
[docs] class Measure(SchedulerSubmodule): r""" Submodule containing parameters to read out the spin state of the NV center. Excitation with a readout laser from the :math:`|0\rangle` to an excited state. Acquisition of photons when decaying back into the :math:`|0\rangle` state. """
[docs] pulse_amplitude: Amplitude = Parameter( initial_value=math.nan, )
"""Amplitude of readout pulse."""
[docs] pulse_duration: Duration = Parameter( initial_value=20e-6, unit="s", )
"""Readout pulse duration."""
[docs] acq_duration: Duration = Parameter( initial_value=50e-6, unit="s", )
"""Duration of the acquisition."""
[docs] acq_delay: Delay = Parameter( initial_value=0.0, unit="s", )
"""Delay between the start of the readout pulse and the start of the acquisition."""
[docs] acq_channel: Hashable = Parameter( initial_value=0, )
"""Acquisition channel of this device element.""" # Optional timetag-related parameters.
[docs] time_source: TimeSource = Parameter( initial_value=TimeSource.FIRST, )
""" Optional time source, in case the :class:`~qblox_scheduler.operations.acquisition_library.Timetag` acquisition protocols are used. Please see that protocol for more information. """
[docs] time_ref: TimeRef = Parameter( initial_value=TimeRef.START, )
""" Optional time reference, in case :class:`~qblox_scheduler.operations.acquisition_library.Timetag` or :class:`~qblox_scheduler.operations.acquisition_library.TimetagTrace` acquisition protocols are used. Please see those protocols for more information. """
[docs] class ChargeReset(SchedulerSubmodule): """ Submodule containing parameters to run an ionization laser square pulse to reset the NV. After resetting, the qubit should be in its negatively charged state. """
[docs] amplitude: Amplitude = Parameter( initial_value=math.nan, )
"""Amplitude of charge reset pulse."""
[docs] duration: Duration = Parameter( initial_value=20e-6, unit="s", )
"""Duration of the charge reset pulse."""
[docs] class CRCount(SchedulerSubmodule): """ Submodule containing parameters to run the ionization laser and the spin pump laser. This uses a photon count to perform a charge and resonance count. """
[docs] readout_pulse_amplitude: Amplitude = Parameter( initial_value=math.nan, )
"""Amplitude of readout pulse."""
[docs] spinpump_pulse_amplitude: Amplitude = Parameter( initial_value=math.nan, )
"""Amplitude of spin-pump pulse."""
[docs] readout_pulse_duration: Duration = Parameter( initial_value=20e-6, unit="s", )
"""Readout pulse duration."""
[docs] spinpump_pulse_duration: Duration = Parameter( initial_value=20e-6, unit="s", )
"""Spin-pump pulse duration."""
[docs] acq_duration: Duration = Parameter( initial_value=50e-6, unit="s", )
"""Duration of the acquisition."""
[docs] acq_delay: Delay = Parameter( initial_value=0.0, unit="s", )
"""Delay between the start of the readout pulse and the start of the acquisition."""
[docs] acq_channel: Hashable = Parameter( initial_value=0, )
"""Default acquisition channel of this device element."""
[docs] class RxyNV(SchedulerSubmodule): """ Submodule containing parameters to perform an Rxy operation using a Hermite or Gaussian pulse. """
[docs] amp180: Amplitude = Parameter( initial_value=math.nan, )
r"""Amplitude of :math:`\pi` pulse."""
[docs] skewness: float = Parameter( initial_value=0.0, vals=Numbers(min_value=-1, max_value=1), )
"""First-order amplitude to the Hermite pulse envelope."""
[docs] duration: Duration = Parameter( initial_value=20e-9, unit="s", )
"""Duration of the pi pulse."""
[docs] pulse_shape: Literal["SkewedHermitePulse", "GaussPulse"] = Parameter( label="Shape of the pulse", initial_value="SkewedHermitePulse", )
"""Shape of the pi pulse."""
[docs] class BasicElectronicNVElement(DeviceElement): """ A device element representing an electronic qubit in an NV center. The submodules contain the necessary device element parameters to translate higher-level operations into pulses. Please see the documentation of these classes. .. admonition:: Examples Qubit parameters can be set through submodule attributes .. jupyter-execute:: from qblox_scheduler import BasicElectronicNVElement device_element = BasicElectronicNVElement("q2") device_element.rxy.amp180 = 0.1 device_element.measure.pulse_amplitude = 0.25 device_element.measure.pulse_duration = 300e-9 device_element.measure.acq_delay = 430e-9 device_element.measure.acq_duration = 1e-6 ... """
[docs] element_type: Literal["BasicElectronicNVElement"] = "BasicElectronicNVElement" # type: ignore[reportIncompatibleVariableOverride]
[docs] spectroscopy_operation: SpectroscopyOperationNV
[docs] ports: Ports
[docs] clock_freqs: ClockFrequencies
[docs] reset: ResetSpinpump
[docs] charge_reset: ChargeReset
[docs] measure: Measure
[docs] pulse_compensation: PulseCompensationModule
[docs] cr_count: CRCount
[docs] rxy: RxyNV
[docs] def _generate_config(self) -> dict[str, dict[str, OperationCompilationConfig]]: """ Generate part of the device configuration specific to a single qubit. This method is intended to be used when this object is part of a device object containing multiple elements. """ device_element_config = { self.name: { "spectroscopy_operation": OperationCompilationConfig( factory_func=pulse_factories.nv_spec_pulse_mw, factory_kwargs={ "duration": self.spectroscopy_operation.duration, "amplitude": self.spectroscopy_operation.amplitude, "port": self.ports.microwave, "clock": f"{self.name}.spec", "pulse_shape": self.spectroscopy_operation.pulse_shape, }, ), "reset": OperationCompilationConfig( factory_func=pulse_library.SquarePulse, factory_kwargs={ "duration": self.reset.duration, "amp": self.reset.amplitude, "port": self.ports.optical_control, "clock": f"{self.name}.ge1", }, ), "charge_reset": OperationCompilationConfig( factory_func=pulse_library.SquarePulse, factory_kwargs={ "duration": self.charge_reset.duration, "amp": self.charge_reset.amplitude, "port": self.ports.optical_control, "clock": f"{self.name}.ionization", }, ), "measure": OperationCompilationConfig( factory_func=measurement_factories.optical_measurement, factory_kwargs={ "pulse_amplitudes": [self.measure.pulse_amplitude], "pulse_durations": [self.measure.pulse_duration], "pulse_ports": [self.ports.optical_control], "pulse_clocks": [f"{self.name}.ge0"], "acq_duration": self.measure.acq_duration, "acq_delay": self.measure.acq_delay, "acq_channel": self.measure.acq_channel, "acq_port": self.ports.optical_readout, "acq_clock": f"{self.name}.ge0", "acq_time_source": self.measure.time_source, "acq_time_ref": self.measure.time_ref, "pulse_type": "SquarePulse", "acq_protocol_default": "TriggerCount", }, gate_info_factory_kwargs=[ "acq_channel_override", "coords", "acq_index", "bin_mode", "acq_protocol", ], ), "pulse_compensation": OperationCompilationConfig( factory_func=None, factory_kwargs={ "port": self.ports.microwave, "clock": f"{self.name}.f_larmor", "max_compensation_amp": self.pulse_compensation.max_compensation_amp, "time_grid": self.pulse_compensation.time_grid, "sampling_rate": self.pulse_compensation.sampling_rate, }, ), "cr_count": OperationCompilationConfig( factory_func=measurement_factories.optical_measurement, factory_kwargs={ "pulse_amplitudes": [ self.cr_count.readout_pulse_amplitude, self.cr_count.spinpump_pulse_amplitude, ], "pulse_durations": [ self.cr_count.readout_pulse_duration, self.cr_count.spinpump_pulse_duration, ], "pulse_ports": [ self.ports.optical_control, self.ports.optical_control, ], "pulse_clocks": [ f"{self.name}.ge0", f"{self.name}.ge1", ], "acq_duration": self.cr_count.acq_duration, "acq_delay": self.cr_count.acq_delay, "acq_channel": self.cr_count.acq_channel, "acq_port": self.ports.optical_readout, "acq_clock": f"{self.name}.ge0", "pulse_type": "SquarePulse", "acq_protocol_default": "TriggerCount", }, gate_info_factory_kwargs=[ "acq_channel_override", "coords", "acq_index", "bin_mode", "acq_protocol", ], ), "Rxy": OperationCompilationConfig( factory_func=pulse_factories.rxy_pulse, factory_kwargs={ "amp180": self.rxy.amp180, "skewness": self.rxy.skewness, "port": self.ports.microwave, "clock": f"{self.name}.spec", "duration": self.rxy.duration, "pulse_shape": self.rxy.pulse_shape, }, gate_info_factory_kwargs=[ "theta", "phi", ], ), } } return device_element_config
[docs] def generate_device_config(self) -> DeviceCompilationConfig: """ Generate a valid device config for the qblox-scheduler. This makes use of the :func:`~.circuit_to_device.compile_circuit_to_device_with_config_validation` function. This enables the settings of this qubit to be used in isolation. .. note: This config is only valid for single qubit experiments. """ cfg_dict = { "elements": self._generate_config(), "clocks": { f"{self.name}.f01": self.clock_freqs.f01, f"{self.name}.spec": self.clock_freqs.spec, f"{self.name}.ge0": self.clock_freqs.ge0, f"{self.name}.ge1": self.clock_freqs.ge1, f"{self.name}.ionization": self.clock_freqs.ionization, }, "edges": {}, } dev_cfg = DeviceCompilationConfig.model_validate(cfg_dict) return dev_cfg