Programming Documentation
UML Diagram
Fig. 39 UML diagram of inheritance and created objects
High Level Interface
Plot
- class Plot[source]
Bases:
objectA small class used to plot figures for a presentation.
- compare_sweeprates(filename='compare-sweeprates', show_inset=False)[source]
Compare different Sweeprates, fits the data and
- Returns
- Return type
None.
- compare_voltages(filename='compare-voltages', show_inset=False)[source]
Compare different applied Voltages.
- Returns
- Return type
compare-voltages.pdf
- fast_sweeprate(show_numbers=[320, 325, 323, 329], xlim=None, filename='FastSR-1', pos2=False, title='\\Huge \\textbf{a) Large Field Sweeps}', show_inset=False)[source]
anas Fast Sweeps over large sweep ranges (1T)
- Parameters
show_numbers –
xlim –
filename (TYPE, optional) – DESCRIPTION. The default is ‘FastSR-1’.
pos2 –
title –
- Returns
- Return type
None.
- first_sweeps(add=False, filename='vacant-sweeps', show_inset=False)[source]
First field sweeps (plus minus 50/75/100/etc.)
- Parameters
add – adding more data
filename – saving as pdf (default: vacant-sweeps)
- Returns
- Return type
vacant-sweeps.pdf
- measplan12_full(name='measplan12-full')[source]
measurement Plan #12 All 14 measurements in one Graph.
- Parameters
name –
- plot_cubes_trees()[source]
Plots difference between RAW data from Cubes and Trees (1st Generation)
- Returns
- Return type
None.
- plot_hloop_gradient(limit=50, filename='hloop-gradient')[source]
Plotting the gradient along the
- Parameters
limit –
filename –
- Returns
- Return type
None.
- plot_hloop_gradient2(limit=50, filename='hloop-gradient')[source]
Plotting the gradient along the
- Parameters
limit –
filename –
- Returns
- Return type
None.
- plot_hloop_gradient3(ax, to_show=[139, 140], limit=50, column='Vx8')[source]
Plotting the gradient along the
- Parameters
ax –
to_show –
limit –
column –
- Returns
- Return type
None.
- plot_hloops(to_show=[54, 55], filename='hloop-compare-1', **kwargs)[source]
Compares Plusses and Crosses between two angles (4 Subplots). Default: 0 and 45 Writes file hloop-compare-1.pdf
- Parameters
to_show (list, optional) – List of 4 measurement numbers, defaults to [54, 55]
filename (str, optional) – DESCRIPTION, defaults to ‘hloop-compare-1’
**kwargs –
- Returns
None
- plot_hloops2(to_show=[58, 59, 61, 62], filename='hloop-compare-3', **kwargs)[source]
Outputs hloop-compare-3.pdf (not used anymore)
- Parameters
to_show (TYPE, optional) – DESCRIPTION. The default is a[85]+a[100].
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-compare-3’.
**kwargs (TYPE) – DESCRIPTION.
- Returns
- Return type
None.
- plot_hloops3(to_show=[139, 140], filename='hloop-repeat', xlim=(- 350, 350), ylim=None, inset_xlim=(- 30, 30), inset_ylim=(0.15, 1.25))[source]
Compares two measurements at the same Angle (repeated measurements).
- Parameters
to_show (TYPE, optional) – DESCRIPTION. The default is [139,140].
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-repeat’.
xlim (TYPE, optional) – DESCRIPTION. The default is (-350, 350).
ylim –
inset_xlim (TYPE, optional) – DESCRIPTION. The default is (-30, 30).
inset_ylim (TYPE, optional) – DESCRIPTION. The default is (.15, 1.25).
- Returns
- Return type
None.
- plot_hloops4(filename='hloop-parallel', figtitle='M57: $90^\\circ$ Parallel measurement', **kwargs)[source]
Plots a single measurement (default: 90deg parallel)
- Parameters
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-parallel’.
figtitle (TYPE, optional) – DESCRIPTION. The default is “M57: $90^circ$ Parallel measurement”.
**kwargs (TYPE) – DESCRIPTION.
- Returns
- Return type
None.
- plot_hloops_90(to_show=[57, 155], filename='hloop-compare-90', **kwargs)[source]
Compares +90deg with -90deg for (180deg Comparison)
- Parameters
to_show (TYPE, optional) – DESCRIPTION. The default is [57,155].
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-compare-90’.
**kwargs (TYPE) – mirror: bool, optional should we mirror the hloop? The default is False
- Returns
- Return type
None.
- plot_hloops_90_nofit(to_show=[57, 155], filename='hloop-compare-90', **kwargs)[source]
Compares +90deg with -90deg for (180deg Comparison) Using not fitted data
- Parameters
to_show (TYPE, optional) – DESCRIPTION. The default is [57,155].
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-compare-90’.
**kwargs (TYPE) – mirror: bool, optional should we mirror the hloop? The default is False
- Returns
- Return type
None.
- plot_hloops_95(to_show=[42, 43, 32, 34], filename='hloop-compare-95', **kwargs)[source]
Compares +95/100/105/110deg with -85/80/75/70deg for (180deg Comparison)
:param filename:Filename to save. The default is ‘hloop-compare-95’. :type filename: str, optional
- Parameters
to_show (list, optional) – Measurement numbers to plot. The default is a[95]+a[-85].
filename –
**kwargs –
- Returns
{filename}.pdf
- plot_hloops_compare_90(to_show=[414, 415], structure='plusses', filename='hloop-repeat-414', xlim=(- 750, 750), ylim=(- 0.36, 0.76), inset_xlim=(- 225, 225), inset_ylim=(- 0.35, 0.75), insets=[((0.05, 0.05, 0.35, 0.35), ((- 225, 225), (- 0.35, 0.75), 'blue', 0.1))], legend_loc='upper right', nograd=False, nodiff=False, figsize=(16, 12))[source]
Compares multiple measurements at 90 deg (repeated measurements).
- Parameters
to_show (TYPE, optional) – DESCRIPTION. The default is [414,415].
structure –
filename (TYPE, optional) – DESCRIPTION. The default is ‘hloop-repeat-414’.
xlim (TYPE, optional) – DESCRIPTION. The default is (-750, 750).
ylim –
inset_xlim (TYPE, optional) – DESCRIPTION. The default is (-100, 100).
inset_ylim (TYPE, optional) – DESCRIPTION. The default is (-1.25, .75).
legend_loc –
nograd –
- Returns
- Return type
None.
- plot_single_hloop(nr=54, filename='hloop-single-1', **kwargs)[source]
Plots a single measurement. Default: 0deg
- Parameters
nr –
filename (str, optional) – File to write. The default is ‘hloop-single-1’.
ylim (xlim /) – limit the plots axes.
- Returns
{filename}.pdf
- plot_static_fields(filename='static-field', show_inset=False)[source]
Plot the Signal Analyzer (SA) Data for static fields (not sweeping)
- Returns
- Return type
None.
- set_size(width_pt, fraction=1, subplots=(1, 1))[source]
Set figure dimensions to sit nicely in our document.
Source: https://jwalton.info/Matplotlib-latex-PGF/
- Parameters
width_pt (float) – Document width in points
fraction (float, optional) – Fraction of the width which you wish the figure to occupy
subplots (array-like, optional) – The number of rows and columns of subplots.
- Returns
fig_dim – Dimensions of figure in inches
- Return type
tuple
HandleM
- class HandleM(*args, **kwargs)[source]
Bases:
ana.multiple.MultipleM- Parameters
args –
kwargs –
Note
Measurement Handler: This class represents all other measurements and is the interface to other measurement objets.
- get_custom_header(kind='RAW', instruments=None)[source]
Help Function to get column names for EVE Measurements.
- Parameters
kind (str, optional) – Kind of measurement, default: ‘RAW’
instruments (list, optional) – Used instruments in order of appearance, default: [‘t’, ‘LS’, ‘IPS’, ‘SR830’]
- Returns
(list header, int skiprows)
- Return type
tuple
- get_info()[source]
Returns information about all measurements and splits it into groups (SA/RAV/MFN/VH).
- load_files(directory, **kwargs)[source]
Loads all files from a specific directory and creates objects for found measurements.
- Parameters
directory (str) – directory to search in
**kwargs –
- Returns
None
- parse_data(data, **kwargs)[source]
imports data and sets info variable.
h.parse_data({'data': pd.DataFrame, 'info': dict(Nr=0, ** kw) })
- Parameters
data (dict) – Data to parse
**kwargs –
- Returns
None
- plot(*args, **kwargs)[source]
Plotting a Measurement
- Parameters
lofm – list of measurements
figsize – tuple size of figure
show_fit – bool
fit_range – tuple/list
plot_settings – list
grid_options – list
f_settings – 1/f line
f2_settings – 1/f^2 line
- Returns
Figure, Axis
- Return type
tuple
- Raises
warning – lofm not available.
KeyError – Measurement does not exist.
MFN
- class MFN(files, **kwargs)[source]
Bases:
ana.multiple.MultipleMMagnetic Flux Noise Measurements. Class to handle and plot MFN Measurements taken with the SR830 DAQ.
if a measurement number is given the files are loaded from folder:
data/MFN/m%s/*.datelse: files must be a list of filenames:
1arg = glob('data/MFN/mXXX/m[0-9.]*.dat') 2mfn = MFN(arg)
- Parameters
files – list of files or measurement number.
kwargs – Different additional parameters.
nr (int) – Measurement number (default: None).
calc_first (bool) – Calculating first spectrum (default: True).
calc_second (bool) – Calculating second spectrum (default: False).
downsample (bool) – downsample timesignal for first spectrum (default: False).
num_first_spectra (int) – Number of spectra to average (default: 64).
timeconstant (float) – Lock-In timeconstant to cut first spectrum at corresponding frequency (see
cut_fmax).cut_fmax (float) – Cut first spectrum at this frequency (default: \(f_{max} = \frac{\tau}{2\pi}\)).
default_cm (matplotlib.cm) – matplotlib color map for contour plots (default: style[‘default_cm’]).
- load_meas(files, **kwargs)[source]
Loading a single magnetic flux noise DAQ measurement.
Files in measrument folder contain multiple measurements with one measurement per field.
- Parameters
files – files to load
- Returns
DataFrame Data, dict Measurement Objects
- Return type
tuple
- plot_alpha(field, s, ax=None, **kwargs)[source]
Fits a spectrum and plots the slope alpha. :param s: pandas.DataFrame of the spectrum :param ax: axis where to plot the data. :return: linear regression fit
- plot_compare_timesignals(fields, **kwargs)[source]
Plots a grid of 2x2 subplots to compare timesignals and histogram. :param fields: list :param factor: float Default: 1e3 (mV) :param nrows: int Default: len(fields) :param ncols: int Default: 2 :param sharey: bool Default: True :param figsize: tuple Default: (11.69, 8.27) in :param plot_range: int Default: -1
- plot_multiple_histograms(steps=4, fields=Series([], dtype: float64), factor=1000.0, xlim=(-1.3, 1.3), **kwargs)[source]
Plots multiple histograms using seaborn
sns.FacetGridwith kdeplot.- default kwargs for subplots:
kde1_kwargs = dict(clip_on=True, shade=True, alpha=1, lw=1.5, bw=.2) kde2_kwargs = kde1_kwargs.update(dict(lw=2, color=’w’) dist_kwargs = dict(hist=True, norm_hist=True, hist_kws={‘alpha’: .5}) ax_kwargs = dict(y=0, lw=2, clip_on=True).set(xlim=(-1.3,1.3))
- plot_noise_chararcteristics(ax=None, fields=None, **kwargs)[source]
- Parameters
ax –
fields –
**kwargs –
- plot_various_timesignals(fields, **kwargs)[source]
Plots a grid of 9 subplots with different timesignals :param fields: list :param nrows: int Default: len(fields) :param ncols: int Default: 2 :param sharey: bool Default: True :param figsize: tuple Default: (11.69, 8.27) in :param plot_range: int Default: -1
Low Level Python Backend
MeasurementClass
- class MeasurementClass[source]
Bases:
objectThe main measurement class for all measurements.
Note
MeasurementClass: This Class is the parent of all other measurement classes.
All measurements have the following variables:
- name: str/None
The name of the Measurement Class (e.g. SA, RAW, etc.)
- info: dict
- Contains informations about the measurement. Typical keys are:
Nr: float, the number of the measurement,
- get_info_from_name(name, **kwargs)[source]
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
SingleM
- class SingleM[source]
Bases:
ana.measurement.MeasurementClassThe main constructor for all single measurements.
- calc_log(df, keys=['f', 'Vx', 'Vy'])
- fit_variable(df, x, y)[source]
Applys a linear regression and adds result to DataFrame
- Parameters
df (pd.DataFrame) – Fit data
x (np.ndarray) – Fit variable.
y (np.ndarray) – Fit variable.
- get_info_from_name(name, **kwargs)
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
- i(*key)
Get a specific measurement number information.
- Parameters
*key –
Will be passed to info.get
- Returns
info.get(*key)
- linear_regression(x, y)
RAW
- class RAW(data, **kwargs)[source]
Bases:
ana.single.SingleM- RAW Measurement:
Measurements with time signal only.
1data = np.array(timesignal, shape=(n,)) 2data = { 3 'data': np.array(timesignal, shape=(n,)) 4 'info': {'Nr': 123, 'Add Info': 'Important Informations'} 5 } 6raw = ana.RAW(data)
- Parameters
data – Different possibilities.
rate (float, default: 2e-4) – At which rate was the timesignal measured (samplingrate) [Hz]
nof_first_spectra (int, default: 64) – Cut timesignal into # pieces and average.
first_spectra_highest_frequency (float, default: rate/8) – Cut higher frequencies in final spectrum than this.
downsample – Downsample the timesignal before calculation.
calc_first (bool, default: False) – Triggers calculation of first spectrum.
highest_octave (int, default: 64) – The highest octave starting point [Hz] of the second spectrum.
minimum_points_in_octave (int, default: 10) – If octave would contain less points, stop.
calc_second (bool, default: False) – Triggers calculation of second spectrum.
- Info
First Spectrum parameters
- Info
Second Spectrum parameters
- calc_first_spectrum()[source]
Sets the variable
spectrumto aspectrumanalyzer.SpectrumAnalyzerobject.Important
Calculates the first spectrum.
- Raises
NameError – spectrumanalyzer is not installed
- Returns
None
- calc_log(df, keys=['f', 'Vx', 'Vy'])
- check_timesignal(timesignal)[source]
converts pd.DataFrame[‘Vx’] and Series into numpy array.
- Parameters
timesignal – input timesignal
- Returns
converted timesignal.
- Return type
numpy.ndarray
- fit_variable(df, x, y)
Applys a linear regression and adds result to DataFrame
- Parameters
df (pd.DataFrame) – Fit data
x (np.ndarray) – Fit variable.
y (np.ndarray) – Fit variable.
- get_info_from_name(name, **kwargs)
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
- i(*key)
Get a specific measurement number information.
- Parameters
*key –
Will be passed to info.get
- Returns
info.get(*key)
- linear_regression(x, y)
- read(file)[source]
Reading data from file.
- Parameters
file (str) –
from. (Filename base to read data) –
- Returns
- Return type
self
SA
- class SA(*args, **kwargs)[source]
Bases:
ana.single.SingleMLoads and analyzes datafiles from :instrument:`SR785`
- Parameters
*args –
**kwargs –
- Returns
None
- Raises
* ValueError –
- calc_log(df, keys=['f', 'Vx', 'Vy'])
- fit(fit_range=(0.01, 0.7))[source]
Fits a linear regression
- Parameters
(tuple (fit_range) – (1e-2, 1e0).):
optional – (1e-2, 1e0).):
default – (1e-2, 1e0).):
- fit_variable(df, x, y)
Applys a linear regression and adds result to DataFrame
- Parameters
df (pd.DataFrame) – Fit data
x (np.ndarray) – Fit variable.
y (np.ndarray) – Fit variable.
- get_info_from_name(name, **kwargs)
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
- i(*key)
Get a specific measurement number information.
- Parameters
*key –
Will be passed to info.get
- Returns
info.get(*key)
- linear_regression(x, y)
- plot(**kwargs)[source]
Plotting the Data on given Axis or creating a new Plot for plotting.
1plot(ax, label=None, color=None, linestyle=None, 2 plot_x='f', plot_y='Vx', dont_set_plot_settings=False, 3 4 xscale='log', yscale='log', xlim=(None, None), ylim=(None, 5 None), legend_location='best', grid=None, title=None, 6 xlabel='f [Hz]', ylabel='S_VH [V2/Hz]', 7 8)
- Parameters
**kwargs –
Note
If dont_set_plot_settings is True, all kwargs below are not used.
Hloop
- class Hloop(measurement_number, **kwargs)[source]
Bases:
ana.single.SingleMThis class represents the measurement of a hysteresis loop. It evaluates and plots the data.
It collects all files that match the following regex:
files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number)
The files should contain a file that contains the word “up” or “down” indicating the direction of the field sweep.
If one file contains the word ‘Gradio’ it expects a gradiometry measurement. If the filename contains the word ‘Parallel’ it expects a parallel measurement. If the filename contains neither of these words the variables need to be set manually.
- Parameters
measurement_number –
**kwargs –
- calc_log(df, keys=['f', 'Vx', 'Vy'])
- calculate_fitted_strayfield()[source]
Calculates the strayfield of the fitted signal and stores it in up and down sweeps.
- calculate_strayfield()[source]
Calculates the strayfield of the signal and stores it in up and down sweeps.
- fit(cond=Series([], dtype: float64))[source]
Fitting the Voltage Signal.
For Parallel measurement:
the empty cross Vx13 is subtracted from the signal Vx8/Vx9
For gradiometry:
A condition can be added to determine the amount of datapoints to fit (default: B < B_min + 150mT).
cond = (self.up.B < self.up.B.min() + 150) m.fit(cond)
- Parameters
cond –
- fit_variable(df, x, y)
Applys a linear regression and adds result to DataFrame
- Parameters
df (pd.DataFrame) – Fit data
x (np.ndarray) – Fit variable.
y (np.ndarray) – Fit variable.
- get_bhminmax()[source]
Returns the minimum and maximum of the measured hall voltage converted to strayfield.
bhmin, bhmax = meas.get_bhminmax()
- get_coercive_field()[source]
Returns the mean and coercive fields calculated.
mean, coer1, coer2 = meas.get_coercive_field() print('Coercive Field (up sweep): (%.3f, %.3f)' % (coer1, mean)) print('Coercive Field (down sweep): (%.3f, %.3f)' % (coer2, mean))
- get_downminusup(n=100000.0)[source]
Returns the magnetic field and difference between down and up sweep.
B, Vx = meas.get_downminusup() plt.plot(B, Vx)
- Parameters
n –
- get_downminusup_strayfield(n=100000.0, fitted_data=False)[source]
Returns the magnetic field and difference between down and up sweep.
B_ext, Bx = meas.get_downminusup_strayfield() plt.plot(B_ext, Bx)
- Parameters
n –
fitted_data –
- get_info_from_name(name, **kwargs)
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
- get_minmax()[source]
Returns the minimum and maximum of the field and voltage.
bmin, bmax, vmin, vmax = meas.get_minmax()
- get_remanence()[source]
Returns the remanent voltage calculated.
rem1, rem2 = meas.get_remanence() print('Remanent Voltage (up sweep): (%d, %.3f)' % (0, rem1)) print('Remanent Voltage (down sweep): (%d, %.3f)' % (0, rem2))
- i(*key)
Get a specific measurement number information.
- Parameters
*key –
Will be passed to info.get
- Returns
info.get(*key)
- linear_regression(x, y)
- plot_downminusup(ax, figtitle='')[source]
Plotting the difference between the down and up sweep. ax: Matplotlib Axis to plot on. figtitle: can be set to a manual figure title.
- Parameters
ax –
figtitle –
- plot_hloop(ax, figtitle='', show_fitted=True, show_rem=False, show_coer=False, **kwargs)[source]
Plotting the hysteresis loop.
- Parameters
ax – Matplotlib Axis to plot on.
figtitle – can be set to a manual figure title.
show_fitted – plot the fitted curve.
show_rem – show the remanent voltage.
show_coer – show the coercive field.
show_original – plots the RAW data.
show_linear_fit – plots the linear fitting line used for the fit
(only gradiometry).
- plot_strayfield(ax, figtitle='', **kwargs)[source]
Plots the strayfield of the data. Strayfield is calculated using the electron concentration at 0 degree measured before (hardcoded in __init__).
- Parameters
ax (matplotlib.pyplot.axis) – where should we plot.
figtitle (str, optional) – Choose your own title above the figure.
**kwargs –
- Returns
- Return type
None.
MultipleM
- class MultipleM[source]
Bases:
ana.measurement.MeasurementClassMultipleM:
Main parent class for all multiple measurements. These Measurements are represent by one data variable.
All multiple measurements have a variable called
multi_infowhich contains a pandas.DataFrame with parameter settings to single measurements.All multiple measurements can be represented by
>>> m = ana.MultipleM() >>> m Nr. 0: Hloop Nr. 0: RAW
- calc_log(df, keys=['f', 'Vx', 'Vy'])
- get_info_from_name(name, **kwargs)
Extracting information from filenames. Using a default regex first or trying to extract ‘key-value’ pairs separated by ‘_’.
- Parameters
name – filename that contains informations.
kwargs –
- str regex
Regular expression for the filename
- Returns
dict(key=value)
- get_lofm(to_show, label, options={})[source]
- Returns the List of Measurements (lofm) for plot functions
(see
ana.HandleM.plot()).
- Parameters
to_show (dict) – Measurement Numbers pointing to label format variables: e.g.
{nr: [(-1, 1), 'Plusses', '5 mT/min']}label (str) – Label to show in plots: e.g.
"Show %s -> %s (%s) %s "-> will be string formated using to_show(dict (options) – empty): Additional plotting parameters
optional – empty): Additional plotting parameters
default – empty): Additional plotting parameters
- Returns
List of Measurements with description:
{nr: [labels]}- Return type
dict
- get_lofm_sweeps(to_show, label, options={})[source]
Workaround to get list of measurements for sweeps.
- Parameters
to_show (dict) – Measurement Numbers pointing to label format variables: e.g. `` {nr: [-1, 1, ‘Plusses’, ‘2 mT/min’]}
label (str) – Label to show in plots: e.g. “Show %s -> %s (%s) %s ” -> will be formated using to_show
(dict (options) – empty): Additional plotting options
default – empty): Additional plotting options
- Returns
List of Measurements with description
- Return type
dict
- i(*key)
Get a specific measurement number information.
- Parameters
*key –
Will be passed to info.get
- Returns
info.get(*key)
- query(request)
PlottingClass
- class PlottingClass(**style)[source]
Bases:
objectDefines the basic plotting style.
- Parameters
**style –
- get(*key)
- save_plot(fname, fext='png')[source]
Saves the current plot as file.
- Parameters
fname (str) – Filename
fext (str) – Extension
- Returns
None
- set_style(**style)[source]
Sets the Style for plots
- Parameters
size (dict, None, or one of {paper, notebook, talk, poster}) – A dictionary of parameters or the name of a preconfigured set.
style (dict, None, or one of {darkgrid, whitegrid, dark, white, ticks}) –
default (bool) – if True it sets default
size=talkandstyle=whitegridnotebook (bool) – if True it sets default
size=notebookandstyle=ticks
- Returns
- Return type
None.
Functions
Helps to handle measurements and style plots.
Source Code
Plot
1class Plot(object):
2 def __init__(self):
3 """A small class used to plot figures for a presentation."""
4
5 super().__init__()
6
7 self.meas = {}
8 self.m = Hloop(57)
9 self.eva = HandleM(directory='data/SR785')
10
11 def get_plot(self, **kwargs):
12 fig, ax = plt.subplots(nrows=kwargs.get('nrows', 1),
13 ncols=kwargs.get('ncols', 1),
14 figsize=kwargs.get('figsize', (16, 12)),
15 **kwargs.get('plot_args',dict())
16 )
17 return fig, ax
18
19 def set_plot_style(self, m=None, **kwargs):
20 if m is None:
21 m = self.m
22
23 m.style.set_style(size=kwargs.get('size', 'talk'),
24 style=kwargs.get('style', 'ticks'),
25 palette=kwargs.get('palette', 'deep'),
26 grid=kwargs.get('grid', True),
27 latex=kwargs.get('latex', True),
28 figsize=kwargs.get('figsize', (16, 12))
29 )
30
31 def plot_single_hloop(self, nr=54, filename='hloop-single-1',
32 **kwargs):
33 """Plots a single measurement. Default: 0deg
34
35 Args:
36 nr:
37 filename (str, optional): File to write. The default is
38 'hloop-single-1'.
39 xlim / ylim: limit the plots axes.
40
41 Returns:
42 {filename}.pdf
43 """
44 fig, ax = self.get_plot(**kwargs)
45
46 if nr not in self.meas:
47 self.meas[nr] = Hloop(nr)
48
49 self.set_plot_style(self.meas[nr])
50 self.meas[nr].plot_strayfield(ax)
51 ax.set_xlim(*kwargs.get('xlim', (-250, 250)))
52 if kwargs.get('ylim'):
53 ax.set_ylim(*kwargs.get('ylim'))
54
55 with sns.color_palette('deep'):
56 self.set_plot_style(self.m)
57 inset = inset_axes(ax, width='100%', height='90%',
58 bbox_to_anchor=(.64, .06, .35, .35),
59 bbox_transform=ax.transAxes)
60 max_b = self.meas[nr].up.B.max()
61 inset.plot([-max_b, max_b], [0, 0], 'r--', linewidth=.75)
62 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
63 inset.plot(B_ext, B_stray)
64 inset.set_ylabel("$\\Delta B_z\\;[\\mathrm{mT}]$")
65 inset.set_title("Difference")
66 inset.set_xlim(*kwargs.get('xlim', (-250, 250)))
67
68 plt.savefig("%s.pdf" % filename)
69
70 def plot_hloops(self, to_show=[54, 55], filename='hloop-compare-1',
71 **kwargs):
72 """Compares Plusses and Crosses between two angles (4 Subplots).
73 Default: 0 and 45 Writes file hloop-compare-1.pdf
74
75 Args:
76 to_show (list, optional): List of 4 measurement numbers,
77 defaults to [54, 55]
78 filename (str, optional): DESCRIPTION, defaults to 'hloop-compare-1'
79 **kwargs:
80
81 Returns:
82 None
83 """
84 self.m.style.set_style(default=True, grid=True,
85 size='talk', style='ticks',
86 palette='deep', latex=True)
87 fig, axes = self.get_plot(nrows=2, ncols=2,
88 plot_args=kwargs.get('plot_args',dict()))
89
90 for i, nr in enumerate(to_show):
91 ax = axes[i // 2][i % 2]
92 if nr not in self.meas:
93 self.meas[nr] = Hloop(nr)
94
95 self.set_plot_style(self.meas[nr])
96 self.meas[nr].plot_strayfield(ax)
97 if nr in [54]:
98 ax.set_ylim(0, 4.5)
99 elif nr in [55]:
100 ax.set_xlim(-300, 300)
101 ax.set_ylim(-0.8, 4.45)
102 if nr in [22, 23]:
103 ax.set_xlim(-150, 150)
104 ax.set_ylim(-0.25, 3.5)
105 elif kwargs.get('xlim'):
106 ax.set_xlim(*kwargs.get('xlim'))
107 else:
108 ax.set_xlim(-250, 250)
109 ax.set_title('')
110
111 with sns.color_palette('deep'):
112 self.set_plot_style(self.m)
113 inset = inset_axes(ax, width='100%', height='90%',
114 bbox_to_anchor=(.65, .09, .35, .35),
115 bbox_transform=ax.transAxes)
116 max_b = self.meas[nr].up.B.max()
117 inset.plot([-max_b, max_b], [0, 0], '--', color='orange', linewidth=.75)
118 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
119 inset.plot(B_ext, B_stray, color='black')
120 inset.set_ylabel("$\\Delta B_z\\;[\\mathrm{mT}]$")
121 inset.set_title("Difference")
122 inset.set_xlim(-250, 250)
123 if nr in [55]:
124 inset.set_xlim(-300, 300)
125 if nr in [22, 23]:
126 inset.set_xlim(-150, 150)
127 if kwargs.get('xlim'):
128 inset.set_xlim(*kwargs.get('xlim'))
129 inset.set_yticks(kwargs.get('diff_yticks', [0, 0.5, 1]))
130
131 title_style = '\\bfseries \\Huge '
132 for ax, struct in zip(axes[0],
133 ['Plusses', 'Crosses']):
134 ax.set_title(title_style + struct)
135 ax.set_xlabel('')
136
137 for ax in axes[:, 1]:
138 ax.set_ylabel('')
139 ax.set_yticklabels([])
140
141 for ax, a in zip(axes[:, 0],
142 [r'{%s $\mathbf{\theta = +45^{\circ}}$}' % title_style,
143 r'{%s $\mathbf{\theta = -45^{\circ}}$}' % title_style]):
144 ax.set_ylabel("%s\n$\\langle B_z \\rangle [\\mathrm{mT}]$" % a)
145
146 axes[0, 0].set_ylim(0,4.8)
147 axes[0, 1].set_ylim(0,4.8)
148 axes[1, 0].set_ylim(-.1,3.45)
149 axes[1, 1].set_ylim(-.1,3.45)
150
151 plt.savefig("%s.pdf" % filename)
152
153 def plot_hloops_95(self,
154 to_show=[42, 43, 32, 34],
155 filename='hloop-compare-95',
156 **kwargs):
157 """Compares +95/100/105/110deg with -85/80/75/70deg for (180deg
158 Comparison)
159
160 :param filename:Filename to save. The default is 'hloop-compare-95'.
161 :type filename: str, optional
162
163 Args:
164 to_show (list, optional): Measurement numbers to plot. The default
165 is a[95]+a[-85].
166 filename:
167 **kwargs:
168
169 Returns:
170 {filename}.pdf
171 """
172 fig, axes = plt.subplots(2, 2, figsize=(16, 12))
173 for i, nr in enumerate(to_show):
174 ax = axes[i // 2][i % 2]
175 if nr not in self.meas:
176 self.meas[nr] = Hloop(nr)
177 self.set_plot_style(self.meas[nr])
178 self.meas[nr].plot_strayfield(ax)
179 if kwargs.get('xlim'):
180 ax.set_xlim(*kwargs.get('xlim'))
181 else:
182 ax.set_xlim(-750, 750)
183 if self.meas[nr].info['Structure'] == 'Crosses' and kwargs.get(
184 'crosses_xlim'):
185 ax.set_xlim(*kwargs.get('crosses_xlim'))
186
187 with sns.color_palette('deep'):
188 self.m.style.set_style(size='talk', style='ticks',
189 grid=True, latex=True)
190 inset = inset_axes(ax, width='100%', height='90%',
191 bbox_to_anchor=(.65, .09, .35, .35),
192 bbox_transform=ax.transAxes)
193 max_b = self.meas[nr].up.B.max()
194 inset.plot([-max_b, max_b], [0, 0], 'r--', linewidth=.75)
195 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
196 inset.plot(B_ext, B_stray)
197 # inset.set_ylabel("$\Delta B_z\\;[\\mathrm{mT}]$")
198 inset.set_title("Difference")
199 if kwargs.get('xlim'):
200 inset.set_xlim(*kwargs.get('xlim'))
201 else:
202 inset.set_xlim(-750, 750)
203 if self.meas[nr].info['Structure'] == 'Crosses' and kwargs.get(
204 'crosses_xlim'):
205 inset.set_xlim(*kwargs.get('crosses_xlim'))
206
207 plt.savefig("%s.pdf" % filename)
208
209 def plot_hloops_90(self,
210 to_show=[57, 155],
211 filename='hloop-compare-90',
212 **kwargs):
213 """Compares +90deg with -90deg for (180deg Comparison)
214
215 Args:
216 to_show (TYPE, optional): DESCRIPTION. The default is [57,155].
217 filename (TYPE, optional): DESCRIPTION. The default is
218 'hloop-compare-90'.
219 **kwargs (TYPE): mirror: bool, optional
220 should we mirror the hloop? The default is False
221
222 Returns:
223 None.:
224 """
225 self.m.style.set_style(default=True, grid=True,
226 size='talk', style='ticks',
227 palette='deep', latex=True)
228 sns.set_palette(sns.color_palette("deep"))
229
230 fig, axes = plt.subplots(2, 2, figsize=(16, 12),
231 **kwargs.get('plot_args', dict()))
232 for i, nr in enumerate(to_show):
233 cur_ax = axes[i]
234 self.m.style.set_style(size='talk', style='ticks',
235 palette='Paired',
236 grid=True, latex=True)
237
238 self.meas[nr] = Hloop(nr)
239 self.set_plot_style(self.meas[nr])
240
241 if not (kwargs.get('mirror')) and nr == 57:
242 self.meas[nr].set_factor(-1)
243 self.meas[nr].factor = 1
244
245 # Plusses
246 ax = cur_ax[0]
247 self.meas[nr].plot_strayfield(ax, nolegend=True,
248 show_plusses=False,
249 show_crosses=False)
250
251 ax.plot(self.meas[nr].up_fitted.B, self.meas[nr].up_fitted.Bx8,
252 label='Up (fitted)')
253 ax.plot(self.meas[nr].down_fitted.B, self.meas[nr].down_fitted.Bx8,
254 label='Down (fitted)')
255
256 # Set Limits (x and y Axis)
257 if kwargs.get('xlim'):
258 ax.set_xlim(*kwargs.get('xlim'))
259 else:
260 ax.set_xlim(-750, 750)
261
262 ax.set_ylim(np.min([self.meas[nr].up_fitted.Bx8.min(),
263 self.meas[nr].down_fitted.Bx8.min()]) - .05,
264 np.max([self.meas[nr].up_fitted.Bx8.max(),
265 self.meas[nr].down_fitted.Bx8.max()]) + .05)
266
267 ax.set_title('')
268 """if nr == 57:
269 ax.set_title('m57: Plusses ($90^\circ$)')
270 else:
271 ax.set_title('m155: Plusses ($-90^\circ$)')"""
272
273 # Inset with Difference
274 with sns.color_palette('bright'):
275 if nr == 57 and not (kwargs.get('mirror')):
276 bbox = (.11, .59, .34, .35)
277 else:
278 bbox = (.12, .12, .34, .35)
279 inset = inset_axes(ax, width='100%', height='100%',
280 bbox_to_anchor=bbox,
281 bbox_transform=ax.transAxes)
282 max_b = self.meas[nr].up.B.max()
283 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield(
284 fitted_data=True)
285 inset.plot([-max_b, max_b], [0, 0], '--', color='orange',
286 linewidth=.75)
287 inset.plot(B_ext, B_stray, color='black')
288 inset.set_xlim(-750, 750)
289 inset.set_title("Difference (fitted)")
290 inset.set_yticks(kwargs.get('diff_yticks', [0, 0.5]))
291
292 ax.legend(loc='upper right') # , ncol=2)
293
294 # Crosses
295 ax = cur_ax[1]
296 self.meas[nr].plot_strayfield(ax, nolegend=True,
297 show_plusses=False,
298 show_crosses=False)
299 ax.plot(self.meas[nr].up_fitted.B,
300 self.meas[nr].up_fitted.Bx9, label='Up (fitted)')
301 ax.plot(self.meas[nr].down_fitted.B,
302 self.meas[nr].down_fitted.Bx9, label='Down (fitted)')
303
304 # Set Limits (x and y Axis)
305 if kwargs.get('xlim'):
306 ax.set_xlim(*kwargs.get('xlim'))
307 else:
308 ax.set_xlim(-750, 750)
309
310 ax.set_ylim(np.min([self.meas[nr].up_fitted.Bx9.min(),
311 self.meas[nr].down_fitted.Bx9.min()]) - .05,
312 np.max([self.meas[nr].up_fitted.Bx9.max(),
313 self.meas[nr].down_fitted.Bx9.max()]) + .05)
314
315 ax.set_title('')
316 """if nr == 57:
317 ax.set_title('M57: Crosses ($90^\circ$)')
318 else:
319 ax.set_title('M155: Crosses ($-90^\circ$)')"""
320
321 # Inset with Difference
322 with sns.color_palette('bright'):
323 if nr == 57 and kwargs.get('mirror'):
324 bbox = (.12, .12, .34, .35)
325 else:
326 bbox = (.12, .59, .34, .35)
327 inset2 = inset_axes(ax, width='100%', height='100%',
328 bbox_to_anchor=bbox,
329 bbox_transform=ax.transAxes)
330 f_up = scipy.interpolate.interp1d(self.meas[nr].up_fitted.B,
331 self.meas[nr].up_fitted.Bx9)
332 f_down = scipy.interpolate.interp1d(
333 self.meas[nr].down_fitted.B,
334 self.meas[nr].down_fitted.Bx9)
335 B = np.linspace(self.meas[nr].up.B.min(),
336 self.meas[nr].up.B.max(), int(1e5))
337 downminusup = f_down(B) - f_up(B)
338 inset2.plot([-max_b, max_b], [0, 0], '--',
339 color='orange', linewidth=.75)
340 inset2.plot(B, downminusup, color='black')
341 inset2.set_xlim(-750, 750)
342 inset2.set_title("Difference (fitted)")
343 if nr == 57:
344 inset.set_yticks([0, .25, 0.5])
345 inset2.set_yticks([0, .25, 0.5])
346
347 ax.legend(loc='upper right') # , ncol=2)
348
349 title_style = '\\bfseries \\Huge '
350 for ax, struct in zip(axes[0],
351 ['Plusses',
352 'Crosses']):
353 ax.set_title(title_style + struct)
354 ax.set_xlabel('')
355
356 for ax in axes[:, 1]:
357 ax.set_ylabel('')
358
359 for ax, a in zip(axes[:, 0],
360 [r'{%s $\mathbf{\theta = +90^{\circ}}$}' % title_style,
361 r'{%s $\mathbf{\theta = -90^{\circ}}$}' % title_style]):
362 ax.set_ylabel("%s\n$\\langle B_z \\rangle [\\mathrm{mT}]$" % a)
363
364 plt.savefig("%s.pdf" % filename)
365
366 def plot_hloops_90_nofit(self, to_show=[57, 155],
367 filename='hloop-compare-90', **kwargs):
368 """Compares +90deg with -90deg for (180deg Comparison) Using not fitted
369 data
370
371 Args:
372 to_show (TYPE, optional): DESCRIPTION. The default is [57,155].
373 filename (TYPE, optional): DESCRIPTION. The default is
374 'hloop-compare-90'.
375 **kwargs (TYPE): mirror: bool, optional
376 should we mirror the hloop? The default is False
377
378 Returns:
379 None.:
380 """
381 self.m.style.set_style(default=True, grid=True,
382 size='talk', style='ticks',
383 palette='deep', latex=True)
384 sns.set_palette(sns.color_palette("deep"))
385
386 fig, axes = plt.subplots(2, 2, figsize=(16, 12))
387 for i, nr in enumerate(to_show):
388 cur_ax = axes[i]
389 self.m.style.set_style(size='talk', style='ticks',
390 palette='Paired',
391 grid=True, latex=True)
392
393 self.meas[nr] = Hloop(nr)
394
395 if not (kwargs.get('mirror')) and nr == 57:
396 self.meas[nr].set_factor(-1)
397 self.meas[nr].factor = 1
398
399 # Plusses
400 ax = cur_ax[0]
401 self.meas[nr].plot_strayfield(ax, nolegend=True,
402 show_plusses=False,
403 show_crosses=False)
404
405 ax.plot(self.meas[nr].up.B, self.meas[nr].up.Bx8, label='Up')
406 ax.plot(self.meas[nr].down.B, self.meas[nr].down.Bx8, label='Down')
407 ax.plot(self.meas[nr].up.B, self.meas[nr].up.Bx13,
408 label='Up (Empty)')
409 ax.plot(self.meas[nr].down.B, self.meas[nr].down.Bx13,
410 label='Down (Empty)')
411
412 # Set Limits (x and y Axis)
413 if kwargs.get('xlim'):
414 ax.set_xlim(*kwargs.get('xlim'))
415 else:
416 ax.set_xlim(-1000, 1000)
417
418 ax.set_ylim(np.min([self.meas[nr].up.Bx8.min(),
419 self.meas[nr].down.Bx8.min()]) - .05,
420 np.max([self.meas[nr].up.Bx8.max(),
421 self.meas[nr].down.Bx8.max()]) + .05)
422
423 if nr == 57:
424 ax.set_title('M57: Plusses ($90^\circ$)')
425 else:
426 ax.set_title('M155: Plusses ($-90^\circ$)')
427
428 # Inset with Difference
429 with sns.color_palette('bright'):
430 if nr == 57 and not (kwargs.get('mirror')):
431 bbox = (.11, .59, .34, .35)
432 else:
433 bbox = (.59, .12, .34, .35)
434 inset = inset_axes(ax, width='100%', height='100%',
435 bbox_to_anchor=bbox,
436 bbox_transform=ax.transAxes)
437 max_b = self.meas[nr].up.B.max()
438 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
439 inset.plot([-max_b, max_b], [0, 0], '--', color='orange',
440 linewidth=.75)
441 inset.plot(B_ext, B_stray)
442 inset.set_xlim(-1000, 1000)
443 inset.set_title("Difference")
444
445 # Crosses
446 ax = cur_ax[1]
447 self.meas[nr].plot_strayfield(ax, nolegend=True,
448 show_plusses=False,
449 show_crosses=False)
450 ax.plot(self.meas[nr].up.B, self.meas[nr].up.Bx9, label='Up')
451 ax.plot(self.meas[nr].down.B, self.meas[nr].down.Bx9, label='Down')
452 ax.plot(self.meas[nr].up.B, self.meas[nr].up.Bx13,
453 label='Up (Empty)')
454 ax.plot(self.meas[nr].down.B, self.meas[nr].down.Bx13,
455 label='Down (Empty)')
456
457 # Set Limits (x and y Axis)
458 if kwargs.get('xlim'):
459 ax.set_xlim(*kwargs.get('xlim'))
460 else:
461 ax.set_xlim(-1000, 1000)
462
463 ax.set_ylim(np.min([self.meas[nr].up.Bx9.min(),
464 self.meas[nr].down.Bx9.min()]) - .05,
465 np.max([self.meas[nr].up.Bx9.max(),
466 self.meas[nr].down.Bx9.max()]) + .05)
467
468 if nr == 57:
469 ax.set_title('M57: Crosses ($90^\circ$)')
470 else:
471 ax.set_title('M155: Crosses ($-90^\circ$)')
472
473 # Inset with Difference
474 with sns.color_palette('bright'):
475 if nr == 57 and kwargs.get('mirror'):
476 bbox = (.11, .12, .34, .35)
477 else:
478 bbox = (.11, .59, .34, .35)
479 inset2 = inset_axes(ax, width='100%', height='100%',
480 bbox_to_anchor=bbox,
481 bbox_transform=ax.transAxes)
482 f_up = scipy.interpolate.interp1d(self.meas[nr].up.B,
483 self.meas[nr].up.Bx9)
484 f_down = scipy.interpolate.interp1d(self.meas[nr].down.B,
485 self.meas[nr].down.Bx9)
486 B = np.linspace(self.meas[nr].up.B.min(),
487 self.meas[nr].up.B.max(), int(1e5))
488 downminusup = f_down(B) - f_up(B)
489 inset2.plot([-max_b, max_b], [0, 0], '--',
490 color='orange', linewidth=.75)
491 inset2.plot(B, downminusup, color='green')
492 inset2.set_xlim(-1000, 1000)
493 inset2.set_title("Difference")
494 if nr == 57:
495 inset.set_yticklabels(['', '$0.0$', '', '$0.5$'])
496 inset2.set_yticklabels(['', '$0.0$', '', '$0.5$'])
497
498 # if nr == 57 and not kwargs.get('mirror'):
499 # ax.legend(loc='upper left')#, ncol=2)
500 if not (nr == 57):
501 ax.legend(loc='upper right') # , ncol=2)
502
503 plt.savefig("%s.pdf" % filename)
504
505 def plot_hloops2(self, to_show=[58, 59, 61, 62],
506 filename='hloop-compare-3', **kwargs):
507 """Outputs hloop-compare-3.pdf (not used anymore)
508
509 Args:
510 to_show (TYPE, optional): DESCRIPTION. The default is a[85]+a[100].
511 filename (TYPE, optional): DESCRIPTION. The default is
512 'hloop-compare-3'.
513 **kwargs (TYPE): DESCRIPTION.
514
515 Returns:
516 None.:
517 """
518 fig, axes = plt.subplots(2, 2, figsize=(16, 12))
519 for i, nr in enumerate(to_show):
520 ax = axes[i // 2][i % 2]
521 self.m.style.set_style(size='talk', style='ticks',
522 grid=True, latex=True)
523 self.meas[nr] = Hloop(nr)
524 self.meas[nr].plot_strayfield(ax)
525 ax.set_xlim(-750, 750)
526 if nr == 58:
527 ax.set_ylim(self.meas[nr].down_fitted.Bx8.max(),
528 self.meas[nr].up_fitted.Bx8.min())
529 with sns.color_palette('deep'):
530 self.m.style.set_style(size='talk', style='ticks',
531 grid=True, latex=True)
532 if nr in [58, 59]:
533 bbox = (.13, .02, .35, .35)
534 else:
535 bbox = (.65, .02, .35, .35)
536 inset = inset_axes(ax, width='100%', height='90%',
537 bbox_to_anchor=bbox,
538 bbox_transform=ax.transAxes)
539 max_b = self.meas[nr].up.B.max()
540 inset.plot([-max_b, max_b], [0, 0], 'r--', linewidth=.75)
541 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
542 inset.plot(B_ext, B_stray)
543 inset.set_xlim(-750, 750)
544 if not (nr in [58, 59]):
545 inset.set_ylabel("$\Delta B_z\\;[\\mathrm{mT}]$")
546 inset.set_title("Difference")
547 inset.set_xticklabels([])
548 inset.set_xticks([])
549
550 plt.savefig("%s.pdf" % filename)
551
552 def plot_hloops3(self, to_show=[139, 140],
553 filename='hloop-repeat',
554 xlim=(-350, 350),
555 ylim=None,
556 inset_xlim=(-30, 30),
557 inset_ylim=(0.15, 1.25)
558 ):
559 """
560 Compares two measurements at the same Angle (repeated
561 measurements).
562
563 Args:
564 to_show (TYPE, optional): DESCRIPTION. The default is [139,140].
565 filename (TYPE, optional): DESCRIPTION. The default is
566 'hloop-repeat'.
567 xlim (TYPE, optional): DESCRIPTION. The default is (-350, 350).
568 ylim:
569 inset_xlim (TYPE, optional): DESCRIPTION. The default is (-30, 30).
570 inset_ylim (TYPE, optional): DESCRIPTION. The default is (.15,
571 1.25).
572
573 Returns:
574 None.:
575 """
576 self.m.style.set_style(size='talk', style='ticks', palette='Paired',
577 grid=True, latex=True)
578 fig, ax = plt.subplots(1, 1, figsize=(16, 12))
579 bmin, bmax = [], []
580 for i, nr in enumerate(to_show):
581 # ax = axes[i//2][i%2]
582 self.meas[nr] = Hloop(nr)
583 self.meas[nr].plot_strayfield(ax, nolegend=True)
584 ax.set_xlim(*xlim)
585 bmin1, bmax1 = self.meas[nr].get_bhminmax()
586 bmin.append(bmin1)
587 bmax.append(bmax1)
588
589 if ylim:
590 ax.set_ylim(*ylim)
591 else:
592 ax.set_ylim(np.min(bmin) - .05, np.max(bmax) + .05)
593 ax.set_title("M%s/%s: %s ($%s^\\circ$)" % (to_show[0], to_show[1],
594 self.meas[nr].info[
595 'Structure'].replace(
596 '_', r'\_'),
597 self.meas[nr].info[
598 'Angle']))
599 ax.legend(["M%d: Up" % to_show[0], "M%d: Down" % to_show[0],
600 "M%d: Up" % to_show[1], "M%d: Down" % to_show[1], ])
601 y1, y2 = inset_ylim[0], inset_ylim[1]
602 x1, x2 = inset_xlim[0], inset_xlim[1]
603 ax.fill([x1, x1, x2, x2], [y1, y2, y2, y1], 'blue', alpha=.1)
604
605 with sns.color_palette('bright'):
606 bbox = (.65, .055, .35, .3)
607 inset = inset_axes(ax, width='100%', height='100%',
608 bbox_to_anchor=bbox,
609 bbox_transform=ax.transAxes)
610 max_b = self.meas[nr].up.B.max()
611 for nr in to_show:
612 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield()
613 inset.plot(B_ext, B_stray)
614 inset.plot([-max_b, max_b], [0, 0], '--', linewidth=.75)
615 inset.set_xlim(*xlim)
616 inset.set_ylabel("$\Delta B_z\\;[\\mathrm{mT}]$")
617 inset.set_title("Difference")
618
619 with sns.color_palette('Paired'):
620 bbox = (.07, .45, .35, .35)
621 inset2 = inset_axes(ax, width='100%', height='100%',
622 bbox_to_anchor=bbox,
623 bbox_transform=ax.transAxes)
624 for nr in to_show:
625 self.meas[nr].plot_strayfield(inset2, nolegend=True)
626 inset2.set_xlim(*inset_xlim)
627 inset2.set_ylim(*inset_ylim)
628 inset2.set_title("")
629 inset2.set_ylabel('')
630
631 with sns.color_palette('Paired'):
632 bbox = (.65, .43, .35, .3)
633 inset3 = inset_axes(ax, width='100%', height='100%',
634 bbox_to_anchor=bbox,
635 bbox_transform=ax.transAxes)
636 self.plot_hloop_gradient3(inset3, nr=to_show,
637 limit=inset_xlim[1], )
638 # inset3.set_xticklabels([''])
639 inset3.set_xlim(*inset_xlim)
640 inset3.set_xlabel('')
641 inset3.legend_.remove()
642
643 plt.savefig("%s.pdf" % filename)
644
645 def plot_hloops_compare_90(self, to_show=[414, 415],
646 structure='plusses',
647 filename='hloop-repeat-414',
648 xlim=(-750, 750),
649 ylim=(-.36, .76),
650 inset_xlim=(-225, 225),
651 inset_ylim=(-.35, .75),
652 insets=[((.05, .05, .35, .35), ((-225, 225), (-.35, .75), 'blue', .1))],
653 legend_loc='upper right',
654 nograd=False,
655 nodiff=False,
656 figsize=(16, 12),
657 ):
658 """
659 Compares multiple measurements at 90 deg (repeated
660 measurements).
661
662 Args:
663 to_show (TYPE, optional): DESCRIPTION. The default is [414,415].
664 structure:
665 filename (TYPE, optional): DESCRIPTION. The default is
666 'hloop-repeat-414'.
667 xlim (TYPE, optional): DESCRIPTION. The default is (-750, 750).
668 ylim:
669 inset_xlim (TYPE, optional): DESCRIPTION. The default is (-100,
670 100).
671 inset_ylim (TYPE, optional): DESCRIPTION. The default is (-1.25,
672 .75).
673 legend_loc:
674 nograd:
675
676 Returns:
677 None.:
678 """
679 self.m.style.set_style(size='talk', style='ticks',
680 palette='Paired',
681 grid=True, latex=True)
682 fig, ax = plt.subplots(1, 1, figsize=figsize)
683 legend = []
684 if structure == 'plusses':
685 column = 'Bx8'
686 elif structure == 'crosses':
687 column = 'Bx9'
688
689 for i, nr in enumerate(to_show):
690 # ax = axes[i//2][i%2]
691 self.meas[nr] = Hloop(nr) # Load measurement
692 self.meas[nr].remove_zero() # Remove measurement Errors
693
694 self.meas[nr].plot_strayfield(ax, nolegend=True,
695 show_plusses=(
696 structure == 'plusses'),
697 show_crosses=(
698 structure == 'crosses'))
699 ax.set_xlim(*xlim)
700
701 legend += ["m%d: Up" % nr, "m%d: Down" % nr]
702
703 m_numbers = '/'.join(map(str, to_show))
704
705 if ylim:
706 ax.set_ylim(*ylim)
707
708 self.m.style.set_style(size='talk', style='ticks', palette='Paired',
709 grid=True, latex=True)
710 ax.set_title("\\textbf{%s} ($%s^{\\circ}$)" % (
711 structure.capitalize(),
712 self.meas[nr].info['Angle']))
713
714 self.m.style.set_style(size='notebook', style='ticks',
715 palette='Paired',
716 grid=True, latex=True)
717 all_insets = []
718 if not nodiff:
719 with sns.color_palette('bright'):
720 bbox = (.7, .06, .3, .3)
721 inset = inset_axes(ax, width='100%', height='100%',
722 bbox_to_anchor=bbox,
723 bbox_transform=ax.transAxes)
724 all_insets.append(inset)
725 max_b = self.meas[nr].up.B.max()
726 for i, nr in enumerate(to_show):
727 if structure == 'plusses':
728 B_ext, B_stray = self.meas[nr].get_downminusup_strayfield(
729 fitted_data=True)
730 else:
731 up = self.meas[nr].up_fitted
732 down = self.meas[nr].down_fitted
733 f_up = scipy.interpolate.interp1d(up.B, up.Bx9)
734 f_down = scipy.interpolate.interp1d(down.B, down.Bx9)
735 B_ext = np.linspace(up.B.min(), up.B.max(), int(1e5))
736 B_stray = f_down(B_ext) - f_up(B_ext)
737 if i == 3:
738 inset.plot(B_ext, B_stray, color='orange')
739 else:
740 inset.plot(B_ext, B_stray)
741 if i == 0:
742 inset.plot([-max_b, max_b], [0, 0], '--', linewidth=.75)
743
744 inset.set_xlim(*xlim)
745 inset.set_ylabel("$\Delta B_z\\;[\\mathrm{mT}]$")
746 inset.set_title("Difference")
747
748 subfig = ['d', 'b', 'a', 'c']
749 for bbox, (inset_xlim, inset_ylim, color, alpha) in insets:
750 with sns.color_palette('Paired'):
751 inset2 = inset_axes(ax, width='100%', height='100%',
752 bbox_to_anchor=bbox,
753 bbox_transform=ax.transAxes)
754 all_insets.append(inset2)
755 for i, nr in enumerate(to_show):
756 inset2.plot(self.meas[nr].up_fitted.B,
757 self.meas[nr].up_fitted[column])
758 inset2.plot(self.meas[nr].down_fitted.B,
759 self.meas[nr].down_fitted[column])
760 inset2.set_xlim(*inset_xlim)
761 inset2.set_ylim(*inset_ylim)
762
763 (ann_x, ann_xx), (ann_yy, ann_y) = inset2.get_xlim(), inset2.get_ylim()
764 ann_x += (ann_xx - ann_x) * .05
765 ann_y -= (ann_y - ann_yy) * .20
766 inset2.text(x=ann_x, y=ann_y, s=subfig.pop(),
767 fontdict=dict(fontweight='bold', fontsize=24),
768 bbox=dict(boxstyle="round,pad=0.1", fc='#ccf', ec="b", lw=1))
769
770 # Draw Area into big plot
771 y1, y2 = inset_ylim[0], inset_ylim[1]
772 x1, x2 = inset_xlim[0], inset_xlim[1]
773 coords = [x1, x1, x2, x2], [y1, y2, y2, y1]
774 inset2.fill(*coords, color, alpha=alpha)
775 ax.fill(*coords, color, alpha=alpha)
776
777 if not nograd:
778 with sns.color_palette('Paired'):
779 bbox = (.08, .67, .27, .3)
780 inset3 = inset_axes(ax, width='100%', height='100%',
781 bbox_to_anchor=bbox,
782 bbox_transform=ax.transAxes)
783 all_insets.append(inset3)
784 c = column.replace('B', 'V')
785 self.plot_hloop_gradient3(inset3, to_show, limit=inset_xlim[1],
786 column=c)
787 inset3.set_xlim(*inset_xlim)
788 inset3.set_xlabel('')
789 inset3.legend_.remove()
790
791 sns.set('notebook')
792 ax.legend(legend, loc=legend_loc, ncol=int(len(to_show) / 2))
793
794 plt.savefig("%s.png" % filename)
795
796 return fig, (ax, all_insets)
797
798 def plot_hloops4(self, filename='hloop-parallel',
799 figtitle="M57: $90^\\circ$ Parallel measurement",
800 **kwargs):
801 """Plots a single measurement (default: 90deg parallel)
802
803 Args:
804 filename (TYPE, optional): DESCRIPTION. The default is
805 'hloop-parallel'.
806 figtitle (TYPE, optional): DESCRIPTION. The default is "M57:
807 $90^\circ$ Parallel measurement".
808 **kwargs (TYPE): DESCRIPTION.
809
810 Returns:
811 None.:
812 """
813 fig, ax = plt.subplots(1, 1, figsize=(16, 12))
814 self.m.style.set_style(size='talk', style='ticks', palette='Paired',
815 grid=True, latex=True)
816 # self.mset_factor(-1)
817 # self.mfactor = 1
818
819 self.m.plot_strayfield(ax, figtitle=figtitle,
820 show_crosses=False)
821 self.m.up_fitted.Bx9 += .25
822 self.m.down_fitted.Bx9 += .25
823 ax.plot(self.m.up_fitted.B, self.m.up_fitted.Bx9,
824 label='Crosses: Up (fitted)')
825 ax.plot(self.m.down_fitted.B, self.m.down_fitted.Bx9,
826 label='Crosses: Down (fitted)')
827 ax.set_xlim(-750, 750)
828 if kwargs.get('ylim'):
829 ax.set_ylim(*kwargs.get('ylim'))
830 else:
831 ax.set_ylim(-.8, 1.8)
832 ax.legend(loc='best')
833
834 with sns.color_palette('bright'):
835 bbox = (.06, .46, .35, .23)
836 inset = inset_axes(ax, width='100%', height='100%',
837 bbox_to_anchor=bbox,
838 bbox_transform=ax.transAxes)
839 max_b = self.m.up.B.max()
840 B_ext, B_stray = self.m.get_downminusup_strayfield()
841 inset.plot([-max_b, max_b], [0, 0], '--', color='orange',
842 linewidth=.75)
843 inset.plot(B_ext, B_stray)
844 inset.set_xlim(-750, 750)
845 inset.set_title("Difference Plusses")
846
847 bbox = (.06, .06, .35, .22)
848 inset2 = inset_axes(ax, width='100%', height='100%',
849 bbox_to_anchor=bbox,
850 bbox_transform=ax.transAxes)
851 f_up = scipy.interpolate.interp1d(self.m.up.B, self.m.up.Bx9)
852 f_down = scipy.interpolate.interp1d(self.m.down.B, self.m.down.Bx9)
853 B = np.linspace(self.m.up.B.min(), self.m.up.B.max(), int(1e5))
854 downminusup = f_down(B) - f_up(B)
855 inset2.plot([-max_b, max_b], [0, 0], '--', color='orange',
856 linewidth=.75)
857 inset2.plot(B, downminusup, color='green')
858 inset2.set_xlim(-750, 750)
859 inset2.set_title("Difference Crosses")
860
861 plt.savefig("%s.pdf" % filename)
862
863 def plot_cubes_trees(self):
864 """Plots difference between RAW data from Cubes and Trees (1st
865 Generation)
866
867 Returns:
868 None.:
869 """
870 self.m.style.set_style(size='talk', style='ticks', palette='deep',
871 grid=True, latex=True)
872 file_list = ['data/Data/%sdeg_%s' % (angle, struct) for
873 angle in [90, -90] for
874 struct in ['cubes', 'Trees']
875 ]
876
877 fig, axes = plt.subplots(2, 2, figsize=(16, 12))
878 comp = {}
879 for i, f in enumerate(file_list):
880 ax = axes[i // 2][i % 2]
881 load_files = ['%s_%s.dat' % (f, direction) for
882 direction in ['a', 'b']]
883 comp[i] = Hloop(0, file_list=load_files)
884 comp[i].set_factor(1e6)
885
886 ax.plot(comp[i].up.B, comp[i].up.Vx8, label='Up')
887 ax.plot(comp[i].down.B, comp[i].down.Vx8, label='Down')
888
889 ax.set_ylabel("$V_x [\\mathrm{\\mu V}]$")
890 ax.set_xlabel("$\\mu_0 H_{ext}$ $[\\mathrm{mT}]$")
891 bmin, bmax, vmin, vmax = comp[i].get_minmax()
892 ax.set_xlim(bmin, bmax)
893
894 if f.find('cubes') > 0:
895 struct = 'Cubes'
896 else:
897 struct = "Trees"
898
899 ax.set_title("%s ($%s^\\circ$)" % (struct, f[10:13].strip('d')))
900
901 with sns.color_palette('bright'):
902 if i % 2 == 1:
903 bbox = (.69, .7, .3, .3)
904 else:
905 bbox = (.12, .7, .29, .3)
906 inset = inset_axes(ax, width='100%', height='100%',
907 bbox_to_anchor=bbox,
908 bbox_transform=ax.transAxes)
909 max_b = self.m.up.B.max()
910 B_ext, B_stray = comp[i].get_downminusup()
911 inset.plot([-max_b, max_b], [0, 0], '--', color='orange',
912 linewidth=.75)
913 inset.plot(B_ext, B_stray)
914 inset.set_xlim(bmin, bmax)
915 # inset.set_title("Difference")
916
917 ax.legend(loc='lower left')
918
919 plt.savefig("compare-cubes-trees.pdf")
920
921 def plot_hloop_gradient(self, limit=50, filename='hloop-gradient'):
922 """Plotting the gradient along the
923
924 Args:
925 limit:
926 filename:
927
928 Returns:
929 None.:
930 """
931 self.m.style.set_style(default=True, grid=True,
932 style='ticks',
933 size='talk',
934 palette='deep',
935 latex=True, )
936
937 grad1 = [np.divide(np.diff(self.m.up.Vx8), np.diff(self.m.up.Time)),
938 np.divide(np.diff(self.m.up.Vx9), np.diff(self.m.up.Time))]
939 grad12 = [
940 np.divide(np.diff(self.m.down.Vx8), np.diff(self.m.down.Time)),
941 np.divide(np.diff(self.m.down.Vx9), np.diff(self.m.down.Time))]
942
943 fig, axes = plt.subplots(2, 1, sharex=True)
944 for i in range(2):
945 ax = axes[i]
946 g = grad1[i]
947 g2 = grad12[i]
948
949 if i == 0:
950 add = 'Plusses'
951 else:
952 add = 'Crosses'
953
954 ax.set_title('M57: Gradient (%s)' % add)
955 x = self.m.up[:-1]
956 ax.plot(x.B[x.B.abs() < limit],
957 g[x.B.abs() < limit] * 1e6,
958 label='Up (%s)' % add)
959 x = self.m.down[:-1]
960 ax.plot(x.B[x.B.abs() < limit],
961 g2[x.B.abs() < limit] * 1e6,
962 label='Down (%s)' % add)
963 ax.set_ylabel('$dV_H/dt\; [\\mu\\mathrm{V/s}]$')
964 ax.set_xlabel('$\\mu_0 H_{ext} [\\mathrm{mT}]$')
965
966 plt.savefig('%s.pdf' % filename)
967
968 def plot_hloop_gradient2(self, limit=50, filename='hloop-gradient'):
969 """Plotting the gradient along the
970
971 Args:
972 limit:
973 filename:
974
975 Returns:
976 None.:
977 """
978 self.m.style.set_style(default=True, grid=True,
979 style='ticks',
980 size='talk',
981 palette='deep',
982 latex=True, )
983
984 grad = [np.gradient(self.m.up.Vx8, np.diff(self.m.up.Time).mean()),
985 np.gradient(self.m.down.Vx8, np.diff(self.m.down.Time).mean())]
986
987 fig, ax = plt.subplots()
988 ax.set_title('M%s: Gradient (%s)' % (self.m.self.measurement_number,
989 self.m.info['Structure']))
990 for i in range(2):
991 g = grad[i]
992
993 if i == 0:
994 x = self.m.up
995 add = 'Up'
996 else:
997 x = self.m.down
998 add = 'Down'
999
1000 ax.plot(x.B[x.B.abs() < limit],
1001 g[x.B.abs() < limit] * 1e6,
1002 label='%s' % add)
1003 ax.set_ylabel('$dV_H/dt\; [\\mu\\mathrm{V/s}]$')
1004 ax.legend(loc='best')
1005 ax.set_xlabel('$\\mu_0 H_{ext} [\\mathrm{mT}]$')
1006
1007 plt.savefig('%s.pdf' % filename)
1008
1009 def plot_hloop_gradient3(self, ax, to_show=[139, 140], limit=50,
1010 column='Vx8'):
1011 """Plotting the gradient along the
1012
1013 Args:
1014 ax:
1015 to_show:
1016 limit:
1017 column:
1018
1019 Returns:
1020 None.:
1021 """
1022 # self.m.style.set_style(default=True, grid=True,
1023 # style='ticks',
1024 # size='talk',
1025 # palette='Paired',
1026 # latex=True,)
1027
1028 limit = np.abs(limit)
1029
1030 ax.set_title('Gradient')
1031 for nr in to_show:
1032 self.m = self.meas[nr]
1033 c = column
1034 grad = [np.divide(np.diff(self.m.up[c]), np.diff(self.m.up.Time)),
1035 np.divide(np.diff(self.m.down[c]),
1036 np.diff(self.m.down.Time))]
1037 for i in range(2):
1038 g = grad[i]
1039
1040 if i == 0:
1041 x = self.m.up[:-1]
1042 direction = 'Up'
1043 else:
1044 x = self.m.down[:-1]
1045 direction = 'Down'
1046
1047 ax.plot(x.B[x.B.abs() < limit],
1048 g[x.B.abs() < limit] * 1e6,
1049 label='M%s: %s' % (nr, direction))
1050
1051 # ax.set_xlim(-limit, limit)
1052 ax.set_ylabel('$dV_H/dt\; [\\mu\\mathrm{V/s}]$')
1053 ax.legend(loc='best')
1054 ax.set_xlabel('$\\mu_0 H_{ext} [\\mathrm{mT}]$')
1055
1056 def plot_hloop_temp(self):
1057 """Plots the Temperature of a Hloop measurement.
1058
1059 Returns:
1060 None.:
1061 """
1062
1063 self.m.style.set_style(default=True, grid=True,
1064 style='ticks',
1065 size='talk',
1066 palette='deep',
1067 latex=True, )
1068
1069 fig, ax = plt.subplots()
1070 ax.set_title(
1071 'M%s: Sample Temperature' % self.m[self.measurement_number])
1072
1073 ax.plot(self.m.up.B, (self.m.up['Sample Temp']), '-', label='Up')
1074 ax.plot(self.m.down.B, (self.m.down['Sample Temp']), '-', label='Down')
1075
1076 ax.set_xlabel('$\\mu_0 H_{ext} [\\mathrm{mT}]$')
1077 ax.set_ylabel('Temperature $[\\mathrm{mK}]$')
1078 ax.legend(loc='best')
1079
1080 plt.savefig('hloop-90-temp.pdf')
1081
1082 #### Plot Static Fields
1083 def plot_static_fields(self, filename='static-field',
1084 show_inset=False):
1085 """Plot the Signal Analyzer (SA) Data for static fields (not sweeping)
1086
1087 Returns:
1088 None.:
1089 """
1090 tmp = {
1091 'Plusses (25 mT)': 360,
1092 'Plusses (-25 mT)': 361,
1093 'Plusses (-9.4 mT)': 282,
1094 'Crosses (-9.4 mT)': 283,
1095 'Plusses (0 T)': 281,
1096 'Crosses (0 T)': 280,
1097 'Empty (0 T)': 310,
1098 # 'Crosses (250 mT)': 315,
1099 }
1100 lofm = {}
1101 for i, j in tmp.items():
1102 lofm.update({j: ['%s' % i, {}]})
1103
1104 self.eva.style.set_style(default=True, grid=True,
1105 style='ticks',
1106 size='talk',
1107 palette='Paired',
1108 latex=True, )
1109
1110 fig, ax = self.eva.plot(lofm,
1111 plot_settings=dict(
1112 title='($90^\\circ$) Static Field',
1113 xlim=(2e-2, 5e0),
1114 ylim=(1e-7, 1e-3),
1115 ),
1116 f_settings=dict(
1117 ymin=5e-5),
1118 f2_settings=dict(disable=True),
1119 )
1120
1121 # ax = plt.gca()
1122 if show_inset:
1123 with sns.color_palette('deep'):
1124 inset = inset_axes(ax, width='100%', height='100%',
1125 bbox_to_anchor=(.25, .58, .45, .4),
1126 bbox_transform=ax.transAxes)
1127 self.m.style.set_style(default=True, grid=True,
1128 style='ticks',
1129 size='talk',
1130 palette='Paired',
1131 latex=True)
1132 self.m.plot_strayfield(inset, 'Strayfield Plusses', nolegend=True)
1133 inset.legend(['Up', # ' ($-M_S \\rightarrow +M_S$)',
1134 'Down']) # ' ($+M_S \\rightarrow -M_S$)'])
1135 inset.grid(b=True, alpha=.4)
1136 inset.set_xlim(-50, 50)
1137 inset.set_ylim(-.65, .45)
1138
1139 def a(x):
1140 return self.m.down.iloc[
1141 ((self.m.down.B - x) ** 2).idxmin()]['Bx8']
1142
1143 def b(x):
1144 return self.m.up.iloc[((self.m.up.B - x) ** 2).idxmin()]['Bx8']
1145
1146 inset.plot((-25, -25), (a(-25), a(-25)), 'o', color='#000099',
1147 alpha=.6)
1148 inset.plot([25, 25], [b(25), b(25)], 'o', color='#9999FF',
1149 alpha=.6)
1150 inset.plot([-9.4, -9.4], [a(-9.4), a(-9.4)], 'o', color='green',
1151 alpha=.6)
1152 inset.plot([0], [a(0)], 'o', color='#FF3333', alpha=.8)
1153
1154 # ana.set_relative_yticks(inset)
1155 inset.set_xticks(np.arange(-50, 51, 25))
1156
1157 plt.savefig('{}.pdf'.format(filename))
1158
1159 #### Sweeping Field
1160 def first_sweeps(self, add=False, filename='vacant-sweeps',
1161 show_inset=False):
1162 """First field sweeps (plus minus 50/75/100/etc.)
1163
1164 Args:
1165 add: adding more data
1166 filename: saving as pdf (default: vacant-sweeps)
1167
1168 Returns:
1169 vacant-sweeps.pdf:
1170 """
1171 self.m.style.set_style(default=True, grid=True,
1172 size='talk', style='ticks', latex=True,
1173 palette='deep')
1174
1175 lofm = {}
1176 to_show = {
1177 # 382: [(-25,25), 'Plusses', 5, {}],
1178 362: [(-50, 50), 'Plusses', 2, {}],
1179 363: [(-75, 75), 'Plusses', 2, {}],
1180 364: [(-100, 100), 'Plusses', 2, {}],
1181 366: [(-125, 125), 'Plusses', 2, {}],
1182 365: [(-150, 150), 'Plusses', 2, {}],
1183 # 352: [("-M_s \\rightarrow -25",25), 'Plusses', .1],
1184 }
1185 if add:
1186 to_show.update({
1187 336: [(-500, 500), 'Plusses', 9.4, {}],
1188 331: [('\\mathrm{C}\\,{%s}' % -100, 100), 'Crosses', 9.4,
1189 {'linestyle': '--'}],
1190 332: [('\\mathrm{C}\\,{%s}' % -300, 300), 'Crosses', 9.4,
1191 {'linestyle': '--'}],
1192 333: [('\\mathrm{C}\\,{%s}' % -750, 750), 'Crosses', 9.4,
1193 {'linestyle': '--'}],
1194 })
1195
1196 for nr, content in to_show.items():
1197 lofm[nr] = ["$%s \\rightarrow %s\\;\\mathrm{mT}$" % (
1198 content[0][0],
1199 content[0][1],
1200 ), content[3]]
1201
1202 fig, ax = self.eva.plot(lofm,
1203 # fit_range=(2e-2, 9e-1),
1204 # show_fit=True,
1205 plot_settings=dict(
1206 title='\\Huge \\textbf{b) Sweeping Inside the Hysteresis (Plusses)}',
1207 xlim=(1e-2, 1e0),
1208 ylim=(4e-7, 7e-2)),
1209 f_settings=dict(
1210 disable=True,
1211 xmin=5e-2,
1212 ymin=1e-5),
1213 f2_settings=dict(
1214 xmin=2e-2,
1215 ymin=1e-2,
1216 ),
1217 )
1218
1219 if show_inset and not add: # Inset with Strayfield
1220 with sns.color_palette('deep'):
1221 inset = inset_axes(ax, width='100%', height='100%',
1222 bbox_to_anchor=(.74, .45, .25, .25),
1223 bbox_transform=ax.transAxes)
1224 # Plotting Lines for each measurement
1225 y1, y2 = -1, 1
1226 for nr, content in to_show.items():
1227 x1 = content[0][1]
1228 inset.plot([x1, x1, -x1, -x1], [y1, y2, y2, y1])
1229
1230 self.m.plot_strayfield(inset, 'Strayfield Plusses',
1231 show_plusses=False,
1232 show_crosses=False,
1233 nolegend=True)
1234
1235 inset.plot(self.m.up_fitted.B, self.m.up_fitted.Bx8, '-',
1236 color=(76 / 255, 114 / 255, 176 / 255),
1237 linewidth=3, label='Up')
1238 inset.plot(self.m.down_fitted.B, self.m.down_fitted.Bx8, 'r-',
1239 color=(221 / 255, 132 / 255, 82 / 255),
1240 linewidth=1.5, label='Down')
1241
1242 inset.legend(['Up', # ($-M_S \\rightarrow +M_S$)',
1243 'Down']) # ($+M_S \\rightarrow -M_S$)'])
1244 inset.grid(b=True, alpha=.4)
1245 inset.set_xlim(-200, 200)
1246 inset.set_ylim(-.65, .45)
1247 inset.set_xticks([-200 + 50 * _ for _ in range(9)])
1248 inset.set_xticks([-175 + 50 * _ for _ in range(8)], minor=True)
1249 inset.set_yticks([-0.25 + .5 * _ for _ in range(2)],
1250 minor=True)
1251 inset.grid(b=True, which='minor', color='#cccccc',
1252 linestyle='-.', alpha=.3)
1253
1254 # X-Ticks (labels)
1255 xt_labels = []
1256 for i in [-200 + 50 * _ for _ in range(9)]:
1257 if i % 100 == 0:
1258 xt_labels += ['$%s$' % i]
1259 else:
1260 xt_labels += ['']
1261 inset.set_xticklabels(xt_labels)
1262
1263 # Inset showing fitted data
1264 with sns.color_palette("deep"):
1265 inset2 = inset_axes(ax, width='100%', height='100%',
1266 bbox_to_anchor=(.44, .75, .3, .25),
1267 bbox_transform=ax.transAxes)
1268 inset3 = inset_axes(ax, width='100%', height='100%',
1269 bbox_to_anchor=(.09, .09, .3, .25),
1270 bbox_transform=ax.transAxes)
1271
1272 i2data = {}
1273 for nr, content in to_show.items():
1274 intercept, slope = self.eva[nr].fit(fit_range=(1e-2, 2e-1))
1275 voltage = content[0][1]
1276 i2data[nr] = {'end field': voltage,
1277 'alpha': -slope,
1278 'amplitude': 10 ** intercept}
1279
1280 inset2.plot(voltage, 10 ** intercept, 'o')
1281 inset3.plot(voltage, -slope, 'o')
1282
1283 inset2.set_xlabel('End field [$\mu_0 \mathrm{H_{ext}}$]')
1284 inset2.set_ylabel('$S_{V_H} (f=1\\;$Hz$)$')
1285 inset2.set_yscale('log')
1286 inset2.set_yticks([8e-7, 9e-7, 1e-6, 2e-6])
1287
1288 inset3.set_xlabel('End field [$\mu_0 \mathrm{H_{ext}}$]')
1289 inset3.set_ylabel('$\\alpha$')
1290
1291 plt.savefig('%s.pdf' % filename)
1292 return i2data
1293
1294 def compare_voltages(self, filename='compare-voltages',
1295 show_inset=False):
1296 """Compare different applied Voltages.
1297
1298 Returns:
1299 compare-voltages.pdf:
1300 """
1301 self.m.style.set_style(default=True, grid=True,
1302 size='talk', style='ticks', latex=True,
1303 palette='deep')
1304
1305 lofm = {}
1306 to_show = {}
1307 for i in range(1, 6):
1308 to_show[i + 377] = i
1309
1310 for nr, content in to_show.items():
1311 lofm[nr] = ["$%s\\;\\mu\\mathrm{A}$" % (
1312 content
1313 ), {}]
1314
1315 fig, ax = self.eva.plot(lofm,
1316 # fit_range=(2e-2, 7e-1),
1317 # show_fit=True,
1318 plot_settings=dict(
1319 title='Current Amplitudes (SR785)',
1320 xlim=(1e-2, 1e0),
1321 ylim=(4e-7, 7e-2)),
1322 f_settings=dict(disable=True,
1323 xmin=5e-2,
1324 ymin=1e-5),
1325 f2_settings=dict(
1326 xmin=1.5e-1,
1327 ymin=2e-3,
1328 ),
1329 )
1330
1331 if show_inset:
1332 # Inset with Strayfield
1333 with sns.color_palette('deep'):
1334 inset = inset_axes(ax, width='100%', height='100%',
1335 bbox_to_anchor=(.07, .38, .26, .26),
1336 bbox_transform=ax.transAxes)
1337 self.m.plot_strayfield(inset, 'Strayfield Plusses',
1338 nolegend=True, )
1339 inset.legend(['Up', # ($-M_S \\rightarrow +M_S$)',
1340 'Down']) # ($+M_S \\rightarrow -M_S$)'])
1341 inset.grid(b=True, alpha=.4)
1342 inset.set_xlim(-50, 50)
1343 inset.set_ylim(-.65, .45)
1344 inset.set_xticks([-50 + 25 * _ for _ in range(5)])
1345 # inset.set_xticks([-45+10*_ for _ in range(10)], minor=True)
1346 inset.grid(b=True, which='minor', color='#cccccc',
1347 linestyle='-.', alpha=.3)
1348 # inset.set_xlabel('')
1349 inset.set_ylabel('')
1350
1351 y1, y2 = -1, 2
1352 inset.fill([-25, -25, 25, 25], [y1, y2, y2, y1], 'blue', alpha=.1)
1353 inset.annotate("", xy=(25, -.35), xytext=(-25, -.2),
1354 arrowprops=dict(arrowstyle="->", color='blue'))
1355
1356 # Inset showing fitted data
1357 with sns.color_palette("deep"):
1358 inset2 = inset_axes(ax, width='100%', height='100%',
1359 bbox_to_anchor=(.54, .75, .3, .25),
1360 bbox_transform=ax.transAxes)
1361 inset3 = inset_axes(ax, width='100%', height='100%',
1362 bbox_to_anchor=(.3, .09, .3, .25),
1363 bbox_transform=ax.transAxes)
1364
1365 i2data = {}
1366 for nr, content in to_show.items():
1367 intercept, slope = self.eva[nr].fit(fit_range=(2e-2, 7e-1))
1368 voltage = content
1369 i2data[nr] = {'voltage': voltage,
1370 'alpha': -slope,
1371 'amplitude': 10**intercept}
1372
1373 inset2.plot(voltage, 10 ** intercept, 'o')
1374 inset3.plot(voltage, -slope, 'o')
1375
1376 inset2.set_xlabel('Current [$\\mu\\mathrm{A}$]')
1377 inset2.set_ylabel('$S_{V_H} (f=1\\;$Hz$)$')
1378 inset2.set_yscale('log')
1379
1380 inset3.set_xlabel('Current [$\\mu\\mathrm{A}$]')
1381 inset3.set_ylabel('$\\alpha$')
1382
1383 plt.savefig('{}.pdf'.format(filename))
1384 return i2data
1385
1386 def compare_sweeprates(self, filename='compare-sweeprates',
1387 show_inset = False):
1388 """Compare different Sweeprates, fits the data and
1389
1390 Returns:
1391 None.:
1392 """
1393 self.m.style.set_style(default=True, grid=True,
1394 size='talk', style='ticks', latex=True,
1395 palette='deep')
1396
1397 lofm = {}
1398 to_show = {
1399 382: [("-M_s \\rightarrow -25", 25), 'Plusses', 5],
1400 354: [("-M_s \\rightarrow -25", 25), 'Plusses', 2],
1401 351: [("-M_s \\rightarrow -25", 25), 'Plusses', 1],
1402 355: [("-M_s \\rightarrow -25", 25), 'Plusses', .5],
1403 353: [("-M_s \\rightarrow -25", 25), 'Plusses', .25],
1404 352: [("-M_s \\rightarrow -25", 25), 'Plusses', .1],
1405 }
1406
1407 for nr, content in to_show.items():
1408 lofm[nr] = ["$%s\\;\\frac{\\mathrm{mT}}{\\mathrm{min}}$" % (
1409 content[2],
1410 ), {}]
1411
1412 fig, ax = self.eva.plot(lofm,
1413 # fit_range=(2e-2, 5e-1),
1414 # show_fit=True,
1415 plot_settings=dict(
1416 title='Sweeprates (SR785)',
1417 xlim=(1e-2, 1.6e0),
1418 ylim=(4e-7, 5e-2)),
1419 f_settings=dict(
1420 xmin=5e-2,
1421 ymin=1e-5,
1422 disable=True),
1423 f2_settings=dict(
1424 xmin=1.5e-1,),
1425 )
1426
1427 ax = plt.gca()
1428 if show_inset:
1429 # Inset with Strayfield
1430 with sns.color_palette('deep'):
1431 inset = inset_axes(ax, width='100%', height='90%',
1432 bbox_to_anchor=(.1, .06, .3, .33),
1433 bbox_transform=ax.transAxes)
1434 self.m.plot_strayfield(inset, 'Strayfield Plusses',
1435 nolegend=True, )
1436 inset.legend(['Up', # ($-M_S \\rightarrow +M_S$)',
1437 'Down']) # ($+M_S \\rightarrow -M_S$)'])
1438 inset.grid(b=True, alpha=.4)
1439 inset.set_xlim(-50, 50)
1440 inset.set_ylim(-.65, .45)
1441 inset.set_xticks([-50 + 25 * _ for _ in range(5)])
1442
1443 y1, y2 = -1, 2
1444 inset.fill([-25, -25, 25, 25], [y1, y2, y2, y1], 'blue', alpha=.1)
1445 inset.annotate("", xy=(25, -.35), xytext=(-25, -.2),
1446 arrowprops=dict(arrowstyle="->", color='blue'))
1447
1448 # Inset showing fitted data
1449 with sns.color_palette("deep"):
1450 inset2 = inset_axes(ax, width='100%', height='100%',
1451 bbox_to_anchor=(.5, .75, .3, .25),
1452 bbox_transform=ax.transAxes)
1453
1454 i2data = {}
1455 for nr, content in to_show.items():
1456 intercept, slope = self.eva[nr].fit(fit_range=(2e-2, 7e-1))
1457 sweep_rate = content[2]
1458 i2data[nr] = {'sweeprate': sweep_rate,
1459 'alpha': -slope,
1460 'amplitude': 10**intercept}
1461
1462 inset2.plot(sweep_rate, 10 ** intercept, 'o')
1463
1464 inset2.set_xlabel(
1465 'Sweeprate $\\frac{d}{dt} \mu_0 H_{ext} \\left[\\frac{\\mathrm{mT}}{\\mathrm{min}}\\right]$')
1466 inset2.set_ylabel('$S_{V_H} (f=1\\;$Hz$)$')
1467 inset2.set_yscale('log')
1468
1469 # Only save if necessary
1470 plt.savefig('{}.pdf'.format(filename))
1471 return i2data
1472
1473 def fast_sweeprate(self, show_numbers=[320, 325, 323, 329, ], xlim=None,
1474 filename='FastSR-1', pos2=False,
1475 title='\\Huge \\textbf{a) Large Field Sweeps}',
1476 show_inset=False):
1477 """anas Fast Sweeps over large sweep ranges (1T)
1478
1479 Args:
1480 show_numbers:
1481 xlim:
1482 filename (TYPE, optional): DESCRIPTION. The default is 'FastSR-1'.
1483 pos2:
1484 title:
1485
1486 Returns:
1487 None.:
1488 """
1489 to_show = {
1490 320: [(2, 1), 'Empty', 9.4],
1491 321: [(2, 1), 'Empty', 4.7],
1492 325: [(2, 1), 'Crosses', 9.4],
1493 326: [(2, 1), 'Crosses', 4.7],
1494 323: [(1, -1), 'Empty', 9.4],
1495 324: [(1, -1), 'Empty', 4.7],
1496 329: [(1, -1), 'Crosses', 9.4],
1497 322: [(.5, -.5), 'Empty', 9.4],
1498 327: [(.5, -.5), 'Crosses', 9.4],
1499 328: [(.5, -.5), 'Crosses', 4.7],
1500 336: [(.5, -.5), 'Plusses', 9.4],
1501 333: [(.75, -.75), 'Crosses', 9.4],
1502 332: [(.3, -.3), 'Crosses', 9.4],
1503 331: [(.1, -.1), 'Crosses', 9.4],
1504 }
1505
1506 lofm = {}
1507 for nr, content in to_show.items():
1508 if content[2] == 4.7:
1509 continue
1510 if not (nr in show_numbers):
1511 continue
1512 leg_str = '\\textbf{%s} $\\;\\mathbf{%s \\rightarrow %s\\,\\mathrm{T}}$'
1513 lofm[nr] = leg_str % (
1514 content[1], # Structure
1515 content[0][0],# Field from
1516 content[0][1],# Field to
1517 # content[2], # Sweeprate
1518 )
1519
1520 self.m.style.set_style(default=True, grid=True, size='talk',
1521 style='ticks',
1522 latex=True, palette='Paired')
1523 fig, ax = plt.subplots(figsize=(16, 12))
1524 for i, j in lofm.items():
1525 label = 'm%s: %s' % (i, j)
1526 if False:
1527 self.eva[i].plot(label=label, ax=ax, color='orange',
1528 dont_set_plot_settings=True)
1529 else:
1530 self.eva[i].plot(label=label, ax=ax,
1531 dont_set_plot_settings=True)
1532
1533 self.eva[i]._set_plot_settings(xlim=(1e-2, 1.6e0), ylim=(1e-7, 5e-2),
1534 title=title,
1535 grid=dict(which='minor',
1536 color='#ffff99',
1537 linestyle='--', alpha=.5))
1538 self.eva[i]._draw_oneoverf(ax, ymin=1e-2, xmin=3e-2, alpha=2,
1539 plot_style='b--', an_color='blue')
1540 # self.eva[i]._draw_oneoverf(ax, xmin=1e-2, ymin=1e-6)
1541 plt.grid(b=True, which='minor', color='#cccccc', linestyle='-.',
1542 alpha=.3)
1543
1544 if show_inset:
1545 # Inset with Strayfield
1546 with sns.color_palette('deep'):
1547 if pos2:
1548 bbox = (.1, .1, .3, .25)
1549 else:
1550 bbox = (.32, .72, .3, .25)
1551 inset = inset_axes(ax, width='100%', height='100%',
1552 bbox_to_anchor=bbox,
1553 bbox_transform=ax.transAxes)
1554
1555 self.m.plot_strayfield(inset, 'Strayfield Crosses',
1556 nolegend=True,
1557 show_plusses=False)
1558 inset.grid(b=True, alpha=.4)
1559 if xlim:
1560 inset.set_xlim(*xlim)
1561 else:
1562 inset.set_xlim(-1000, 1000)
1563 inset.set_ylim(-.8, -1.45)
1564
1565 if pos2:
1566 inset.set_xticks([-300 + 200 * _ for _ in range(4)],
1567 minor=True)
1568 for color, x1 in [((202 / 255, 178 / 255, 214 / 255), 300),
1569 ((106 / 255, 61 / 255, 154 / 255), 100)]:
1570 y1, y2 = -2, 2
1571 inset.plot([x1, x1, -x1, -x1], [y1, y2, y2, y1],
1572 color=color)
1573 # inset.fill([-500, -500, 500, 500], [y1, y2, y2, y1], 'blue', alpha=.1)
1574 # inset.annotate("", xy=(25, .35), xytext=(-25, .2),
1575 # arrowprops=dict(arrowstyle="->", color='blue'))
1576
1577 plt.savefig('%s.pdf' % filename)
1578
1579 def sv_temp_effect(self, filename='sv-temp-effect',
1580 show_inset=False):
1581 self.m.style.set_style(default=True, grid=True,
1582 size='talk', style='ticks', latex=True,
1583 palette='deep')
1584
1585 lofm = {}
1586 to_show = {
1587 351: [("-M_s \\rightarrow -25", 25), 'Plusses', 1, 30],
1588 355: [("-M_s \\rightarrow -25", 25), 'Plusses', 0.5, 25],
1589 356: [("-M_s \\rightarrow -25", 25), 'Plusses', 0.5, 20],
1590 357: [("-M_s \\rightarrow -25", 25), 'Plusses', 0.5, 15],
1591 358: [("-M_s \\rightarrow -25", 25), 'Plusses', 0.5, 10],
1592 359: [("-M_s \\rightarrow -25", 25), 'Plusses', 0.5, 5],
1593 }
1594
1595 for nr, content in to_show.items():
1596 lofm[nr] = ['$%s\\;\mathrm{K}$' % (
1597 content[3],
1598 ), {}]
1599 # self.eva[nr].Vx *= grad
1600
1601 fig, ax = self.eva.plot(lofm,
1602 # fit_range=(2e-2, 7e-1),
1603 # show_fit=True,
1604 plot_settings=dict(
1605 title='',#Temperature Effect',
1606 xlim=(1e-2, 1.6e0),
1607 ylim=(6e-7, 5e-2)
1608 ),
1609 f_settings=dict(disable=True),
1610 f2_settings=dict(
1611 xmin=7e-2
1612 )
1613 )
1614
1615 if show_inset:
1616 # Inset with Strayfield
1617 with sns.color_palette('deep'):
1618 inset = inset_axes(ax, width='100%', height='90%',
1619 bbox_to_anchor=(.75, .4, .25, .25),
1620 bbox_transform=ax.transAxes)
1621 self.m.plot_strayfield(inset, 'Strayfield Plusses',
1622 nolegend=True, )
1623 inset.legend(['Up', 'Down'],
1624 loc='upper right')
1625 inset.grid(b=True, alpha=.4)
1626 inset.set_xlim(-50, 50)
1627 inset.set_ylim(-.65, .45)
1628 inset.set_ylabel('')
1629
1630 y1, y2 = -1, 2
1631 inset.fill([-25, -25, 25, 25], [y1, y2, y2, y1], 'blue', alpha=.1)
1632 inset.annotate("", xy=(25, -.35), xytext=(-25, -.2),
1633 arrowprops=dict(arrowstyle="->", color='blue'))
1634
1635 # Inset showing fitted data
1636 with sns.color_palette("deep"):
1637 inset2 = inset_axes(ax, width='100%', height='100%',
1638 bbox_to_anchor=(.5, .75, .3, .25),
1639 bbox_transform=ax.transAxes)
1640 inset3 = inset_axes(ax, width='100%', height='100%',
1641 bbox_to_anchor=(.1, .09, .3, .3),
1642 bbox_transform=ax.transAxes)
1643
1644 i2data = {}
1645 for nr, content in to_show.items():
1646 intercept, slope = self.eva[nr].fit(fit_range=(2e-2, 7e-1))
1647 temp = content[3]
1648 i2data[nr] = {'temp': temp,
1649 'alpha': -slope,
1650 'amplitude': 10 ** intercept}
1651
1652 inset2.plot(temp, 10 ** intercept, 'o')
1653 inset3.plot(temp, -slope, 'o')
1654
1655 inset2.set_xlabel('Temperature [$\mathrm{K}$]')
1656 inset2.set_ylabel('$S_{V_H} (f=1\\;$Hz$)$')
1657 inset2.set_yscale('log')
1658 inset2.set_xticks([5 + 10 * _ for _ in range(3)], minor=True)
1659
1660 inset3.set_xlabel('Temperature [$\mathrm{K}$]')
1661 inset3.set_ylabel('$\\alpha$')
1662 inset3.set_xticks([5 + 10 * _ for _ in range(3)], minor=True)
1663
1664 # Only save if necessary
1665 plt.savefig('{}.pdf'.format(filename))
1666 return i2data
1667
1668 def multiple_fspans(self, filename='multiple-fspans'):
1669 self.m.style.set_style(default=True, grid=True,
1670 size='talk', style='ticks', latex=True,
1671 palette='Paired')
1672
1673 lofm = {}
1674 to_show = {
1675 340: [(25, -25), 'Empty', 0.46, {}],
1676 349: [("-25", 25), 'Empty', 0.5, {}],
1677 338: [(25, -25), 'Plusses', 0.46, {}],
1678 342: [("+M_s \\rightarrow 25 \\rightarrow 8.3", -25), 'Plusses',
1679 +0.11, {}],
1680 343: [("-M_s \\rightarrow -25 \\rightarrow -8.3", 25), 'Plusses',
1681 0.11, {'color': 'red'}],
1682 }
1683
1684 for nr, content in to_show.items():
1685 if content[2] == 9.4:
1686 continue
1687 lofm[nr] = ['$%s \\rightarrow %s$ mT %s ($%s \\frac{mT}{min}$)' % (
1688 content[0][0],
1689 content[0][1],
1690 content[1],
1691 content[2],
1692 ), content[3]]
1693
1694 self.eva.plot(lofm,
1695 plot_settings=dict(
1696 title='($90^\\circ$) Field Sweeps ($B = \\pm 25 mT$)',
1697 xlim=(6e-3, 1.6e0),
1698 # ylim=()
1699 ),
1700 f_settings=dict(disable=True,
1701 xmin=5e-2,
1702 ymin=1e-5),
1703 f2_settings=dict(
1704 xmin=1e-2, )
1705 )
1706
1707 ax = plt.gca()
1708 with sns.color_palette('Dark2'):
1709 inset = inset_axes(ax, width='100%', height='90%',
1710 bbox_to_anchor=(.7, .44, .3, .3),
1711 bbox_transform=ax.transAxes)
1712 self.m.plot_strayfield(inset, 'Strayfield Plusses',
1713 show_crosses=False,
1714 nolegend=True)
1715 inset.legend(['Up', 'Down'])
1716 inset.grid(b=True, alpha=.4)
1717 s = 30
1718 inset.set_xlim(-s, s)
1719 inset.set_ylim(-.65, .45)
1720 s = 20
1721 inset.set_xticks(np.linspace(-s, s, 5))
1722 inset.set_xticks([-s - 5 + 10 * _ for _ in range(6)], minor=True)
1723
1724 y1, y2 = -1, 2
1725 inset.fill([-25, -25, -8.3, -8.3], [y1, y2, y2, y1], 'red',
1726 alpha=.2)
1727 inset.fill([25, 25, 8.3, 8.3], [y1, y2, y2, y1], 'green', alpha=.2)
1728 inset.fill([8.3, 8.3, -8.3, -8.3], [y1, y2, y2, y1], 'blue',
1729 alpha=.05)
1730 # inset.plot([8.3, 8.3], [y1, y2], 'b-.', alpha=.8)
1731
1732 # Only save if necessary
1733 plt.savefig('{}.pdf'.format(filename))
1734
1735 def negative_sweeps(self):
1736 """Testing without file output.
1737
1738 Returns:
1739 None.:
1740 """
1741 self.m.style.set_style(default=True, grid=True,
1742 size='talk', style='ticks', latex=True,
1743 palette='Paired')
1744 lofm = {}
1745 to_show = {
1746 340: [(25, -25), 'Empty', 0.46, {}],
1747 338: [(25, -25), 'Plusses', 0.46, {}],
1748 348: [("-M_s \\rightarrow 36.56", -291), 'Empty', 0.5, {}],
1749 347: [("-M_s \\rightarrow 36.56", -291), 'Plusses', 0.5, {}],
1750 }
1751
1752 for nr, content in to_show.items():
1753 if content[2] == 9.4:
1754 continue
1755 options = content[3]
1756 lofm[nr] = ['$%s \\rightarrow %s$ mT %s ($%s \\frac{mT}{min}$)' % (
1757 content[0][0],
1758 content[0][1],
1759 content[1],
1760 content[2],
1761 ), options]
1762
1763 fig, ax = self.eva.plot(lofm,
1764 plot_settings=dict(
1765 title='($90^\\circ$) Field Sweeps (measurement Plan \#2)',
1766 xlim=(6e-3, 1.6e0),
1767 # ylim=()
1768 ),
1769 f_settings=dict(
1770 xmin=6e-3,
1771 ymin=1e-5))
1772
1773 with sns.color_palette('muted'):
1774 inset = inset_axes(ax, width='100%', height='90%',
1775 bbox_to_anchor=(.28, .73, .29, .24),
1776 bbox_transform=ax.transAxes)
1777 self.m.plot_strayfield(inset, 'Strayfield', nolegend=True)
1778 inset.legend(['Up ($-M_S \\rightarrow +M_S$)',
1779 'Down ($+M_S \\rightarrow -M_S$)'])
1780 inset.grid(b=True, alpha=.4)
1781 inset.set_xlim(-650, 50)
1782 inset.set_ylim(-.65, .45)
1783
1784 y1, y2 = -1, 2
1785 inset.fill([-25, -25, 25, 25], [y1, y2, y2, y1], 'blue', alpha=.1)
1786 inset.fill([-291.13, -291.13, 36.56, 36.56], [y1, y2, y2, y1],
1787 'green', alpha=.1)
1788 inset.fill([-611, -611, -443, -443], [y1, y2, y2, y1], 'orange',
1789 alpha=.1)
1790 inset.fill([-291, -291, -443, -443], [y1, y2, y2, y1], 'darkred',
1791 alpha=.1)
1792
1793 def measplan12(self):
1794 c1 = sns.color_palette("hls", 6)
1795 # sns.color_palette("Reds_r", 14)
1796 self.m.style.set_style(default=True, grid=True,
1797 size='talk', style='ticks', latex=True,
1798 palette='deep')
1799 sns.set_palette(c1)
1800 lofm = {}
1801 to_show = {
1802 383: [("+M_s \\rightarrow 25", -25), 'Plusses', +0.5, {}],
1803 384: [("-25", -75), 'Plusses', +0.5,
1804 {'color': 'orange', 'linestyle': '--'}],
1805 }
1806
1807 for i in range(6):
1808 to_show.update(
1809 {
1810 (385 + i): [("+M_s \\rightarrow %d" % (-(i * 50 + 25)),
1811 (-(i * 50 + 25) - 50)), 'Plusses', +0.5, {}],
1812 }
1813 )
1814
1815 for nr, content in to_show.items():
1816 if content[2] == 9.4:
1817 continue
1818 options = content[3]
1819 lofm[nr] = ['$%s \\rightarrow %s\;\mathrm{mT}$' % (
1820 content[0][0],
1821 content[0][1],
1822 ), options]
1823
1824 fig, ax = self.eva.plot(lofm,
1825 plot_settings=dict(
1826 title='($90^\\circ$) Field Sweeps (Plusses $0.5\;\mathrm{mT}/\mathrm{min}$)',
1827 xlim=(1e-2, 1.6e0),
1828 ylim=(3e-3, 6e-7)
1829 ),
1830 f_settings=dict(
1831 xmin=1e-2,
1832 ymin=3e-5),
1833 f2_settings=dict( # disable=True
1834 xmin=2e-2,
1835 plot_style='k-.',
1836 an_color='k'
1837 ),
1838 )
1839
1840 ax = plt.gca()
1841 with sns.color_palette(c1):
1842 inset = inset_axes(ax, width='100%', height='90%',
1843 bbox_to_anchor=(.4, .73, .29, .24),
1844 bbox_transform=ax.transAxes)
1845 self.m.plot_strayfield(inset, 'Strayfield', nolegend=True)
1846 inset.legend(['Up ($-M_S \\rightarrow +M_S$)',
1847 'Down ($+M_S \\rightarrow -M_S$)'])
1848 inset.grid(b=True, alpha=.4)
1849 inset.set_xlim(-650, 50)
1850 inset.set_ylim(-.65, .45)
1851
1852 y1, y2 = -1, 2
1853 for j in [25] + [-25 - 50 * i for i in range(
1854 5)]: # + [-300-50*i for i in range(5)] + [-575]:
1855 inset.fill([j, j, j - 50, j - 50], [y1, y2, y2, y1], alpha=.4)
1856 # for j in [-300-50*i for i in range(5)] + [-575]:
1857 # inset.fill([j, j, j-50, j-50], [y1, y2, y2, y1], alpha=.4)
1858 ax.annotate('Without Saturation', (1.2e-2, 1e-3), color='orange',
1859 rotation=-55)
1860 ax.annotate('With Saturation', (1.01e-2, .8e-3), color='yellow',
1861 rotation=-37)
1862
1863 plt.savefig('self.measplan12-1.pdf')
1864
1865 def measplan12_2(self):
1866 c1 = sns.color_palette("hls", 7)
1867 # sns.color_palette("Reds_r", 14)
1868 self.m.style.set_style(default=True, grid=True,
1869 size='talk', style='ticks', latex=True,
1870 palette='deep')
1871 sns.set_palette(c1)
1872 lofm = {}
1873 to_show = {}
1874 for i, j in enumerate([-(_ * 50 + 300) for _ in range(5)] + [-575]):
1875 to_show.update(
1876 {
1877 (391 + i): [("+M_s \\rightarrow %d" % (j), (j - 50)),
1878 'Plusses', +0.5, {}],
1879 }
1880 )
1881
1882 j = -625
1883 to_show[397] = [("-M_s \\rightarrow %d" % (j), (j + 50)), 'Plusses',
1884 +0.5, {}]
1885
1886 for nr, content in to_show.items():
1887 if content[2] == 9.4:
1888 continue
1889 options = content[3]
1890 lofm[nr] = ['$%s \\rightarrow %s\;\mathrm{mT}$' % (
1891 content[0][0],
1892 content[0][1],
1893 ), options]
1894
1895 fig, ax = self.eva.plot(lofm,
1896 plot_settings=dict(
1897 title='($90^\\circ$) Field Sweeps (Plusses $0.5\;\mathrm{mT}/\mathrm{min}$)',
1898 xlim=(1e-2, 1.6e0),
1899 ylim=(2e-4, 2e-7)
1900 ),
1901 f_settings=dict(
1902 xmin=1e-2,
1903 ymin=2e-5),
1904 f2_settings=dict( # disable=True
1905 xmin=1e-2,
1906 plot_style='k-.',
1907 an_color='k',
1908 disable=True,
1909 ),
1910 )
1911
1912 with sns.color_palette(c1):
1913 inset = inset_axes(ax, width='100%', height='90%',
1914 bbox_to_anchor=(.4, .73, .29, .24),
1915 bbox_transform=ax.transAxes)
1916 self.m.plot_strayfield(inset, 'Strayfield Plusses', nolegend=True)
1917 inset.legend(['Up ($-M_S \\rightarrow +M_S$)',
1918 'Down ($+M_S \\rightarrow -M_S$)'])
1919 inset.grid(b=True, alpha=.4)
1920 inset.set_xlim(-650, 50)
1921 inset.set_ylim(-.65, .45)
1922
1923 y1, y2 = -1, 2
1924 for j in [-300 - 50 * i for i in range(5)] + [-575]:
1925 inset.fill([j, j, j - 50, j - 50], [y1, y2, y2, y1], alpha=.4)
1926
1927 plt.savefig('self.measplan12-2.pdf')
1928
1929 def measplan12_full(self, name='measplan12-full'):
1930 """measurement Plan #12 All 14 measurements in one Graph.
1931
1932 Args:
1933 name:
1934 """
1935 # c1 = sns.color_palette("twilight", 14)
1936 c1 = sns.color_palette("hls", 14)
1937 self.m.style.set_style(default=True, grid=True,
1938 size='talk', style='ticks', latex=True)
1939 sns.set_palette(c1)
1940
1941 # Choosing measurement Numbers to plot
1942 lofm = {}
1943 to_show = {
1944 383: [("+M_s \\rightarrow 25", -25), 'Plusses', +0.5, {}],
1945 384: [("-25", -75), 'Plusses', +0.5,
1946 {'color': 'orange', 'linestyle': '--'}],
1947 }
1948
1949 for i in range(6):
1950 to_show.update(
1951 {
1952 (385 + i): [("+M_s \\rightarrow %d" % (-(i * 50 + 25)),
1953 (-(i * 50 + 25) - 50)), 'Plusses', +0.5, {}],
1954 }
1955 )
1956
1957 for i, j in enumerate([-(_ * 50 + 300) for _ in range(5)] + [-575]):
1958 to_show.update(
1959 {
1960 (391 + i): [("+M_s \\rightarrow %d" % (j), (j - 50)),
1961 'Plusses', +0.5, {}],
1962 }
1963 )
1964
1965 j = -625
1966 to_show[397] = [("-M_s \\rightarrow %d" % (j), (j + 50)), 'Plusses',
1967 +0.5, {}]
1968
1969 # Converting them to a list of measurements (lofm)
1970 for nr, content in to_show.items():
1971 if content[2] == 9.4:
1972 continue
1973 options = content[3]
1974 lofm[nr] = ['$%s \\rightarrow %s\;\mathrm{mT}$' % (
1975 content[0][0],
1976 content[0][1],
1977 ), options]
1978
1979 fig, ax = self.eva.plot(lofm,
1980 plot_settings=dict(
1981 title='($90^\\circ$) Field Sweeps (Plusses;' + \
1982 ' $\\Delta B = 50\\mathrm{mT}$;' + \
1983 ' $0.5\\;\\mathrm{mT}/\\mathrm{min}$)',
1984 xlim=(1e-2, 1.6e0),
1985 ylim=(5e-2, 5e-8),
1986 legend_settings=dict(loc='best', ncol=2)
1987 ),
1988 f_settings=dict(
1989 xmin=1e-2,
1990 ymin=3e-5),
1991 f2_settings=dict( # disable=True
1992 xmin=2e-2,
1993 plot_style='k-.',
1994 an_color='k'
1995 ),
1996 )
1997
1998 with sns.color_palette(c1):
1999 inset = inset_axes(ax, width='100%', height='100%',
2000 bbox_to_anchor=(.07, .06, .34, .26),
2001 bbox_transform=ax.transAxes)
2002 self.m.plot_strayfield(inset,
2003 'Strayfield Plusses',
2004 show_plusses=False,
2005 show_crosses=False,
2006 nolegend=True)
2007
2008 y1, y2 = -1, 2
2009 for j in [25 - 50 * i for i in range(6)] + \
2010 [-300 - 50 * i for i in range(5)] + [-575]:
2011 inset.fill([j, j, j - 50, j - 50], [y1, y2, y2, y1], alpha=.8)
2012
2013 inset.plot(self.m.up_fitted.B, self.m.up_fitted.Bx8, 'b-',
2014 linewidth=1.5, label='Up')
2015 inset.plot(self.m.down_fitted.B, self.m.down_fitted.Bx8, 'r-',
2016 linewidth=3, label='Down')
2017
2018 inset.legend(loc='best')
2019
2020 inset.grid(b=True, alpha=.4)
2021 inset.set_xlim(-650, 50)
2022 inset.set_ylim(-.65, .45)
2023 inset.set_xlabel('')
2024 inset.set_ylabel('')
2025
2026 ax.annotate('Without Saturation', (1.2e-2, 5e-4), color='orange',
2027 rotation=-55)
2028 ax.annotate('With Saturation', (1.01e-2, 4e-4), color='orange',
2029 rotation=-35)
2030
2031 plt.savefig('%s.pdf' % name)
2032
2033 def set_size(self, width_pt, fraction=1, subplots=(1, 1)):
2034 """Set figure dimensions to sit nicely in our document.
2035
2036 Source: https://jwalton.info/Matplotlib-latex-PGF/
2037
2038 Parameters
2039 ----------
2040 width_pt: float
2041 Document width in points
2042 fraction: float, optional
2043 Fraction of the width which you wish the figure to occupy
2044 subplots: array-like, optional
2045 The number of rows and columns of subplots.
2046 Returns
2047 -------
2048 fig_dim: tuple
2049 Dimensions of figure in inches
2050 """
2051 # Width of figure (in pts)
2052 fig_width_pt = width_pt * fraction
2053 # Convert from pt to inches
2054 inches_per_pt = 1 / 72.27
2055
2056 # Golden ratio to set aesthetic figure height
2057 golden_ratio = (5 ** .5 - 1) / 2
2058
2059 # Figure width in inches
2060 fig_width_in = fig_width_pt * inches_per_pt
2061 # Figure height in inches
2062 fig_height_in = fig_width_in * golden_ratio * (subplots[0] / subplots[1])
2063
2064 return (fig_width_in, fig_height_in)
HandleM
1class HandleM(MultipleM):
2 def __init__(self, *args, **kwargs):
3 """
4 Args:
5 args:
6 kwargs:
7
8 Note:
9 **Measurement Handler:** This class represents all other
10 measurements and is the interface to other measurement objets.
11 """
12 super().__init__()
13 self.logger = logging.getLogger('Handle')
14 self.data = {}
15 self.info = pd.DataFrame()
16 self.info_dict = {}
17 self.sa_dict = {}
18 self.hloop_dict = {}
19 if kwargs.get('data'):
20 data = kwargs.get('data')
21 for nr, d in data:
22 data.update(
23 self._process_data(nr, d['data'], d['info']))
24 self.parse_data(data, **kwargs)
25 elif kwargs.get('directory'):
26 self.load_files(kwargs.pop('directory'), **kwargs)
27
28 def __getitem__(self, key):
29 """
30 Args:
31 key:
32 """
33 if isinstance(key, str):
34 if key[1] == 'm':
35 d = self.data.get(float(key[1:]))
36 if not d:
37 raise AttributeError("Measurement Nr. %s not found" % key)
38 else:
39 return d
40 else:
41 d = self.data.get(float(key))
42 if not d:
43 raise AttributeError("Measurement Nr. %s not found" % key)
44 else:
45 return d
46 return self.query('Nr == %f' % key)
47
48 def __setitem__(self, key, val):
49 """
50 Args:
51 key:
52 val:
53 """
54 self.logger.warning("Setting Items is not allowed.")
55 return
56
57 def __contains__(self, key):
58 """
59 Args:
60 key:
61 """
62 return (key in self.data)
63
64 def load_files(self, directory, **kwargs):
65 """Loads all files from a specific directory and creates objects for
66 found measurements.
67
68 Args:
69 directory (str): directory to search in
70 **kwargs:
71
72 Returns:
73 None
74 """
75 file_list = glob(os.sep.join([directory, '**/*.dat']), recursive=True)
76 if file_list:
77 self.load_folder(file_list, **kwargs)
78 else:
79 self.logger.error('No Files found: %s' % file_list)
80
81 def get_custom_header(self, kind='RAW',
82 instruments=None):
83 """Help Function to get column names for EVE Measurements.
84
85 Args:
86 kind (str, optional): Kind of measurement, default: 'RAW'
87 instruments (list, optional): Used instruments in order of
88 appearance, default: ['t', 'LS', 'IPS', 'SR830']
89
90 Returns:
91 tuple: (list header, int skiprows)
92 """
93 if not instruments:
94 instruments = ['t', 'LS', 'IPS', 'SR830']
95
96 if kind == 'RAW':
97 return ['Time', "Vx", "Vy"], 0
98 elif kind == 'SA':
99 return ['f', 'Vx', 'Vy'], 6
100
101 def_skiprows = 3
102 def_header = []
103 for k in instruments:
104 if k == 't':
105 def_header += ["Time", ]
106 if k == 'LS':
107 def_header += ["Temp%s" % i for i in range(6)]
108 if k == 'IPS':
109 def_header += ["B", ]
110 if k == 'SR830':
111 def_header += [
112 "Vx", "Vy", "Vr", "Vth",
113 # ["V%s%d" % (var, gpib) for gpib in [8, 9, 13]
114 # for var in ['x', 'y', 'r', 'th',]]
115 ]
116 return def_header, def_skiprows
117
118 def load_folder(self, file_list, **kwargs):
119
120 """
121 Args:
122 file_list:
123 **kwargs:
124 """
125 self.logger.warning('Start loading folder: %s' % file_list[0])
126 d_dict = dict()
127
128 for f in file_list:
129 directory, filename = f.split(os.sep)[-2:]
130 regex = re.match(
131 '(.*)[mM]([0-9.]+)(.*)([Pp]lusses|[Cc]rosses|[Ee]mpty)(.*).dat',
132 filename)
133 if not regex:
134 regex = re.match('(.*)[mM]([0-9.]+)(.*).dat', filename)
135 if not regex:
136 self.logger.warning("Regex doesn't match: %s" % f)
137 continue
138 else:
139 folder, nr, add_info = regex.groups()
140 struct = None
141 m_type = 'NA'
142 struct = 'NA'
143 else:
144 folder, nr, m_type, struct, add_info = regex.groups()
145
146 try:
147 nr = float(nr)
148 except Exception:
149 self.logger.error('Not a valid Measurement Nr. (%s)\n' % nr + \
150 'Debug kind/struct/f (%s, %s, %s)' % (
151 m_type, struct, f))
152 continue
153
154 if nr == int(nr):
155 nr = int(nr)
156 def_header, def_skiprows = self.get_custom_header('SA')
157 elif 'SR830' in add_info:
158 def_header, def_skiprows = self.get_custom_header('RAW')
159 else:
160 def_header, def_skiprows = self.get_custom_header(
161 instruments=['t', 'LS', 'IPS', 'SR830'])
162
163 data = pd.read_csv(f, sep=kwargs.get('sep', '\t'),
164 # header=kwargs.get('header', def_header_index),
165 names=kwargs.get('names', def_header),
166 skiprows=kwargs.get('skiprows', def_skiprows))
167
168 if int(nr) == nr:
169 nr = int(nr)
170 subnr = False
171 else:
172 i, subnr = str(nr).split('.')
173
174 info = {'type': 'SA',
175 'Nr': nr,
176 'filename': f.split(os.sep)[-1],
177 'folder': folder,
178 'technique': m_type.strip('_'),
179 'Structure': struct,
180 'Additional': add_info.strip('_')}
181 if subnr:
182 info['type'] = 'RAW'
183 info['subnr'] = subnr
184
185 d_dict.update(self._process_data(nr, data, info))
186 if kwargs.get('drop_unused_columns', True):
187 if 'Temp0' in def_header:
188 d_dict[nr]['data'] = d_dict[nr]['data'].drop(
189 ["Temp%s" % i for i in range(6)] + \
190 ["Vr", "Vth"],
191 axis=1,
192 )
193 self.parse_data(d_dict)
194
195 def parse_data(self, data, **kwargs):
196 """imports data and sets info variable.
197
198 .. code-block:: python
199
200 h.parse_data({'data': pd.DataFrame,
201 'info': dict(Nr=0, ** kw) })
202
203 Args:
204 data (dict): Data to parse
205 **kwargs:
206
207 Returns:
208 None
209 """
210 self.logger.debug('Start parsing data: %s' % data.items())
211 for nr, df in data.items():
212 if not df.get('info'):
213 continue
214 m_type = df['info'].get('type', 'Unknown')
215 self.info_dict.update({nr: df['info']})
216 if df['info'].get('Structure'):
217 kwargs['Structure'] = df['info']['Structure']
218 if df['info'].get('Angle'):
219 kwargs['Angle'] = df['info']['Angle']
220 if df['info'].get('Additional'):
221 kwargs['Additional'] = df['info']['Additional']
222
223 if m_type == 'SA':
224 self.data[nr] = SA(df)
225 self.sa_dict[nr] = self.data[nr]
226 elif m_type == 'Hloop':
227 self.data[nr] = Hloop(nr, down_only=True,
228 **kwargs)
229 self.hloop_dict[nr] = self.data[nr]
230 elif m_type == 'RAW':
231 self.data[nr] = RAW(df, **kwargs)
232 self.hloop_dict[nr] = self.data[nr]
233 else:
234 self.logger.error(
235 'Measurement %s (type: %s) not processed (unknown type).' % (
236 nr, m_type))
237
238 self.info = pd.DataFrame(self.info_dict).T
239 # self.info.set_index('Nr', inplace=True)
240 self.logger.debug('Parsing Measurement Finished\n' + \
241 'Debug size data/info (%s, %s)' % (len(self.data),
242 self.info.shape))
243
244 def _process_data(self, nr, data, info):
245 """
246 Args:
247 nr:
248 data:
249 info:
250 """
251 self.logger.info('%s\n%s' % (data, info))
252
253 # Logging statistical values for info
254 for key in data.keys():
255 # Converting Data to numeric
256 if data[key].dtype == object:
257 try:
258 data[key] = pd.to_numeric(data[key], errors='coerce')
259 except Exception:
260 self.logger.error('Not Numeric column: %s/%s' % (nr, key))
261
262 info['%s_count' % key] = data[key].count()
263 info['%s_mean' % key] = data[key].mean()
264 info['%s_var' % key] = data[key].var()
265 info['%s_min' % key] = data[key].min()
266 info['%s_max' % key] = data[key].max()
267
268 d_dict = {nr: {
269 'data': data,
270 'info': info
271 }}
272 return d_dict
273
274 def plot(self, *args, **kwargs):
275 """Plotting a Measurement
276
277 Args:
278 lofm: list of measurements
279 figsize: tuple size of figure
280 show_fit: bool
281 fit_range: tuple/list
282 plot_settings: list
283 grid_options: list
284 f_settings: 1/f line
285 f2_settings: 1/f^2 line
286
287
288 Returns:
289 tuple: Figure, Axis
290
291 Raises:
292 warning: lofm not available.
293 KeyError: Measurement does not exist.
294 """
295 lofm = kwargs.get('lofm', args[0])
296 if not (lofm):
297 self.logger.warning("No list of measurements (lofm) available.")
298 return
299
300 fig, ax = plt.subplots(figsize=kwargs.get('figsize', (16, 12)))
301 for i, j in lofm.items():
302 if i not in self.data:
303 raise KeyError(i)
304
305 label = 'm%s: %s' % (i, j[0])
306 plot_options = j[1]
307
308 if kwargs.get('fit_range'):
309 intercept, slope = self.data[i].fit(kwargs.get('fit_range'))
310 x = np.linspace(*kwargs.get('fit_range'))
311 if kwargs.get('show_fit'):
312 ax.plot(x, (10 ** intercept * np.power(x, slope)),
313 label='Fit m%s' % i)
314 if kwargs.get('show_fit_label'):
315 label += ' $\\alpha = %.3f, S_R(f=0) = 10^{%.3f}$' % (
316 slope, intercept)
317
318 if kwargs.get('norm_S'):
319 self.data[i].df['norm'] = self.data[i].df['Vx'] * \
320 self.data[i].df['f']
321 plot_options['plot_y'] = 'norm'
322
323 self.data[i].plot(label=label, ax=ax,
324 dont_set_plot_settings=True,
325 **plot_options)
326
327 tmp = kwargs.get('plot_settings', dict())
328 if not (tmp):
329 tmp = {}
330 plot_settings = dict(
331 xlim=(1e-2, 1e0),
332 ylim=(1e-7, 5e-2),
333 title='($90^\circ$) Field sweeps',
334 grid=dict(
335 which='minor',
336 color='#ffff99',
337 linestyle='--',
338 alpha=.5))
339 plot_settings.update(tmp)
340 self.data[i]._set_plot_settings(**plot_settings)
341
342 grid_options = dict(
343 b=True,
344 which='minor',
345 color='#cccccc',
346 linestyle='-.',
347 alpha=.3)
348 grid_options.update(kwargs.get('grid_options', dict()))
349 plt.grid(**grid_options)
350
351 # Draw 1 over f lines
352 f_settings = dict(xmin=1e-2, ymin=1e-6)
353 f_settings.update(kwargs.get('f_settings', {}))
354 f2_settings = dict(ymin=1e-3, alpha=2,
355 plot_style='b--', an_color='blue')
356 f2_settings.update(kwargs.get('f2_settings', {}))
357
358 self.data[i]._draw_oneoverf(ax, **f_settings)
359 self.data[i]._draw_oneoverf(ax, **f2_settings)
360
361 return fig, ax
362
363 def __repr__(self):
364 ret = 'HandleM\n'
365 if not self.data:
366 return ret + 'Empty'
367
368 for nr, info in self.info_dict.items():
369 ret += 'Nr. %s:\t Type: %s\t Structure: %s\tTechnique: %s\n' % (
370 nr,
371 info['type'],
372 info['Structure'],
373 info['technique'])
374 return ret
375
376 def get_info(self):
377 """Returns information about all measurements
378 and splits it into groups (SA/RAV/MFN/VH)."""
379
380 info = self.info
381 sa = info[info['type'] == 'SA']
382 raw = info[info['type'] == 'RAW']
383 mfn = info[info['technique'] == 'MFN']
384 vh = info[info['technique'] == 'VH']
385
386 groups = dict(vh=vh, mfn=mfn, sa=sa, raw=raw)
387
388 return info, groups
MFN
1class MFN(MultipleM):
2 def __init__(self, files, **kwargs):
3 r"""Magnetic Flux Noise Measurements.
4 Class to handle and plot MFN Measurements
5 taken with the SR830 DAQ.
6
7 if a measurement number is given the files are
8 loaded from folder:
9 ``data/MFN/m%s/*.dat``
10
11 else: files must be a list of filenames:
12
13 .. code:: python
14 :linenos:
15 :caption: Example
16
17 arg = glob('data/MFN/mXXX/m[0-9.]*.dat')
18 mfn = MFN(arg)
19
20 :param files: list of files or measurement number.
21 :param kwargs: Different additional parameters.
22 :param int nr: Measurement number
23 (default: None).
24 :param bool calc_first: Calculating first spectrum
25 (default: True).
26 :param bool calc_second: Calculating second spectrum
27 (default: False).
28 :param bool downsample: downsample timesignal for first spectrum
29 (default: False).
30 :param int num_first_spectra: Number of spectra to average
31 (default: 64).
32 :param float timeconstant: Lock-In timeconstant to cut first spectrum
33 at corresponding frequency (see ``cut_fmax``).
34 :param float cut_fmax: Cut first spectrum at this frequency
35 (default: :math:`f_{max} = \frac{\tau}{2\pi}`).
36 :param default_cm: matplotlib color map for contour plots
37 (default: style['default_cm']).
38 :type default_cm: matplotlib.cm
39 """
40 super().__init__()
41 self.name = 'Magnetic Flux Noise'
42 self.default_cm = kwargs.get('default_cm', self.style.get('default_cm'))
43 self.style['default_cm'] = self.default_cm
44
45 self.info = {
46 'Nr': kwargs.get('nr'),
47 'first': kwargs.get('calc_first', True),
48 'second': kwargs.get('calc_second', False),
49 'num': kwargs.get('num_first_spectra', 64),
50 'downsample': kwargs.get('downsample', False),
51 'length': kwargs.get('length'),
52 }
53
54 if isinstance(files, int):
55 self.info['Nr'] = files
56 files = glob('data/MFN/m%s/*' % self.info['Nr'])
57
58 self.logger = logging.getLogger('MFNMeasurement (%s)' %
59 self.info['Nr'])
60
61 self.data, self.measurements = self.load_meas(files, **kwargs)
62
63 # Stop here if we have no data
64 if len(self.data) == 0:
65 return
66
67 # Cut too long measurements
68 if kwargs.get('equalize_length', True):
69 self.__equalize_length()
70
71 self.info['timeconstant'] = kwargs.get('timeconstant')
72
73 # Cut higher frequencies if timeconstant is known
74 fmax_rate = self.info.get('rate') / (2 * np.pi) if self.info.get('rate') else 1e6
75 fmax_tc = 1 / (2 * np.pi * self.info['timeconstant']) if self.info['timeconstant'] else 1e6
76 fmax = np.min([fmax_rate, fmax_tc])
77 self.info['fmax'] = fmax
78 if fmax < 1e6 or kwargs.get('cut_fmax'):
79 self.cut_fmax(kwargs.get('cut_fmax', fmax))
80
81 def load_meas(self, files, **kwargs):
82 """
83 Loading a single magnetic flux noise DAQ measurement.
84
85 Files in measrument folder contain multiple measurements
86 with one measurement per field.
87
88 :param files: files to load
89
90 :return: DataFrame Data, dict Measurement Objects
91 :rtype: tuple
92 """
93
94 max_len = 0
95 meas = {}
96 all_data = pd.DataFrame({'Field': [], 'Time': [], 'Vx': [], 'Vy': []})
97 for f in files:
98 info_dict = self.get_info_from_name(f)
99
100 field = info_dict.get('field')
101 nr = info_dict.get('nr')
102
103 if not self.info.get('Nr'):
104 self.info['Nr'] = nr
105
106 data_df = pd.read_csv(f, sep='\t')
107 if data_df.empty:
108 continue
109
110 if len(data_df['Vx']) % 1024:
111 d = data_df['Vx'].iloc[:-(len(data_df['Vx']) % 1024)]
112 else:
113 d = data_df.Vx
114
115 max_len = len(d)
116
117 data = {
118 'data': d,
119 'info': {
120 'Nr': nr,
121 'field': field,
122 'rate': 1 / data_df.time.diff().mean(),
123 'length': max_len * data_df.time.diff().mean(),
124 }
125 }
126
127 if not self.info['length']:
128 self.info['length'] = max_len * data_df.time.diff().mean()
129 if not self.info.get('rate'):
130 self.info['rate'] = 1 / data_df.time.diff().mean()
131
132 # Calculate the first and second spectrum
133 meas[field] = self._get_raw_meas(data, **kwargs)
134
135 if meas[field] == None:
136 meas.pop(field)
137
138 tmp_df = pd.DataFrame(
139 {'Field': field,
140 'Time': data_df.time.iloc[:max_len],
141 'Vx': data_df.Vx.iloc[:max_len] * kwargs.get('factor', 1e3),
142 'Vy': data_df.Vy.iloc[:max_len] * kwargs.get('factor', 1e3),
143 })
144 all_data = pd.concat([all_data, tmp_df]) # , how='outer')
145
146 return all_data, meas
147
148 def _get_raw_meas(self, data, **kwargs):
149 """
150 Returns the RAW Measurement.
151 :param dict data: time signal and info
152 :param bool calculate_second_by_hand: if we calc
153 :return: RAWMeasurement
154 """
155 if len(data['data']) == 0:
156 return None
157
158 ret = RAW(data,
159 rate=data['info']['rate'],
160 nof_first_spectra=self.info.get('num', 64),
161 calc_first=self.info['first'],
162 calc_second=self.info['second'],
163 downsample=self.info['downsample'],
164 **kwargs.get('raw_kwargs', dict())
165 )
166
167 if kwargs.get('calculate_second_by_hand', False):
168 ret.calc_second_spectrum(
169 highest_octave=kwargs.get(
170 'highest_octave', ret.avg_spec.freq.max()),
171 minimum_points_in_octave=kwargs.get(
172 'minimum_points_in_octave', 3),
173 peak_filter=kwargs.get(
174 'peak_filter', False),
175 )
176
177 return ret
178
179 def cut_fmax(self, fmax):
180 """Cut frequencies higher than maximum allowed frequencies.
181
182 Args:
183 fmax:
184 """
185 for field, spec in self.measurements.items():
186 # Drop all frequencies larger than fmax
187 s = spec.spectrum.first_spectra_df
188 s.drop(s.query('Frequency > %s' % fmax).index.tolist(),
189 axis=0, inplace=True
190 )
191 freq = spec.spectrum.frequency_span_array
192 freq = freq[freq <= fmax]
193 spec.spectrum.frequency_span_array = freq
194
195 # Reset First Spectrum Variables
196 spec.spectrum.finalize_first_spectrum(s)
197 spec.avg_spec = pd.DataFrame({
198 'freq': freq,
199 'S': spec.spectrum.first_spectrum_avg.loc[:] # copy
200 })
201
202 def __equalize_length(self):
203 """
204 Check that data for all fields have same amount of data.
205 :return:
206 """
207 # Check if all fields have same amount of data
208 nofvalues = self.data.groupby('Field').count()
209 if nofvalues.Vx.max() - nofvalues.Vx.min() > 0:
210 fields_with_more_data = nofvalues[
211 nofvalues.Vx > nofvalues.Vx.median()].index.tolist()
212 fields_with_less_data = nofvalues[
213 nofvalues.Vx < nofvalues.Vx.median()].index.tolist()
214 self.logger.warning(
215 'Not all fields have same amount of data (median = %s):\n' % (
216 nofvalues.Vx.quantile(.5))
217 + 'Fields with more data: %s\n' % fields_with_more_data
218 + 'Fields with less data: %s\n' % fields_with_less_data
219 + '%s\n' % nofvalues.Vx.describe()
220 + 'To avoid data loss use the option "equalize_length=False"!')
221
222 for field in self.data.Field.unique():
223 condition = 'Field == %s' % field
224 if field in fields_with_more_data:
225 # Cut data
226 cut_data = self.data.query(condition).iloc[
227 :int(nofvalues.Vx.median())]
228 self.data.drop(
229 self.data.query(condition).index.tolist(),
230 axis=0, inplace=True)
231 self.data = pd.merge(self.data, cut_data, how='outer')
232
233 if self.data.query(condition).shape[0] > 0:
234 # recalculate Spectrum
235 rate = 1 / self.data.query(condition).Time.diff().median()
236 self.measurements[field] = self._get_raw_meas(
237 {'data': self.data.query(condition).Vx.to_numpy(),
238 'info': {
239 'Nr': self.info['Nr'],
240 'field': field,
241 'rate': rate,
242 }
243 },
244 )
245 else:
246 # Remove field from measurements
247 self.measurements.pop(field)
248 self.data.drop(
249 self.data.query(condition).index.tolist(),
250 axis=0, inplace=True)
251
252 self.logger.error(
253 'This field (%s) has more data '
254 '(%s) than median (%s): Cutting data!' % (
255 field,
256 nofvalues.Vx.loc[field],
257 nofvalues.Vx.median()
258 )
259 )
260
261 if field in fields_with_less_data:
262 self.logger.error(
263 'This field (%s) has less data '
264 '(%s) than median (%s): Deleting field from data!' % (
265 field,
266 nofvalues.Vx.loc[field],
267 nofvalues.Vx.median()
268 )
269 )
270 self.data.drop(
271 self.data.query(condition).index,
272 inplace=True)
273 self.measurements.pop(field)
274
275 def plot_first_and_second(self, **kwargs):
276 """
277 Args:
278 **kwargs:
279 """
280 sns.set_palette(sns.color_palette("hls", len(self.measurements)))
281 fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1,
282 figsize=(11.69, 8.27), )
283
284 fields = kwargs.get('fields', self.data.sort_values(
285 'Field')['Field'].unique())
286 for field in fields:
287 # Setting shortcuts
288 try:
289 meas = self.measurements[field]
290 except KeyError:
291 continue
292
293 s = meas.avg_spec
294
295 ax1.plot(field, s.S.sum(), 'o')
296
297 if hasattr(meas.spectrum, 'second_spectra'):
298 marker = "+x*ds"
299 color = "rbgyk"
300 for octave in range(
301 meas.spectrum.number_of_octaves):
302 second = meas.spectrum.second_spectra[octave]
303 ax2.plot(field,
304 second.sum(),
305 "%s%s" % (color[octave], marker[octave])
306 )
307
308 ax2.set_title('$\\sum$ Second Spectrum')
309 ax2.set_ylabel('$S$ [a.u.]')
310
311 # Legend for second spectrum
312 field = fields[-1]
313 labels = []
314 if hasattr(self.measurements[field].spectrum, 'second_spectra'):
315 for octave in range(
316 self.measurements[field].spectrum.number_of_octaves):
317 labels.append('%s Octave' % (octave + 1))
318 ax2.legend(labels)
319
320 ax1.set_title(
321 'm%s: $\\sum$ First Spectrum Noise' % self.info['nr'])
322 ax1.set_ylabel(
323 '$\\langle S_V \\rangle$ [$\\mathrm{V}^2/\\mathrm{Hz}$]')
324
325 for ax in [ax2, ax1]:
326 ax.set_yscale('log')
327 plt.show()
328
329 def plot_alpha(self, field, s, ax=None, **kwargs):
330 """
331 Fits a spectrum and plots the slope alpha.
332 :param s: pandas.DataFrame of the spectrum
333 :param ax: axis where to plot the data.
334 :return: linear regression fit
335 """
336 if not ax:
337 fig, ax = plt.subplots()
338
339 # Fitting alpha
340 s['lnf'] = np.log10(s.freq)
341 s['lnS'] = np.log10(s.S)
342
343 fit_indices = s.freq < kwargs.get('fit_range', 0.7)
344
345 f = scipy.stats.linregress(
346 s.lnf.loc[fit_indices],
347 s.lnS.loc[fit_indices])
348
349 plt_obj, = ax.plot(field, -f.slope, kwargs.get('alpha_symbol', 'o'), label='$\\alpha$')
350
351 return f, plt_obj
352
353 def plot_info2(self, **kwargs):
354 """
355 Args:
356 **kwargs:
357 """
358 if kwargs.get('fig'):
359 fig = kwargs.get('fig')
360 (ax11, ax12) = kwargs.get('axes')
361 else:
362 fig, (ax11, ax12) = plt.subplots(nrows=2, ncols=1,
363 figsize=(5, 10), )
364 fields = kwargs.get('fields',
365 self.data.sort_values('Field')['Field'].unique())
366 self.plot_noise_chararcteristics(ax11, fields=fields)
367 self.spectra_field_contour(ax12, fields=fields)
368
369 def plot_noise_chararcteristics(self, ax=None, fields=None, **kwargs):
370 """
371 Args:
372 ax:
373 fields:
374 **kwargs:
375 """
376 if not ax:
377 fig, ax = plt.subplots()
378 fields = kwargs.get('fields', self.data.sort_values('Field')['Field'].unique())
379
380 par2 = ax.twinx()
381
382 spectra_df = pd.DataFrame()
383 for i, field in enumerate(fields):
384 # Setting shortcut for Average Spectrum s
385 try:
386 meas = self.measurements[field]
387 if meas == None or len(meas.avg_spec) < 2:
388 continue
389 except KeyError:
390 continue
391
392 s = meas.avg_spec
393
394 spectra_df['S%s' % field] = s.S
395
396 f, alpha = self.plot_alpha(field, s, par2, alpha_symbol="bo",
397 **kwargs)
398 meas.info['alpha'] = -f.slope,
399 meas.info['power'] = f.intercept
400
401 integral, = ax.plot(field, s.S.sum() * s.freq.diff().iloc[1],
402 'gd', label='Integral')
403 intercept, = ax.plot(field, 10 ** f.intercept, 'r*',
404 label='$S(f=1~\\mathrm{Hz})$')
405 ax.set_yscale('log')
406 plt.legend(handles=[integral, intercept, alpha], frameon=True,
407 fancybox=True, framealpha=.8)
408 # ('Integral', '$S(f=1~\\mathrm{Hz})$', '$\\alpha$'))
409
410 def plot_info(self, show_field=0, **kwargs):
411 """Plots basic mfn infos
412
413 Args:
414 show_field:
415 kwargs:
416 """
417
418 sns.set_palette(sns.color_palette("hls", len(self.measurements)))
419 fig, ((ax11, ax12),
420 (ax21, ax22),
421 (ax31, ax32)) = plt.subplots(nrows=3, ncols=2,
422 figsize=self.style.get('figsize'),
423 gridspec_kw=kwargs.get('gridspec_kw', dict()),
424 # wspace=.3,
425 # hspace=.45)
426 )
427 fields = kwargs.get('fields',
428 self.data.sort_values('Field')['Field'].unique())
429 spectra_df = pd.DataFrame()
430 second = pd.DataFrame()
431 spectra_fit = dict()
432 for i, field in enumerate(fields):
433 # Setting shortcut for Average Spectrum s
434 try:
435 meas = self.measurements[field]
436 if meas == None or len(meas.avg_spec) < 2:
437 continue
438 except KeyError:
439 continue
440
441 s = meas.avg_spec
442
443 spectra_df['S%s' % field] = s.S
444
445 ax11.loglog(s.freq, s.S*s.freq, label='%s T' % field)
446
447 f, _ = self.plot_alpha(field, s, ax21, **kwargs)
448 meas.info['alpha'] = -f.slope,
449 meas.info['amplitude'] = f.intercept
450 meas.info['power'] = s.S.sum() * s.freq.diff().iloc[1]
451
452 ax22.plot(field, meas.info['power'], 'o')
453 spectra_fit[field] = {
454 'alpha': meas.info['alpha'][0],
455 'amplitude': meas.info['amplitude'],
456 'power': meas.info['power']}
457
458 if field != float(field):
459 raise ValueError
460
461 time_df = self.data.query('Field == %f' % field)
462 m = time_df['Vx'].mean()
463 std = time_df['Vx'].std()
464 ax32.errorbar(field, m, yerr=2 * std, elinewidth=2, capsize=4)
465
466 if hasattr(meas.spectrum, 'second_spectra'):
467 if len(meas.spectrum.second_spectra) > 0:
468 second['S%s' % field] = meas.spectrum.second_spectra[0]
469
470 self.spectra_fit_df = pd.DataFrame(spectra_fit).T
471 self.spectra_fit_df.index.name = 'Field'
472
473 # Plot Contour Plot of Averaged Spectra per Field
474 if kwargs.get('plot_field_contour', True):
475 self.spectra_field_contour(ax12, **kwargs)
476 else:
477 field = fields[-1]
478 if kwargs.get('plot_second_sum', False):
479 marker = "+x*ds"
480 color = "rbgyk"
481 for octave in range(
482 self.measurements[
483 field].spectrum.number_of_octaves):
484 ax12.plot(field,
485 self.measurements[
486 field].spectrum.second_spectra[
487 octave].sum(),
488 "%s%s" % (color[octave], marker[octave])
489 )
490 ax12.set_ylabel('$S_2 (f)$')
491 elif kwargs.get('plot_second_freq', False):
492 frequencies = self.measurements[
493 field].spectrum.frequency_span_array_second_spectra
494 smin, smax = second.min().min(), second.max().max()
495 levels = np.logspace(np.log10(smin),
496 np.log10(smax), 20)
497 cs = ax12.contourf(fields,
498 frequencies,
499 second,
500 norm=LogNorm(vmin=smin, vmax=smax),
501 levels=levels,
502 cmap=kwargs.get('contour_cm',
503 self.style.get('default_cm')),
504 )
505 cbar = plt.colorbar(cs, ax=ax12)
506 cbar.set_ticklabels(['%.2e' % _ for _ in levels])
507 ax12.set_ylabel('$f$ [$\\mathrm{Hz}$]')
508 ax12.set_title('Second Spectrum over Field')
509 ax12.set_xlabel('$\\mu_0 H_{ext}$ [$T$]')
510 else:
511 self.data.query("Field == @show_field").plot(
512 x='Time', y='Vx', ax=ax12)
513
514 # # Plot 1/f line in Spectrum
515 # xmin, ymin = kwargs.get('xymin', (2e-1, 5e-14))
516 # factor, alpha = (1.5, 2)
517 # ax11.plot([xmin, xmin * factor],
518 # [ymin, ymin / (factor ** alpha)],
519 # 'k--')
520 # ax11.annotate('$1/f^{}$'.format(alpha),
521 # (
522 # xmin * np.sqrt(factor),
523 # ymin / (factor ** (alpha / 2))
524 # )
525 # )
526
527 if not kwargs.get('disable_PSD_grid'):
528 #ax11.set_xticks([.2+_/10 for _ in range(9)], minor=True)
529 ax11.grid(b=True, which='minor',
530 color='#cccccc',
531 linestyle='-.',
532 alpha=.3)
533
534 # Set nice title and axis labels
535 ax11.set_title('\\textbf{I. PSD}')
536 ax11.set_xlabel('$f$ [$\\mathrm{Hz}$]')
537 ax11.set_ylabel('$S_V (f) \cdot f$')
538
539 ax21.set_title('\\textbf{III. Slope} $\\mathbf{S_V \sim 1/f^{\\alpha}}$')
540 ax21.set_ylabel('$\\alpha$')
541
542 ax22.set_title('\\textbf{IV. PSD Integral}')
543
544 for ax in [ax22, ax12]:
545 ax.set_yscale('log')
546
547 self.plot_field_contour(ax31, show_field, **kwargs)
548
549 ax32.set_title('\\textbf{VI. Hysteresis Loop}')
550 ax32.set_ylabel('$V_H$ [$%s\\mathrm{V}$]' % (kwargs.get('unit', 'm')))
551 ax32.set_xlabel('$\\mu_0 H_{ext}$ [$T$]')
552
553 if not kwargs.get('notitle'):
554 fig.suptitle('m%d: ' % self.info.get('Nr', 431)
555 + 'Rate=$%d\\,\\mathrm{Hz}$, ' % self.info['rate']
556 + 'Length=$%s\\,\\mathrm{s}$ %s' % (self.info['length'],
557 kwargs.get(
558 'add_info', '')))
559
560 def plot_field_contour(self, ax31, show_field=0, **kwargs):
561 # Show specific field
562 field_data = self.data.query('Field == %f' % show_field)
563 if field_data.empty:
564 self.logger.error("Can not find timesignal for field (%s)" %
565 show_field)
566 else:
567 try:
568 field_spectra = self.measurements[show_field]
569 if kwargs.get('plot_timesignal', False):
570 field_data.plot(x='Time', y='Vx', legend=False, ax=ax31)
571 ax31.set_title(
572 'Timesignal ($\\mu_0 H_{ext} = %.2f\\,\\mathrm{mT}$)' % (
573 show_field * 1e3))
574 ax31.set_ylabel(
575 '$V_H$ [$%s\\mathrm{V}$]' % (kwargs.get('unit', 'm')))
576
577 # show chunksize of first spectra generated
578 num = field_spectra.spectrum.number_of_first_spectra
579 len_first_spectra = len(field_data) / num
580 for i in range(1, num):
581 x = field_data.iloc[int(len_first_spectra * i)]['Time']
582 ax31.axvline(x, linewidth=.5)
583 elif len(field_spectra.spectrum.first_spectra_df) > 1:
584 s = field_spectra.spectrum.first_spectra_df
585 freq = s.Frequency.to_numpy()
586 first_spectra = s.drop('Frequency', axis=1)
587 freq_table = np.tile(freq, (64,1)).T
588 first_spectra = first_spectra * freq_table
589 freq = field_spectra.avg_spec.freq
590 smin = kwargs.get('smin', first_spectra.min().min())
591 smax = kwargs.get('smax', first_spectra.max().max())
592 levels = np.logspace(np.log10(smin),
593 np.log10(smax),
594 kwargs.get('numlevels', 10))
595 times = np.linspace(0, field_spectra.info['Time'],
596 field_spectra.num)
597 cs = ax31.contourf(
598 times, # x = Time
599 freq, # y = Frequency
600 first_spectra, # z = all spectra
601 norm=LogNorm(vmin=smin, vmax=smax),
602 levels=levels,
603 cmap=kwargs.get('contour_cm',
604 self.style.get('default_cm')),
605 # levels=kwargs.get('contour_levels'),
606 )
607 self.time_spectra_field_contour_df = first_spectra.copy(deep=True)
608 self.time_spectra_field_contour_df['freq'] = freq
609 self.time_spectra_field_contour_df.set_index('freq', inplace=True)
610 self.time_spectra_field_contour_df.rename(
611 {'S{}'.format(_): times[_] for _ in range(field_spectra.num)},
612 axis='columns', inplace=True)
613
614 cbar = plt.gcf().colorbar(cs, ax=ax31)
615 cbar.set_label('$S_V^{(n)} (f) \cdot f$')
616 cbar.set_ticklabels(['%.2e' % _ for _ in levels])
617 ax31.set_yscale('log')
618 ax31.set_title(
619 '\\textbf{V. Time-resolved PSD} ($\\mu_0 H_{ext} = '
620 '%.0f\\,\\mathrm{mT}$)' % (show_field * 1e3)
621 )
622 ax31.set_ylabel('$f$ [Hz]')
623 except Exception as e:
624 self.logger.error("Can not plot field (H = %s) data: %s" %
625 (show_field, e))
626 raise ValueError("Can not plot field (H = %s) data: %s" %
627 (show_field, e))
628 ax31.set_xlabel('Time [s]')
629
630 def plot_various_noise_repr(self):
631 sns.set_palette(sns.color_palette("hls", len(self.measurements)))
632 fig, axes = plt.subplots(nrows=4, ncols=4,
633 gridspec_kw=dict(wspace=.3, hspace=.45))
634 for i in range(16):
635 ax = axes[i // 4][i % 4]
636 for field in self.data.sort_values(
637 'Field', ascending=False)['Field'].unique():
638 # Setting shortcut for Average Spectrum s
639 s = self.measurements[field].avg_spec
640
641 ax.plot(field, s.iloc[i]['S'], 'o')
642 ax.set_title('$S_V (f=%.3f)$' % s.iloc[i].freq)
643 plt.show()
644
645 def plot_various_noise_fits(self, **kwargs):
646 """
647 Args:
648 **kwargs:
649 """
650 sns.set_palette(sns.color_palette("hls", len(self.measurements)))
651 fig, axes = plt.subplots(nrows=kwargs.get('nrows', 3),
652 ncols=kwargs.get('ncols', 3),
653 gridspec_kw=dict(wspace=.3, hspace=.45),
654 figsize=(11.69, 8.27))
655 for i, fit_range in enumerate(kwargs.get('fit_ranges', range(3, 11))):
656 ax = axes[i // kwargs.get('nrows', 3)][i % kwargs.get('ncols', 3)]
657 for field in self.data.sort_values('Field')['Field'].unique():
658 # Setting shortcut for Average Spectrum s
659 s = self.measurements[field].avg_spec
660 fit = scipy.stats.linregress(s.lnf.iloc[:fit_range],
661 s.lnS.iloc[:fit_range])
662
663 if kwargs.get('value', 'intercept') == 'intercept':
664 ax.plot(field, 10 ** fit.intercept, 'o')
665 ax.set_yscale('log')
666 else:
667 ax.plot(field, -1 * fit.slope, 'o')
668
669 ax.set_title('Fit Range = %s' % fit_range)
670 fig.suptitle(kwargs.get('title', 'Noise Fit at 1 Hz'))
671
672 def plot_compare_timesignals(self, fields, **kwargs):
673 """ Plots a grid of 2x2 subplots to compare timesignals and histogram.
674 Args:
675 fields: list
676 factor: float Default: 1e3 (mV)
677 nrows: int Default: len(fields)
678 ncols: int Default: 2
679 sharey: bool Default: True
680 figsize: tuple Default: (11.69, 8.27) in
681 plot_range: int Default: -1
682 """
683 fig, axes = plt.subplots(nrows=kwargs.get('nrows', 1),
684 ncols=kwargs.get('ncols', len(fields)),
685 sharey=kwargs.get('sharey', False),
686 figsize=kwargs.get('figsize', (11.69, 8.27)),
687 **kwargs.get('subplot_kw', dict()))
688 twinax = []
689 factor = kwargs.get('factor', 1e3)
690 colors = kwargs.get('colors',['red',
691 'blue',
692 'green',
693 'orange',
694 'purple'])
695 for i, (f, c) in enumerate(zip(fields, colors)):
696 ax = axes[i]
697 d = self.data[self.data['Field'] ==
698 f].iloc[:kwargs.get('plot_range', -1)]
699 ax.plot(d['Time'], d['Vx']*factor, color=c, **kwargs.get('plot_kw', {}))
700 twinax.append(ax.twiny())
701 sns.distplot(d['Vx']*factor, ax=twinax[i], vertical=True, color=c, **kwargs.get('dist_kw', {}))
702 ax.legend(['$V_H (\\mathbf{\\mu_0 H_{ext}=%s\\,mT})$' % int(float(f * 1e3))])
703
704 return fig, axes, twinax
705
706 def plot_various_timesignals(self, fields, **kwargs):
707 """ Plots a grid of 9 subplots with different timesignals
708 Args:
709 fields: list
710 nrows: int Default: len(fields)
711 ncols: int Default: 2
712 sharey: bool Default: True
713 figsize: tuple Default: (11.69, 8.27) in
714 plot_range: int Default: -1
715 """
716 fig, axes = plt.subplots(nrows=kwargs.get('nrows', len(fields)),
717 ncols=kwargs.get('ncols', 2),
718 sharey=kwargs.get('sharey', True),
719 figsize=kwargs.get('figsize', (11.69, 8.27))
720 )
721 for i, f in enumerate(fields):
722 ax = axes[i // kwargs.get('nrows', 3)][i % kwargs.get('ncols', 3)]
723 d = self.data[self.data['Field'] ==
724 f].iloc[:kwargs.get('plot_range', -1)]
725 ax.plot(d['Time'], d['Vx'])
726 ax.legend(['$V_H (\\mu_0 H_{ext}=%.1f\\,\\mathrm{mT})$' % (f * 1e3)])
727
728 def spectra_field_contour(self, ax=None, **kwargs):
729 """
730 Args:
731 ax:
732 **kwargs:
733 """
734 if not ax:
735 fig, ax = plt.subplots()
736 fields = kwargs.get('fields',
737 self.data.sort_values('Field')['Field'].unique())
738
739 # Create variable for third dimension
740 spectra_df = pd.DataFrame()
741 for f in fields:
742 average_spectrum = self.measurements[f].avg_spec
743 spectra_df[f] = average_spectrum.S * \
744 average_spectrum.freq
745 frequencies = self.measurements[f].avg_spec['freq']
746 smin = kwargs.get('smin', spectra_df.min().min())
747 smax = kwargs.get('smax', spectra_df.max().max())
748 levels = np.logspace(np.log10(smin),
749 np.log10(smax),
750 kwargs.get('numlevels', 10))
751
752 size_fields = len(fields)
753 size_freq = len(frequencies)
754 if spectra_df.shape != (size_freq, size_fields):
755 self.logger.error('Cannot plot contour:\n' + \
756 'spectra_df.shape (%s) != ' + \
757 'size_freq (%s), size_fields (%s)', (
758 spectra_df.shape, size_freq, size_fields))
759 return
760
761 if size_fields < 2 or size_freq < 2:
762 self.logger.error('Cannot plot contour: ' + \
763 'size_freq (%s), size_fields (%s) incorrect' % (
764 size_freq, size_fields))
765 return
766
767 cs = ax.contourf(fields * 1e3, frequencies, spectra_df,
768 norm=LogNorm(vmin=smin, vmax=smax),
769 levels=levels,
770 cmap=kwargs.get('contour_cm',
771 self.style.get('default_cm')),
772 )
773 self.spectra_field_contour_df = spectra_df.copy(deep=True)
774 self.spectra_field_contour_df['freq'] = frequencies
775 self.spectra_field_contour_df.set_index('freq', inplace=True)
776
777 if kwargs.get('cbar', True):
778 cbar = plt.colorbar(cs, ax=ax)
779 cbar.set_ticklabels(['%.2e' % _ for _ in levels])
780 cbar.set_label('$S_{V} (f) \cdot f$')
781 ax.set_yscale('log')
782 ax.set_ylabel('$f$ [$\\mathrm{Hz}$]')
783 ax.set_xlabel('$\\mu_0 H_{ext}$ [$\\mathrm{mT}$]')
784 ax.set_title(kwargs.get('title', '\\textbf{II. PSD Contour Plot}'))
785
786 def write(self, file):
787 """Write data to a file.
788
789 Args:
790 file (str):
791 """
792 if not os.path.exists(file):
793 os.makedirs(file)
794
795 self.data.to_csv("%s_fields.dat" % file, index=False)
796 for field, m in self.measurements.items():
797 m.write("%s_spectrum_%s" % (file, field))
798
799 def read(self, file):
800 """Write data to a file.
801
802 Args:
803 file (str):
804 """
805 self.data = pd.read_csv("%s_fields.dat" % file)
806 for field in self.data['Field'].unique():
807 df = self.data.query('Field == %s' % field)
808 self.measurements[field] = \
809 RAW({'data': df['Vx']},
810 rate=1 / df['Time'].diff().median(),
811 )
812 if not self.measurements[field].read("%s_spectrum_%s" % (file,
813 field)):
814 self.measurements.pop(field)
815
816 def subtract_mean(self, inplace=False, **kwargs):
817 """Subtracts mean value from timesignal.
818
819 :param inplace: Change variable inside this object?
820 :type inplace: bool
821
822 :param fields: list of fields to fit
823
824 :return: normalized timesignal
825 """
826
827 df_fit = pd.DataFrame()
828 fields = kwargs.get('fields',
829 self.data.sort_values('Field')['Field'].unique())
830 for field in fields:
831 signal = self.data.query('Field == @field').copy()
832 mean = signal.Vx.mean()
833 signal.loc[:, 'normedVx'] = signal.Vx.copy() - mean
834
835 if inplace:
836 self.data[self.data.Field == field].loc[:, 'normedVx'] = \
837 signal.loc[:, 'normedVx']
838
839 df_fit = pd.concat([df_fit, signal])
840 return df_fit
841
842 def plot_multiple_histograms(self, steps=4,
843 fields=pd.Series(dtype=np.float64),
844 factor=1e3,
845 xlim=(-1.3, 1.3),
846 **kwargs):
847 """Plots multiple histograms using seaborn ``sns.FacetGrid``
848 with kdeplot.
849
850 default kwargs for subplots:
851 kde1_kwargs = dict(clip_on=True, shade=True, alpha=1, lw=1.5, bw=.2)
852 kde2_kwargs = kde1_kwargs.update(dict(lw=2, color='w')
853 dist_kwargs = dict(hist=True, norm_hist=True, hist_kws={'alpha': .5})
854 ax_kwargs = dict(y=0, lw=2, clip_on=True).set(xlim=(-1.3,1.3))
855 """
856 if fields.empty:
857 fields = self.data.Field.unique()
858
859 field_range = abs(fields.max() - fields.min())
860 stepsize = (field_range / steps)
861 for i in range(steps):
862 start = fields.min() + i * stepsize
863 end = start + stepsize
864 df_tmp = self.subtract_mean().query('Field >= @start and Field < @end').copy()
865 df_tmp.loc[:, 'normedVx'] *= factor
866
867 # Initialize the FacetGrid object
868 pal = sns.dark_palette(color="blue", n_colors=10, input='huls')
869 face_kwargs = dict(aspect=5, height=.8, palette=pal)
870 face_kwargs.update(kwargs.get('face_kwargs', {}))
871 kde1_kwargs = dict(clip_on=True, shade=True, alpha=1, lw=1.5, bw=.2)
872 kde1_kwargs.update(kwargs.get('kde1_kwargs', {}))
873 kde2_kwargs = kde1_kwargs.copy()
874 kde2_kwargs.update(dict(lw=2, bw=.2, color='w'))
875 kde2_kwargs.update(kwargs.get('kde2_kwargs', {}))
876 dist_kwargs = dict(hist=True, norm_hist=True, hist_kws={'alpha': .5})
877 dist_kwargs.update(kwargs.get('dist_kwargs', {}))
878 ax_kwargs = dict(y=0, lw=2, clip_on=True)
879 ax_kwargs.update(kwargs.get('ax_kwargs', {}))
880
881 g = sns.FacetGrid(df_tmp, row="Field", hue="Field", **face_kwargs)
882 g.map(sns.kdeplot, "normedVx", **kde1_kwargs)
883 g.map(sns.kdeplot, "normedVx", **kde2_kwargs)
884 # g.map(sns.distplot, "normedVx", **dist_kwargs)
885 g.map(plt.axhline, **ax_kwargs).set(xlim=xlim)
886
887 def label(x, color, label):
888 ax = plt.gca()
889 ax.text(0, kwargs.get('adjust_hspace', .3), label, fontweight="bold", color=color,
890 ha="left", va="center", fontsize=16,
891 transform=ax.transAxes)
892
893 g.map(label, "normedVx")
894
895 # Set the subplots to overlap
896 g.fig.subplots_adjust(-kwargs.get('adjust_hspace', .3))
897
898 # Remove axes details that don't play well with overlap
899 g.set_titles("")
900 g.set(yticks=[]) # , xticks=[-100 + 50*_ for _ in range(5)])
901 g.despine(bottom=True, left=True)
902 unit = 'mV' if factor == 1e3 else '\\mu{}V' if factor == 1e6 else 'V'
903 plt.gca().set_xlabel('$V_H$ [$\\mathrm{%s}$]' % unit)
904
905 def load_voltages(self, nr=508, figsize=(16, 12)):
906 def load_data(datapath):
907 meas_data = {}
908 meas_info = {}
909 all_data = {}
910 for f in datapath:
911 f_info = self.get_info_from_name(f)
912 sr = f_info['Vin']
913 nr = f_info['nr']
914 meas_info[sr] = f_info
915 meas_data[sr] = pd.read_csv(f, sep='\t')
916 new_df = meas_data[sr]
917 new_df['Vin'] = float(sr[:-2])
918 if nr in all_data.keys():
919 all_data[nr] = pd.concat([all_data[nr], new_df])
920 else:
921 all_data[nr] = new_df
922 return meas_data, meas_info, all_data
923
924 def calc_PSD(meas_data):
925 meas_obj = {}
926 for sr, data_df in meas_data.items():
927 if len(data_df['Vx']) % 1024:
928 avg = len(data_df['Vx']) // 1024
929 d = data_df['Vx'].iloc[:-(len(data_df['Vx']) % 1024)]
930 else:
931 d = data_df.Vx
932
933 max_len = len(d)
934
935 data = {
936 'data': d,
937 'info': {
938 'Nr': meas_info[sr]['nr'],
939 'rate': 1 / data_df.time.diff().mean(),
940 'length': max_len * data_df.time.diff().mean(),
941 }
942 }
943
944 meas_obj[sr] = RAW(data,
945 rate=data['info']['rate'],
946 nof_first_spectra=32,
947 calc_first=True,
948 downsample=False,
949 )
950 return meas_obj
951
952 def merge_data(meas_obj, cutoff_frequency=.9):
953 diff_voltages = pd.DataFrame()
954 for sr, m in meas_obj.items():
955 s = m.avg_spec
956 s = s[s.freq < cutoff_frequency]
957 if len(s) < 2:
958 continue
959 newdf = pd.DataFrame()
960 newdf['freq'] = s.freq
961 newdf['S'] = s.S
962 newdf['Vin'] = float(sr[:-2])
963 diff_voltages = pd.concat([diff_voltages, newdf])
964 return diff_voltages
965
966 def plot_PSD_classic(diff_voltages, title, groupby_category='Vin',
967 num=10, style=[['science'], {'context': 'talk', 'style': 'white', 'palette': 'bright', }]):
968 set_style(style)
969 c1 = sns.color_palette("hls", num)
970 sns.set_palette(c1)
971 fig, ax = plt.subplots(figsize=(12, 8))
972 # g = sns.relplot(x='freq', y='S', hue='Vin', data=diff_voltages, height=5, kind='line')
973 grouped = diff_voltages.groupby(groupby_category)
974 for group in grouped.groups.keys():
975 grouped.get_group(group).plot(x='freq', y='S', kind='line',
976 loglog=True, ax=ax,
977 label=group,
978 xlabel='Frequency [Hz]',
979 ylabel='$S_{V_H}$ [$\\mathrm{V}^2/\\mathrm{Hz}$]',
980 )
981 ax.set_title(title)
982 # save_plot('m506', 'png')
983
984 def show_PSD_classic(diff_voltages, title, ax=None, groupby_category='Vin',
985 num=10, style=[['science'], {'context': 'talk', 'style': 'white', 'palette': 'bright', }]):
986 if not ax:
987 fig, ax = plt.subplots(figsize=(12, 8))
988 set_style(style)
989 c1 = sns.color_palette("hls", num)
990 sns.set_palette(c1)
991 # g = sns.relplot(x='freq', y='S', hue='Vin', data=diff_voltages, height=5, kind='line')
992 grouped = diff_voltages.groupby(groupby_category)
993 for group in grouped.groups.keys():
994 grouped.get_group(group).plot(x='freq', y='S', kind='line',
995 loglog=True, ax=ax,
996 label=group,
997 xlabel='Frequency [Hz]',
998 ylabel='$S_{V_H}$ [$\\mathrm{V}^2/\\mathrm{Hz}$]',
999 )
1000 ax.set_title(title)
1001 return ax
1002
1003 datapath = glob('./data/MFN/m%s/*' % nr)
1004 meas_data, meas_info, all_data = load_data(datapath)
1005 meas_obj = calc_PSD(meas_data)
1006 diff_voltages = merge_data(meas_obj)
1007 fig, ax = plt.subplots(figsize=figsize)
1008 show_PSD_classic(diff_voltages, 'm%s: Compare different Voltages' % nr, ax=ax)
1009
1010 inset2 = inset_axes(ax, width='100%', height='100%',
1011 bbox_to_anchor=(.54, .75, .3, .25),
1012 bbox_transform=ax.transAxes)
1013 inset3 = inset_axes(ax, width='100%', height='100%',
1014 bbox_to_anchor=(.1, .1, .3, .25),
1015 bbox_transform=ax.transAxes)
1016
1017 grouped = diff_voltages.groupby('Vin')
1018 for group in grouped.groups.keys():
1019 g = grouped.get_group(group)
1020 fit_area = g.query('freq > %f and freq < %f' % (2e-2, 7e-1))
1021 fit_area['lnf'] = np.log10(fit_area.freq)
1022 fit_area['lnS'] = np.log10(fit_area.S)
1023 fit = scipy.stats.linregress(fit_area.lnf, fit_area.lnS)
1024 intercept, slope = fit.intercept, -fit.slope
1025 voltage = group
1026
1027 inset2.plot(voltage, 10 ** intercept, 'o')
1028 inset3.plot(voltage, slope, 'o')
1029
1030 inset2.set_xlabel('Voltage [$\mathrm{V}$]')
1031 inset2.set_ylabel('$S_{V_H} (f=1\\;$Hz$)$')
1032 inset2.set_yscale('log')
1033
1034 inset3.set_xlabel('Voltage [$\mathrm{V}$]')
1035 inset3.set_ylabel('$\\alpha$')
MeasurementClass
1class MeasurementClass(object):
2 """
3 The main measurement class for all measurements.
4 """
5 def __init__(self):
6 """
7 .. note:: **MeasurementClass:**
8 This Class is the parent of all other measurement classes.
9
10
11 All measurements have the following variables:
12
13 name: str/None
14 The name of the Measurement Class (e.g. SA, RAW, etc.)
15
16 info: dict
17 Contains informations about the measurement. Typical keys are:
18 Nr: float, the number of the measurement,
19
20 """
21 self.name = ''
22 self.data = {}
23 self.info = {'Nr': 0}
24 self.info_file = {}
25 self.style = PlottingClass()
26
27 def i(self, *key):
28 """
29 Get a specific measurement number information.
30
31
32 :param *key: Will be passed to info.get
33
34 :return: info.get(*key)
35
36 """
37 return self.info.get(*key)
38
39
40 __repr__ = lambda self: '%s (Nr. %s)\n' % (self.name, self.i('Nr', 0))
41
42 __str__ = lambda self: '%s (Nr. %s)\n' % (self.name, self.i('Nr', 0)) + \
43 '\n'.join(['%s:\t%s' % (key, val) for key, val in self.info.items()])
44
45
46 def get_info_from_name(self, name, **kwargs):
47 """
48 Extracting information from filenames.
49 Using a default regex first or trying to extract 'key-value' pairs
50 separated by '_'.
51
52 :param name: filename that contains informations.
53 :param kwargs:
54 :str regex: Regular expression for the filename
55 :return: dict(key=value)
56 """
57
58 # Default Regex Structure
59 # Nr, Struct, deg, Type1
60 # Type2, Field, Date, Time
61 # I1, I2, Lock-In (Connections)
62 # Voltage In, Resistors (R11, R12)
63 # R13, R21
64 # Capacitors (C11, C21)
65 # Temp
66 def_regex = ".*[Mm]([0-9.]*)_([A-Za-z]*)_([0-9.-]*)deg_([A-Za-z]*)_" \
67 "([A-Za-z]*)_*B_([0-9.-]*)T_([0-9]*)_([0-9]*)_I1-([" \
68 "0-9\\-]*)_I2-([0-9\\-]*)_G[PB]I[BP].-([0-9\\-]*)_Vin-([" \
69 "0-9.]*)V_R11-([0-9]*.)O_R12-([0-9.]*.)O_R13-([" \
70 "0-9.]*.)_R21-([0-9.]*.)O_C11-([0-9]*)_C21-([0-9]*)_T-([" \
71 "0-9]*)K.*"
72 regex = kwargs.get('regex', def_regex)
73 reg = re.match(regex, name)
74
75 info_dict = dict()
76 if not reg:
77 filename = name.split(os.sep)[-1][:-4]
78 raw_info = filename.split('_')
79 for element in raw_info:
80 regex_dict = {
81 'nr': 'm([0-9.]*)',
82 'deg': '((neg)?[0-9.-]*)deg',
83 'dir': '([Uu]p|[Dd]own)',
84 'date': '(20[12][90][0-9]*)',
85 'time': '([012][0-9][0-6]*)',
86 'type1': '(RAW|Hloop)',
87 'type2': '(Parallel|Gradio)',
88 'struct': '([Pp]lusses|[Cc]rosses|[Ee]mpty)',
89 'field': 'p?m?([0-9.-]+)m?T',
90 }
91 if re.match(regex_dict['field'], element):
92 info_dict['field'] = re.match(regex_dict['field'], element).groups()[0]
93 continue
94
95 items = element.split('-')
96 if len(items) == 1:
97 for key, single_regex in regex_dict.items():
98 reg = re.match(single_regex, items[0])
99 if reg:
100 info_dict[key] = reg.groups()[0]
101 elif len(items) == 2:
102 key, value = items
103 info_dict[key] = value
104 elif len(items) > 2:
105 value = '-'.join(items[1:])
106 info_dict[items[0]] = value
107 else:
108 nr, struct, deg, type1, \
109 type2, field, date, time, \
110 i1, i2, lock_in, \
111 vin, r11, r12, r13, r21, \
112 c11, c21, temp = reg.groups()
113 try:
114 info_dict['nr'] = float(nr)
115 info_dict['field'] = float(field)
116 except ValueError:
117 print('Expected float field and nr, got:'
118 ' %s, %s' % (field, nr))
119 info_dict['srtuct'] = struct
120 info_dict['deg'] = deg
121 info_dict['type1'] = type1
122 info_dict['type2'] = type2
123 info_dict['date'] = date
124 info_dict['time'] = time
125 info_dict['i1'] = i1
126 info_dict['i2'] = i2
127 info_dict['lock_in'] = lock_in
128 info_dict['r11'] = r11
129 info_dict['r12'] = r12
130 info_dict['r13'] = r13
131 info_dict['r21'] = r21
132 info_dict['vin'] = vin
133 info_dict['c11'] = c11
134 info_dict['c21'] = c21
135 info_dict['temp'] = temp
136
137 self.info_file = info_dict
138
139 return info_dict
140
141 def calc_log(self, df, keys=['f', 'Vx', 'Vy']):
142 for key in keys:
143 df['ln%s' % key] = np.log10(df[key])
SingleM
1class SingleM(MeasurementClass):
2 def __init__(self):
3 """The main constructor for all single measurements."""
4 super().__init__()
5 self.data = pd.DataFrame()
6
7 linear_regression = lambda self, x, y: scipy.stats.linregress(x, y)
8
9 def fit_variable(self, df, x, y):
10 """Applys a linear regression and adds result to DataFrame
11
12 Args:
13 df (pd.DataFrame): Fit data
14 x (np.ndarray): Fit variable.
15 y (np.ndarray): Fit variable.
16 """
17 fit = self.linear_regression(x[1], y[1])
18 for var in [x, y]:
19 df[var[0]] = var[1]
20
21 return df
RAW
1class RAW(SingleM):
2 def __init__(self, data, **kwargs):
3 """
4 RAW Measurement:
5 Measurements with time signal only.
6
7 .. code-block:: python
8 :linenos:
9 :caption: Example
10
11 data = np.array(timesignal, shape=(n,))
12 data = {
13 'data': np.array(timesignal, shape=(n,))
14 'info': {'Nr': 123, 'Add Info': 'Important Informations'}
15 }
16 raw = ana.RAW(data)
17
18 :param data: Different possibilities.
19
20 :param rate: At which rate was the timesignal measured (samplingrate) [Hz]
21 :type rate: float, default: 2e-4
22
23 :info: First Spectrum parameters
24 :param nof_first_spectra: Cut timesignal into # pieces and average.
25 :type nof_first_spectra: int, default: 64
26
27 :param first_spectra_highest_frequency: Cut higher frequencies in final spectrum than this.
28 :type first_spectra_highest_frequency: float, default: rate/8
29
30 :param downsample: Downsample the timesignal before calculation.
31 :type downsamle: bool, default: True
32
33 :param calc_first: Triggers calculation of first spectrum.
34 :type calc_first: bool, default: False
35
36 :info: Second Spectrum parameters
37 :param highest_octave: The highest octave starting point [Hz] of the second spectrum.
38 :type highest_octave: int, default: 64
39
40 :param minimum_points_in_octave: If octave would contain less points, stop.
41 :type minimum_points_in_octave: int, default: 10
42
43 :param calc_second: Triggers calculation of second spectrum.
44 :type calc_second: bool, default: False
45 """
46 super().__init__()
47 self.logger = logging.getLogger('RAW')
48 self.name = 'RAW'
49 # Default Parameter for the first and second spectrum
50 timesignal = data.get('data',
51 kwargs.get('timesignal',
52 pd.Series([], dtype='float64')
53 )
54 )
55 self.info = data.get('info', {})
56 self.data = data
57 self.rate = kwargs.get('rate', 2e-4)
58
59 # Define dummies
60 self.avg = 0
61 self.spectrum = None
62 self.spec = []
63 self.avg_spec = pd.DataFrame()
64 self.freq2 = np.array([])
65 self.time_signal_second_spectrum_transposed_normalized = np.array([])
66 self.second_spectrum_time_array = np.array([])
67
68 self.timesignal = self.check_timesignal(timesignal)
69 self.n = int(1024 * self.avg)
70 self.highest_octave = kwargs.get('highest_octave', 64)
71 self.minimum_points_in_octave = kwargs.get('minimum_points_in_octave',
72 10)
73 self.shifting_method = False
74 self.short_mode = 1
75 self.filter_type = 'lowpass'
76 self.passband_factor = 1
77 self.filter_order = 6
78 self.filter_analog = False
79 self.downsample = kwargs.get('downsample', True)
80 self.f_max = kwargs.get(
81 'first_spectra_highest_frequency', self.rate / 8)
82 self.num = kwargs.get('nof_first_spectra', 64)
83
84 self.info.update({
85 "Time": self.n / self.rate,
86 "Num": self.n,
87 "Rate": self.rate,
88 "Avg": self.avg,
89 "downsample": self.downsample,
90 "f_max": self.f_max,
91 "NofSpectra": self.num,
92 })
93
94 if kwargs.get('calc_first'):
95 self.calc_first_spectrum()
96
97 if self.avg_spec.empty:
98 return
99
100 max_freq = self.avg_spec['freq'].max()
101 self.info.update({
102 # "f_min": freq[0],
103 # 'f_max': freq.max(),
104 "f_min": self.avg_spec['freq'].min(),
105 'f_max': max_freq,
106 })
107
108 if kwargs.get('calc_second'):
109 self.calc_second_spectrum(highest_octave=max_freq)
110
111 def check_timesignal(self, timesignal):
112 """ converts pd.DataFrame['Vx'] and Series into numpy array.
113
114 :param timesignal: input timesignal
115 :return: converted timesignal.
116 :rtype: numpy.ndarray
117 """
118 if isinstance(timesignal, pd.DataFrame):
119 ts = np.array(timesignal['Vx'])
120 elif isinstance(timesignal, pd.Series):
121 ts = timesignal.to_numpy()
122 else:
123 ts = timesignal
124
125 self.avg = (len(ts) / 1024.)
126 if self.avg != int(self.avg):
127 self.avg = int(len(ts) // 1024)
128 ts = ts[:int(self.avg * 1024)]
129
130 if self.rate == 0:
131 self.rate = 1
132
133 return ts
134
135 def calc_first_spectrum(self):
136 """
137 Sets the variable ``spectrum`` to a
138 :class:`spectrumanalyzer.SpectrumAnalyzer` object.
139
140 .. important::
141 Calculates the first spectrum.
142
143
144 :raises NameError: spectrumanalyzer is not installed
145 :return: None
146
147 """
148
149 try:
150 self.spectrum = SpectrumAnalyzer(
151 # filepath=filename,
152 timesignal=self.timesignal,
153 samplingrate=self.rate,
154 averages=self.avg,
155 shifting_method=self.shifting_method,
156 short_mode=self.short_mode,
157 filter_type=self.filter_type,
158 passband_factor=self.passband_factor,
159 filter_order=self.filter_order,
160 filter_analog=self.filter_analog,
161 first_spectra_highest_frequency=self.f_max,
162 downsample=self.downsample,
163 )
164 except NameError:
165 raise NameError("SpectrumAnalyzer module not installed.")
166
167 for spectrum_arr, freq, k in self.spectrum.cut_timesignal(self.num):
168 self.spec.append(spectrum_arr)
169
170 self.avg_spec = pd.DataFrame({
171 'freq': self.spectrum.frequency_span_array,
172 'S': self.spectrum.first_spectrum})
173
174 def calc_second_spectrum(self, **kwargs):
175 """
176 Calculating the second spectrum.
177 """
178
179 self.spectrum.calculate_second_spectrum(
180 highest_octave=kwargs.get('highest_octave', self.highest_octave),
181 minimum_points_in_octave=kwargs.get('minimum_points_in_octave',
182 self.minimum_points_in_octave),
183 peak_filter=kwargs.get('peak_filter', False)
184 )
185
186 self.freq2 = self.spectrum.frequency_span_array[
187 self.spectrum.frequency_span_array > 0
188 ]
189
190 octaves = [self.highest_octave]
191 for i in range(30):
192 freq = np.fft.fftfreq(
193 int(self.n / self.spectrum.number_of_first_spectra),
194 1. / self.rate)[3:]
195 freq = freq[freq > 0]
196 freq = freq[freq < self.f_max]
197 points_in_octave = len(
198 freq[freq < self.highest_octave / (2. ** (i + 1))])
199 number_of_octaves = i
200 if points_in_octave < self.minimum_points_in_octave:
201 break
202 else:
203 octaves = np.append([min(octaves) / 2.], octaves)
204
205 self.info.update({
206 'NofOct': number_of_octaves,
207 })
208
209 # Calculate Timesignal if needed
210 if not kwargs.get('skip_timesignal', False):
211 self.calc_time_signal_second_spectrum()
212
213 def calc_time_signal_second_spectrum(self):
214 time_signal_second_spectrum_transposed = \
215 self.spectrum.time_signal_second_spectrum.transpose()
216 second_spectrum_time_array = \
217 np.arange(0, len(time_signal_second_spectrum_transposed[0])) * \
218 self.spectrum.timestep_second_spectrum
219 time_signal_second_spectrum_transposed_normalized = \
220 np.zeros((len(time_signal_second_spectrum_transposed),
221 len(time_signal_second_spectrum_transposed[0])))
222 time_signal_second_spectrum_transposed_normalized = \
223 np.fliplr(time_signal_second_spectrum_transposed_normalized)
224
225 for p in range(self.spectrum.number_of_octaves):
226 time_signal_second_spectrum_transposed_normalized[p] = \
227 time_signal_second_spectrum_transposed[p] / np.mean(
228 time_signal_second_spectrum_transposed[p])
229
230 self.time_signal_second_spectrum_transposed_normalized = \
231 time_signal_second_spectrum_transposed_normalized
232 self.second_spectrum_time_array = second_spectrum_time_array
233
234
235 def plot_spectrum(self, ax, ):
236 self.avg_spec.plot(x='freq', y='S', loglog=True, ax=ax)
237
238 def plot_time(self, ax, ):
239 ax.plot(np.arange(len(self.timesignal)), self.timesignal,
240 color='red', label='time', linewidth=.2)
241
242 def subtract_mean(self, inplace=False):
243 """Subtracts mean value from timesignal.
244
245 :param inplace: Change variable inside this object?
246 :type inplace: bool
247
248 :return: normalized timesignal
249 """
250 norm_ts = self.timesignal - np.mean(self.timesignal, axis=1)
251
252 if inplace:
253 self.timesignal = norm_ts
254
255 return norm_ts
256
257 def write(self, file):
258 """
259 Writing data to a file.
260
261 Parameters
262 ----------
263 file: str
264 """
265 if self.avg_spec.empty:
266 return
267
268 if not os.path.exists(file):
269 os.makedirs(file)
270
271 all_first_spectra = self.spectrum.first_spectra_df.loc[:]
272 all_first_spectra['Avg'] = self.avg_spec['S']
273
274 all_first_spectra.to_csv("%s_first.dat" % file, index=False)
275 # all_first_spectra.to_feather("%s_first.feather" % file)
276
277 if not self.spectrum.second_spectra.any():
278 return
279
280 all_second_spectra = pd.DataFrame(self.spectrum.second_spectra,
281 index=False)
282 all_second_spectra['Frequency'] = \
283 self.spectrum.frequency_span_array_second_spectra
284 all_second_spectra.to_csv('%s_second.dat' % file,
285 index=False)
286 # all_second_spectra.to_feather('%s_second.feather' % file)
287
288 all_second_spectra_time = pd.DataFrame(
289 self.spectrum.time_signal_second_spectrum)
290 all_second_spectra_time.to_csv('%s_second_time.dat' % file,
291 index=False)
292 # all_second_spectra_time.to_feather('%s_second_time.feather' % file)
293
294 def read(self, file):
295 """
296 Reading data from file.
297
298 Parameters
299 ----------
300 file: str
301 Filename base to read data from.
302
303 Returns
304 -------
305 self
306 """
307
308 try:
309 first_spectra = pd.read_csv("%s_first.dat" % file)
310 except FileNotFoundError:
311 self.logger.error('First Spectrum at %s_first.dat not found!',
312 file)
313 return False
314
315 try:
316 self.spectrum = SpectrumAnalyzer(
317 timesignal=self.timesignal,
318 samplingrate=self.rate,
319 averages=self.avg,
320 shifting_method=self.shifting_method,
321 short_mode=self.short_mode,
322 filter_type=self.filter_type,
323 passband_factor=self.passband_factor,
324 filter_order=self.filter_order,
325 filter_analog=self.filter_analog,
326 first_spectra_highest_frequency=self.f_max,
327 downsample=self.downsample,
328 )
329 except Exception as e:
330 self.logger.error("Can not create SpectrumAnalyzer Object: %s", e)
331 return False
332
333 try:
334 self.avg_spec = first_spectra[['Frequency', 'Avg']].loc[:]
335 self.avg_spec.rename({'Avg': 'S',
336 'Frequency': 'freq'},
337 axis='columns', inplace=True)
338
339 self.num = first_spectra.shape[1] - 2
340 self.spectrum.number_of_first_spectra = self.num
341
342 self.spectrum.frequency_span_array = first_spectra['Frequency']
343 self.spectrum.finalize_first_spectrum(
344 first_spectra.drop('Avg', axis=1))
345 except Exception as e:
346 self.logger.error("Can not load variables: %s", e)
347 return False
348
349 try:
350 second_spectrum = pd.read_csv("%s_second.dat" % file)
351 second_spectrum_time = pd.read_csv("%s_second_time.dat" % file)
352 except FileNotFoundError:
353 self.logger.info('Second Spectrum (%s_second.dat) not found!',
354 file)
355 return True
356
357 try:
358 self.spectrum.frequency_span_array_second_spectra = \
359 second_spectrum['Frequency']
360 self.spectrum.second_spectra = second_spectrum.drop('Frequency',
361 axis=1)
362 self.spectrum.time_signal_second_spectrum = second_spectrum_time
363 except Exception as e:
364 self.logger.error("Can not load second spectrum variables: %s", e)
365 return False
366
367 self.logger.warning("Second Spectrum loaded but not all variables"
368 "are implemented yet. Only access to \n"
369 "- second_spectra\n"
370 "- time_signal_second_spectrum\n"
371 "- frequency_span_array_second_spectra")
372 return True
SA
1class SA(SingleM):
2 """Loads and analyzes datafiles from :instrument:`SR785`"""
3 def __init__(self, *args, **kwargs):
4 """
5 Args:
6 *args:
7 **kwargs:
8
9 Returns:
10 None
11
12 Raises:
13 * ValueError
14 """
15 super().__init__()
16 self.logger = logging.getLogger('SA')
17 self.name = 'SA'
18 try:
19 self.data = kwargs.get('data', args[0])
20 except Exception:
21 error_message = "Not Able to find DataFrame. Please use SA_measurement(data)."
22 self.logger.critical(error_message)
23 raise ValueError(error_message)
24
25 if isinstance(self.data, dict):
26 self.df = self.data.get('data')
27 self.info = self.data.get('info')
28 else:
29 self.df = self.data
30
31 # Convert data to numeric values
32 try:
33 self.df.f = pd.to_numeric(self.df.f, errors='coerce')
34 self.df.Vx = pd.to_numeric(self.df.Vx, errors='coerce')
35 self.df.Vy = pd.to_numeric(self.df.Vy, errors='coerce')
36 except Exception:
37 error_message = "Unable to convert data to numeric."
38 self.logger.critical(error_message)
39 raise ValueError(error_message)
40
41 # Redirect functions
42 self._draw_oneoverf = self.style.draw_oneoverf
43 self._set_plot_settings = self.style.set_plot_settings
44 self._calc_log = self.calc_log
45
46 def __repr__(self):
47 ret = 'SA Measurement (Nr. %s)\n' % self.info.get('Nr', 0)
48 for key, val in self.info.items():
49 ret += '%s:\t%s\n' % (key, val)
50 return ret
51
52
53 def plot(self, **kwargs):
54 """Plotting the Data on given Axis or creating a new Plot for plotting.
55
56 .. code-block:: python
57 :linenos:
58 :caption: Signature
59
60 plot(ax, label=None, color=None, linestyle=None,
61 plot_x='f', plot_y='Vx', dont_set_plot_settings=False,
62
63 xscale='log', yscale='log', xlim=(None, None), ylim=(None,
64 None), legend_location='best', grid=None, title=None,
65 xlabel='f [Hz]', ylabel='S_VH [V2/Hz]',
66
67 )
68
69 Args:
70 **kwargs:
71
72 Note:
73 If **dont_set_plot_settings** is True, all kwargs below are not
74 used.
75 """
76 ax = kwargs.get('ax')
77 if not ax:
78 fig, ax = plt.subplots()
79
80 plotargs = {}
81 if 'label' in kwargs:
82 plotargs['label'] = kwargs.get('label')
83 if 'color' in kwargs:
84 plotargs['color'] = kwargs.get('color')
85 if 'linestyle' in kwargs:
86 plotargs['linestyle'] = kwargs.get('linestyle')
87
88 ax.plot(self.df[kwargs.get('plot_x', 'f')],
89 self.df[kwargs.get('plot_y', 'Vx')],
90 **plotargs)
91
92 if not (kwargs.get('dont_set_plot_settings', False)):
93 self._set_plot_settings(**kwargs)
94
95 def fit(self, fit_range=(1e-2, 7e-1)):
96 """Fits a linear regression
97
98 Args:
99 fit_range (tuple, optional, default: (1e-2, 1e0).):
100 """
101 self.calc_log(self.df, ['f', 'Vx', 'Vy'])
102 xmin, xmax = fit_range
103
104 fit_area = self.df[self.df['f'] < xmax]
105 fit_area = fit_area[fit_area.f > xmin]
106 f = scipy.stats.linregress(fit_area.lnf, fit_area.lnVx)
107
108 self.info['Slope'] = f.slope
109 self.info['Intercept'] = f.intercept
110 return f.intercept, f.slope
Hloop
1class Hloop(SingleM):
2 """This class represents the measurement of a hysteresis loop. It evaluates
3 and plots the data.
4
5 It collects all files that match the following regex:
6
7 .. code-block:: python
8
9 files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number)
10
11 The files should contain a file that contains the word "up" or "down"
12 indicating the direction of the field sweep.
13
14 If one file contains the word 'Gradio' it expects a gradiometry
15 measurement. If the filename contains the word 'Parallel' it expects a
16 parallel measurement. If the filename contains neither of these words the
17 variables need to be set manually.
18 """
19
20 def __init__(self, measurement_number, **kwargs):
21 """
22 Args:
23 measurement_number:
24 **kwargs:
25 """
26 super().__init__()
27 self.logger = logging.getLogger('Hloop')
28 self.name = 'Hloop'
29 # Defining Constants
30 header_gradio = ["Time", "B", "Vx8", "Vy8", "Vr8", "Vth8", "TempRO CU",
31 "TempRO YOKE", "TempCX CU", "SampTemp", "Temp1K Pot",
32 "TempCharcoal"]
33 header_parallel = ["Time", "B", "Vx8", "Vy8", "Vr8", "Vth8", "Vx9",
34 "Vy9", "Vr9", "Vth9", "Vx13", "Vy13", "Vr13",
35 "Vth13", "TempRO CU", "TempRO YOKE", "TempCX CU",
36 "SampTemp", "Temp1K Pot", "TempCharcoal"]
37 self.c_list = [0, 6.8, 10, 18, 27, 39, 56, 82, 100, 150, 180, 270,
38 330] # Condensators in pF
39 self.r_list = [0, 10, 100, 200, 300, 392, 475, 562, 619, 750, 825, 909,
40 1e3] # Resistors in k Ohm
41
42 self.n = 1369288448319507.5 # Carrier Concentration calculated from perp. sweep
43
44 if isinstance(measurement_number, float) or isinstance(
45 measurement_number, int):
46 files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number)
47 else:
48 files = glob(measurement_number)
49 files = kwargs.get('file_list', files)
50 if not (files):
51 raise NameError(
52 "No File for measurement Nr. %s found." % measurement_number)
53
54 for fname in files:
55 file_info = None
56 try:
57 if fname.find('Gradio') > 0:
58 reg = re.match(
59 ".*[Mm]([0-9.]*)_([A-Za-z]*)_([0-9.-]*)deg_"
60 "([A-Za-z]*)_"
61 "([A-Za-z]*)_*([A-Za-z]*)_([0-9]*)_([0-9]*)_"
62 "I1-([0-9\-]*)_I2-([0-9\-]*)_G.*-([0-9\-]*)_"
63 "Vin-([0-9.]*)V_R11-([0-9]*.)O_R12-([0-9.]*.)O_"
64 "R13-([0-9.]*.)_R21-([0-9.]*.)O_"
65 "C11-([0-9]*)_C21-([0-9]*)_"
66 "T-([0-9]*)K_SR-([0-9.]*)-T-min",
67 fname)
68 if not reg:
69 raise NameError("%s not fitting Regex" % fname)
70 m_no, struct, deg, m_type, \
71 m_dir, m_type2, m_date, m_time, \
72 m_i1, m_i2, m_li1, \
73 m_vin, m_r11, m_r12, \
74 m_r13, m_r21, \
75 m_c11, m_c21, \
76 m_T, m_SR = reg.groups()
77 header = header_gradio
78 self.parallel = False
79 elif fname.find('Parallel') > 0:
80 reg = re.match(
81 ".*[Mm]([0-9.]*)_([A-Za-z_]*)_([0-9.-]*)deg_([A-Za-z]*)_"
82 + "([A-Za-z]*)_*([A-Za-z]*)_([0-9]*)_([0-9]*)_" \
83 + "I-([0-9\-]*)_(G.*-[0-9\-]*)_" \
84 + "Vin-([0-9.]*)V_R11-([0-9]*.)O" \
85 + ".*_" \
86 + "T-([0-9]*)K_SR-([0-9.]*)-T-min",
87 fname)
88 if not reg:
89 raise NameError("%s not fitting Regex" % fname)
90 m_no, struct, deg, m_type, \
91 m_dir, m_type2, m_date, m_time, \
92 m_i1, m_li1, \
93 m_vin, m_r11, \
94 m_T, m_SR = reg.groups()
95 m_i2 = m_r12 = m_r13 = m_r21 = m_c11 = m_c21 = '0'
96 header = header_parallel
97 self.parallel = True
98 else:
99 raise NameError("%s not fitting Regex" % fname)
100 except NameError:
101 file_info = self.get_info_from_name(fname)
102
103 self.parallel = file_info.get('type2') == 'Parallel'
104 header = header_gradio if file_info.get('type2') == 'Gradio' else header_parallel
105 header = kwargs.get('header', header)
106 kwargs.update(file_info)
107
108 if isinstance(file_info.get('deg'), str) and \
109 file_info.get('deg').find('neg') > -1:
110 file_info['deg'] = file_info.get('deg').replace('neg', '-')
111 deg = file_info.get('deg') if file_info.get('deg') else 0.01
112 m_dir = file_info.get('dir', 'down').lower()
113
114 if fname.find('b.dat') > 0:
115 m_dir = 'up'
116
117
118 ### Loading File
119 if (m_dir.lower() == 'up'):
120 self.fname_up = fname
121 self.up = pd.read_csv(fname, sep='\t', names=header,
122 skiprows=3)
123 self.up.B = self.up.B * 1e3
124 else:
125 self.fname_down = fname
126 self.down = pd.read_csv(fname, sep='\t', names=header,
127 skiprows=3)
128 self.down.B = self.down.B * 1e3
129
130 if (kwargs.get('down_only')):
131 self.up = self.down
132 if (kwargs.get('up_only')):
133 self.down = self.up
134
135 self.measurement_number = measurement_number
136 self.factor = 1
137
138 # Mirror all Angles bigger than 90
139 if kwargs.get('force_mirror') or \
140 (kwargs.get('auto_mirror', True) and \
141 float(deg) >= 90):
142 self.set_factor(-1)
143 self.factor = 1
144
145 if file_info:
146 self.info = file_info
147 self.info.update({
148 'Type': "%s (%s)" % (file_info.get('type'),
149 file_info.get('type2')),
150 'Structure': file_info.get('struct'),
151 'Angle': file_info.get('deg'),
152 })
153 else:
154 m_datetime = "%s.%s.%s %s:%s" % (
155 m_date[6:], m_date[4:6], m_date[:4], m_time[:2], m_time[2:])
156 self.info = {
157 'Type': "%s (%s)" % (m_type, m_type2),
158 'Date': m_datetime,
159 'Structure': struct,
160 'Angle': deg,
161 'I1': m_i1,
162 'I2': m_i2,
163 'Vin': m_vin,
164 'R11': m_r11,
165 'R12': m_r12,
166 'R13': m_r13,
167 'R21': m_r21,
168 'C11': self.c_list[int(m_c11)],
169 'C21': self.c_list[int(m_c21)],
170 'T': m_T,
171 'SR': m_SR,
172 'Additional': ''}
173
174 # Update data from kwargs
175 for key, value in kwargs.items():
176 if key in self.info.keys():
177 self.info[key] = value
178
179 def set_factor(self, factor):
180 """Multiplying the Voltage by a Factor
181
182 Args:
183 factor:
184 """
185 self.up.Vx8 /= self.factor
186 self.down.Vx8 /= self.factor
187 if (self.parallel):
188 self.up.Vx9 /= self.factor
189 self.up.Vx13 /= self.factor
190 self.down.Vx9 /= self.factor
191 self.down.Vx13 /= self.factor
192
193 self.factor = factor
194
195 self.up.Vx8 *= self.factor
196 self.down.Vx8 *= self.factor
197 if (self.parallel):
198 self.up.Vx9 *= self.factor
199 self.up.Vx13 *= self.factor
200 self.down.Vx9 *= self.factor
201 self.down.Vx13 *= self.factor
202
203 def __repr__(self):
204 return self.get_default_title()
205
206 def calc_B(self, V):
207 """
208 Args:
209 V:
210 """
211 e = scipy.constants.physical_constants['electron volt'][0]
212 R = V / 2.5e-6
213 B = R * self.n * e
214 return B
215
216 def remove_zero(self, columns=None):
217 """Removes the values 0.0 from up and down sweep where the lock-in
218 didn't return any values (read error will be saved as 0.0).
219
220 Args:
221 columns:
222 """
223 # Set Columns to remove values from
224 if columns == None:
225 if self.parallel:
226 columns = ['Vx8', 'Vx9', 'Vx13']
227 else:
228 columns = ['Vx8']
229
230 # Remove zero values from columns (= measurement error)
231 for v in columns:
232 self.up = self.up[self.up[v] != 0.0]
233 self.down = self.down[self.down[v] != 0.0]
234
235 def fit(self, cond=pd.Series()):
236 """
237 Fitting the Voltage Signal.
238
239 For Parallel measurement:
240
241 the empty cross Vx13 is subtracted from the signal Vx8/Vx9
242
243 For gradiometry:
244
245 A condition can be added to determine the amount of
246 datapoints to fit (default: B < B_min + 150mT).
247
248 .. code-block:: python
249 :caption: Example
250
251 cond = (self.up.B < self.up.B.min() + 150)
252 m.fit(cond)
253
254 Args:
255 cond:
256 """
257 # Set Fitting Condition
258 if (cond.empty):
259 cond = (self.up.B < self.up.B.min() + 150)
260
261 ### Fitting Graph
262 self.up_fitted = self.up.copy()
263 self.down_fitted = self.down.copy()
264
265 if (self.parallel):
266 self.up_fitted.Vx8 -= self.up_fitted.Vx13
267 self.up_fitted.Vx9 -= self.up_fitted.Vx13
268 self.down_fitted.Vx8 -= self.down_fitted.Vx13
269 self.down_fitted.Vx9 -= self.down_fitted.Vx13
270 else:
271 fit_area = self.up[cond].copy()
272 fit = scipy.stats.linregress(fit_area.B, fit_area.Vx8)
273 self.up_fitted['fit'] = (
274 self.up_fitted.B * fit.slope + fit.intercept)
275 self.down_fitted['fit'] = (
276 self.down_fitted.B * fit.slope + fit.intercept)
277 fit_area['fit'] = (fit_area.loc[:,
278 'B'].to_numpy() * fit.slope + fit.intercept)
279 self.up_fitted.Vx8 = self.up_fitted.Vx8 - self.up_fitted.fit
280 self.down_fitted.Vx8 = self.down_fitted.Vx8 - self.down_fitted.fit
281
282 def plot_hloop(self, ax, figtitle="", show_fitted=True,
283 show_rem=False, show_coer=False,
284 **kwargs):
285 """Plotting the hysteresis loop.
286
287 :param ax: Matplotlib Axis to plot on.
288 :param figtitle: can be set to a manual figure title.
289 :param show_fitted: plot the fitted curve.
290 :param show_rem: show the remanent voltage.
291 :param show_coer: show the coercive field.
292 :param show_original: plots the RAW data.
293 :param show_linear_fit: plots the linear fitting line used for the fit
294 (only gradiometry).
295 """
296
297 if kwargs.get('show_original'): # Drawing RAW Data
298 ax.plot(self.up.B, self.up.Vx8, label="up")
299 ax.plot(self.down.B, self.down.Vx8, label='down')
300 if (self.parallel):
301 ax.plot(self.up.B, self.up.Vx9, label="up (LI 9)")
302 ax.plot(self.down.B, self.down.Vx9, label='down (LI 9)')
303 ax.plot(self.up.B, self.up.Vx13, label="up (LI 13)")
304 ax.plot(self.down.B, self.down.Vx13, label='down (LI 13)')
305
306 if show_fitted: # Drawing fitted data
307 # Already fitted?
308 if not (hasattr(self, 'down_fitted')):
309 self.fit()
310 if (self.parallel):
311 ax.plot(self.up_fitted.B, self.up_fitted.Vx8,
312 label='Plusses: Up (fitted)')
313 ax.plot(self.down_fitted.B, self.down_fitted.Vx8,
314 label='Plusses: Down (fitted)')
315 ax.plot(self.up_fitted.B, self.up_fitted.Vx9,
316 label='Crosses: Up (fitted)')
317 ax.plot(self.down_fitted.B, self.down_fitted.Vx9,
318 label='Crosses: Down (fitted)')
319 else:
320 ax.plot(self.up_fitted.B, self.up_fitted.Vx8,
321 label='Up (fitted)')
322 ax.plot(self.down_fitted.B, self.down_fitted.Vx8,
323 label='Down (fitted)')
324 if kwargs.get('show_linear_fit'): # Drawing linear Fit
325 ax.plot(self.up_fitted.B, self.up_fitted.fit,
326 label='Linear Fit')
327
328 ### Remanent Field ###
329 if show_rem:
330 rem1, rem2 = self.get_remanence()
331 ax.plot([0, 0],
332 [self.up_fitted.Vx8.min(), self.down_fitted.Vx8.max()],
333 'r-.', linewidth='.5')
334 ax.plot(rem1['B'], rem1['Vx8'], 'bo', markersize=12)
335 ax.plot(rem2['B'], rem2['Vx8'], 'bo', markersize=12)
336 ax.annotate("$V_{rem} = %.3f \\mu V$" % rem1['Vx8'],
337 xy=(rem1['B'], rem1['Vx8']),
338 xytext=(150, -100), textcoords='offset points',
339 arrowprops={'arrowstyle': '->', 'color': 'black'})
340 ax.annotate("$V_{rem} = %.3f \\mu V$" % rem2['Vx8'],
341 xy=(rem2['B'], rem2['Vx8']),
342 xytext=(-150, 100), textcoords='offset points',
343 arrowprops={'arrowstyle': '->', 'color': 'black'})
344
345 ### Coercive Field
346 if show_coer:
347 mean, coer, coer2 = self.get_coercive_field()
348 ax.plot([self.up_fitted.B.min(), self.up_fitted.B.max()],
349 [mean, mean], 'r-.', linewidth='.5')
350 ax.plot(coer['B'], coer['Vx8'], 'go', markersize=12)
351 ax.plot(coer2['B'], coer2['Vx8'], 'go', markersize=12)
352 ax.annotate("$B_{coer} = %.4f T$" % coer.B,
353 xy=(coer['B'], coer['Vx8']),
354 xytext=(50, -30), textcoords='offset points',
355 arrowprops={'arrowstyle': '->', 'color': 'black'})
356 ax.annotate("$B_{coer} = %.4f T$" % coer2.B,
357 xy=(coer2['B'], coer2['Vx8']),
358 xytext=(-200, 20), textcoords='offset points',
359 arrowprops={'arrowstyle': '->', 'color': 'black'})
360
361 # Making a nice plot
362 ax.legend(loc='best')
363 bmin, bmax, vmin, vmax = self.get_minmax()
364 ax.set_xlim(bmin, bmax)
365 ax.set_xlabel("$B\\;[\\mathrm{mT}]$")
366
367 # Setting correct Y Label
368 yunit = '$V_x$'
369 if (self.factor == 1e3):
370 yunit += ' $[\\mathrm{mV}]$'
371 elif (self.factor == 1e6):
372 yunit += ' $[\\mathrm{\\mu V}]$'
373
374 ax.set_ylabel("%s" % yunit)
375
376 if (figtitle == ''):
377 figtitle = self.get_default_title()
378 ax.set_title(figtitle)
379
380 def calculate_strayfield(self):
381 """Calculates the strayfield of the signal and stores it in up and down
382 sweeps.
383 """
384 self.up['Bx8'] = self.calc_B(self.up.Vx8)
385 self.down['Bx8'] = self.calc_B(self.down.Vx8)
386 if (self.parallel):
387 self.up['Bx9'] = self.calc_B(self.up.Vx9)
388 self.down['Bx9'] = self.calc_B(self.down.Vx9)
389 self.up['Bx13'] = self.calc_B(self.up.Vx13)
390 self.down['Bx13'] = self.calc_B(self.down.Vx13)
391
392 def calculate_fitted_strayfield(self):
393 """Calculates the strayfield of the fitted signal and stores it in up
394 and down sweeps.
395 """
396 self.up_fitted['Bx8'] = self.calc_B(self.up_fitted.Vx8)
397 self.down_fitted['Bx8'] = self.calc_B(self.down_fitted.Vx8)
398 if (self.parallel):
399 self.up_fitted['Bx9'] = self.calc_B(self.up_fitted.Vx9)
400 self.down_fitted['Bx9'] = self.calc_B(self.down_fitted.Vx9)
401 self.up_fitted['Bx13'] = self.calc_B(self.up_fitted.Vx13)
402 self.down_fitted['Bx13'] = self.calc_B(self.down_fitted.Vx13)
403
404 def plot_strayfield(self, ax, figtitle="", **kwargs):
405 """Plots the strayfield of the data. Strayfield is calculated using the
406 electron concentration at 0 degree measured before
407 (hardcoded in __init__).
408
409 Args:
410 ax (matplotlib.pyplot.axis): where should we plot.
411 figtitle (str, optional): Choose your own title above the figure.
412 **kwargs:
413
414 Returns:
415 None.:
416 """
417 self.set_factor(kwargs.get('factor', 1e3))
418 self.calculate_strayfield()
419
420 if kwargs.get('show_original'): # Drawing RAW Data
421 ax.plot(self.up.B, self.up.Bx8, label="up")
422 ax.plot(self.down.B, self.down.Bx8, label='down')
423
424 if kwargs.get('show_fitted', True): # Drawing fitted data
425 # Already fitted?
426 if not (hasattr(self, 'down_fitted')):
427 self.fit()
428 self.calculate_fitted_strayfield()
429
430 if self.parallel:
431 if kwargs.get('show_plusses', True):
432 ax.plot(self.up_fitted.B, self.up_fitted.Bx8,
433 label='Plusses: Up (fitted)')
434 ax.plot(self.down_fitted.B, self.down_fitted.Bx8,
435 label='Plusses: Down (fitted)')
436
437 if kwargs.get('show_crosses', True):
438 ax.plot(self.up_fitted.B, self.up_fitted.Bx9,
439 label='Crosses: Up (fitted)')
440 ax.plot(self.down_fitted.B, self.down_fitted.Bx9,
441 label='Crosses: Down (fitted)')
442 else:
443 ax.plot(self.up_fitted.B, self.up_fitted.Bx8,
444 label='Up (fitted)')
445 ax.plot(self.down_fitted.B, self.down_fitted.Bx8,
446 label='Down (fitted)')
447
448 # Making a nice plot
449 if not (kwargs.get('nolegend')):
450 ax.legend(loc='best')
451 bmin, bmax, vmin, vmax = self.get_minmax()
452 ax.set_xlim(bmin, bmax)
453 ax.set_xlabel("$\\mu_0 H_{ext}$ $[\\mathrm{mT}]$")
454
455 # Setting correct Y Label
456 bhmin, bhmax = self.get_bhminmax()
457 ax.set_ylim(bhmin - .05, bhmax + .05)
458 ax.set_ylabel("$\\langle B_z \\rangle$ $[\\mathrm{mT}]$")
459
460 # Setting Title
461 if (figtitle == ''):
462 figtitle = self.get_default_title()
463 if (self.parallel):
464 figtitle = "M%s: $%s^\\circ$" % (self.measurement_number,
465 self.info['Angle'])
466 ax.set_title(figtitle)
467
468 def plot_downminusup(self, ax, figtitle=""):
469 """Plotting the difference between the down and up sweep. ax: Matplotlib
470 Axis to plot on. figtitle: can be set to a manual figure title.
471
472 Args:
473 ax:
474 figtitle:
475 """
476 B, vx = self.get_downminusup()
477 ax.plot(B, vx)
478 ax.set_xlabel("$B [\\mathrm{mT}]$")
479 ax.set_ylabel("$V_x [\\mathrm{\\mu V}]$")
480 if (figtitle == ''):
481 figtitle = self.get_default_title()
482 ax.set_title(figtitle)
483
484 def get_downminusup(self, n=1e5):
485 """Returns the magnetic field and difference between down and up sweep.
486
487 .. code-block:: python
488 :caption: Example
489
490 B, Vx = meas.get_downminusup() plt.plot(B, Vx)
491
492 Args:
493 n:
494 """
495 f_up = scipy.interpolate.interp1d(self.up.B, self.up.Vx8)
496 f_down = scipy.interpolate.interp1d(self.down.B, self.down.Vx8)
497 B = np.linspace(self.up.B.min(), self.up.B.max(), int(n))
498 downminusup = f_down(B) - f_up(B)
499 return B, downminusup
500
501 def get_downminusup_strayfield(self, n=1e5, fitted_data=False):
502 """Returns the magnetic field and difference between down and up sweep.
503
504 .. code-block:: python
505 :caption: Example
506
507 B_ext, Bx = meas.get_downminusup_strayfield() plt.plot(B_ext, Bx)
508
509 Args:
510 n:
511 fitted_data:
512 """
513 if fitted_data:
514 up = self.up_fitted
515 down = self.down_fitted
516 else:
517 up = self.up
518 down = self.down
519 f_up = scipy.interpolate.interp1d(up.B, up.Bx8)
520 f_down = scipy.interpolate.interp1d(down.B, down.Bx8)
521 B = np.linspace(up.B.min(), up.B.max(), int(n))
522 downminusup = f_down(B) - f_up(B)
523 return B, downminusup
524
525 def get_coercive_field(self):
526 """Returns the mean and coercive fields calculated.
527
528 .. code-block:: python
529 :caption: Example
530
531 mean, coer1, coer2 = meas.get_coercive_field()
532 print('Coercive Field (up sweep): (%.3f, %.3f)' % (coer1, mean))
533 print('Coercive Field (down sweep): (%.3f, %.3f)' % (coer2, mean))
534 """
535 mean = (self.up_fitted['Vx8'].min() + self.down_fitted[
536 'Vx8'].max()) / 2
537 coer = self.up_fitted.iloc[np.abs(self.up_fitted[ \
538 self.up_fitted.B.abs() < 350][
539 'Vx8'] - mean).argmin()]
540 coer2 = self.down_fitted.iloc[np.abs(self.down_fitted[ \
541 self.down_fitted.B.abs() < 350][
542 'Vx8'] - mean).argmin()]
543 return mean, coer, coer2
544
545 def plot_hysteresis(self, y="B", **kwargs):
546 fig, ax = plt.subplots(figsize=self.style.get('figsize'))
547
548 if y == 'B':
549 self.plot_strayfield(ax, **kwargs)
550 elif y == 'V':
551 self.plot_hloop(ax, **kwargs)
552 else:
553 self.plot_downminusup(ax, **kwargs)
554
555 def get_remanence(self):
556 """Returns the remanent voltage calculated.
557
558 .. code-block:: python
559 :caption: Example
560
561 rem1, rem2 = meas.get_remanence()
562 print('Remanent Voltage (up sweep): (%d, %.3f)' % (0, rem1))
563 print('Remanent Voltage (down sweep): (%d, %.3f)' % (0, rem2))
564 """
565 rem1 = self.up_fitted.iloc[self.up_fitted.B.abs().idxmin()]
566 rem2 = self.down_fitted.iloc[self.down_fitted.B.abs().idxmin()]
567 return rem1, rem2
568
569 def get_minmax(self):
570 """Returns the minimum and maximum of the field and voltage.
571
572 .. code-block:: python
573 :caption: Example
574
575 bmin, bmax, vmin, vmax = meas.get_minmax()
576 """
577 bmin = np.min([self.down.B.min(), self.up.B.min()])
578 bmax = np.max([self.down.B.max(), self.up.B.max()])
579 if (self.parallel):
580 max_8 = self.up.Vx8.max()
581 max_9 = self.up.Vx9.max()
582 max_13 = self.up.Vx13.max()
583 vmax = np.max([max_8, max_9, max_13])
584
585 min_8 = self.up.Vx8.min()
586 min_9 = self.up.Vx9.min()
587 min_13 = self.up.Vx13.min()
588 vmin = np.min([min_8, min_9, min_13])
589 else:
590 vmax = self.up.Vx8.max()
591 vmin = self.up.Vx8.min()
592
593 return bmin, bmax, vmin, vmax
594
595 def get_bhminmax(self):
596 """Returns the minimum and maximum of the measured hall voltage
597 converted to strayfield.
598
599 .. code-block:: python
600 :caption: Example
601
602 bhmin, bhmax = meas.get_bhminmax()
603 """
604 max_8 = np.max([self.up_fitted.Bx8.max(),
605 self.down_fitted.Bx8.max()])
606 min_8 = np.min([self.up_fitted.Bx8.min(),
607 self.down_fitted.Bx8.min()])
608 if (self.parallel):
609 max_9 = np.max([self.up_fitted.Bx9.max(),
610 self.down_fitted.Bx9.max()])
611 vmax = np.max([max_8, max_9])
612
613 min_9 = np.min([self.up_fitted.Bx9.min(),
614 self.down_fitted.Bx9.min()])
615 vmin = np.min([min_8, min_9])
616 else:
617 vmax = max_8
618 vmin = min_8
619
620 return vmin, vmax
621
622 def get_default_title(self):
623 if (self.parallel):
624 return "m%s: $%s^\\circ$" % (self.measurement_number,
625 self.info['Angle'])
626 return 'm%s: %s ($%s^\\circ$)' % (self.measurement_number,
627 self.info['Structure'],
628 self.info['Angle'])
MultipleM
1class MultipleM(MeasurementClass):
2 def __init__(self):
3 """MultipleM:
4
5 Main parent class for all multiple measurements. These Measurements
6 are represent by one data variable.
7
8 All multiple measurements have a variable called ``multi_info`` which
9 contains a pandas.DataFrame with parameter settings to single
10 measurements.
11
12 All multiple measurements can be represented by
13
14 >>> m = ana.MultipleM()
15 >>> m
16 Nr. 0: Hloop
17 Nr. 0: RAW
18 """
19
20 super().__init__()
21 self.multi_info = pd.DataFrame()
22 if os.path.exists('data/mfn_info.csv'):
23 self.read_csv('data/mfn_info.csv')
24
25
26 query = lambda self, request: self.multi_info.query(request)
27
28 def get_lofm_sweeps(self, to_show, label, options={}):
29 """Workaround to get list of measurements for sweeps.
30
31 Args:
32 to_show (dict): Measurement Numbers pointing to label format
33 variables: e.g. `` {nr: [-1, 1, 'Plusses', '2 mT/min']}
34 label (str): Label to show in plots: e.g. "Show %s -> %s (%s) %s "
35 -> will be formated using to_show
36 options (dict, default: empty): Additional plotting options
37
38 Returns:
39 dict: List of Measurements with description
40 """
41
42 return self.get_lofm(to_show,
43 label,
44 options)
45
46 def read_csv(self, *args, **kwargs):
47 """Updates multi_info DataFrame. Reads csv and adds information to
48 self.multi_info
49
50 Args:
51 *args:
52 **kwargs:
53
54 Returns:
55 None
56 """
57
58 self.multi_info = pd.concat([self.multi_info, pd.read_csv(*args, **kwargs)])
59
60 def get_lofm(self, to_show, label, options={}):
61 """Returns the List of Measurements (lofm) for plot functions
62 (see :meth:`ana.HandleM.plot`).
63
64 Args:
65 to_show (dict): Measurement Numbers pointing to label format
66 variables: e.g. ``{nr: [(-1, 1), 'Plusses', '5 mT/min']}``
67 label (str): Label to show in plots: e.g.
68 ``"Show %s -> %s (%s) %s "`` -> will be string formated using
69 to_show
70 options (dict, optional, default: empty): Additional plotting
71 parameters
72
73 Returns:
74 dict: List of Measurements with description: ``{nr: [labels]}``
75 """
76 lofm = {}
77 for nr, content in to_show.items():
78 if isinstance(content[0], tuple):
79 form = content[0][0], content[0][1], content[1], content[2]
80 # elif isinstance(content[0], str) or \
81 # isinstance(content[0], int) or \
82 # isinstance(content[0], float):
83 # form = content[0], content[1], content[2]
84 else:
85 form = content
86
87 lofm[nr] = [label % form, options]
88
89 return lofm
PlottingClass
1class PlottingClass(object):
2 """Defines the basic plotting style."""
3
4 def __init__(self, **style):
5 """
6 Args:
7 **style:
8 """
9 default_style = {
10 'dpi': 300,
11 'figure.autolayout': False,
12 'figsize': (8, 6),
13 'axes.labelsize': 18,
14 'axes.titlesize': 20,
15 'font.size': 16,
16 'lines.linewidth': 2.0,
17 'lines.markersize': 8,
18 'legend.fontsize': 14,
19 'mpl_style': False,
20 'latex': True,
21 'default_cm': cm.Blues,
22 }
23
24 self.name = 'Default Plotting Class'
25 self.style = default_style
26 self.set_style(**style)
27
28 get = lambda self, *key: self.style.get(*key)
29
30 __repr__ = lambda self: '%s\n' % self.name
31
32 __str__ = lambda self: '%s\n' % self.name + \
33 '\n'.join(['%s:\t%s' % (key, val) for key, val in self.style.items()])
34
35 def update(self, other):
36
37 """
38 Args:
39 other:
40 """
41 self.style.update(other)
42
43 def __setitem__(self, key, value):
44 """
45 Args:
46 key:
47 value:
48 """
49 self.update(dict(key=value))
50
51 def __add__(self, other):
52 self.update(other)
53
54 # Set Plotting Style
55 def set_style(self, **style):
56 """
57 Sets the Style for plots
58
59 :param size: A dictionary of parameters or the name of a preconfigured set.
60 :type size: dict, None, or one of {paper, notebook, talk, poster}
61
62 :param style:
63 :type style: dict, None, or one of {darkgrid, whitegrid, dark, white, ticks}
64
65 :param default: if True it sets default
66 :code:`size=talk` and
67 :code:`style=whitegrid`
68 :type default: bool
69
70 :param notebook: if True it sets default
71 :code:`size=notebook` and
72 :code:`style=ticks`
73
74 :type notebook: bool
75
76
77 Returns
78 -------
79 None.
80
81 """
82 self.style.update(style)
83 if self.get('default'):
84 def_size = 'talk'
85 def_style = 'whitegrid'
86 elif self.get('notebook'):
87 def_size = 'notebook'
88 def_style = 'ticks'
89 else:
90 def_size = 'talk'
91 def_style = 'whitegrid'
92
93 sns.set(self.get('size', def_size),
94 self.get('style', def_style),
95 self.get('palette', 'deep'),
96 self.get('font', 'sans-serif'),
97 self.get('font-scale', 1))
98
99 if style.get('grid'):
100 plt.rcParams['axes.grid'] = True
101 plt.rcParams['grid.linestyle'] = '--'
102
103 latex = 'latex' in self.style
104 plt.rcParams['text.usetex'] = latex
105 if latex: # True activates latex output in fonts!
106 plt.rcParams[
107 'text.latex.preamble'] = ''
108
109 if self.get('set_mpl_params', False):
110 params = style.get('mpl_params', {
111 'figure.dpi': self.get('dpi', 300),
112 'savefig.dpi': self.get('dpi', 300),
113 'figure.figsize': self.get('figsize'),
114 'figure.autolayout': False,
115 'axes.labelsize': 18,
116 'axes.titlesize': 20,
117 'font.size': 16,
118 'lines.linewidth': 2.0,
119 'lines.markersize': 8,
120 'legend.fontsize': 14,
121 'figure.subplot.hspace': 0.3,
122 'figure.subplot.wspace': 0.3,
123 'savefig.transparent': False,
124 'savefig.bbox': 'tight',
125 'savefig.pad_inches': 0.1,
126 })
127 update_keys = ['figure.autolayout',
128 'axes.labelsize',
129 'axes.titlesize',
130 'font.size',
131 'lines.linewidth',
132 'lines.markersize',
133 'legend.fontsize']
134 params.update({
135 update_keys[key]: self.get(update_keys[key]) \
136 for key in np.argwhere([_ in style \
137 for _ in update_keys]).ravel()
138 })
139 matplotlib.rcParams.update(params)
140
141 if self.get('mpl_style'):
142 matplotlib.style.use(self.get('mpl_style'))
143
144 def get_style(self, custom=None):
145 """
146 Args:
147 custom:
148 """
149 if custom:
150 return plt.style.context(custom)
151 return plt.style.context(self.get('mpl_style'))
152
153 def set_plot_settings(self, **kwargs):
154 """
155 Args:
156 **kwargs:
157 """
158 plt.xscale(kwargs.get('xscale', 'log'))
159 plt.yscale(kwargs.get('yscale', 'log'))
160
161 xmin, xmax = kwargs.get('xlim', (None, None))
162 ymin, ymax = kwargs.get('ylim', (None, None))
163 if xmin:
164 plt.xlim(xmin, xmax)
165 if ymin:
166 plt.ylim(ymin, ymax)
167 if not kwargs.get('legend_settings'):
168 plt.legend(loc=kwargs.get('legend_location', 'best'))
169 else:
170 plt.legend(**kwargs.get('legend_settings'))
171
172 if kwargs.get('grid'):
173 plt.grid(b=True, **kwargs['grid'])
174
175 title = kwargs.get('title')
176 if title:
177 plt.title(title)
178
179 xlabel = kwargs.get('xlabel', '$f$ [Hz]')
180 ylabel = kwargs.get('ylabel', '$S_{V_H}$ [${V^2}$/{Hz}]')
181 plt.xlabel(xlabel)
182 plt.ylabel(ylabel)
183
184 def draw_oneoverf(self, ax, **kwargs):
185 # Draw 1/f
186 """
187 Args:
188 ax:
189 **kwargs:
190 """
191 xmin = kwargs.get('xmin', .1)
192 ymin = kwargs.get('ymin', 1e-6)
193 factor = kwargs.get('factor', 10)
194 alpha = kwargs.get('alpha', 1)
195 if kwargs.get('plot_label'):
196 label = kwargs.get('plot_label')
197 elif alpha == 1:
198 label = '$1/f$'
199 else:
200 label = '$1/f^%s$' % alpha
201
202 mean_x = kwargs.get('mean_x', (xmin * np.sqrt(factor)))
203 mean_y = kwargs.get('mean_y', (ymin / (factor ** (alpha / 2))))
204 if not (kwargs.get('disable')):
205 ax.plot([xmin, xmin * factor], [ymin, ymin / (factor ** alpha)],
206 kwargs.get('plot_style', 'k--'), label=label, linewidth=2)
207 ax.annotate(label, (mean_x, mean_y),
208 color=kwargs.get('an_color', 'black'),
209 size=kwargs.get('an_size', 16))
210
211 def save_plot(self, fname, fext='png'):
212 """Saves the current plot as file.
213
214 Args:
215 fname (str): Filename
216 fext (str): Extension
217
218 Returns:
219 None
220 """
221
222 base = os.path.basename(fname)
223 if not os.path.exists(base):
224 os.makedirs(base)
225
226 if fext == 'pgf':
227 matplotlib.rcParams.update({
228 "pgf.texsystem": "pdflatex",
229 'font.family': 'serif',
230 'text.usetex': True,
231 'pgf.rcfonts': False,
232 'text.latex.preamble': r'\usepackage[utf8]{inputenc}\DeclareUnicodeCharacter{2212}{-}'
233 })
234 plt.savefig('%s.%s' % (fname, fext), backend='pgf')
235 plt.show()
236
237 else:
238 plt.savefig('%s.%s' % (fname, fext))
Functions
1# SPDX-FileCopyrightText: 2020/2021 Jonathan Pieper <ody55eus@mailbox.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
4
5"""
6Helps to handle measurements and style plots.
7"""
8
9from glob import glob
10
11from functools import wraps
12from time import time
13
14from .plotting import PlottingClass
15from .measurement import MeasurementClass
16
17plot = PlottingClass()
18set_sns = plot.set_style
19set_plot_settings = plot.set_plot_settings
20
21meas = MeasurementClass()
22get_info_from_name = meas.get_info_from_name
23
24
25def getpath(*arg):
26 """Alias for ``glob``.
27
28 Args:
29 *arg:
30 """
31 return glob(*arg)
32
33
34def timing(f):
35 """Wraps a function to measure the time it takes.
36
37 Args:
38 f:
39 """
40 @wraps(f)
41 def wrap(*args, **kw):
42 ts = time()
43 result = f(*args, **kw)
44 te = time()
45 print('func:%r took: %2.4f sec' % \
46 (f.__name__, te-ts))
47 return result
48 return wrap