diff --git a/quam/components/gates/single_qubit_gates.py b/quam/components/gates/single_qubit_gates.py new file mode 100644 index 00000000..3cf84bab --- /dev/null +++ b/quam/components/gates/single_qubit_gates.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod +from typing import Dict +from dataclasses import field +from quam.core import quam_dataclass, QuamComponent +from quam.components.pulses import Pulse + + +@quam_dataclass +class SingleQubitGate(QuamComponent, ABC): + @property + def qubit(self): + from ..qubit import Qubit + + if isinstance(self.parent, Qubit): + return self.parent + elif hasattr(self.parent, "parent") and isinstance(self.parent.parent, Qubit): + return self.parent.parent + else: + raise AttributeError( + "SingleQubitGate is not attached to a qubit. 1Q_gate: {self}" + ) + + def __call__(self): + self.execute() + + @abstractmethod + def execute(self, *args, **kwargs): # TODO Accomodate differing arguments + pass + + +@quam_dataclass +class SinglePulseGate(SingleQubitGate): + """Single-qubit gate for a qubit consisting of a single pulse""" + + pulse_label: str + + def execute(self, amplitude_scale=None, duration=None): + self.qubit.xy.play( # TODO Introduce a "play" method to the qubit + 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 new file mode 100644 index 00000000..8d00cf39 --- /dev/null +++ b/quam/components/gates/two_qubit_gates.py @@ -0,0 +1,106 @@ +from abc import ABC, abstractmethod +from typing import Dict +from dataclasses import field +from copy import copy + +from quam.components.pulses import Pulse +from quam.core import quam_dataclass, QuamComponent +from quam.utils import string_reference as str_ref + + +__all__ = ["TwoQubitGate", "CZGate"] + + +@quam_dataclass +class TwoQubitGate(QuamComponent, ABC): + @property + def qubit_pair(self): + from ..qubit_pair import QubitPair + + if isinstance(self.parent, QubitPair): + return self.parent + elif hasattr(self.parent, "parent") and isinstance( + self.parent.parent, QubitPair + ): + return self.parent.parent + else: + raise AttributeError( + "TwoQubitGate is not attached to a QubitPair. 2Q_gate: {self}" + ) + + @property + def qubit_control(self): + return self.qubit_pair.qubit_control + + @property + def qubit_target(self): + return self.qubit_pair.qubit_target + + def __call__(self): + self.execute() + + +@quam_dataclass +class CZGate(TwoQubitGate): + """CZ Operation for a qubit pair""" + + # Pulses will be added to qubit elements + # The reason we don't add "flux_to_q1" directly to q1.z is because it is part of + # the CZ operation, i.e. it is only applied as part of a CZ operation + + flux_pulse_control: Pulse + + phase_shift_control: float = 0.0 + phase_shift_target: float = 0.0 + + @property + def gate_label(self) -> str: + try: + return self.parent.get_attr_name(self) + except AttributeError: + return "CZ" + + @property + def flux_pulse_control_label(self) -> str: + if self.flux_pulse_control.id is not None: + pulse_label = self.flux_pulse_control.id + else: + pulse_label = "flux_pulse_control" + + return f"{self.gate_label}{str_ref.DELIMITER}{pulse_label}" + + def execute(self, amplitude_scale=None): + self.qubit_control.z.play( + self.flux_pulse_control_label, + validate=False, + 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) + self.qubit_control.align(self.qubit_target) + + @property + def config_settings(self): + return {"after": [self.qubit_control.z]} + + def apply_to_config(self, config: dict) -> None: + pulse = copy(self.flux_pulse_control) + pulse.id = self.flux_pulse_control_label + pulse.parent = None # Reset parent so it can be attached to new parent + pulse.parent = self.qubit_control.z + + if self.flux_pulse_control_label in self.qubit_control.z.operations: + raise ValueError( + "Pulse name already exists in pulse operations. " + f"Channel: {self.qubit_control.z.get_reference()}, " + f"Pulse: {self.flux_pulse_control.get_reference()}, " + f"Pulse name: {self.flux_pulse_control_label}" + ) + + pulse.apply_to_config(config) + + element_config = config["elements"][self.qubit_control.z.name] + element_config["operations"][self.flux_pulse_control_label] = pulse.pulse_name diff --git a/quam/components/qubit.py b/quam/components/qubit.py new file mode 100644 index 00000000..06659eff --- /dev/null +++ b/quam/components/qubit.py @@ -0,0 +1,43 @@ +from typing import Dict, Any +from dataclasses import field + +from quam.core import quam_dataclass, QuamComponent +from .gates.single_qubit_gates import SingleQubitGate +from .gates.two_qubit_gates import TwoQubitGate + + +__all__ = ["Qubit"] + + +@quam_dataclass +class Qubit(QuamComponent): + id: str + gates: Dict[str, SingleQubitGate] = field(default_factory=dict) + + def __matmul__(self, other): + """Allows access to qubit pairs using the '@' operator, e.g. (q1 @ q2)""" + if not isinstance(other, Qubit): + raise ValueError( + "Cannot create a qubit pair (q1 @ q2) with a non-qubit object, " + f"where q1={self} and q2={other}" + ) + + if self is other: + raise ValueError( + "Cannot create a qubit pair with same qubit (q1 @ q1), where q1={self}" + ) + + if not hasattr(self._root, "qubit_pairs"): + raise AttributeError( + "Qubit pairs not found in the root component. " + "Please add a 'qubit_pairs' attribute to the root component." + ) + + for qubit_pair in self._root.qubit_pairs: + if qubit_pair.qubit_control is self and qubit_pair.qubit_target is other: + return qubit_pair + else: + raise ValueError( + "Qubit pair not found: qubit_control={self.name}, " + "qubit_target={other.name}" + ) \ No newline at end of file diff --git a/quam/components/qubit_pair.py b/quam/components/qubit_pair.py new file mode 100644 index 00000000..6e7e7807 --- /dev/null +++ b/quam/components/qubit_pair.py @@ -0,0 +1,13 @@ +from typing import Dict +from dataclasses import field + +from quam.core import quam_dataclass, QuamComponent +from quam.components.qubit import Qubit +from quam.components.gates.two_qubit_gates import TwoQubitGate + + +@quam_dataclass +class QubitPair(QuamComponent): + qubit_control: Qubit # TODO Discuss alternatives to "control" and "target" + qubit_target: Qubit + gates: Dict[str, TwoQubitGate] = field(default_factory=dict)