Skip to content

Commit

Permalink
Merge branch 'main' into feat/gate-operations
Browse files Browse the repository at this point in the history
  • Loading branch information
nulinspiratie committed Nov 22, 2024
2 parents c88be57 + 0d6e3a3 commit 2cfe960
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# creation wheel and sdist package archives. Then the draft GitHub release is
# created with attached built python package archives.

name: Create release
name: Create GitHub draft release

on:
push:
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/release_pypi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Publish release to PyPI

on:
release:
types: [ published ]
workflow_dispatch:
inputs:
ref:
type: string
description: Ref to checkout
required: true
env:
# make sure the poetry creates the venv inside the workspace under .venv
POETRY_VIRTUALENVS_IN_PROJECT: true

jobs:
build-release:
name: Publish from release
if: github.event_name == 'release'
uses: qua-platform/quam/.github/workflows/reusable-build.yaml@main
build-manually:
name: Publish from manual trigger
if: github.event_name == 'workflow_dispatch'
uses: qua-platform/quam/.github/workflows/reusable-build.yaml@main
with:
ref: ${{inputs.ref}}

release:
name: Release package to PyPi
runs-on: ubuntu-latest
needs:
- build-release
- build-manually
if: | # TODO: remove when only release trigger be kept
always()
&& contains(needs.*.result, 'success')
&& !contains(needs.*.result, 'failure')
permissions:
id-token: write
contents: read
checks: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# This is necessary so that we have the tags.
fetch-depth: 0
ref: ${{inputs.ref}}

- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
pattern: python-package-*

- name: Publish distribution to PyPI
uses: pypa/[email protected]
with:
password: ${{ secrets.PYPI_API_TOKEN }}
8 changes: 6 additions & 2 deletions .github/workflows/reusable-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@

name: Build python package
on:
workflow_call
workflow_call:
inputs:
ref:
type: string
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: actions/setup-python@v5
with:
python-version: "3.8"
cache: "pip"


- name: Install python deps
run: python -m pip install -e .[build]

Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
## [Unreleased]
### Fixed
- Change location of port feedforward and feedback filters in config


## [0.3.7]
### Added
- Added `WaveformPulse` to allow for pre-defined waveforms.


## [0.3.6]
### Changed
- Modified `MWChannel` to also have `RF_frequency` and `LO_frequency` to match the signature of `IQChannel`.
This is done by letting both inherit from a new base class `_OutComplexChannel`.

## [0.3.5]
### Added
- Added `DragCosinePulse`.
- Added support for sticky channels through the `StickyChannelAddon` (see documentation)
- Added `Channel.thread`, which defaults to None
- QUAM can now be installed through PyPi

### Changed
- Added ports for different hardware. As a consequence we now also support the LF-FEM and MW-FEM
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,33 @@ To install QuAM, first ensure you have 3.8 ≤ Python ≤ 3.11 installed on your
Then run the following command:

```bash
pip install git+https://github.com/qua-platform/quam.git
pip install quam
```

## Quick Start
Here’s a basic example to get you started with QuAM:

