diff --git a/quam/components/gates/single_qubit_gates.py b/quam/components/gates/single_qubit_gates.py index 3cf84bab..8a75c36b 100644 --- a/quam/components/gates/single_qubit_gates.py +++ b/quam/components/gates/single_qubit_gates.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Dict +from typing import Dict, Union from dataclasses import field from quam.core import quam_dataclass, QuamComponent from quam.components.pulses import Pulse @@ -30,11 +30,16 @@ def execute(self, *args, **kwargs): # TODO Accomodate differing arguments @quam_dataclass class SinglePulseGate(SingleQubitGate): - """Single-qubit gate for a qubit consisting of a single pulse""" + """Single-qubit gate for a qubit consisting of a single pulse - pulse_label: str + Args: + pulse (Union[Pulse, str]): The pulse to be played + + """ + + pulse: Union[Pulse, str] def execute(self, amplitude_scale=None, duration=None): - self.qubit.xy.play( # TODO Introduce a "play" method to the qubit + self.qubit.play_pulse( self.pulse_label, amplitude_scale=amplitude_scale, duration=duration ) diff --git a/quam/components/gates/two_qubit_gates.py b/quam/components/gates/two_qubit_gates.py index 8d00cf39..452e8154 100644 --- a/quam/components/gates/two_qubit_gates.py +++ b/quam/components/gates/two_qubit_gates.py @@ -76,7 +76,6 @@ def execute(self, amplitude_scale=None): amplitude_scale=amplitude_scale, ) self.qubit_control.align(self.qubit_target) - self.qubit_control.xy.frame_rotation(self.phase_shift_control) self.qubit_target.xy.frame_rotation(self.phase_shift_target) diff --git a/quam/components/qubit.py b/quam/components/qubit.py index 06659eff..301b58e1 100644 --- a/quam/components/qubit.py +++ b/quam/components/qubit.py @@ -1,6 +1,7 @@ from typing import Dict, Any from dataclasses import field +from quam.components.channels import Channel from quam.core import quam_dataclass, QuamComponent from .gates.single_qubit_gates import SingleQubitGate from .gates.two_qubit_gates import TwoQubitGate @@ -14,6 +15,10 @@ class Qubit(QuamComponent): id: str gates: Dict[str, SingleQubitGate] = field(default_factory=dict) + @property + def name(self): + return self.id if isinstance(self.id, str) else f"q{self.id}" + def __matmul__(self, other): """Allows access to qubit pairs using the '@' operator, e.g. (q1 @ q2)""" if not isinstance(other, Qubit): @@ -40,4 +45,81 @@ def __matmul__(self, other): raise ValueError( "Qubit pair not found: qubit_control={self.name}, " "qubit_target={other.name}" - ) \ No newline at end of file + ) + + def play_pulse( + self, + pulse_name: str, + amplitude_scale: Union[float, AmpValuesType] = None, + duration: QuaNumberType = None, + condition: QuaExpressionType = None, + chirp: ChirpType = None, + truncate: QuaNumberType = None, + timestamp_stream: StreamType = None, + continue_chirp: bool = False, + target: str = "", + validate: bool = True, + ): + """Play a pulse on this channel. + + Args: + pulse_name (str): The name of the pulse to play. Should be registered in + `self.operations`. + amplitude_scale (float, _PulseAmp): Amplitude scale of the pulse. + Can be either a float, or qua.amp(float). + duration (int): Duration of the pulse in units of the clock cycle (4ns). + If not provided, the default pulse duration will be used. It is possible + to dynamically change the duration of both constant and arbitrary + pulses. Arbitrary pulses can only be stretched, not compressed. + chirp (Union[(list[int], str), (int, str)]): Allows to perform + piecewise linear sweep of the element's intermediate + frequency in time. Input should be a tuple, with the 1st + element being a list of rates and the second should be a + string with the units. The units can be either: 'Hz/nsec', + 'mHz/nsec', 'uHz/nsec', 'pHz/nsec' or 'GHz/sec', 'MHz/sec', + 'KHz/sec', 'Hz/sec', 'mHz/sec'. + truncate (Union[int, QUA variable of type int]): Allows playing + only part of the pulse, truncating the end. If provided, + will play only up to the given time in units of the clock + cycle (4ns). + condition (A logical expression to evaluate.): Will play analog + pulse only if the condition's value is true. Any digital + pulses associated with the operation will always play. + timestamp_stream (Union[str, _ResultSource]): (Supported from + QOP 2.2) Adding a `timestamp_stream` argument will save the + time at which the operation occurred to a stream. If the + `timestamp_stream` is a string ``label``, then the timestamp + handle can be retrieved with + `qm._results.JobResults.get` with the same ``label``. + validate (bool): If True (default), validate that the pulse is registered + in Channel.operations + + Note: + The `element` argument from `qm.qua.play()`is not needed, as it is + automatically set to `self.name`. + + """ + attrs = self.get_attrs(follow_references=False, include_defaults=True) + channels = {key: val for key, val in attrs.items() if isinstance(val, Channel)} + for channel in channels.values(): + if pulse_name not in channel.operations: + continue + + channel.play( + pulse_name=pulse_name, + amplitude_scale=amplitude_scale, + duration=duration, + condition=condition, + chirp=chirp, + truncate=truncate, + timestamp_stream=timestamp_stream, + continue_chirp=continue_chirp, + target=target, + validate=validate, + ) + break + else: + raise ValueError( + f"Pulse name not found in any channel operations of qubit: " + f"{pulse_name=}\nqubit={self.name}" + )