Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/on-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: poetry run poe lint

- name: Run tests
run: poetry run poe test --junitxml=test-results-${{ matrix.python-version }}.xml
run: poetry run poe test --junitxml=test-results-${{ matrix.python-version }}.xml --cloudsim-host "${{ secrets.CI_CLOUD_SIMULATOR_HOST }}" --cloudsim-pwd "${{ secrets.CI_CLOUD_SIMULATOR_PASSWORD }}" --cloudsim-email "${{ secrets.CI_CLOUD_SIMULATOR_USER_EMAIL }}"

- name: Build
run: poetry build
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]
### Added
- results - Add `fetch_xarray_data` to fetch QUA job results and organize them into an `xarray.Dataset` aligned with the QUA iterables (sweep) structure. Requires `qm-qua>=1.3.1`; raises a clear `ImportError` on older versions while keeping `qualang_tools` importable.

### Changed
- requirements - `xarray` is now a core dependency instead of an optional extra (previously under the `datahandler` and `two-qubit-rb` extras).


## [0.22.0] - 2026-04-01
Expand Down
262 changes: 218 additions & 44 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ qm-qua = ">=1.2.3"
scikit-learn = ">=1.5.0"
grpclib = "0.4.5"
attrs = ">=22.2.0"
packaging = ">=20.0"

