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
3 changes: 3 additions & 0 deletions docs/changes/newsfragments/8232.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed :meth:`qcodes.dataset.DataSet.to_xarray_dataset` raising an
``AlignmentError`` when two data variables do not share the same setpoints
and one of them is stored as a non-grid (multi-index) array parameter.
35 changes: 35 additions & 0 deletions src/qcodes/dataset/exporters/export_to_xarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,41 @@ def load_to_xarray_dataset(
dataset, data, use_multi_index=use_multi_index
)

# When two data variables do not share the same setpoints, one may be
# exported using a pandas MultiIndex (non-grid data) while another uses a
# standalone dimension for a shared setpoint coordinate. xr.merge raises
# an AlignmentError in that case because the same coordinate name has
# different Index objects in the two sub-datasets. Resolve this by
# unstacking the multi_index into proper independent dimensions (accepting
# NaN for missing grid points) so that the shared coordinate becomes a
# plain 1-D dimension in all sub-datasets.
#
# This only applies when there are at least two variables and there is a
# mix of multi_index and non-multi_index sub-datasets, so we short-circuit
# when the dataset has only one variable or when all variables share the
# same multi_index structure.
if len(xr_dataset_dict) > 1:
multi_index_keys = [
k for k, ds in xr_dataset_dict.items() if "multi_index" in ds.dims
]
if multi_index_keys:
standalone_dims: set[Hashable] = set()
for key, ds in xr_dataset_dict.items():
if key not in multi_index_keys:
standalone_dims.update(ds.dims)
if standalone_dims:
for key in multi_index_keys:
ds = xr_dataset_dict[key]
# Coordinates that are components of the multi_index have
# "multi_index" as their only dimension.
multi_index_level_coords = {
c
for c in ds.coords
if c != "multi_index" and "multi_index" in ds.coords[c].dims
}
if multi_index_level_coords & standalone_dims:
xr_dataset_dict[key] = ds.unstack("multi_index")

# When shapes are inconsistent (e.g. incomplete measurements), different
# code paths may represent the same setpoint parameter as a coordinate in
# some sub-datasets and a data variable in others. xr.merge refuses to
Expand Down
67 changes: 67 additions & 0 deletions tests/dataset/test_dataset_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2326,3 +2326,70 @@ def test_incomplete_measurement_with_shared_setpoint(
assert "signal_1d" in xr_ds.data_vars
assert "signal_2d" in xr_ds.data_vars
assert "x" in xr_ds.coords


def test_export_to_xarray_dataset_different_setpoints(
experiment: Experiment,
) -> None:
"""
Regression test for https://github.com/microsoft/Qcodes/issues/8232

Export to xarray should succeed when two data variables do not share the
same setpoints. Specifically, this covers the case where one parameter is
an array-type with setpoints (f_stop, freq) that are NOT on a regular grid
(so the multi_index path is used), while a second parameter has only the
scalar setpoint (f_stop,). Previously this raised an AlignmentError
because xr.merge could not reconcile the different Index objects for the
shared 'f_stop' coordinate.
"""
n_pts = 5

meas = Measurement(exp=experiment)
meas.register_custom_parameter("f_stop", paramtype="numeric")
meas.register_custom_parameter("freq", paramtype="array")
meas.register_custom_parameter(
"spectrum", setpoints=("f_stop", "freq"), paramtype="array"
)
meas.register_custom_parameter("bar", setpoints=("f_stop",), paramtype="numeric")

f_stop_vals = [10.0, 20.0]
with meas.run() as datasaver:
for f_stop in f_stop_vals:
freqs = np.linspace(0.0, f_stop, n_pts)
spectrum = freqs**2
bar = f_stop**2
datasaver.add_result(
("f_stop", f_stop),
("freq", freqs),
("spectrum", spectrum),
("bar", bar),
)

ds = datasaver.dataset

# This previously raised:
# AlignmentError: cannot align objects on coordinate 'f_stop' because of
# conflicting indexes
xr_ds = ds.to_xarray_dataset()

assert "spectrum" in xr_ds.data_vars
assert "bar" in xr_ds.data_vars
assert "f_stop" in xr_ds.coords

# bar should be recoverable along the f_stop axis
assert set(xr_ds["bar"].dims) == {"f_stop"}
np.testing.assert_allclose(
xr_ds["bar"].dropna("f_stop").values,
np.array([f**2 for f in f_stop_vals]),
)

# spectrum should have f_stop as a dimension (possibly with NaN for
# off-grid entries after unstacking the multi_index)
assert "f_stop" in xr_ds["spectrum"].dims
# Recover spectrum values for each f_stop: drop all-NaN freq slices and
# check that the non-NaN values match freq**2
for f_stop in f_stop_vals:
expected_freqs = np.linspace(0.0, f_stop, n_pts)
expected_spectrum = expected_freqs**2
actual = xr_ds["spectrum"].sel(f_stop=f_stop).dropna("freq").values
np.testing.assert_allclose(actual, expected_spectrum)
Loading