Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
### Fixed
- Remove qualibrate and xarray from the requirements.

## [0.1.2] - 2025-07-25
### Added
- QuamMacros for Cross Resonance and CZ.
- Architecture components for Flux Tunable Cross Drive TransmonPair.
- Fix for `upconverter_frequency` property under `XYDriveMW`.

## [0.1.1] - 2025-07-14
### Added
- Optional `num_IQ_pairs` argument to `declare_qua_variables`.
Expand All @@ -18,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Builder functions for the general QUAM wiring.
- Builder functions for Transmons.

[Unreleased]: https://github.com/qua-platform/quam-builder/compare/v0.1.1...HEAD
[Unreleased]: https://github.com/qua-platform/quam-builder/compare/v0.1.2...HEAD
[0.1.2]: https://github.com/qua-platform/quam-builder/releases/tag/v0.1.2
[0.1.1]: https://github.com/qua-platform/quam-builder/releases/tag/v0.1.1
[0.1.0]: https://github.com/qua-platform/quam-builder/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = [{ name = "Theo Laudat", email = "theo@quantum-machines.co" }]
requires-python = ">=3.9,<=3.12"
dependencies = [
"qualang-tools>=0.19.0",
"qualibration-libs@git+https://github.com/qua-platform/qualibration-libs.git",
"qualibration-libs>=0.1.0",
"quam>=0.4.0",
]

Expand Down
3 changes: 3 additions & 0 deletions quam_builder/architecture/superconducting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
zz_drive,
xy_drive,
)
from .custom_gates.cross_resonance import CRGate


