Repository: microsoft/Qcodes

Issue:
Incorrect expansion in get_parameter_data in mixed numeric/array measurements if param_type=="array"
With the following setup

```
import numpy as np
from qcodes.dataset.measurements import Measurement
from qcodes.tests.instrument_mocks import (DummyChannelInstrument,
                                           DummyInstrument)
dac = DummyInstrument('dummy_dac', gates=['ch1', 'ch2'])
channel_array_instrument = DummyChannelInstrument('dummy_channel_inst')
```

compare 

```
param = channel_array_instrument.A.dummy_array_parameter
meas = Measurement()
meas.register_parameter(dac.ch1, paramtype="array")
meas.register_parameter(param, setpoints=(dac.ch1,), paramtype="array")
n_points = 10
with meas.run() as datasaver:
    for i, v1 in enumerate(np.linspace(-1, 1, n_points)):
        datasaver.add_result((dac.ch1, v1),
                             (param, param.get()))
for key, data in datasaver.dataset.get_parameter_data()['dummy_channel_inst_ChanA_dummy_array_parameter'].items():
    print(key, data.shape)
Starting experimental run with id: 183. 
dummy_channel_inst_ChanA_dummy_array_parameter (10, 5)
dummy_dac_ch1 (10, 1)
dummy_channel_inst_ChanA_array_setpoint_param_this_setpoint (10, 5)
```

with

```
param = channel_array_instrument.A.dummy_array_parameter
meas = Measurement()
meas.register_parameter(dac.ch1)
meas.register_parameter(param, setpoints=(dac.ch1,))
n_points = 10
with meas.run() as datasaver:
    for i, v1 in enumerate(np.linspace(-1, 1, n_points)):
        datasaver.add_result((dac.ch1, v1),
                             (param, param.get()))
for key, data in datasaver.dataset.get_parameter_data()['dummy_channel_inst_ChanA_dummy_array_parameter'].items():
    print(key, data.shape)
Starting experimental run with id: 184. 
dummy_channel_inst_ChanA_dummy_array_parameter (10, 5)
dummy_dac_ch1 (10, 5)
dummy_channel_inst_ChanA_array_setpoint_param_this_setpoint (10, 5)
```
Incorrect expansion in get_parameter_data in mixed numeric/array measurements if param_type=="array"
With the following setup

```
import numpy as np
from qcodes.dataset.measurements import Measurement
from qcodes.tests.instrument_mocks import (DummyChannelInstrument,
                                           DummyInstrument)
dac = DummyInstrument('dummy_dac', gates=['ch1', 'ch2'])
channel_array_instrument = DummyChannelInstrument('dummy_channel_inst')
```

compare 

```
param = channel_array_instrument.A.dummy_array_parameter
meas = Measurement()
meas.register_parameter(dac.ch1, paramtype="array")
meas.register_parameter(param, setpoints=(dac.ch1,), paramtype="array")
n_points = 10
with meas.run() as datasaver:
    for i, v1 in enumerate(np.linspace(-1, 1, n_points)):
        datasaver.add_result((dac.ch1, v1),
                             (param, param.get()))
for key, data in datasaver.dataset.get_parameter_data()['dummy_channel_inst_ChanA_dummy_array_parameter'].items():
    print(key, data.shape)
Starting experimental run with id: 183. 
dummy_channel_inst_ChanA_dummy_array_parameter (10, 5)
dummy_dac_ch1 (10, 1)
dummy_channel_inst_ChanA_array_setpoint_param_this_setpoint (10, 5)
```

with

```
param = channel_array_instrument.A.dummy_array_parameter
meas = Measurement()
meas.register_parameter(dac.ch1)
meas.register_parameter(param, setpoints=(dac.ch1,))
n_points = 10
with meas.run() as datasaver:
    for i, v1 in enumerate(np.linspace(-1, 1, n_points)):
        datasaver.add_result((dac.ch1, v1),
                             (param, param.get()))
for key, data in datasaver.dataset.get_parameter_data()['dummy_channel_inst_ChanA_dummy_array_parameter'].items():
    print(key, data.shape)
Starting experimental run with id: 184. 
dummy_channel_inst_ChanA_dummy_array_parameter (10, 5)
dummy_dac_ch1 (10, 5)
dummy_channel_inst_ChanA_array_setpoint_param_this_setpoint (10, 5)
```


Discussion:

