Repository: microsoft/Qcodes

Issue:
Getting a DelegateParameter with scale fails if it is not initialized
If you create a DelegateParameter and pass a scale argument, it is not possible to get its cache before it is initialized. The scale gets applied to the None that resides in the source parameter and gives a TypeError.

### Steps to reproduce
```python
from qcodes import Parameter, DelegateParameter

p = Parameter("p")
dp = DelegateParameter("dp", p, scale=1.1)

print(dp.source.cache.get(False))  # gives back None
print(dp.cache.get(False))  # fails
```

### Expected behaviour
I expected to get None back, just as you would a plain uninitialized Parameter.

A workaround is to give nan as an initial cache value. This works:
```python
from qcodes import Parameter, DelegateParameter
import numpy as np

p = Parameter("p")
dp = DelegateParameter("dp", p, scale=1.1, initial_cache_value=np.nan)

print(dp.source.cache.get(False))
print(dp.cache.get(False))
```

### Actual behaviour
I got a TypeError:
```python
Traceback (most recent call last):

  File "D:\users\hw_sw_integration\projects\low-level-software\src\lls_input\amber\scripts\example_02.py", line 14, in <module>
    print(dp.cache.get(False))

  File "D:\users\hw_sw_integration\venv-3.9\lib\site-packages\qcodes\instrument\parameter.py", line 1541, in get
    return self._parameter._from_raw_value_to_value(

  File "D:\users\hw_sw_integration\venv-3.9\lib\site-packages\qcodes\instrument\parameter.py", line 601, in _from_raw_value_to_value
    value = value / self.scale

TypeError: unsupported operand type(s) for /: 'NoneType' and 'float'
```

It appears that `_from_raw_value_to_raw_value` applies the scale on the value even if it's None. A simple solution would be to put a big `if value is not None:` in the method, but this may not cover all the use cases (e.g. more intricate ways of handling the scale than dividing/multiplying).

### System
Windows 10
qcodes 0.30.1


Discussion:
@haroldmeerwaldt thank you for reporting the issue!

i think your suggestion is good! but let's make the value None check only for offset and the scale, so on line https://github.com/QCoDeS/Qcodes/blob/c66a8af4ac8fa21498849a24734e4e1749952304/qcodes/instrument/parameter.py#L582 and https://github.com/QCoDeS/Qcodes/blob/c66a8af4ac8fa21498849a24734e4e1749952304/qcodes/instrument/parameter.py#L598.

Would you be willing to start a PR? and also add a few tests for this in this file https://github.com/QCoDeS/Qcodes/blob/master/qcodes/tests/parameter/test_parameter_scale_offset.py ?
@astafan8 That sounds like a good plan! Allowing None for the parser and in the inverse_val_mapping dictionary makes sense. I'll start a PR with tests.

Resolving patch:
diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py
--- a/qcodes/instrument/parameter.py
+++ b/qcodes/instrument/parameter.py
@@ -579,7 +579,7 @@ def _from_raw_value_to_value(self, raw_value: ParamRawDataType
             value = raw_value
 
         # apply offset first (native scale)
-        if self.offset is not None:
+        if self.offset is not None and value is not None:
             # offset values
             try:
                 value = value - self.offset
@@ -595,7 +595,7 @@ def _from_raw_value_to_value(self, raw_value: ParamRawDataType
                     raise
 
         # scale second
-        if self.scale is not None:
+        if self.scale is not None and value is not None:
             # Scale values
             try:
                 value = value / self.scale


Test patch:
diff --git a/qcodes/tests/parameter/test_delegate_parameter.py b/qcodes/tests/parameter/test_delegate_parameter.py
--- a/qcodes/tests/parameter/test_delegate_parameter.py
+++ b/qcodes/tests/parameter/test_delegate_parameter.py
@@ -250,21 +250,24 @@ def test_delegate_get_updates_cache(make_observable_parameter, numeric_val):
     assert t.get_instr_val() == initial_value
 
 
-def test_delegate_parameter_get_and_snapshot_raises_with_none():
+def test_delegate_parameter_get_and_snapshot_with_none_source():
     """
-    Test that a delegate parameter raises on get and snapshot if
-    the source has a value of None and a scale is used.
-    But works correctly if the source is remapped to a real parameter.
-
+    Test that a delegate parameter returns None on get and snapshot if
+    the source has a value of None and an offset or scale is used.
+    And returns a value if the source is remapped to a real parameter.
     """
     none_param = Parameter("None")
     source_param = Parameter('source', get_cmd=None, set_cmd=None, initial_value=2)
     delegate_param = DelegateParameter(name='delegate', source=none_param)
+    delegate_param.offset = 4
+    assert delegate_param.get() is None
+    assert delegate_param.snapshot()['value'] is None
+
+    delegate_param.offset = None
     delegate_param.scale = 2
-    with pytest.raises(TypeError):
-        delegate_param.get()
-    with pytest.raises(TypeError):
-        delegate_param.snapshot()
+    assert delegate_param.get() is None
+    assert delegate_param.snapshot()['value'] is None
+
     assert delegate_param.cache._parameter.source.cache is none_param.cache
     delegate_param.source = source_param
     assert delegate_param.get() == 1