Skip to content
Merged
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/8570.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed NetCDF export of measurements without setpoint dependencies, including
scalar and array-valued parameters measured with ``do0d`` or ``dond``.
The exported data retains its shape using parameter-specific dimensions.
23 changes: 23 additions & 0 deletions src/qcodes/dataset/exporters/export_to_xarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,29 @@ def _xarray_data_set_direct(
shape = sub_dict[name].shape
expected_size = prod(shape)

if not deps:
dimensions = tuple(f"{name}_dim_{axis}" for axis in range(len(shape)))

def reshape_without_dependencies(data: npt.NDArray) -> npt.NDArray:
if data.size != expected_size:
raise ValueError(
f"Parameter contains {data.size} values, "
f"but {expected_size} were expected"
)
return data.reshape(shape)

independent_data_vars: dict[str, tuple[tuple[str, ...], npt.NDArray]] = {
name: (dimensions, reshape_without_dependencies(sub_dict[name]))
}
for inf in inferred:
if inf.name in sub_dict:
independent_data_vars[inf.name] = (
dimensions,
reshape_without_dependencies(sub_dict[inf.name]),
)

return xr.Dataset(independent_data_vars)

if len(deps) != len(shape):
raise ValueError(
f"Parameter {name!r} has shape {shape}, but has {len(deps)} dependencies"
Expand Down
33 changes: 33 additions & 0 deletions tests/dataset/dond/test_do0d.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import matplotlib.axes
import numpy as np
import pytest
import xarray as xr
from hypothesis import HealthCheck, given, settings

from qcodes import config, validators
Expand Down Expand Up @@ -95,6 +96,38 @@ def test_do0d_output_data(_param) -> None:
assert loaded_data == np.array([_param.get()])


@pytest.mark.usefixtures("experiment")
def test_do0d_export_to_netcdf(_param, tmp_path) -> None:
dataset = do0d(_param, do_plot=False)[0]

dataset.export(export_type="netcdf", path=tmp_path)

export_path = dataset.export_info.export_paths["nc"]
with xr.open_dataset(export_path) as exported_dataset:
assert exported_dataset[_param.name].dims == (f"{_param.name}_dim_0",)
np.testing.assert_array_equal(
exported_dataset[_param.name].values, np.array([_param.get()])
)


@pytest.mark.usefixtures("experiment")
def test_do0d_array_export_to_netcdf(tmp_path) -> None:
param = ArrayshapedParam(
name="paramwitharrayval", vals=validators.Arrays(shape=(10,))
)
dataset = do0d(param, do_plot=False)[0]
expected_data = dataset.get_parameter_data()[param.name][param.name]

dataset.export(export_type="netcdf", path=tmp_path)

export_path = dataset.export_info.export_paths["nc"]
with xr.open_dataset(export_path) as exported_dataset:
assert exported_dataset[param.name].dims == (f"{param.name}_dim_0",)
np.testing.assert_array_equal(
exported_dataset[param.name].values, expected_data
)


@pytest.mark.usefixtures("experiment")
@pytest.mark.parametrize(
"multiparamtype",
Expand Down
55 changes: 55 additions & 0 deletions tests/dataset/test_dataset_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ def _make_direct_export_dataset(experiment: Experiment) -> DataSet:
return dataset


@pytest.fixture(name="independent_export_dataset")
def _make_independent_export_dataset(experiment: Experiment) -> DataSet:
dataset = new_data_set("independent_export_dataset")
signalparam = ParamSpecBase("signal", "numeric")
inferredparam = ParamSpecBase("inferred", "numeric")
idps = InterDependencies_(inferences={inferredparam: (signalparam,)})
dataset.set_interdependencies(idps)
return dataset


@pytest.fixture(name="mock_dataset_grid_incomplete")
def _make_mock_dataset_grid_incomplete(experiment: Experiment) -> DataSet:
dataset = new_data_set("dataset")
Expand Down Expand Up @@ -1756,6 +1766,51 @@ def test_xarray_data_set_direct_skips_missing_inferred_data(
assert set(xarray_dataset.data_vars) == {"signal"}


@pytest.mark.parametrize("shape", [(), (1,), (4,), (2, 2)])
@pytest.mark.parametrize("include_inferred", [True, False])
def test_xarray_data_set_direct_without_dependencies(
independent_export_dataset: DataSet,
shape: tuple[int, ...],
include_inferred: bool,
) -> None:
signal = np.arange(np.prod(shape, dtype=int)).reshape(shape)
data = {"signal": signal}
if include_inferred:
data["inferred"] = (signal + 10).ravel()

xarray_dataset = _xarray_data_set_direct(independent_export_dataset, "signal", data)

dimensions = tuple(f"signal_dim_{axis}" for axis in range(len(shape)))
assert set(xarray_dataset.coords) == set()
assert xarray_dataset["signal"].dims == dimensions
assert xarray_dataset["signal"].shape == shape
assert_array_equal(xarray_dataset["signal"].values, signal)
if include_inferred:
assert set(xarray_dataset.data_vars) == {"signal", "inferred"}
assert xarray_dataset["inferred"].dims == dimensions
assert xarray_dataset["inferred"].shape == shape
assert_array_equal(xarray_dataset["inferred"].values, signal + 10)
else:
assert set(xarray_dataset.data_vars) == {"signal"}


@pytest.mark.parametrize("inferred_size", [0, 3, 5])
def test_xarray_data_set_direct_without_dependencies_rejects_invalid_inferred_size(
independent_export_dataset: DataSet,
inferred_size: int,
) -> None:
data = {
"signal": np.arange(4).reshape(2, 2),
"inferred": np.arange(inferred_size),
}

with pytest.raises(
ValueError,
match=f"^Parameter contains {inferred_size} values, but 4 were expected$",
):
_xarray_data_set_direct(independent_export_dataset, "signal", data)


def test_multi_index_options_incomplete_grid(
mock_dataset_grid_incomplete: DataSet,
) -> None:
Expand Down
Loading