__all__ = [
*qpu.__all__,
*qubit.__all__,
*qubit_pair.__all__,
*custom_gates.__all__,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from quam.core import quam_dataclass
from quam.components.channels import IQChannel, MWChannel
from quam_builder.architecture.superconducting.components.xy_drive import XYDriveMW, XYDriveIQ


__all__ = ["XYDetunedDriveIQ", "XYDetunedDriveMW"]

@quam_dataclass
class XYDetunedDriveBase:
xy_RF_frequency: float = None
xy_intermediate_frequency: float = None
detuning: float = None


@quam_dataclass
class XYDetunedDriveIQ(XYDriveIQ, XYDetunedDriveBase):
RF_frequency: float = None
intermediate_frequency: float = None

@property
def inferred_RF_frequency(self) -> float:
return self.xy_RF_frequency + self.detuning

@property
def inferred_intermediate_frequency(self) -> float:
return self.xy_intermediate_frequency + self.detuning


@quam_dataclass
class XYDetunedDriveMW(XYDriveMW, XYDetunedDriveBase):
RF_frequency: float = None
intermediate_frequency: float = None

@property
def inferred_RF_frequency(self) -> float:
return self.xy_RF_frequency + self.detuning

@property
def inferred_intermediate_frequency(self) -> float:
return self.xy_intermediate_frequency + self.detuning
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,6 @@ def set_output_power(
class XYDriveMW(MWChannel, XYDriveBase):
intermediate_frequency: float = "#./inferred_intermediate_frequency"

@property
def upconverter_frequency(self):
"""Returns the up-converter/LO frequency in Hz."""
return self.opx_output.upconverter_frequency

def get_output_power(self, operation, Z=50) -> float:
"""
Calculate the output power in dBm of the specified operation.
Expand Down
11 changes: 11 additions & 0 deletions quam_builder/architecture/superconducting/custom_gates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from quam_builder.architecture.superconducting.custom_gates import (
cross_resonance,
cz,
stark_induced_cz,
)

__all__ = [
*cross_resonance.__all__,
*cz.__all__,
*stark_induced_cz.__all__,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
from typing import Optional, Literal
from qm.qua import *
from quam.core import quam_dataclass
from quam.components.pulses import Pulse
from quam.components.macro import QubitPairMacro
from qm.qua._dsl import QuaExpression, QuaVariable

__all__ = ["CRGate"]


qua_T = QuaVariable | QuaExpression


@quam_dataclass
class CRGate(QubitPairMacro):
qc_correction_phase: float = 0.0

def apply(
self,
cr_type: Literal[
"direct", "direct+cancel", "direct+echo", "direct+cancel+echo"
] = "direct",
wf_type: Literal["square", "cosine", "gauss", "flattop"] = "square",
cr_drive_amp_scaling: Optional[float | qua_T] = None,
cr_drive_phase: Optional[float | qua_T] = None,
cr_cancel_amp_scaling: Optional[float | qua_T] = None,
cr_cancel_phase: Optional[float | qua_T] = None,
cr_duration_clock_cycles: Optional[float | qua_T] = None,
qc_correction_phase: Optional[float | qua_T] = None,
) -> None:
qc = self.qubit_pair.qubit_control
qt = self.qubit_pair.qubit_target
cr = self.qubit_pair.cross_resonance
cr_elems = [qc.xy.name, qt.xy.name, cr.name]

def _play_cr_pulse(
elem,
wf_type: str = wf_type,
amp_scale: Optional[float | qua_T] = None,
duration: Optional[float | qua_T] = None,
sgn: int = 1,
):
if amp_scale is None and duration is None:
elem.play(wf_type)
elif amp_scale is None:
elem.play(wf_type, duration=duration)
elif duration is None:
elem.play(wf_type, amplitude_scale=sgn * amp_scale)
else:
elem.play(wf_type, amplitude_scale=sgn * amp_scale, duration=duration)

def cr_drive_shift_phase():
if cr_drive_phase is not None:
cr.frame_rotation_2pi(cr_drive_phase)

def cr_cancel_shift_phase():
if cr_cancel_phase is not None:
qt.xy.frame_rotation_2pi(cr_cancel_phase)

def qc_shift_correction_phase():
if qc_correction_phase is not None:
qc.xy.frame_rotation_2pi(qc_correction_phase)

def cr_drive_play(
sgn: Literal["direct", "echo"] = "direct",
wf_type=wf_type,
):
_play_cr_pulse(
elem=cr,
wf_type=wf_type,
amp_scale=cr_drive_amp_scaling,
duration=cr_duration_clock_cycles,
sgn=1 if sgn == "direct" else -1,
)

def cr_cancel_play(
sgn: Literal["direct", "echo"] = "direct",
wf_type=wf_type,
):
_play_cr_pulse(
elem=qt.xy,
wf_type=f"cr_{wf_type}_{self.qubit_pair.name}",
amp_scale=cr_cancel_amp_scaling,
duration=cr_duration_clock_cycles,
sgn=1 if sgn == "direct" else -1,
)

if cr_type == "direct":
cr_drive_shift_phase()
align(*cr_elems)

cr_drive_play(sgn="direct")
align(*cr_elems)

reset_frame(cr.name)
qc_shift_correction_phase()
align(*cr_elems)

elif cr_type == "direct+echo":
cr_drive_shift_phase()
align(*cr_elems)

cr_drive_play(sgn="direct")
align(*cr_elems)

qc.xy.play("x180")
align(*cr_elems)

cr_drive_play(sgn="echo")
align(*cr_elems)

qc.xy.play("x180")
align(*cr_elems)

reset_frame(cr.name)
qc_shift_correction_phase()
align(*cr_elems)

elif cr_type == "direct+cancel":
cr_drive_shift_phase()
cr_cancel_shift_phase()
align(*cr_elems)

cr_drive_play(sgn="direct")
cr_cancel_play(sgn="direct")
align(*cr_elems)

reset_frame(cr.name)
reset_frame(qt.xy.name)
qc_shift_correction_phase()
align(*cr_elems)

elif cr_type == "direct+cancel+echo":
cr_drive_shift_phase()
cr_cancel_shift_phase()
align(*cr_elems)

cr_drive_play(sgn="direct")
cr_cancel_play(sgn="direct")
align(*cr_elems)

qc.xy.play("x180")
align(*cr_elems)

cr_drive_play(sgn="echo")
cr_cancel_play(sgn="echo")
align(*cr_elems)

qc.xy.play("x180")
align(*cr_elems)

reset_frame(cr.name)
reset_frame(qt.xy.name)
qc_shift_correction_phase()
align(*cr_elems)
87 changes: 87 additions & 0 deletions quam_builder/architecture/superconducting/custom_gates/cz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from typing import Literal, Union

import numpy as np
from qm.qua import Cast, assign, broadcast, declare, fixed, while_

from quam.components.macro import QubitMacro, QubitPairMacro
from quam.components.pulses import Pulse, ReadoutPulse
from quam.core import quam_dataclass
from quam.utils.qua_types import QuaVariableBool, QuaVariableFloat, QuaVariableInt

__all__ = ["CZGate"]


def get_pulse_name(pulse: Pulse) -> str:
"""
Get the name of the pulse. If the pulse has an id, return it.
"""
if pulse.id is not None:
return pulse.id
elif pulse.parent is not None:
return pulse.parent.get_attr_name(pulse)
else:
raise AttributeError(f"Cannot infer id of {pulse} because it is not attached to a parent")


@quam_dataclass
class CZGate(QubitPairMacro):
flux_pulse_control: Union[Pulse, str]
coupler_flux_pulse: Pulse = None

pre_wait: int = 4

phase_shift_control: float = 0.0
phase_shift_target: float = 0.0

@property
def coupler(self): # -> "TunableCoupler":
return self.qubit_pair.coupler

@property
def flux_pulse_control_label(self) -> str:
pulse = (
self.qubit_control.get_pulse(self.flux_pulse_control)
if isinstance(self.flux_pulse_control, str)
else self.flux_pulse_control
)
return get_pulse_name(pulse)

@property
def coupler_flux_pulse_label(self) -> str:
pulse = (
self.coupler.get_pulse(self.coupler_flux_pulse)
if isinstance(self.coupler_flux_pulse, str)
else self.coupler_flux_pulse
)
return get_pulse_name(pulse)

def apply(
self,
*,
amplitude_scale=None,
phase_shift_control=None,
phase_shift_target=None,
**kwargs,
) -> None:
self.qubit_pair.qubit_control.z.play(
self.flux_pulse_control_label,
validate=False,
amplitude_scale=amplitude_scale,
)

if self.coupler_flux_pulse is not None:
self.qubit_pair.coupler.play(self.coupler_flux_pulse_label, validate=False)

self.qubit_pair.align()
if phase_shift_control is not None:
self.qubit_pair.qubit_control.xy.frame_rotation_2pi(phase_shift_control)
elif np.abs(self.phase_shift_control) > 1e-6:
self.qubit_pair.qubit_control.xy.frame_rotation_2pi(self.phase_shift_control)
if phase_shift_target is not None:
self.qubit_pair.qubit_target.xy.frame_rotation_2pi(phase_shift_target)
elif np.abs(self.phase_shift_target) > 1e-6:
self.qubit_pair.qubit_target.xy.frame_rotation_2pi(self.phase_shift_target)

self.qubit_pair.qubit_control.xy.play("x180", amplitude_scale=0.0, duration=4)
self.qubit_pair.qubit_target.xy.play("x180", amplitude_scale=0.0, duration=4)
self.qubit_pair.align()
Loading