Source code for gigaanalysis.highfield

"""GigaAnalysis - High Field - :mod:`gigaanalysis.highfield`
---------------------------------------------------------------

This program has a series of useful tools for conducting experiments in
certain high field labs.
"""

from .data import *
from . import diglock

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from scipy.signal import savgol_filter  # For PulsedLockIn.lockin_Volt_smooth
from scipy.interpolate import interp1d # For example_field


[docs]def example_field(max_field, peak_time, length, sample_rate, as_Data=False): """Produces a data set with a field profile. This field profile matches the KS3 magnet in HLD. The pulse time and maximum field has been normalised, and is get by the user. Originally they where 68.9 Tesla and 0.0336 seconds. I find this useful for simulating test measurements. Parameters ---------- max_field : float The maximum field value that the data will reach. peak_time : float The time that the magnet reaches peak field. length : int The number of data points in the file. sample_rate: float The sample frequency of the data. as_Data : bool, optional If true a :class:`.Data` class is returned. Returns ------- field_profile : numpy.ndarray or Data The field values simulated for the parameters given. """ field = np.array([ -0.000e+00, 0.000e+00, 4.660e-02, 9.440e-02, 1.376e-01, 1.785e-01, 2.182e-01, 2.569e-01, 2.949e-01, 3.322e-01, 3.687e-01, 4.046e-01, 4.397e-01, 4.740e-01, 5.076e-01, 5.402e-01, 5.719e-01, 6.027e-01, 6.326e-01, 6.614e-01, 6.892e-01, 7.160e-01, 7.416e-01, 7.662e-01, 7.896e-01, 8.118e-01, 8.328e-01, 8.527e-01, 8.713e-01, 8.887e-01, 9.048e-01, 9.197e-01, 9.333e-01, 9.456e-01, 9.567e-01, 9.665e-01, 9.750e-01, 9.823e-01, 9.883e-01, 9.931e-01, 9.966e-01, 9.989e-01, 9.999e-01, 9.998e-01, 9.984e-01, 9.958e-01, 9.920e-01, 9.871e-01, 9.810e-01, 9.738e-01, 9.655e-01, 9.561e-01, 9.457e-01, 9.343e-01, 9.221e-01, 9.091e-01, 8.954e-01, 8.810e-01, 8.660e-01, 8.505e-01, 8.345e-01, 8.180e-01, 8.012e-01, 7.840e-01, 7.666e-01, 7.490e-01, 7.311e-01, 7.131e-01, 6.950e-01, 6.769e-01, 6.587e-01, 6.405e-01, 6.223e-01, 6.042e-01, 5.862e-01, 5.683e-01, 5.505e-01, 5.329e-01, 5.154e-01, 4.982e-01, 4.812e-01, 4.644e-01, 4.479e-01, 4.316e-01, 4.156e-01, 3.999e-01, 3.845e-01, 3.693e-01, 3.545e-01, 3.400e-01, 3.259e-01, 3.120e-01, 2.985e-01, 2.854e-01, 2.725e-01, 2.600e-01, 2.479e-01, 2.360e-01, 2.245e-01, 2.134e-01, 2.026e-01, 1.921e-01, 1.819e-01, 1.721e-01, 1.626e-01, 1.534e-01, 1.445e-01, 1.360e-01, 1.277e-01, 1.197e-01, 1.121e-01, 1.047e-01, 9.760e-02, 9.080e-02, 8.430e-02, 7.800e-02, 7.200e-02, 6.620e-02, 6.070e-02, 5.550e-02, 5.040e-02, 4.560e-02, 4.110e-02, 3.670e-02, 3.260e-02, 2.860e-02, 2.490e-02, 2.140e-02, 1.800e-02, 1.480e-02, 1.180e-02, 9.000e-03, 6.300e-03, 3.800e-03, 1.400e-03, -8.000e-04, -2.900e-03, -4.800e-03, -6.600e-03, -8.300e-03, -9.800e-03, -1.130e-02, -1.260e-02, -1.390e-02, -1.500e-02, -1.600e-02, -1.700e-02, -1.790e-02, -1.860e-02, -1.930e-02, -2.000e-02, -2.050e-02, -2.100e-02, -2.140e-02, -2.180e-02, -2.210e-02, -2.230e-02, -2.250e-02, -2.260e-02, -2.270e-02, -2.280e-02, -2.280e-02, -2.280e-02, -2.270e-02, -2.260e-02, -2.250e-02, -2.230e-02, -2.210e-02, -2.190e-02, -2.170e-02, -2.140e-02, -2.120e-02, -2.090e-02, -2.060e-02, -2.020e-02, -1.990e-02, -1.950e-02, -1.910e-02, -1.870e-02, -1.830e-02, -1.790e-02, -1.740e-02, -1.700e-02, -1.650e-02, -1.600e-02, -1.550e-02, -1.500e-02, -1.450e-02, -1.400e-02, -1.340e-02, -1.290e-02, -1.230e-02, -1.180e-02, -1.120e-02, -1.070e-02, -1.010e-02, -9.500e-03, -8.900e-03, -8.400e-03, -7.800e-03, -7.200e-03, -6.600e-03, -6.000e-03, -5.400e-03, -4.900e-03, -4.300e-03, -3.700e-03, -3.100e-03, -2.600e-03, -2.000e-03, -1.500e-03, -9.000e-04, -4.000e-04, -2.000e-04, -2.000e-04, -1.000e-04, -1.000e-04, -1.000e-04, -0.000e+00, -0.000e+00, -0.000e+00, -0.000e+00, -0.000e+00, -0.000e+00, -0.000e+00,]) time = np.arange(225)/42. field_interp = interp1d(peak_time*time, max_field*field, kind='linear', fill_value=0, bounds_error=False, assume_sorted=True) sim_time = np.arange(length)/sample_rate sim_field = field_interp(sim_time) if as_Data: return Data(sim_time, sim_field) else: return sim_field
[docs]def PUtoB(PU_signal, field_factor, fit_points, to_fit='PU'): """Converts the voltage from the pick up coil to field. This is used for pulsed field measurements, where to obtain the filed the induced voltage in a coil is integrated. A fit is also applied because slight differences in the grounding voltage can cause a large change in the field so this needs to be corrected for. Parameters ---------- PU_signal : numpy.ndarray, Data The signal from pick up coil. field_factor : float Factor to convert integral to magnetic field. Bare in mind this will change if the acquisition rate changes, for the same coil. fit_points : int Number of point at each end to remove offset. to_fit : {'PU', 'field'} optional If to correct an offset voltage the PU signal is fit or the field. Returns ------- field : numpy.ndarray, Data An array of magnetic field the same length as PU_signal. If a :class:`.Data` is given then the y values are processed and a :class:`.Data` is returned. """ as_data = isinstance(PU_signal, Data) if as_data: x_vals, PU_signal = PU_signal.both count = np.arange(len(PU_signal)) ends = np.concatenate([count[:fit_points], count[-fit_points:]]) if to_fit == 'PU': a, b = np.polyfit(ends, PU_signal[ends], 1) PU_flat = PU_signal - a*count - b field_output = np.cumsum(PU_flat*field_factor) elif to_fit == 'field': field = np.cumsum(PU_signal*field_factor) a, b = np.polyfit(ends, field[ends], 1) field_output = field - a*count - b else: raise ValueError( f"to_fit needs to be either 'PU' or 'field' but was {to_fit}.") if as_data: return Data(x_vals, field_output) else: return field_output
[docs]def pick_pulse_side(field, B_min, side, skip_num=1, give_slice=True): """Produces a slice that selects a certain section of a filed pulse. This takes a field profile and produces a slice that has one side or both in. Parameters ---------- field : numpy.ndarray Field values in a 1D numpy array, the field needs to be pulsed in the positive direction. If you want to analyse a negative sweep first take the negative of it. B_min : float or None The value of field to cut all the lower data off. This is used as sometimes the tails of the pulses can be very long. If it is set to None the full range is kept. side : {'up', 'down', 'both'} Which side of the the pulse to take. 'up' takes the first side, 'down' takes the second, and 'both' includes both sides of the pulse. skip_num : int, optional The ratio of points to skip to reduce the size of the data set. The default is `1`, which doesn't slip any points. give_slice : bool, optional If the default of `True` a slice is returned as described. If `False` then the field is returned with the slice applied to it. Returns ------- B_slice : slice, numpy.ndarray The slice to apply to take one field side. If `give_slice` is `False` then the filed array is returned with the slice applied. """ if abs(np.max(field)) < abs(np.min(field)): raise ValueError( f"The field goes in the negative direction this function only " f"takes positive field pulses.") maxB_arg = np.argmax(field) peak_field = field[maxB_arg] if B_min is not None and B_min > peak_field: raise ValueError( f"The maximum value of the field is smaller than the given " f"value of B_min.") if side not in ['up', 'down', 'both']: raise ValueError( f"side was given as {side} but can only be 'up', 'down', or " f"'both'.") if side == 'down' and maxB_arg == len(field) - 1: raise ValueError( f"down was selected but the highest field was at the end of the " f"field profile given.") elif side == 'up' and maxB_arg == 0: raise ValueError( f"up was selected but the highest field was at the start of the " f"field profile given.") if B_min is None: pass elif side == 'down' and B_min > np.min(field[maxB_arg:]): pass elif side == 'up' and B_min > np.min(field[:maxB_arg]): pass elif side == 'both' and B_min > np.min(field): pass else: raise ValueError( f"The value of B_min was below the lowest field value in the " f"side you selected.") if side == 'down': startB_arg = maxB_arg if B_min is None: endB_arg = None else: endB_arg = maxB_arg + \ np.where(field[maxB_arg:]<B_min)[0][0] B_slice = slice(endB_arg, startB_arg, -skip_num) elif side == 'up': if B_min is None: startB_arg = None else: startB_arg = np.where(field>B_min)[0][0] endB_arg = maxB_arg B_slice = slice(startB_arg, endB_arg, skip_num) elif side == 'both': if B_min is None: startB_arg, endB_arg = None, None else: startB_arg, endB_arg = np.where(field>B_min)[0][[0, -1]] B_slice = slice(startB_arg, endB_arg, skip_num) else: raise ValueError("side must be either 'up', 'down', or 'both'.") if give_slice: return B_slice else: return field[B_slice]
[docs]class PulsedLockIn(): """Performs a digital lock in on pulse field magnetotransport data. This class is used to process data from pulsed field measurements using digital phase sensitive detection. The class is designed and named for it to be used for magnetotransport measurements, it can and has also been used for other types of experiments such as torque magnetometry. The type lock-in process it uses is convolution with a Hamming window. As well as the simple phase sensitive detection functionality it also has tools for finding the phase shift, smoothing signal, and filtering out voltage spikes. The data produces can be accessed from the attributes or output as a :class:`.Data` object using one of the methods. Parameters ---------- field : numpy.ndarray Field values in Tesla sorted in a 1D numpy array. The field will be changed to positive field is a negative sweep is given. current : numpy.ndarray Voltage reading across a shunt resistor in the form of a 1D numpy array. voltage : numpy.ndarray Voltage readings in the measurement channel in the form of a 1D numpy array. sample_freq : float, optional The rate of the data acquisition in Hertz. The default is `15e6` which is a common pulse field sample frequency. R_shunt : float, optional Value of the shunt resistor to measure the current in Ohms. The default value is `100`. preamp : float, optional Value of the amplification of the voltage signal before being measured. The default is `1`, so assumes no amplification. skip_num : int, optional The ratio of points to skip when outputting the data. This is used because the object sizes can become unwieldy if all the data is saved. The default is `200`, which drops 199 points for every one it keeps. B_min : float or None, optional The minimum value of the field to keep points lower in field to this will be dropped. If set to `None` all of the field range is kept. The default value is `0`, which only drops negative field values. side : {'up', 'down', 'both'}, optional The side of the pulse to produce the data for. 'up' is the first side, 'down' is the second, and 'both' takes both sides. The default is 'down'. Attributes ---------- field : numpy.ndarray The given numpy array containing the field values, if this is a negative field pulse then the sign of the field values are inverted. Iv : numpy.ndarray The numpy array given for the measurement current voltage. Volt : numpy.ndarray The numpy array given for the measurement voltage. time : numpy.ndarray The time values in milliseconds the same size as the given arrays. fs : float The sample frequency given in Hertz. R_shunt : float The given shunt voltage which is used to converted the measurement current voltage into current. preamp : float The given amplification that is used to convert the measured voltage into the generated voltage. field_direction : bool `True` if it is a positive pulse, `False` if it is a negative pulse. peak_field : float The maximum field value reached in the magnet pulse. slice : slice The slice that selects the data of interest out of the complete measurement. This is set by the `B_min`, `side`, and `step_size` keywords. freq : float The frequency of the applied measurement current voltage. phase : float The phase shift from the start of the file of the measurement current voltage in degrees. Irms : float The average applied current in root mean squared Amps. time_const : float The given time constant used for the voltage lock in seconds. phase_shift : float The given phase shift between the current measurement voltage and the experiment measurement voltage in degrees. loc_Volt : numpy.ndarray The experimental voltage after the lock in process considering the amplification in root mean squared Volts. loc_Volt_out : numpy.ndarray Equivalent to `loc_Volt` but for the out of phase component of the experimental voltage. loc_I : numpy.ndarray The applied current after a lock in process in root mean squared Amps. """ def __init__(self, field, current, voltage, sample_freq=15e6, R_shunt=100., preamp=1., skip_num=200, B_min=0., side='down'): for name, array in {'field':field, 'current':current, 'voltage':voltage}.items(): if not isinstance(array, np.ndarray): raise TypeError( f"{name} needs to be a numpy array but was of type " f"{type(array)}.") elif not np.isfinite(array).all(): raise ValueError( f"The array {name} contains non-finite values.") elif array.ndim != 1: raise ValueError( f"{name} needs to be a 1D array but had shape " f"{array.shape}.") if field.size != current.size or field.size != voltage.size: raise ValueError( f"field, current, and voltage all need to be the same " f"size but had sizes {field.size}, {current.size}, and " f"{voltage.size}.") # Save the original data self.field = field self.Iv = current self.Volt = voltage self.preamp = preamp self.R_shunt = R_shunt # Generate time self.fs = sample_freq self.time = 1000*np.arange(len(self.field))/self.fs # Make field positive,and save direction self.field_direction = (abs(np.max(self.field)) > abs(np.min(self.field))) if not self.field_direction: self.field = -self.field self.peak_field = np.max(self.field) # Make the slice self.slice = pick_pulse_side(self.field, B_min, side, skip_num=skip_num) # Find frequencies and phases self.freq = diglock.find_freq(self.Iv, self.fs, padding=10) self.phase = diglock.find_phase(self.Iv, self.fs, self.freq) self.Irms = np.average((self.Iv*diglock.gen_ref(self.freq, self.fs, self.phase, len(self.Iv)))[1000:-1000])*np.sqrt(2)/R_shunt # Attributes to be set self.time_const = None self.phase_shift = 0 self.loc_Volt = None self.loc_Volt_out = None self.loc_I = None def _has_locked(self): """This is just for checking that the lock in process has happened before trying to output or manipulate the locked in data. """ if self.loc_Volt is None: raise AttributeError( f"The lock in process has not yet been performed. Please " f"call the method lockin_Volt first.") def _has_locked_I(self): """This is just for checking that the lock in process has happened before trying to output or manipulate the locked in data. """ if self.loc_I is None: raise AttributeError( f"The lock in process on the current has not yet been " f"performed. Please call the method lockin_current first.")
[docs] def lockin_Volt(self, time_const, phase_shift=None): """This preforms a lock in process on the measurement signal. This method sets the attributes :attr:`loc_Volt` and :attr:`loc_Volt_out`. The lock in process is performed using :func:`.diglock.ham_lock_in`. Does not return anything. Parameters ---------- time_const : float Time for averaging in seconds. phase_shift : float, optional Phase difference between the current voltage and the measurement voltage, this defaults to the attribute :attr:`phase_shift`. This is in degrees. """ self.time_const = time_const if phase_shift is not None: self.phase_shift = phase_shift self.loc_Volt = diglock.ham_lock_in(self.Volt, time_const, self.fs, self.freq, self.phase + self.phase_shift, )/self.preamp self.loc_Volt_out = diglock.ham_lock_in(self.Volt, time_const, self.fs, self.freq, self.phase + self.phase_shift + 90., )/self.preamp
[docs] def lockin_current(self, time_const, phase=0,): """This preforms a lock in process on the current signal. This uses :func:`.diglock.ham_lock_in` to perform the lock in and sets the attribute :attr:`loc_I`. Parameters ---------- time_const : float Time for averaging in seconds. phase : float, optional An applied phase shift for the current, in degrees. The default value is 0. """ self.loc_I = diglock.ham_lock_in(self.Volt, time_const, self.fs, self.freq, self.phase + phase,)/self.R_shunt
[docs] def spike_lockin_Volt(self, time_const, phase_shift=None, sdl=2, region=1001): """Spike removing lock in process applied to the measurement signal. This preforms a lock in process on the measurement signal, with the aim of removing spikes in the raw first. This can be useful as some magnets can see high voltage spikes. This uses :func:`.diglock.spike_lock_in`. Nothing is returned but the following attributes are updated, :attr:`time_const`, :attr:`phase_shift`, :attr:`loc_Volt`, and :attr:`loc_Volt_out`. Parameters ---------- time_const : float Time for averaging in seconds. phase_shift : float, optional Phase difference between the current voltage and the measurement voltage in degrees. This defaults to the attribute :attr:`phase_shift`. sdl : float, optional The number of standard deviations the data to be deviate from to be considered a outlier. Outliers are identified as spikes. The default is 2. region : int, optional The number of points around the outlier that are also considered compromised. The default is 1001. """ self.time_const = time_const if phase_shift is not None: self.phase_shift = phase_shift self.loc_Volt = diglock.spike_lock_in(self.Volt, time_const, self.fs, self.freq, self.phase + self.phase_shift, sdl=sdl, region=region,)/self.preamp self.loc_Volt_out = diglock.spike_lock_in(self.Volt, time_const, self.fs, self.freq, self.phase + self.phase_shift + 90., sdl=sdl, region=region,)/self.preamp
[docs] def smooth_Volts(self, smooth_time, smooth_order=2): """This smooths the measurement signal. This must be applied after the lock in process. It changes the attributes :attr:`loc_Volt` and :attr:`loc_Volt_out`. The smoothing is done with a pass of a Savitzky-Golay filter from :func:`scipy.signal.savgol_filter`. This is particularly useful to remove small aliasing issues that can arise when using a short lock in window. Parameters ---------- smooth_points : float The time window to fit the polynomial for smoothing, in seconds. smooth_order : int, optional The order of the poly to fit for the smoothing, the default is 2. """ self._has_locked() smooth_points = int(np.ciel(smooth_time/self.fs/2)*2 + 1) self.loc_Volt = savgol_filter(self.loc_Volt, smooth_points, smooth_order,) self.loc_Volt_out = savgol_filter(self.loc_Volt_out, smooth_points, smooth_order,)
[docs] def rephase(self, phase_shift, trial=False): """Rephases the signal to the new phase. This changes the attributes :attr:`loc_Volt` and :attr:`loc_Volt_out` to shift the phase by a certain amount. If the `trial` is set to `True` then the result is returned instead of updating the attributes. The phase shift given is absolute and :attr:`phase_shft` is also updated. Parameters ---------- phase_shift : float The new phase shift to use in degrees. This is absolute so the result is independent to the current phase shift. trial : bool, optional Whether to keep the new rephasing or to return the result instead. The default value is `False` which updates the attributes and returns nothing. """ self._has_locked() phase_difference = phase_shift - self.phase_shift v_in_new = self.loc_Volt*np.cos(phase_difference*np.pi/180) + \ self.loc_Volt_out*np.sin(phase_difference*np.pi/180) v_out_new = self.loc_Volt_out*np.cos(phase_difference*np.pi/180) - \ self.loc_Volt*np.sin(phase_difference*np.pi/180) if trial == False: self.phase_shift = phase_shift self.loc_Volt = v_in_new self.loc_Volt_out = v_out_new else: return v_in_new, v_out_new
[docs] def auto_phase(self, aim='change'): """Finds the value of the phase_shift to achieve a certain result. This finds the phase which makes the out of phase a flat as possible and also has the in phase be majority positive. It can also be set to move the majority of the signal into the in-phase channel using the `aim` parameter. Uses :func:`.diglock.phase_in`. Parameters ---------- aim : {'change', 'value'} Weather to minimise the change in signal or the signal total in the out of phase channel. Returns ------- phase : float A value between 0 and 360 which produces which most achieves the set goal in degrees. """ self._has_locked() v_in = self.loc_Volt_out[self.slice] v_out = self.loc_Volt_out[self.slice] return (self.phase_shift + diglock.phase_in(v_in, v_out, aim=aim)) % 360
[docs] def find_phase(self, skip_num=10, start_auto='change', to_zero=False): """Returns a function that makes a graph for phasing. This produces a function which when called plots a graph showing the in and out of phase signal, the one argument is the phase. The default value of the one argument is set by the start_phase argument. One way to use this is with the library `ipywidgets <https://ipywidgets.readthedocs.io/en/latest>`_ which can make a slider in notebooks by running :: find_phase_function = PulsedLockIn.find_phase() ipywidgets.interact(find_phase_function, phase=ipywidgets.FloatSlider(min=0, max=360, step=1)) Parameters ---------- skip_num : int, optional The ratio of points to skip when plotting. As this requires a lot of calculation it can be beneficial to only plot a fraction speed up the process. The default value is 10. start_auto : int, float, {'change', 'value'}, optional Decides in what phase to start the graph at. If a string is given it is passed to :meth:`auto_phase`. If an number is given the phase is set to that value. to_zero : bool If `True` the in phase and out of phase components are set to zero at the lowest field. This can make the changes easier to inspect. The default is `False`. Returns ------- plotting : Calculable A function with one keyword argument of `phase` which plots the in and out of phase signal when called. """ self._has_locked() if isinstance(start_auto, (int, float, np.int_, np.float_)): start_phase = start_auto elif start_auto == 'change': start_phase = self.auto_phase(aim='change') elif start_auto == 'value': start_phase = self.auto_phase(aim='value') else: raise ValueError("start_auto must either be a value to start" " or 'change' or 'value' to be passed to auto_phase.") # Gets data to plot v_in, v_out = self.rephase(0, trial=True) v_in = v_in[self.slice][::skip_num] v_out = v_out[self.slice][::skip_num] if to_zero: v_in -= v_in[0] v_out -= v_out[0] field = self.field[self.slice][::skip_num] # Make plotting function def plotting(phase=start_phase): v_in_new = v_in*np.cos(phase*np.pi/180) + \ v_out*np.sin(phase*np.pi/180) v_out_new = v_out*np.cos(phase*np.pi/180) - \ v_in*np.sin(phase*np.pi/180) plt.plot(field[[0, -1]], [0, 0], color='0.5') plt.plot(field, v_in_new, 'b', label='V In') plt.plot(field, v_out_new, 'r', label='V Out') plt.ylabel('Voltage (V)') plt.xlabel('Field (T)') plt.legend(loc='upper left', title=f"phase: {phase:.1f}") plt.show() return plotting
[docs] def reset_slice(self, skip_num='No', B_min='No', side='No', trial=False): """This reproduces the slice which selects the data of interest. This is used to change the attribute :attr:`slice`. It also has a trial option that will return a new slice instead of updating the existing attribute. The parameters will try to all default to the values to reproduce the current slice, this maybe not be exacltly the same with `B_min`. Parameters ---------- skip_num : int, optional The ratio of points to skip when outputting the data. This is used because the object sizes can become unwieldy if all the data is saved. it keeps. B_min : float or None, optional The minimum value of the field to keep points lower in field to this will be dropped. If set to `None` all of the field range is kept. side : {'up', 'down', 'both'}, optional The side of the pulse to produce the data for. 'up' is the first side, 'down' is the second, and 'both' takes both sides. trial : bool, optional If `True` the slice is not saved and instead returned. The default is `False` which updates :attr:`slice`. """ if skip_num == 'No': skip_num = np.abs(self.slice.step) if B_min == 'No': B_min = np.min(self.field[[self.slice.start, self.slice.stop]]) if side == 'No': if self.slice.step < 0: side = 'down' elif np.argmax(self.field[self.slice][::-1]) == 0: side = 'up' else: side = 'both' if trial: return pick_pulse_side(self.field, B_min, side, skip_num=skip_num) else: self.slice = pick_pulse_side(self.field, B_min, side, skip_num=skip_num)
def _make_Data(self, y_values, as_Data, x_axis): """This is used to return the data after the lock in process in a certain form. """ if as_Data: if x_axis == 'field': return Data(self.field[self.slice], y_values) elif x_axis == 'time': return Data(np.arange(len(self.field))[self.slice]/self.fs, y_values) else: raise ValueError("x_axis must either be 'field' or 'time'.") else: return y_values
[docs] def volts_in(self, as_Data=True, x_axis='field'): """The signal from the locked in, in phase voltage. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- volts_in : Data, numpy.ndarray The locked in measurement signal from the in phase channel. """ self._has_locked() return self._make_Data(self.loc_Volt[self.slice], as_Data=as_Data, x_axis=x_axis)
[docs] def volts_out(self, as_Data=True, x_axis='field'): """The signal from the locked in, out of phase voltage. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- volts_out : Data, numpy.ndarray The locked in measurement signal from the out of phase channel. """ self._has_locked() return self._make_Data(self.loc_Volt_out[self.slice], as_Data=as_Data, x_axis=x_axis)
[docs] def res_in(self, as_Data=True, x_axis='field'): """The locked in voltage signal, in phase in units of Ohms. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- res_in : Data, numpy.ndarray The locked in voltage signal, from the in phase channel divided by the average current to obtain the units in Ohms. """ self._has_locked() return self._make_Data(self.loc_Volt[self.slice]/self.Irms, as_Data=as_Data, x_axis=x_axis)
[docs] def res_out(self, as_Data=True, x_axis='field'): """The locked in voltage signal, out of phase in units of Ohms. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- res_out : Data, numpy.ndarray The locked in voltage signal, from the out of phase channel divided by the average current to obtain the units in Ohms. """ self._has_locked() return self._make_Data(self.loc_Volt_out[self.slice]/self.Irms, as_Data=as_Data, x_axis=x_axis)
[docs] def volts_over_current(self, as_Data=True, x_axis='field'): """The locked in voltage signal over the current signal. The is for the same purpose as :meth:`res_in` but if the applied current is for some reason not stable. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- res_in : Data, numpy.ndarray The locked in voltage signal from the in phase channel divided by the locked in current signal. This also obtains the value in units of Ohms but allows to take into consideration variable current flow. """ self._has_locked() self._has_locked_I() v_over_i = self.loc_Volt[self.slice]/self.loc_I[self.slice] return self._make_Data(v_over_i, as_Data=as_Data, x_axis=x_axis)
[docs] def current_in(self, as_Data=True, x_axis='field'): """The locked in current signal in units of Amps rms. Parameters ---------- as_Data : bool, optional If `True`, which is the default, the data is returned as a :class:`.Data` object. If `False` it is returned as a :class:`numpy.ndarray`. x_axis : {'field', 'time'} For the :class:`.Data` object whether the independent variable should be the applied field or the time. The default is the field. Returns ------- current_in : Data, numpy.ndarray The locked in current signal in units of Amps rms. """ self._has_locked() self._has_locked_I() return self._make_Data(self.loc_I[self.slice], as_Data=as_Data, x_axis=x_axis)