Resolving patch:
diff --git a/qcodes/dataset/sqlite/queries.py b/qcodes/dataset/sqlite/queries.py
--- a/qcodes/dataset/sqlite/queries.py
+++ b/qcodes/dataset/sqlite/queries.py
@@ -305,34 +305,54 @@ def _expand_data_to_arrays(data: List[List[Any]], paramspecs: Sequence[ParamSpec
     types = [param.type for param in paramspecs]
     # if we have array type parameters expand all other parameters
     # to arrays
-    if 'array' in types and ('numeric' in types or 'text' in types
-                             or 'complex' in types):
-        first_array_element = types.index('array')
-        numeric_elms = [i for i, x in enumerate(types)
-                        if x == "numeric"]
-        complex_elms = [i for i, x in enumerate(types)
-                        if x == 'complex']
-        text_elms = [i for i, x in enumerate(types)
-                     if x == "text"]
+    if 'array' in types:
+
+        if ('numeric' in types or 'text' in types
+                or 'complex' in types):
+            first_array_element = types.index('array')
+            numeric_elms = [i for i, x in enumerate(types)
+                            if x == "numeric"]
+            complex_elms = [i for i, x in enumerate(types)
+                            if x == 'complex']
+            text_elms = [i for i, x in enumerate(types)
+                         if x == "text"]
+            for row in data:
+                for element in numeric_elms:
+                    row[element] = np.full_like(row[first_array_element],
+                                                row[element],
+                                                dtype=np.dtype(np.float64))
+                    # todo should we handle int/float types here
+                    # we would in practice have to perform another
+                    # loop to check that all elements of a given can be cast to
+                    # int without loosing precision before choosing an integer
+                    # representation of the array
+                for element in complex_elms:
+                    row[element] = np.full_like(row[first_array_element],
+                                                row[element],
+                                                dtype=np.dtype(np.complex128))
+                for element in text_elms:
+                    strlen = len(row[element])
+                    row[element] = np.full_like(row[first_array_element],
+                                                row[element],
+                                                dtype=np.dtype(f'U{strlen}'))
+
         for row in data:
-            for element in numeric_elms:
-                row[element] = np.full_like(row[first_array_element],
-                                            row[element],
-                                            dtype=np.dtype(np.float64))
-                # todo should we handle int/float types here
-                # we would in practice have to perform another
-                # loop to check that all elements of a given can be cast to
-                # int without loosing precision before choosing an integer
-                # representation of the array
-            for element in complex_elms:
-                row[element] = np.full_like(row[first_array_element],
-                                            row[element],
-                                            dtype=np.dtype(np.complex128))
-            for element in text_elms:
-                strlen = len(row[element])
-                row[element] = np.full_like(row[first_array_element],
-                                            row[element],
-                                            dtype=np.dtype(f'U{strlen}'))
+            # now expand all one element arrays to match the expected size
+            # one element arrays are introduced if scalar values are stored
+            # with an explicit array storage type
+            sizes = tuple(array.size for array in row)
+            max_size = max(sizes)
+            max_index = sizes.index(max_size)
+
+            for i, array in enumerate(row):
+                if array.size != max_size:
+                    if array.size == 1:
+                        row[i] = np.full_like(row[max_index],
+                                              row[i],
+                                              dtype=row[i].dtype)
+                    else:
+                        log.warning(f"Cannot expand array of size {array.size} "
+                                    f"to size {row[max_index].size}")
 
 
 def _get_data_for_one_param_tree(conn: ConnectionPlus, table_name: str,


Test patch:
diff --git a/qcodes/tests/dataset/test_data_set_cache.py b/qcodes/tests/dataset/test_data_set_cache.py
--- a/qcodes/tests/dataset/test_data_set_cache.py
+++ b/qcodes/tests/dataset/test_data_set_cache.py
@@ -306,10 +306,7 @@ def test_cache_1d_array_in_1d(experiment, DAC, channel_array_instrument, n_point
                 shape = (n_rows_written, param.shape[0])
             else:
                 shape = (n_rows_written * param.shape[0],)
-            if storage_type != 'array':
-                # with explicit array types the shape is incorrect
-                # https://github.com/QCoDeS/Qcodes/issues/2105
-                assert data[param.full_name][DAC.ch1.full_name].shape == shape
+            assert data[param.full_name][DAC.ch1.full_name].shape == shape
             assert data[param.full_name][setpoint_name].shape == shape
             assert data[param.full_name][param.full_name].shape == shape
             _assert_parameter_data_is_identical(dataset.get_parameter_data(),
@@ -344,10 +341,7 @@ def test_cache_multiparam_in_1d(experiment, DAC, channel_array_instrument, n_poi
                 else:
                     expected_shape = n_rows_written * np.prod(param.shapes[j])
                 assert data[subparam][subparam].shape == expected_shape
-                if storage_type != 'array':
-                    # with explicit array types the shape is incorrect
-                    # https://github.com/QCoDeS/Qcodes/issues/2105
-                    assert data[subparam][DAC.ch1.full_name].shape == expected_shape
+                assert data[subparam][DAC.ch1.full_name].shape == expected_shape
                 for setpoint_name in param.setpoint_full_names[j]:
                     assert data[subparam][setpoint_name].shape == expected_shape
             _assert_parameter_data_is_identical(dataset.get_parameter_data(),
@@ -390,10 +384,7 @@ def test_cache_complex_array_param_in_1d(experiment, DAC, channel_array_instrume
             else:
                 expected_shape = n_rows_written * np.prod(param.shape)
             assert data[param.full_name][param.full_name].shape == expected_shape
-            if storage_type != 'array':
-                # with explicit array types the shape is incorrect
-                # https://github.com/QCoDeS/Qcodes/issues/2105
-                assert data[param.full_name][outer_param.full_name].shape == expected_shape
+            assert data[param.full_name][outer_param.full_name].shape == expected_shape
             for setpoint_name in param.setpoint_full_names:
                 assert data[param.full_name][setpoint_name].shape == expected_shape
             _assert_parameter_data_is_identical(dataset.get_parameter_data(),