```python
from quam.components import BasicQuAM, SingleChannel, pulses
from qm import qua

# Create a root-level QuAM instance
machine = BasicQuAM()

# Add an OPX output channel
channel = SingleChannel(opx_output=("con1", 1))
machine.channels["output"] = channel
# Add a qubit connected to an OPX output channel
qubit = SingleChannel(opx_output=("con1", 1))
machine.channels["qubit"] = qubit

# Add a Gaussian pulse to the channel
channel.operations["gaussian"] = pulses.Gaussian(
qubit.operations["gaussian"] = pulses.GaussianPulse(
length=100, # Pulse length in ns
amplitude=0.5, # Peak amplitude of Gaussian pulse
sigma=20, # Standard deviation of Guassian pulse
)

# Play the Gaussian pulse on the channel within a QUA program
with program() as prog:
channel.play("gaussian")
with qua.program() as prog:
qubit.play("gaussian")

# Generate the QUA configuration from QuAM
qua_configuration = machine.generate_config()
Expand Down
3 changes: 2 additions & 1 deletion docs/components/custom-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ First create the following folder structure
```
my-quam
├── my_quam
│ └── __init__.py
│ └── components
│ └── __init__.py
└── pyproject.toml
```
The file `__init__.py` should be empty, and `pyproject.toml` should have the following contents:
The `__init__.py` files should be empty, and `pyproject.toml` should have the following contents:

/// details | pyproject.toml
```toml
Expand Down
4 changes: 2 additions & 2 deletions docs/demonstration.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ for idx in range(num_qubits):
# Add resonator channel
transmon.resonator = InOutIQChannel(
id=idx,
opx_output_I=("con1", 3 * idx + 1),
opx_output_Q=("con1", 3 * idx + 2),
opx_output_I=("con1", 1),
opx_output_Q=("con1", 2),
opx_input_I=("con1", 1),
opx_input_Q=("con1", 2,),
frequency_converter_up=FrequencyConverter(
Expand Down
24 changes: 12 additions & 12 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,29 @@ QuAM is not just a tool but a gateway to streamlined and efficient quantum compu
- **State Management:** Effortlessly save and load your QuAM state, enabling consistent results and reproducibility in experiments.

```python
from quam.components import *
from quam.components import BasicQuAM, SingleChannel, pulses
from qm import qua

# Create a root-level QuAM instance
machine = BasicQuAM()

# Add an OPX output channel
channel = SingleChannel(opx_output=("con1", 1))
machine.channels["output"] = channel
# Add a qubit connected to an OPX output channel
qubit = SingleChannel(opx_output=("con1", 1))
machine.channels["qubit"] = qubit

# Add a Gaussian pulse to the channel
channel.operations["gaussian"] = pulses.Gaussian(
length=100, amplitude=0.5, sigma=20
qubit.operations["gaussian"] = pulses.GaussianPulse(
length=100, # Pulse length in ns
amplitude=0.5, # Peak amplitude of Gaussian pulse
sigma=20, # Standard deviation of Guassian pulse
)

# Play the Gaussian pulse within a QUA program
with program() as prog:
channel.play("gaussian")
# Play the Gaussian pulse on the channel within a QUA program
with qua.program() as prog:
qubit.play("gaussian")

# Generate the QUA configuration from QuAM
qua_configuration = machine.generate_config()

# Save QuAM to a JSON file
machine.save("state.json")
```
</div>

Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ QuAM can be installed directly using `pip` or by cloning the repository from Git
The easiest way to install QuAM is directly using `pip`:

```bash
pip install git+https://github.com/qua-platform/quam.git
pip install quam
```

### Developer installation from Git
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "quam"
version = "0.3.4"
version = "0.3.7"
#dynamic = ["version"]
description = "Quantum Abstract Machine (QuAM) facilitates development of abstraction layers in experiments."
authors = [
Expand Down
91 changes: 50 additions & 41 deletions quam/components/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,43 +882,11 @@ def measure_sliced(


@quam_dataclass
class IQChannel(Channel):
"""QuAM component for an IQ output channel.
class _OutComplexChannel(Channel, ABC):
"""Base class for IQ and MW output channels."""

Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "X90") and value is a Pulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output_I (Tuple[str, int]): Channel I output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_Q (Tuple[str, int]): Channel Q output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset_I float: The offset of the I channel. Default is 0.
opx_output_offset_Q float: The offset of the Q channel. Default is 0.
intermediate_frequency (float): Intermediate frequency of the mixer.
Default is 0.0
LO_frequency (float): Local oscillator frequency. Default is the LO frequency
of the frequency converter up component.
RF_frequency (float): RF frequency of the mixer. By default, the RF frequency
is inferred by adding the LO frequency and the intermediate frequency.
frequency_converter_up (FrequencyConverter): Frequency converter QuAM component
for the IQ output.
"""

opx_output_I: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogOutputPort]
opx_output_Q: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogOutputPort]

opx_output_offset_I: float = None
opx_output_offset_Q: float = None

frequency_converter_up: BaseFrequencyConverter

LO_frequency: float = "#./frequency_converter_up/LO_frequency"
RF_frequency: float = "#./inferred_RF_frequency"

_default_label: ClassVar[str] = "IQ"
LO_frequency: float
RF_frequency: float

@property
def inferred_RF_frequency(self) -> float:
Expand Down Expand Up @@ -986,6 +954,46 @@ def inferred_LO_frequency(self) -> float:
)
return self.RF_frequency - self.intermediate_frequency


@quam_dataclass
class IQChannel(_OutComplexChannel):
"""QuAM component for an IQ output channel.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "X90") and value is a Pulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output_I (Tuple[str, int]): Channel I output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_Q (Tuple[str, int]): Channel Q output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset_I float: The offset of the I channel. Default is 0.
opx_output_offset_Q float: The offset of the Q channel. Default is 0.
intermediate_frequency (float): Intermediate frequency of the mixer.
Default is 0.0
LO_frequency (float): Local oscillator frequency. Default is the LO frequency
of the frequency converter up component.
RF_frequency (float): RF frequency of the mixer. By default, the RF frequency
is inferred by adding the LO frequency and the intermediate frequency.
frequency_converter_up (FrequencyConverter): Frequency converter QuAM component
for the IQ output.
"""

opx_output_I: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogOutputPort]
opx_output_Q: Union[Tuple[str, int], Tuple[str, int, int], LFAnalogOutputPort]

