diff --git a/docs/changes/newsfragments/7732.improved b/docs/changes/newsfragments/7732.improved new file mode 100644 index 00000000000..00f74c6896b --- /dev/null +++ b/docs/changes/newsfragments/7732.improved @@ -0,0 +1,3 @@ +Added parameters that are controlled by a ParameterWithSetpoints setpoints to +``ParameterWithSetpoints.unpack_self``. Dependent parameters will now be +automatically saved in the correct shape alike the corresponding setpoints. diff --git a/src/qcodes/parameters/parameter_with_setpoints.py b/src/qcodes/parameters/parameter_with_setpoints.py index d9ef9badf4d..49cde0f9f5b 100644 --- a/src/qcodes/parameters/parameter_with_setpoints.py +++ b/src/qcodes/parameters/parameter_with_setpoints.py @@ -160,16 +160,30 @@ def depends_on(self) -> ParameterSet: return ParameterSet(self.setpoints) def unpack_self(self, value: ValuesType) -> list[tuple[ParameterBase, ValuesType]]: + """ + Unpacks the ParameterWithSetpoints, its setpoints and any inferred + parameters controlled by the setpoints. + + Args: + value(ValuesType): The data acquired from this parameter. + + Returns: + A list of tuples of parameters and values to be added as results + to the dataset. + """ unpacked_results: list[tuple[ParameterBase, ValuesType]] = [] - setpoint_params = [] - setpoint_data = [] - for setpointparam in self.setpoints: - these_setpoints = setpointparam.get() - setpoint_params.append(setpointparam) - setpoint_data.append(these_setpoints) - output_grids = np.meshgrid(*setpoint_data, indexing="ij") - for param, grid in zip(setpoint_params, output_grids): - unpacked_results.append((param, grid)) + setpoint_params = list(self.setpoints) + setpoint_data = [param.get() for param in setpoint_params] + output_grids = list(np.meshgrid(*setpoint_data, indexing="ij")) + for i, param in enumerate(setpoint_params[:]): + for inferred_param in param.has_control_of: + copy_setpoint_data = setpoint_data[:] + copy_setpoint_data[i] = inferred_param.get() + setpoint_params.append(inferred_param) + output_grids.append( + np.meshgrid(*copy_setpoint_data, indexing="ij")[i] + ) + unpacked_results = list(zip(setpoint_params, output_grids)) unpacked_results.extend( super().unpack_self(value) ) # Must come last to preserve original ordering diff --git a/tests/dataset/measurement/test_self_unpacking.py b/tests/dataset/measurement/test_self_unpacking.py index 7e0245ab17b..19c09249b74 100644 --- a/tests/dataset/measurement/test_self_unpacking.py +++ b/tests/dataset/measurement/test_self_unpacking.py @@ -37,9 +37,13 @@ def __init__( def set_raw(self, value: ParamRawDataType) -> None: # Set all dependent parameters based on their mapping functions + self.value = value for param, slope_offset in self._components_dict.items(): param(value * slope_offset[0] + slope_offset[1]) + def get_raw(self) -> ParamRawDataType: + return self.value + def unpack_self( self, value: "ValuesType" ) -> list[tuple["ParameterBase", "ValuesType"]]: @@ -142,6 +146,50 @@ def test_add_result_self_unpack_with_PWS(controlling_parameters, experiment): assert pws_data["pws"].shape == (11, 11) assert pws_data["pws_setpoints"].shape == (11, 11) +def test_add_result_self_unpack_with_PWS_and_inferred_setpoints( + experiment, controlling_parameters): + """ + Test that a ParameterWithSetpoints that has setpoints which themselves + have inferred parameters controlled by a ControllingParameter unpacks + correctly. + """ + control1, comp1, comp2 = controlling_parameters + for param in (control1, comp1, comp2): + param.vals = Arrays(shape=(11,)) + pws_other_setpoints = Parameter( + "pws_other_setpoints", + get_cmd=lambda: np.linspace(-1, 1, 13), + vals=Arrays(shape=(13,)), + ) + pws = ParameterWithSetpoints( + "pws", + setpoints=(control1, pws_other_setpoints,), + vals=Arrays(shape=(11,13)), + get_cmd=lambda: np.zeros((11,13)), + ) + + meas = Measurement(experiment) + meas.register_parameter(pws) + + assert all( + param in meas._registered_parameters + for param in (comp1, comp2, control1, pws) + ) + control1.set(np.linspace(-1, 1, 11)) + with meas.run() as datasaver: + datasaver.add_result((pws, pws())) + ds = datasaver.dataset + + dataset_data = ds.get_parameter_data() + pws_data = dataset_data.get("pws", None) + assert (pws_data) is not None + print(pws_data.keys()) + assert all( + param_name in pws_data.keys() + for param_name in ("pws", "comp1", "comp2", "control1", "pws_other_setpoints") + ) + for result in ('control1', 'comp1', 'comp2', 'pws', 'pws_other_setpoints'): + assert pws_data[result].shape == (1, 11, 13) # Testing equality methods for deduplication def test_non_numeric_values_are_equal() -> None: