From 62953daf835deb7e5323084b03623f4cd0a6f967 Mon Sep 17 00:00:00 2001 From: Esben Bork Hansen Date: Mon, 2 Mar 2026 12:34:33 +0100 Subject: [PATCH 1/4] added shapes to DatasetDefinition and use it to set shape of measurement. extended tests to cover new uses. --- src/qcodes/dataset/measurement_extensions.py | 8 + tests/dataset/test_measurement_extensions.py | 150 +++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/qcodes/dataset/measurement_extensions.py b/src/qcodes/dataset/measurement_extensions.py index 719c6695fa0b..639fc472cb56 100644 --- a/src/qcodes/dataset/measurement_extensions.py +++ b/src/qcodes/dataset/measurement_extensions.py @@ -16,6 +16,7 @@ from qcodes.parameters.parameter_base import ParameterBase if TYPE_CHECKING: + from qcodes.dataset.descriptions.versioning.rundescribertypes import Shapes from qcodes.dataset.experiment_container import Experiment TRACER = trace.get_tracer(__name__) @@ -37,6 +38,11 @@ class DataSetDefinition: experiment: Experiment | None = None """An optional argument specifying which Experiment this dataset should be written to""" + shapes: Shapes | None = None + """An optional dictionary from dependent parameter names to a tuple of + integers describing the shape of the measurement. This is used to + specify the shape of the dataset. + See :class:`qcodes.dataset.measurements.Measurement.set_shapes`.""" metadata: Mapping[str, Any] | None = None """An optional dictionary of metadata that will be added to the dataset generated by this definition""" @@ -69,6 +75,8 @@ def setup_measurement_instances( meas.register_parameter(param) for param in ds_def.dependent: meas.register_parameter(param, setpoints=ds_def.independent) + if ds_def.shapes is not None: + meas.set_shapes(ds_def.shapes) measurements.append(meas) return measurements diff --git a/tests/dataset/test_measurement_extensions.py b/tests/dataset/test_measurement_extensions.py index 87a342b7323f..b65d8a4b425a 100644 --- a/tests/dataset/test_measurement_extensions.py +++ b/tests/dataset/test_measurement_extensions.py @@ -21,6 +21,7 @@ LinSweeper, datasaver_builder, dond_into, + setup_measurement_instances, ) from qcodes.parameters import Parameter, ParameterWithSetpoints from qcodes.validators import Arrays @@ -490,3 +491,152 @@ def test_context_with_override_experiment( assert datasets[0].exp_name == experiment2.name assert datasets[1].exp_name == experiment2.name + + +def test_shapes_in_dataset_definition_with_scalar_params( + default_params, default_database_and_experiment +): + """Test that shapes specified in DataSetDefinition are stored on the dataset.""" + _ = default_database_and_experiment + set1, set2, _, meas1, meas2, _ = default_params + + expected_shapes = { + meas1.register_name: (11, 11), + meas2.register_name: (11, 11), + } + dataset_definition = [ + DataSetDefinition( + name="dataset_with_shapes", + independent=[set1, set2], + dependent=[meas1, meas2], + shapes=expected_shapes, + ), + ] + with datasaver_builder(dataset_definition) as datasavers: + for _ in LinSweeper(set1, 0, 10, 11, 0.001): + sweep1 = LinSweep(set2, 0, 10, 11, 0.001) + dond_into(datasavers[0], sweep1, meas1, meas2, additional_setpoints=(set1,)) + datasets = [datasaver.dataset for datasaver in datasavers] + + assert datasets[0].description.shapes == expected_shapes + + +def test_shapes_in_dataset_definition_with_pws( + pws_params, default_database_and_experiment +): + """Test that shapes for ParameterWithSetpoints are stored on the dataset.""" + _ = default_database_and_experiment + pws1, set1 = pws_params + + expected_shapes = { + pws1.register_name: (11, 11), + } + dataset_definition = [ + DataSetDefinition( + name="dataset_pws_shapes", + independent=[set1], + dependent=[pws1], + shapes=expected_shapes, + ), + ] + with datasaver_builder(dataset_definition) as datasavers: + for _ in LinSweeper(set1, 0, 10, 11, 0.001): + dond_into(datasavers[0], pws1, additional_setpoints=(set1,)) + datasets = [datasaver.dataset for datasaver in datasavers] + + assert datasets[0].description.shapes == expected_shapes + + +def test_shapes_none_by_default(default_params, default_database_and_experiment): + """Test that shapes is None when not specified in DataSetDefinition.""" + _ = default_database_and_experiment + set1, _, _, meas1, _, _ = default_params + + dataset_definition = [ + DataSetDefinition( + name="dataset_no_shapes", + independent=[set1], + dependent=[meas1], + ), + ] + with datasaver_builder(dataset_definition) as datasavers: + for _ in LinSweeper(set1, 0, 5, 6, 0.001): + datasavers[0].add_result((set1, set1()), (meas1, meas1())) + datasets = [datasaver.dataset for datasaver in datasavers] + + assert datasets[0].description.shapes is None + + +def test_setup_measurement_instances_sets_shapes( + default_params, default_database_and_experiment +): + """Test that setup_measurement_instances correctly sets shapes on Measurement.""" + _ = default_database_and_experiment + set1, _, _, meas1, _, _ = default_params + + expected_shapes = {meas1.register_name: (5,)} + dataset_definitions = [ + DataSetDefinition( + name="test_shapes", + independent=[set1], + dependent=[meas1], + shapes=expected_shapes, + ), + ] + measurements = setup_measurement_instances(dataset_definitions) + assert len(measurements) == 1 + assert measurements[0]._shapes == expected_shapes + + +def test_setup_measurement_instances_no_shapes( + default_params, default_database_and_experiment +): + """Test that setup_measurement_instances leaves shapes as None when not specified.""" + _ = default_database_and_experiment + set1, _, _, meas1, _, _ = default_params + + dataset_definitions = [ + DataSetDefinition( + name="test_no_shapes", + independent=[set1], + dependent=[meas1], + ), + ] + measurements = setup_measurement_instances(dataset_definitions) + assert len(measurements) == 1 + assert measurements[0]._shapes is None + + +def test_shapes_with_multiple_datasets( + default_params, default_database_and_experiment +): + """Test shapes are correctly applied to multiple datasets independently.""" + _ = default_database_and_experiment + set1, set2, set3, meas1, _, meas3 = default_params + + shapes_1 = {meas1.register_name: (11, 11)} + shapes_2 = {meas3.register_name: (11, 11)} + dataset_definition = [ + DataSetDefinition( + name="dataset_1", + independent=[set1, set2], + dependent=[meas1], + shapes=shapes_1, + ), + DataSetDefinition( + name="dataset_2", + independent=[set1, set3], + dependent=[meas3], + shapes=shapes_2, + ), + ] + with datasaver_builder(dataset_definition) as datasavers: + for _ in LinSweeper(set1, 0, 10, 11, 0.001): + sweep1 = LinSweep(set2, 0, 10, 11, 0.001) + sweep2 = LinSweep(set3, -10, 0, 11, 0.001) + dond_into(datasavers[0], sweep1, meas1, additional_setpoints=(set1,)) + dond_into(datasavers[1], sweep2, meas3, additional_setpoints=(set1,)) + datasets = [datasaver.dataset for datasaver in datasavers] + + assert datasets[0].description.shapes == shapes_1 + assert datasets[1].description.shapes == shapes_2 From 2bd623ffbb04b3561599fc5337bba056c874ae71 Mon Sep 17 00:00:00 2001 From: Esben Bork Hansen Date: Mon, 2 Mar 2026 12:42:40 +0100 Subject: [PATCH 2/4] added newfragments and formating --- docs/changes/newsfragments/7891.improved | 1 + tests/dataset/test_measurement_extensions.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 docs/changes/newsfragments/7891.improved diff --git a/docs/changes/newsfragments/7891.improved b/docs/changes/newsfragments/7891.improved new file mode 100644 index 000000000000..7154a99b7d10 --- /dev/null +++ b/docs/changes/newsfragments/7891.improved @@ -0,0 +1 @@ +Enabled setting shape of Measurement in measurement_extensions.py. Added `shapes` to DataSetDefinition and use it in setup_measurement_isntances to set shape of measurement. Added new tests for new use cases. \ No newline at end of file diff --git a/tests/dataset/test_measurement_extensions.py b/tests/dataset/test_measurement_extensions.py index b65d8a4b425a..6531131ae48f 100644 --- a/tests/dataset/test_measurement_extensions.py +++ b/tests/dataset/test_measurement_extensions.py @@ -607,9 +607,7 @@ def test_setup_measurement_instances_no_shapes( assert measurements[0]._shapes is None -def test_shapes_with_multiple_datasets( - default_params, default_database_and_experiment -): +def test_shapes_with_multiple_datasets(default_params, default_database_and_experiment): """Test shapes are correctly applied to multiple datasets independently.""" _ = default_database_and_experiment set1, set2, set3, meas1, _, meas3 = default_params From 79bb01e1dc4c552c2c5f185fcf7baa4c0b468486 Mon Sep 17 00:00:00 2001 From: esben <87641615+esbenh4@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:49:03 +0100 Subject: [PATCH 3/4] Update src/qcodes/dataset/measurement_extensions.py Co-authored-by: Jens Hedegaard Nielsen --- src/qcodes/dataset/measurement_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qcodes/dataset/measurement_extensions.py b/src/qcodes/dataset/measurement_extensions.py index 639fc472cb56..11767772a49e 100644 --- a/src/qcodes/dataset/measurement_extensions.py +++ b/src/qcodes/dataset/measurement_extensions.py @@ -42,7 +42,7 @@ class DataSetDefinition: """An optional dictionary from dependent parameter names to a tuple of integers describing the shape of the measurement. This is used to specify the shape of the dataset. - See :class:`qcodes.dataset.measurements.Measurement.set_shapes`.""" + See :class:`qcodes.dataset.measurements.Measurement.set_shapes`.""" metadata: Mapping[str, Any] | None = None """An optional dictionary of metadata that will be added to the dataset generated by this definition""" From e6f8ba6027bdbe3f5ef16d698277f1d4bad2c4da Mon Sep 17 00:00:00 2001 From: esben <87641615+esbenh4@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:49:14 +0100 Subject: [PATCH 4/4] Update docs/changes/newsfragments/7891.improved Co-authored-by: Jens Hedegaard Nielsen --- docs/changes/newsfragments/7891.improved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changes/newsfragments/7891.improved b/docs/changes/newsfragments/7891.improved index 7154a99b7d10..d86c0b198044 100644 --- a/docs/changes/newsfragments/7891.improved +++ b/docs/changes/newsfragments/7891.improved @@ -1 +1 @@ -Enabled setting shape of Measurement in measurement_extensions.py. Added `shapes` to DataSetDefinition and use it in setup_measurement_isntances to set shape of measurement. Added new tests for new use cases. \ No newline at end of file +Enabled setting shape of Measurement in measurement_extensions.py. Added `shapes` to DataSetDefinition and use it in setup_measurement_isntances to set shape of measurement. Added new tests for new use cases.