opx_output_offset_I: float = None
opx_output_offset_Q: float = None

frequency_converter_up: BaseFrequencyConverter

LO_frequency: float = "#./frequency_converter_up/LO_frequency"
RF_frequency: float = "#./inferred_RF_frequency"

_default_label: ClassVar[str] = "IQ"

@property
def local_oscillator(self) -> Optional[LocalOscillator]:
return getattr(self.frequency_converter_up, "local_oscillator", None)
Expand Down Expand Up @@ -1571,7 +1579,7 @@ class InIQOutSingleChannel(SingleChannel, InIQChannel):


@quam_dataclass
class MWChannel(Channel):
class MWChannel(_OutComplexChannel):
"""QuAM component for a MW FEM output channel
Args:
Expand All @@ -1592,13 +1600,16 @@ class MWChannel(Channel):
opx_output: MWFEMAnalogOutputPort
upconverter: int = 1

LO_frequency: float = "#./opx_output/upconverter_frequency"
RF_frequency: float = "#./inferred_RF_frequency"

def apply_to_config(self, config: Dict) -> None:
super().apply_to_config(config)

element_config = config["elements"][self.name]
element_config["MWInput"] = {
"port": self.opx_output.port_tuple,
"upconverter": self.upconverter
"upconverter": self.upconverter,
}


Expand Down Expand Up @@ -1626,9 +1637,7 @@ def apply_to_config(self, config: Dict) -> None:
super().apply_to_config(config)

element_config = config["elements"][self.name]
element_config["MWOutput"] = {
"port": self.opx_input.port_tuple
}
element_config["MWOutput"] = {"port": self.opx_input.port_tuple}
element_config["smearing"] = self.smearing
element_config["time_of_flight"] = self.time_of_flight

Expand Down
8 changes: 6 additions & 2 deletions quam/components/ports/analog_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ def get_port_properties(self):
if self.crosstalk is not None:
port_properties["crosstalk"] = self.crosstalk
if self.feedforward_filter is not None:
port_properties["feedforward_filter"] = list(self.feedforward_filter)
port_properties.setdefault("filter", {})["feedforward"] = list(
self.feedforward_filter
)
if self.feedback_filter is not None:
port_properties["feedback_filter"] = list(self.feedback_filter)
port_properties.setdefault("filter", {})["feedback"] = list(
self.feedback_filter
)
if self.offset is not None:
port_properties["offset"] = self.offset
return port_properties
Expand Down
Loading

0 comments on commit 2cfe960

Please sign in to comment.