Skip to content
Open
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
6 changes: 6 additions & 0 deletions docs/changes/newsfragments/7725.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Parameters using ``has_control_of`` are now correctly handled when exporting to
xarray. Controlled parameters are no longer treated as independent top-level
parameters, preventing duplicate data rows. Additionally, inferred parameters
are now included as data variables in the xarray dataset when exporting via the
pandas-based path, and a warning is logged when the inferred parameter data size
does not match the expected xarray dataset dimensions.
10 changes: 10 additions & 0 deletions src/qcodes/dataset/descriptions/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,16 @@ def top_level_parameters(self) -> tuple[ParamSpecBase, ...]:
for node_id, in_degree in self._dependency_subgraph.in_degree
if in_degree == 0
}
# Parameters that are inferred from other parameters (have outgoing
# edges in the inference subgraph) should not be independent top-level
# parameters, since their data is part of the tree of the parameter
# they are inferred from.
parameters_inferred_from_others = {
self._node_to_paramspec(node_id)
for node_id, out_degree in self._inference_subgraph.out_degree
if out_degree > 0
}
dependency_top_level = dependency_top_level - parameters_inferred_from_others
standalone_top_level = {
self._node_to_paramspec(node_id)
for node_id, degree in self._graph.degree
Expand Down
77 changes: 74 additions & 3 deletions src/qcodes/dataset/exporters/export_to_xarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from math import prod
from typing import TYPE_CHECKING, Literal

import numpy as np
from packaging import version as p_version

from qcodes.dataset.linked_datasets.links import links_to_str
Expand Down Expand Up @@ -61,6 +62,68 @@ def _calculate_index_shape(idx: pd.Index | pd.MultiIndex) -> dict[Hashable, int]
return expanded_shape


def _add_inferred_data_vars(
dataset: DataSetProtocol,
name: str,
sub_dict: Mapping[str, npt.NDArray],
xr_dataset: xr.Dataset,
) -> xr.Dataset:
Comment thread
jenshnielsen marked this conversation as resolved.
"""Add inferred parameters as data variables to an xarray dataset.

Parameters that are inferred from the top-level measurement parameter
and present in sub_dict but not yet in the dataset are added as data
variables along the existing dimensions.
"""

interdeps = dataset.description.interdeps
meas_paramspec = interdeps.graph.nodes[name]["value"]
_, deps, inferred = interdeps.all_parameters_in_tree_by_group(meas_paramspec)

dep_names = {dep.name for dep in deps}
dims = tuple(d for d in xr_dataset.dims)

for inf in inferred:
if inf.name in dep_names:
continue
if inf.name in xr_dataset:
continue
if inf.name not in sub_dict:
continue

inf_data = sub_dict[inf.name]
if inf_data.dtype == np.dtype("O"):
try:
flat = np.concatenate(inf_data)
except ValueError:
flat = inf_data.ravel()
else:
flat = inf_data.ravel()

# Only add if the flattened data can be reshaped to the dataset
# dimensions. This is more robust than checking individual parent
# sizes because an inferred parameter may have multiple parents
# with different sizes.
expected_shape = tuple(xr_dataset.sizes[d] for d in dims)
expected_size = prod(expected_shape)
if flat.shape[0] == expected_size:
xr_dataset[inf.name] = (dims, flat.reshape(expected_shape))
else:
_LOG.warning(
"Cannot add inferred parameter '%s' to xarray dataset for '%s' "
"(run_id=%s): data size %d does not match the dataset "
"dimensions %s (size %d). This is likely a user error in the "
"measurement setup.",
inf.name,
name,
dataset.run_id,
flat.shape[0],
dict(zip(dims, expected_shape)),
expected_size,
)

return xr_dataset


def _load_to_xarray_dataset_dict_no_metadata(
dataset: DataSetProtocol,
datadict: Mapping[str, Mapping[str, npt.NDArray]],
Expand Down Expand Up @@ -100,25 +163,33 @@ def _load_to_xarray_dataset_dict_no_metadata(
interdeps=dataset.description.interdeps,
dependent_parameter=name,
).to_xarray()
xr_dataset_dict[name] = xr_dataset
xr_dataset_dict[name] = _add_inferred_data_vars(
dataset, name, sub_dict, xr_dataset
)
Comment thread
jenshnielsen marked this conversation as resolved.
elif index_is_unique:
df = _data_to_dataframe(
sub_dict,
index,
interdeps=dataset.description.interdeps,
dependent_parameter=name,
)
xr_dataset_dict[name] = _xarray_data_set_from_pandas_multi_index(
xr_dataset = _xarray_data_set_from_pandas_multi_index(
dataset, use_multi_index, name, df, index
)
xr_dataset_dict[name] = _add_inferred_data_vars(
dataset, name, sub_dict, xr_dataset
)
else:
df = _data_to_dataframe(
sub_dict,
index,
interdeps=dataset.description.interdeps,
dependent_parameter=name,
)
xr_dataset_dict[name] = df.reset_index().to_xarray()
xr_dataset = df.reset_index().to_xarray()
xr_dataset_dict[name] = _add_inferred_data_vars(
dataset, name, sub_dict, xr_dataset
)

return xr_dataset_dict

Expand Down
Loading
Loading