Skip to content

Commit

Permalink
Added ability to perform automatic reverse sweeps in experiments.
Browse files Browse the repository at this point in the history
  • Loading branch information
PP501 committed Jan 10, 2025
1 parent bc726aa commit 494fe6a
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 11 deletions.
2 changes: 2 additions & 0 deletions UnitTests/UTestExperimentConfiguration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ instruments:
type: sqdtoolz.Drivers.dummyGENmwSource.DummyGENmwSrc
virMWS2:
type: sqdtoolz.Drivers.dummyGENmwSource.DummyGENmwSrc
virSMU:
type: sqdtoolz.Drivers.dummySMU.DummySMU
68 changes: 66 additions & 2 deletions UnitTests/testLaboratory.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,13 +624,15 @@ def initialise(self):
self.lab.load_instrument('virAWG')
self.lab.load_instrument('virMWS')
self.lab.load_instrument('virMWS2')
self.lab.load_instrument('virSMU')

#Initialise test-modules
hal_acq = ACQ("dum_acq", self.lab, 'virACQ')
hal_ddg = DDG("ddg", self.lab, 'virDDG', )
awg_wfm = WaveformAWG("Wfm1", self.lab, [('virAWG', 'CH1'), ('virAWG', 'CH2')], 1e9)
hal_mw = GENmwSource("MW-Src", self.lab, 'virMWS', 'CH1')
hal_mw2 = GENmwSource("MW-Src2", self.lab, 'virMWS2', 'CH1')
hal_smu = GENsmu('SMU', self.lab, 'virSMU')

#Reinitialise the waveform
read_segs = []
Expand Down Expand Up @@ -792,6 +794,68 @@ def test_Exp(self):
shutil.rmtree('test_save_dir')
self.cleanup()


def test_ExpReverseSweep(self):
self.initialise()