# Scipy
scipy = [
Expand All @@ -46,7 +47,7 @@ docutils = { version = ">=0.14.0 <= 0.21", optional = true }
waitress = { version = "^3.0.1", optional = true }

# datahandler
xarray = { version = "^2023.0.0", optional = true }
xarray = "^2023.0.0"
netCDF4 = { version = "^1.7.2", optional = true }

# two-qubit-rb
Expand All @@ -56,12 +57,13 @@ cirq = [
]
tqdm = { version = "^4.67.1", optional = true }

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^6.2.5"
black = "^24.3"
poethepoet = "^0.18.0"
flake8 = "^5.0.4"
setuptools = "^69.0.2"
qm-saas = "^1.1.7"

[tool.poetry.extras]
interplot = ["dill", "pypiwin32", "ipython"]
Expand All @@ -77,8 +79,8 @@ configbuilder = [
"docutils",
"waitress",
]
datahandler = ["xarray", "netcdf4"]
two-qubit-rb = ["xarray", "cirq", "tqdm"]
datahandler = ["netcdf4"]
two-qubit-rb = ["cirq", "tqdm"]

[tool.black]
line-length = 120
Expand Down
6 changes: 6 additions & 0 deletions qualang_tools/results/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ for i in range(len(freqs_external)): # Loop over the LO frequencies
...
```

## QUA Iterables Processing

Tools for fetching QUA program results as structured [`xarray.Dataset`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html) objects, with axes automatically labelled according to the QUA iterables.

See [qua_iterables_processing/README.md](qua_iterables_processing/README.md) for full documentation.

## Data handler

The `DataHandler` is used to easily save data once a measurement has been performed.
Expand Down
10 changes: 9 additions & 1 deletion qualang_tools/results/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@
from qualang_tools.results.results import wait_until_job_is_paused

from qualang_tools.results.data_handler import DataHandler, data_processors
from qualang_tools.results.qua_iterables_processing.qua_iterable_postprocess import fetch_xarray_data

__all__ = ["fetching_tool", "progress_counter", "wait_until_job_is_paused", "DataHandler", "data_processors"]
__all__ = [
"fetching_tool",
"progress_counter",
"wait_until_job_is_paused",
"DataHandler",
"data_processors",
"fetch_xarray_data",
]
128 changes: 128 additions & 0 deletions qualang_tools/results/qua_iterables_processing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
## fetch_xarray_data

`fetch_xarray_data` fetches results from a completed QUA job and returns them as an [`xarray.Dataset`](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html), with each result stream automatically shaped and labelled according to the QUA iterables.

It accepts the iterables as either a plain list of `IterableBase` objects or a `QuaProduct` (both are created with qm-qua >= 1.3.1 iterables). The function handles reassembling per-index streams produced by native iterables, and transposes axes so that dimension ordering in the returned Dataset always matches the original iteration order.

**Requirements:** `xarray >= 2024.1`, `qm-qua >= 1.3.1`. On older qm-qua, `fetch_xarray_data` raises a clear `ImportError`; the rest of `qualang_tools` remains importable.

### Basic usage

The simplest way to use `fetch_xarray_data` is to pass your iterables as a plain list — the same list you passed to `QuaProduct` to drive the program:

```python
import numpy as np
from qm.qua import program, declare_with_stream, assign, fixed
from qm.qua.extensions.qua_iterators import (
QuaProduct, QuaIterableRange, PythonIterable, QuaIterable,
)
from qualang_tools.results import fetch_xarray_data

frequencies = np.linspace(100e6, 200e6, 11)
qubits = ["q1", "q2", "q3"]
n_shots = 100

iterables = [
QuaIterableRange("shot", n_shots), # QUA iterable: averaged over on-FPGA if desired
PythonIterable("qubit", qubits), # Python iterable: loops in Python
QuaIterable("frequency", frequencies), # QUA iterable
]

with program() as prog:
for args in QuaProduct(iterables):
I = declare_with_stream(fixed, "I")
assign(I, args.frequency) # store something per iteration point

job = qm.execute(prog)
job.result_handles.wait_for_all_values()

ds = fetch_xarray_data(job, iterables)
# ds is an xr.Dataset with a variable "I" of shape (100, 3, 11)
# and coordinates: shot=[0..99], qubit=["q1","q2","q3"], frequency=[100e6..200e6]
print(ds)
```

### Stream averaging

When a stream is declared with `average_axes`, the averaged dimensions are dropped from the returned Dataset. Other streams using the same iterables retain their full dimensionality.

```python
iterables = [
QuaIterableRange("shot", 100),
PythonIterable("qubit", ["q1", "q2"]),
QuaIterable("frequency", np.linspace(100e6, 200e6, 11)),
]

with program() as prog:
for args in QuaProduct(iterables):
raw = declare_with_stream(fixed, "I_raw")
avg = declare_with_stream(float, "I_avg", average_axes=["shot"])
assign(raw, args.frequency)
assign(avg, args.frequency)

ds = fetch_xarray_data(job, iterables)
# ds["I_raw"]: dims (shot, qubit, frequency), shape (100, 2, 11)
# ds["I_avg"]: dims (qubit, frequency), shape (2, 11) — shot averaged out
```

### Coordinate metadata (units)

Pass a `metadata` dict with a `"unit"` key to any iterable to have that unit attached to the corresponding coordinate in the Dataset:

```python
iterables = [
QuaIterableRange("shot", 100),
QuaIterable("frequency", np.linspace(100e6, 200e6, 11), metadata={"unit": "Hz"}),
PythonIterable("qubit", ["q1", "q2"], metadata={"unit": "qubit_id"}),
]

ds = fetch_xarray_data(job, iterables)
print(ds.coords["frequency"].attrs["unit"]) # "Hz"
```

### Blocking until the job finishes

Pass `wait_until_done=True` to block inside `fetch_xarray_data` rather than waiting manually:

```python
ds = fetch_xarray_data(job, iterables, wait_until_done=True)
```

### QuaZip support

`QuaZip` groups multiple iterables that advance together (zipped, not a full product). The zip group is treated as a single dimension named by the `QuaZip`'s `name` argument:

```python
from qm.qua.extensions.qua_iterators import QuaZip

iterables = [
QuaIterableRange("shot", 10),
QuaZip([
PythonIterable("qubit", ["q1", "q2", "q3"]),
PythonIterable("flux", [0.1, 0.2, 0.3]),
], name="qb_flux"),
QuaIterable("frequency", frequencies),
]

ds = fetch_xarray_data(job, iterables)
# ds["my_stream"]: dims (shot, qb_flux, frequency), shape (10, 3, 11)
```

### Passing a QuaProduct directly

If you already hold a `QuaProduct` object (e.g. because you constructed it before the program), you can pass it directly — `fetch_xarray_data` accepts either form:

```python
qua_product = QuaProduct([
QuaIterableRange("shot", n_shots),
PythonIterable("qubit", qubits),
QuaIterable("frequency", frequencies),
])

with program() as prog:
for args in qua_product:
I = declare_with_stream(fixed, "I")
assign(I, args.frequency)

ds = fetch_xarray_data(job, qua_product) # QuaProduct passed directly
```
Empty file.
Loading
Loading