Repository: microsoft/Qcodes

Issue:
Parameter `step` attribute does not work for set-only parameters
If a parameter with no `get_cmd` has a value `step>0`, the ramping function fails and never recovers. The warning message says the intended behavior is the value will jump, but in reality the value is not changed at all.

### Background
- Set-only parameters are useful in cases where a hardware instrument is unable to return the current state
- It seems the behavior and [warning message generated in `_BaseParameter.get_ramp_values`](https://github.com/QCoDeS/Qcodes/blob/master/qcodes/instrument/parameter.py#L672) do not match
  - Warning message suggests the parameter will "jump" instead of sweep to the final value
  - Actual behavior is that the parameter is never updated
- Related to other issues involving the same warning, see #426 and #1118

### Steps to reproduce
Run the following snippet
```py
import qcodes

def set_x(value):
    print('set x to %f' % value)

x = qcodes.Parameter('x', initial_value=0, step=0.1, inter_delay=0.1, set_cmd=set_x)

x.set(1)
```
The assumption is that in some actual instrument driver `set_x` would set the value, but getting the value is not possible due to limitations in the instrument.

### Expected behaviour
The first call to `set` (from `initial_value`) would jump, since the state of the instrument is not knowable, but subsequent calls ramp as normal, since the current state is known from the cache of `x`.

The expected output would be:
```
cannot sweep x from None to 0 - jumping.
set x to 0.000000
set x to 0.100000
set x to 0.200000
set x to 0.300000
set x to 0.400000
set x to 0.500000
set x to 0.600000
set x to 0.700000
set x to 0.800000
set x to 0.900000
set x to 1.000000
```

### Actual behaviour
Neither call to `set` ever ends up calling `set_x`
Actual output:
```
cannot sweep x from None to 0 - jumping.
cannot sweep x from None to 1 - jumping.
```

### Proposed solution
`get_ramp_values` should return `[value]` if the current value is `None`. Currently it returns `[]`

### System
OS: Windows 10 x64
Python 3.7
Qcodes: qcodes-0.17.0


Discussion:

Resolving patch:
diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py
--- a/qcodes/instrument/parameter.py
+++ b/qcodes/instrument/parameter.py
@@ -662,16 +662,15 @@ def get_ramp_values(self, value: Union[float, Sized],
             if self.get_latest() is None:
                 self.get()
             start_value = self.get_latest()
-
             if not (isinstance(start_value, (int, float)) and
                     isinstance(value, (int, float))):
-                # something weird... parameter is numeric but one of the ends
-                # isn't, even though it's valid. probably MultiType with a
-                # mix of numeric and non-numeric types... So just set the
-                # endpoint and move on.
+                # parameter is numeric but either one of the endpoints
+                # is not or the starting point is unknown. The later
+                # can happen for a non gettable parameter in the initial set
+                # operation.
                 log.warning(f'cannot sweep {self.name} from {start_value!r} '
                             f'to {value!r} - jumping.')
-                return []
+                return [value]
 
             # drop the initial value, we're already there
             return permissive_range(start_value, value, step)[1:] + [value]


Test patch:
diff --git a/qcodes/tests/parameter/test_non_gettable_parameter.py b/qcodes/tests/parameter/test_non_gettable_parameter.py
new file mode 100644
--- /dev/null
+++ b/qcodes/tests/parameter/test_non_gettable_parameter.py
@@ -0,0 +1,33 @@
+import logging
+
+import numpy as np
+from numpy.testing import assert_array_almost_equal
+
+from qcodes.instrument.parameter import Parameter
+
+
+def test_setting_non_gettable_parameter_with_finite_step(caplog):
+    initial_value = 0
+    step_size = 0.1
+    set_value = 1.0
+
+    # when the parameter is initially set from
+    # the initial_value the starting point is unknown
+    # so this should cause a warning but the parameter should still be set
+    with caplog.at_level(logging.WARNING):
+        x = Parameter('x', initial_value=initial_value,
+                      step=step_size,
+                      set_cmd=None)
+        assert len(caplog.records) == 1
+        assert f"cannot sweep x from None to {initial_value}" in str(caplog.records[0])
+    assert x.cache.get() == 0
+
+    # afterwards the stepping should work as expected.
+    with caplog.at_level(logging.WARNING):
+        caplog.clear()
+        assert_array_almost_equal(np.array(x.get_ramp_values(set_value, step_size)),
+                                  (np.arange(initial_value+step_size, set_value+step_size, step_size)))
+        x.set(set_value)
+        assert x.cache.get() == set_value
+        assert len(caplog.records) == 0
+
diff --git a/qcodes/tests/parameter/test_parameter_ramp.py b/qcodes/tests/parameter/test_parameter_ramp.py
--- a/qcodes/tests/parameter/test_parameter_ramp.py
+++ b/qcodes/tests/parameter/test_parameter_ramp.py
@@ -30,8 +30,9 @@ def test_step_ramp(caplog):
 
     # Test error conditions
     with caplog.at_level(logging.WARNING):
-        assert p.get_ramp_values("A", 1) == []
+        assert p.get_ramp_values("A", 1) == ["A"]
         assert len(caplog.records) == 1
+        assert "cannot sweep test_step from 40 to 'A'" in str(caplog.records[0])
     with pytest.raises(RuntimeError):
         p.get_ramp_values((1, 2, 3), 1)