diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6152d6..37e7e88a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ - Added `Channel.update_frequency` to allow for updating the frequency of a channel - Added `OctaveOld.connectivity` as it was needed for (deprecated) compatibility with multiple OPX instruments - Added ports for different hardware. As a consequence we now also support the LF-FEM and MW-FEM +- Added `DragCosinePulse`. ### Changed - Allow `QuamBase.get_reference(attr)` to return a reference of one of its attributes - Octave RF input 2 has `LO_source = "external"` by default +- Rename `DragPulse -> DragGaussianPulse`, deprecate `DragPulse` ### Fixed - Fix quam object instantiation error when a parameter type uses pipe operator diff --git a/quam/components/pulses.py b/quam/components/pulses.py index 6781a806..c181d827 100644 --- a/quam/components/pulses.py +++ b/quam/components/pulses.py @@ -12,6 +12,8 @@ "Pulse", "BaseReadoutPulse", "ReadoutPulse", + "DragGaussianPulse", + "DragCosinePulse", "DragPulse", "SquarePulse", "SquareReadoutPulse", @@ -396,7 +398,7 @@ def integration_weights_function(self) -> List[Tuple[Union[complex, float], int] @quam_dataclass -class DragPulse(Pulse): +class DragGaussianPulse(Pulse): """Gaussian-based DRAG pulse that compensate for the leakage and AC stark shift. These DRAG waveforms has been implemented following the next Refs.: @@ -431,6 +433,9 @@ class DragPulse(Pulse): detuning: float = 0.0 subtracted: bool = True + def __post_init__(self) -> None: + return super().__post_init__() + def waveform_function(self): from qualang_tools.config.waveform_tools import drag_gaussian_pulse_waveforms @@ -451,6 +456,66 @@ def waveform_function(self): return I_rot + 1.0j * Q_rot +@quam_dataclass +class DragPulse(DragGaussianPulse): + def __post_init__(self) -> None: + warnings.warn( + "DragPulse is deprecated. Use DragGaussianPulse instead.", + DeprecationWarning, + ) + return super().__post_init__() + + +@quam_dataclass +class DragCosinePulse(Pulse): + """Cosine based DRAG pulse that compensate for the leakage and AC stark shift. + + These DRAG waveforms has been implemented following the next Refs.: + Chen et al. PRL, 116, 020501 (2016) + https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.116.020501 + and Chen's thesis + https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf + + Args: + length (int): The pulse length in ns. + axis_angle (float, optional): IQ axis angle of the output pulse in radians. + If None (default), the pulse is meant for a single channel or the I port + of an IQ channel + If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y). + amplitude (float): The amplitude in volts. + alpha (float): The DRAG coefficient. + anharmonicity (float): f_21 - f_10 - The differences in energy between the 2-1 + and the 1-0 energy levels, in Hz. + detuning (float): The frequency shift to correct for AC stark shift, in Hz. + """ + + axis_angle: float + amplitude: float + alpha: float + anharmonicity: float + detuning: float = 0.0 + + def __post_init__(self) -> None: + return super().__post_init__() + + def waveform_function(self): + from qualang_tools.config.waveform_tools import drag_cosine_pulse_waveforms + + I, Q = drag_cosine_pulse_waveforms( + amplitude=self.amplitude, + length=self.length, + alpha=self.alpha, + anharmonicity=self.anharmonicity, + detuning=self.detuning, + ) + I, Q = np.array(I), np.array(Q) + + I_rot = I * np.cos(self.axis_angle) - Q * np.sin(self.axis_angle) + Q_rot = I * np.sin(self.axis_angle) + Q * np.cos(self.axis_angle) + + return I_rot + 1.0j * Q_rot + + @quam_dataclass class SquarePulse(Pulse): """Square pulse QuAM component. diff --git a/tests/components/pulses/test_pulses.py b/tests/components/pulses/test_pulses.py index b525f1e3..89e67d00 100644 --- a/tests/components/pulses/test_pulses.py +++ b/tests/components/pulses/test_pulses.py @@ -7,8 +7,8 @@ from quam.utils.dataclass import get_dataclass_attr_annotations -def test_drag_pulse(): - drag_pulse = pulses.DragPulse( +def test_drag_gaussian_pulse(): + drag_pulse = pulses.DragGaussianPulse( amplitude=1, sigma=4, alpha=2, anharmonicity=200e6, length=20, axis_angle=0 ) @@ -33,6 +33,30 @@ def test_drag_pulse(): assert np.iscomplexobj(waveform) +def test_drag_cosine_pulse(): + drag_pulse = pulses.DragCosinePulse( + amplitude=1, alpha=2, anharmonicity=200e6, length=20, axis_angle=0 + ) + + assert drag_pulse.operation == "control" + assert drag_pulse.length == 20 + assert drag_pulse.get_attrs() == { + "id": None, + "length": 20, + "axis_angle": 0.0, + "digital_marker": None, + "amplitude": 1, + "alpha": 2, + "anharmonicity": 200000000.0, + "detuning": 0.0, + } + + waveform = drag_pulse.calculate_waveform() + assert len(waveform) == 20 + assert isinstance(waveform, np.ndarray) + assert np.iscomplexobj(waveform) + + def test_channel(): channel = Channel() d = channel.to_dict() @@ -192,3 +216,13 @@ def test_pulse_attr_annotations(): attr_annotations = get_dataclass_attr_annotations(pulses.SquareReadoutPulse) assert list(attr_annotations["required"]) == ["length", "amplitude"] + + +def test_deprecated_drag_pulse(): + with pytest.warns( + DeprecationWarning, + match="DragPulse is deprecated. Use DragGaussianPulse instead.", + ): + pulses.DragPulse( + axis_angle=0, amplitude=1, sigma=4, alpha=2, anharmonicity=200e6, length=20 + )