Implement a Measurement
Important
If you have implementations of components, please consider sharing them to help expand and improve COHESIVM. You may also simply propose the implementation of a specific device or interface.
Read the Contributing Guidelines for more information.
This tutorial will guide you through the process of implementing a new measurement procedure following
the Measurement abstract base class. To simulate a realistic use case, the tutorials
are based on the measurement of the sheet resistance and resistivity of materials using a four-point probe.
Here, we want to implement a routine which can be used to obtain the data for the four-point probe measurement. Usually, you would only need a single datapoint but in some materials, e.g., semiconductors, you might observe a current dependency of the sheet resistance. This gives us the possibility to actually collect arrays of datapoints which is more application-oriented this tutorial as well as for the Implement an Analysis tutorial.
Measurement Class
Firstly, we have to define the private class attributes, beginning with the InterfaceType
which we implemented in the tutorial Implement an Interface already. Then follows the list of required channels,
which on the first position is a CurrentSource and on the second position a
Voltmeter. Finally, the output_type is defined
which is used to generate a structured array from the data.
After that, we implement the constructor and the abstract run():
import time
import numpy as np
import multiprocessing as mp
from typing import Tuple
from cohesivm.channels import CurrentSource, Voltmeter
from cohesivm.measurements import Measurement
from cohesivm.data_stream import FakeQueue
class FPPMeasurement(Measurement):
"""A class for performing four point probe measurements at multiple currents.
:param currents: The currents in A which should be sources to measure the voltage.
:param temperature: The temperature of the sample during the measurement in K.
:param film_thickness: The thickness of the measured conductive film in mm.
"""
_interface_type = FPPInterfaceType
_required_channels = [(CurrentSource,), (Voltmeter,)]
_output_type = [('Current (A)', float), ('Voltage (V)', float)]
def __init__(self, currents: Tuple[float, ...], temperature: float, film_thickness: float = None) -> None:
self._currents = currents
settings = {
'currents': currents,
'temperature': temperature,
'film_thickness': film_thickness
}
super().__init__(settings=settings, output_shape=(len(currents), 2))
def run(self, device: Device, data_stream: mp.Queue = None) -> np.ndarray:
if data_stream is None:
data_stream = FakeQueue()
results = []
with device.connect():
for current in self._currents:
device.channels[0].source_current(current)
result = current, device.channels[1].measure_voltage()
data_stream.put(result)
results.append(result)
time.sleep(1) # for running the measurement in the GUI
time.sleep(2)
return np.array(results, dtype=self.output_type)
During initialization, we ask for an explicit tuple of currents to be measured instead of a value range because it is
easier to implement. Additionally, the temperature has to and the film_thickness can be provided which are then
stored in the Metadata object. The former value might be necessary for compliance and data
completeness while the latter one can be important for the corresponding Analysis. The
dictionary of these settings is then passed to the constructor of the parent class.
For the measurement routine itself, we firstly handle the multiprocessing.Queue object which is used to stream
measurement data to an output (e.g., for real-time plotting) and is a FakeQueue by
default. Then, we open the device connection as a resource and loop over the currents where on the first channel,
the current is set and on the second channel, the voltage is measured. The result is put into the data_stream and
stored in a results list to, finally, return the data array.
Example Usage
This example depends on what we implemented in the tutorials Implement a Device as well as part of the fpp_connect.py module which is defined in the usage example. Additional resources are not necessary, so let’s run a measurement:
>>> cs = CurrentSourceChannel(False, 5.)
>>> vm = VoltmeterChannel()
>>> device = FPPDevice('123', [cs, vm])
>>> currents = [1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1]
>>> measurement = FPPMeasurement(currents, 300)
>>> measurement.run(device)
array([(1.e-06, 2.e-04), (1.e-05, 2.e-03), (1.e-04, 2.e-02),
(1.e-03, 2.e-01), (1.e-02, 2.e+00), (1.e-01, 0.e+00)],
dtype=[('Current (A)', '<f8'), ('Voltage (V)', '<f8')])
The result is exactly what we would expect because we know the resistance is 100 Ω which yields according to Ohm’s
Law a 100-times higher voltage than the sourced current. For 0.1 A, we would measure a voltage of 10 V which is
above the max_voltage from the device settings, so we receive 0 V as final result. If we set a current above
0.2 A, we would trigger a ValueError from the CurrentSourceChannel.