#Check basic parameter storage with 1D reverse sweep...
exp = Experiment("test", self.lab.CONFIG('testConf'))
self.lab.VAR('myFreq').Value = 5
res = self.lab.run_single(exp, [(self.lab.VAR("testAmpl"), np.arange(0,3,1))], delay=0.1, reverse_index=0, rec_params=[self.lab.VAR('myFreq'), self.lab.VAR('testAmpl')])
assert hasattr(exp, 'last_rec_params'), "Running an experiment with rec_params did not create an attribute \'last_rec_params\'."
assert self.arr_equality(np.array(exp.last_rec_params.get_numpy_array().shape), np.array([3,4])), "Reverse sweeping experiment in 1D did not produce the right number of dependent parameters."
assert self.arr_equality(np.array(exp.last_rec_params.get_numpy_array()), np.array([[5,0]*2,[5,1]*2,[5,2]*2])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[0]), np.arange(0,3,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."
#
#Check basic parameter storage with 1D reverse sweep...
exp = Experiment("test", self.lab.CONFIG('testConf'))
self.lab.VAR('myFreq').Value = 2
self.lab.UpdateStateEnabled = False
self.lab.HAL('SMU').Voltage = 0
res = self.lab.run_single(exp, [(self.lab.VAR("testAmpl"), np.arange(0,3,1))], delay=0.1, reverse_index=0, rec_params=[self.lab.VAR('myFreq'), self.lab.VAR('testAmpl'), (self.lab.HAL('SMU'), 'Voltage')])
assert hasattr(exp, 'last_rec_params'), "Running an experiment with rec_params did not create an attribute \'last_rec_params\'."
arr = exp.last_rec_params.get_numpy_array()
assert self.arr_equality(np.array(arr.shape), np.array([3,6])), "Reverse sweeping experiment in 1D did not produce the right number of dependent parameters."
assert self.arr_equality(np.array(arr[:,0:2]), np.array([[2,0],[2,1],[2,2]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.array(arr[:,3:5]), np.array([[2,0],[2,1],[2,2]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.diff(np.concatenate([arr[:,2], arr[:,5][::-1]])), np.ones(5)), "Reverse sweeping experiment in 1D did not store the data in correct order."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[0]), np.arange(0,3,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."
#
#Check basic parameter storage with 2D reverse sweep...
exp = Experiment("test", self.lab.CONFIG('testConf'))
self.lab.VAR('myFreq').Value = 2
self.lab.UpdateStateEnabled = False
self.lab.HAL('SMU').Voltage = 0
res = self.lab.run_single(exp, [(self.lab.VAR("testAmpl"), np.arange(0,3,1)), (self.lab.VAR("myFreq"), np.arange(1,5,1))], delay=0.1, reverse_index=0, rec_params=[self.lab.VAR('myFreq'), self.lab.VAR('testAmpl'), (self.lab.HAL('SMU'), 'Voltage')])
assert hasattr(exp, 'last_rec_params'), "Running an experiment with rec_params did not create an attribute \'last_rec_params\'."
arr = exp.last_rec_params.get_numpy_array()
assert self.arr_equality(np.array(arr.shape), np.array([3,4,6])), "Reverse sweeping experiment in 1D did not produce the right number of dependent parameters."
assert self.arr_equality(np.array(arr[:,:,0:2]), np.array([[[1,0],[2,0],[3,0],[4,0]], [[1,1],[2,1],[3,1],[4,1]], [[1,2],[2,2],[3,2],[4,2]]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.array(arr[:,:,3:5]), np.array([[[1,0],[2,0],[3,0],[4,0]], [[1,1],[2,1],[3,1],[4,1]], [[1,2],[2,2],[3,2],[4,2]]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.diff(np.concatenate([np.ndarray.flatten(arr[:,:,2]), np.ndarray.flatten(arr[:,:,5][::-1])])), np.ones(23)), "Reverse sweeping experiment in 1D did not store the data in correct order."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[0]), np.arange(0,3,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[1]), np.arange(1,5,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."
#
#Check basic parameter storage with 2D reverse sweep...
exp = Experiment("test", self.lab.CONFIG('testConf'))
self.lab.VAR('myFreq').Value = 2
self.lab.UpdateStateEnabled = False
self.lab.HAL('SMU').Voltage = 0
res = self.lab.run_single(exp, [(self.lab.VAR("testAmpl"), np.arange(0,3,1)), (self.lab.VAR("myFreq"), np.arange(1,5,1))], delay=0.1, reverse_index=1, rec_params=[self.lab.VAR('myFreq'), self.lab.VAR('testAmpl'), (self.lab.HAL('SMU'), 'Voltage')])
assert hasattr(exp, 'last_rec_params'), "Running an experiment with rec_params did not create an attribute \'last_rec_params\'."
arr = exp.last_rec_params.get_numpy_array()
assert self.arr_equality(np.array(arr.shape), np.array([3,4,6])), "Reverse sweeping experiment in 1D did not produce the right number of dependent parameters."
assert self.arr_equality(np.array(arr[:,:,0:2]), np.array([[[1,0],[2,0],[3,0],[4,0]], [[1,1],[2,1],[3,1],[4,1]], [[1,2],[2,2],[3,2],[4,2]]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.array(arr[:,:,3:5]), np.array([[[1,0],[2,0],[3,0],[4,0]], [[1,1],[2,1],[3,1],[4,1]], [[1,2],[2,2],[3,2],[4,2]]])), "Reverse sweeping experiment in 1D did not store the correct data."
assert self.arr_equality(np.diff(np.ndarray.flatten(np.concatenate([(arr[x,:,2], arr[x,:,5][::-1]) for x in range(3)]))), np.ones(23)), "Reverse sweeping experiment in 1D did not store the data in correct order."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[0]), np.arange(0,3,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."
assert self.arr_equality(np.array(exp.last_rec_params.param_vals[1]), np.arange(1,5,1)), "Reverse sweeping experiment in 1D did not produce the right parameter values."


shutil.rmtree('test_save_dir')
self.cleanup()


def test_ExpSweepAndFullColdReload(self):
self.initialise()

Expand Down Expand Up @@ -1283,6 +1347,6 @@ def test_SnakeExp(self):
self.cleanup()

if __name__ == '__main__':
temp = TestColdReload()
temp.test_VARs()
temp = TestSweeps()
temp.test_ExpReverseSweep()
unittest.main()
108 changes: 108 additions & 0 deletions docs/User/Exp_Sweep.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This article covers:
- [Basic Sweeps](#basic-sweeps)
- [One-Many Sweeps](#one-many-sweeps)
- [Changing the sampled order in sweeps](#changing-the-sampled-order-in-sweeps)
- [Reverse sweeps](#reverse-sweeps)


## Basic sweeps
Expand Down Expand Up @@ -201,3 +202,110 @@ Note that **the order of entries in the HDF5 data file is the same, only the sam

- The `sweep_orders` is given as a list of `ExSwp*` objects where it shuffles the order with the listed functions from left to right. That is, each shuffling transformatio is applied in ascending order.
- See the [other article](Exp_SweepPerm.md) for a list of available `ExpSwp*` classes.


## Reverse sweeps

When measuring hysteretic devices, the data will be different when sweeping a given parameter in the forward and backward directions. When setting up a parametric sweep, one may automatically take the reverse sweep. The subsequent data is added to a different channel with the suffix `'_reverse'`. Simply add the `reverse_index` parameter to highlight the index (of the sweeping parameter in the list) to reverse when running the experiment:


```python
#Assuming that exp is an Experiment object and lab is a Laboratory object
lab.run_single(exp, [(lab.VAR('volt'), np.arange(-1, 1, 0.1)), (lab.VAR('flux'), np.arange(-20,20,0.1))], reverse_index=1)
```


```python
lab.VAR("volt").Value = -1
lab.VAR("flux").Value = -20
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to normal channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = 19.9
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = 19.9
# --- Get Data --- # store to reverse channel
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to reverse channel
...
lab.VAR("flux").Value = -20
# --- Get Data --- # store to reverse channel

lab.VAR("volt").Value = -0.9
lab.VAR("flux").Value = -20
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to normal channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = 19.9
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = 19.9
# --- Get Data --- # store to reverse channel
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to reverse channel
...
lab.VAR("flux").Value = -20
# --- Get Data --- # store to reverse channel

...
```

However, if `reverse_index=0`:

```python
lab.VAR("volt").Value = -1
lab.VAR("flux").Value = -20
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to normal channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to normal channel

lab.VAR("volt").Value = -0.9
lab.VAR("flux").Value = -20
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to normal channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to normal channel

...

lab.VAR("volt").Value = 0.9
lab.VAR("flux").Value = -20
# --- Get Data --- # store to normal channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to normal channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to normal channel

lab.VAR("volt").Value = 0.9
lab.VAR("flux").Value = -20
# --- Get Data --- # store to reverse channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to reverse channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to reverse channel

lab.VAR("volt").Value = 0.8
lab.VAR("flux").Value = -20
# --- Get Data --- # store to reverse channel
lab.VAR("flux").Value = -19.9
# --- Get Data --- # store to reverse channel
...
lab.VAR("flux").Value = 19.8
# --- Get Data --- # store to reverse channel

...
```

Optionally, if the suffix for the reverse channels is to be changed, one may provide it in the `reverse_variable_suffix` argument (default value is `'_reverse'`) in `run_single`.
100 changes: 100 additions & 0 deletions sqdtoolz/Drivers/dummySMU.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from qcodes import Instrument, InstrumentChannel, VisaInstrument, validators as vals

class DummySMU(Instrument):
'''
Dummy driver to emulate a SMU instrument.
'''
def __init__(self, name, **kwargs):
super().__init__(name, **kwargs)
self.increment_voltage = kwargs.get('increment_voltage', True)
self.increment_voltage_step = kwargs.get('increment_voltage_step', 1)
self._voltage = kwargs.get('increment_voltage_start', 0) - self.increment_voltage_step
self._current = 0
self._output = True
self._comp_current = 1
self._comp_voltage = 1
self._mode = 'SrcI_MeasV'

@property
def Voltage(self):
self._voltage += self.increment_voltage_step
return self._voltage
@Voltage.setter
def Voltage(self, val):
self._voltage = val

@property
def Current(self):
return self._current
@Current.setter
def Current(self, val):
self._current = val

@property
def SenseVoltage(self):
return self._voltage

@property
def SenseCurrent(self):
return self._current

@property
def Output(self):
return self._output
@Output.setter
def Output(self, val):
self._output = val

@property
def ComplianceCurrent(self):
return self._comp_current
@ComplianceCurrent.setter
def ComplianceCurrent(self, val):
self._comp_current = val

@property
def ComplianceVoltage(self):
return self._comp_voltage
@ComplianceVoltage.setter
def ComplianceVoltage(self, val):
self._comp_voltage = val

@property
def Mode(self):
return self._mode
@Mode.setter
def Mode(self, val):
self._mode = val

@property
def SupportsSweeping(self):
return False

@property
def RampRateVoltage(self):
return 1
@RampRateVoltage.setter
def RampRateVoltage(self, val):
pass

@property
def RampRateCurrent(self):
return 1
@RampRateCurrent.setter
def RampRateCurrent(self, val):
pass

@property
def ProbeType(self):
return 'TwoWire'
@ProbeType.setter
def ProbeType(self, val):
pass #Can't set this one...

def get_idn(self):
return {
"vendor": "QCoDeS",
"model": str(self.__class__),
"seral": "NA",
"firmware": "NA",
}
Loading

0 comments on commit 494fe6a

Please sign in to comment.