Coverage for ana/hloop.py : 67%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-FileCopyrightText: 2020 Jonathan Pieper <jpieper@stud.uni-frankfurt.de>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5"""
6Evaluates Hysteresis Loops.
7"""
9from .single import SingleM
11import logging
12import re
13from glob import glob
15import numpy as np
16import pandas as pd
17import scipy.interpolate
18import scipy.constants
19import scipy.stats
22class Hloop(SingleM):
23 """This class represents the measurement of a hysteresis loop. It evaluates
24 and plots the data.
26 It collects all files that match the following regex:
28 .. code-block:: python
30 files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number)
32 The files should contain a file that contains the word "up" or "down"
33 indicating the direction of the field sweep.
35 If one file contains the word 'Gradio' it expects a gradiometry
36 measurement. If the filename contains the word 'Parallel' it expects a
37 parallel measurement. If the filename contains neither of these words the
38 variables need to be set manually.
39 """
41 def __init__(self, measurement_number, **kwargs):
42 """
43 Args:
44 measurement_number:
45 **kwargs:
46 """
47 super().__init__()
48 self.logger = logging.getLogger('Hloop')
49 self.name = 'Hloop'
50 # Defining Constants
51 header_gradio = ["Time", "B", "Vx8", "Vy8", "Vr8", "Vth8", "TempRO CU",
52 "TempRO YOKE", "TempCX CU", "SampTemp", "Temp1K Pot",
53 "TempCharcoal"]
54 header_parallel = ["Time", "B", "Vx8", "Vy8", "Vr8", "Vth8", "Vx9",
55 "Vy9", "Vr9", "Vth9", "Vx13", "Vy13", "Vr13",
56 "Vth13", "TempRO CU", "TempRO YOKE", "TempCX CU",
57 "SampTemp", "Temp1K Pot", "TempCharcoal"]
58 self.c_list = [0, 6.8, 10, 18, 27, 39, 56, 82, 100, 150, 180, 270,
59 330] # Condensators in pF
60 self.r_list = [0, 10, 100, 200, 300, 392, 475, 562, 619, 750, 825, 909,
61 1e3] # Resistors in k Ohm
63 self.n = 1369288448319507.5 # Carrier Concentration calculated from perp. sweep
65 if isinstance(measurement_number, float) or isinstance(
66 measurement_number, int):
67 files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number)
68 else:
69 files = glob(measurement_number)
70 files = kwargs.get('file_list', files)
71 if not (files):
72 raise NameError(
73 "No File for measurement Nr. %s found." % measurement_number)
75 for fname in files:
76 file_info = None
77 try:
78 if fname.find('Gradio') > 0:
79 reg = re.match(
80 ".*[Mm]([0-9.]*)_([A-Za-z]*)_([0-9.-]*)deg_"
81 "([A-Za-z]*)_"
82 "([A-Za-z]*)_*([A-Za-z]*)_([0-9]*)_([0-9]*)_"
83 "I1-([0-9\-]*)_I2-([0-9\-]*)_G.*-([0-9\-]*)_"
84 "Vin-([0-9.]*)V_R11-([0-9]*.)O_R12-([0-9.]*.)O_"
85 "R13-([0-9.]*.)_R21-([0-9.]*.)O_"
86 "C11-([0-9]*)_C21-([0-9]*)_"
87 "T-([0-9]*)K_SR-([0-9.]*)-T-min",
88 fname)
89 if not reg:
90 raise NameError("%s not fitting Regex" % fname)
91 m_no, struct, deg, m_type, \
92 m_dir, m_type2, m_date, m_time, \
93 m_i1, m_i2, m_li1, \
94 m_vin, m_r11, m_r12, \
95 m_r13, m_r21, \
96 m_c11, m_c21, \
97 m_T, m_SR = reg.groups()
98 header = header_gradio
99 self.parallel = False
100 elif fname.find('Parallel') > 0:
101 reg = re.match(
102 ".*[Mm]([0-9.]*)_([A-Za-z_]*)_([0-9.-]*)deg_([A-Za-z]*)_"
103 + "([A-Za-z]*)_*([A-Za-z]*)_([0-9]*)_([0-9]*)_" \
104 + "I-([0-9\-]*)_(G.*-[0-9\-]*)_" \
105 + "Vin-([0-9.]*)V_R11-([0-9]*.)O" \
106 + ".*_" \
107 + "T-([0-9]*)K_SR-([0-9.]*)-T-min",
108 fname)
109 if not reg:
110 raise NameError("%s not fitting Regex" % fname)
111 m_no, struct, deg, m_type, \
112 m_dir, m_type2, m_date, m_time, \
113 m_i1, m_li1, \
114 m_vin, m_r11, \
115 m_T, m_SR = reg.groups()
116 m_i2 = m_r12 = m_r13 = m_r21 = m_c11 = m_c21 = '0'
117 header = header_parallel
118 self.parallel = True
119 else:
120 raise NameError("%s not fitting Regex" % fname)
121 except NameError:
122 file_info = self.get_info_from_name(fname)
124 self.parallel = file_info.get('type2') == 'Parallel'
125 header = header_gradio if file_info.get('type2') == 'Gradio' else header_parallel
126 header = kwargs.get('header', header)
127 kwargs.update(file_info)
129 if isinstance(file_info.get('deg'), str) and \
130 file_info.get('deg').find('neg') > -1:
131 file_info['deg'] = file_info.get('deg').replace('neg', '-')
132 deg = file_info.get('deg') if file_info.get('deg') else 0.01
133 m_dir = file_info.get('dir', 'down').lower()
135 if fname.find('b.dat') > 0:
136 m_dir = 'up'
139 ### Loading File
140 if (m_dir.lower() == 'up'):
141 self.fname_up = fname
142 self.up = pd.read_csv(fname, sep='\t', names=header,
143 skiprows=3)
144 self.up.B = self.up.B * 1e3
145 else:
146 self.fname_down = fname
147 self.down = pd.read_csv(fname, sep='\t', names=header,
148 skiprows=3)
149 self.down.B = self.down.B * 1e3
151 if (kwargs.get('down_only')):
152 self.up = self.down
153 if (kwargs.get('up_only')):
154 self.down = self.up
156 self.measurement_number = measurement_number
157 self.factor = 1
159 # Mirror all Angles bigger than 90
160 if kwargs.get('force_mirror') or \
161 (kwargs.get('auto_mirror', True) and \
162 float(deg) >= 90):
163 self.set_factor(-1)
164 self.factor = 1
166 if file_info:
167 self.info = file_info
168 self.info.update({
169 'Type': "%s (%s)" % (file_info.get('type'),
170 file_info.get('type2')),
171 'Structure': file_info.get('struct'),
172 'Angle': file_info.get('deg'),
173 })
174 else:
175 m_datetime = "%s.%s.%s %s:%s" % (
176 m_date[6:], m_date[4:6], m_date[:4], m_time[:2], m_time[2:])
177 self.info = {
178 'Type': "%s (%s)" % (m_type, m_type2),
179 'Date': m_datetime,
180 'Structure': struct,
181 'Angle': deg,
182 'I1': m_i1,
183 'I2': m_i2,
184 'Vin': m_vin,
185 'R11': m_r11,
186 'R12': m_r12,
187 'R13': m_r13,
188 'R21': m_r21,
189 'C11': self.c_list[int(m_c11)],
190 'C21': self.c_list[int(m_c21)],
191 'T': m_T,
192 'SR': m_SR,
193 'Additional': ''}
195 # Update data from kwargs
196 for key, value in kwargs.items():
197 if key in self.info.keys():
198 self.info[key] = value
200 def set_factor(self, factor):
201 """Multiplying the Voltage by a Factor
203 Args:
204 factor:
205 """
206 self.up.Vx8 /= self.factor
207 self.down.Vx8 /= self.factor
208 if (self.parallel):
209 self.up.Vx9 /= self.factor
210 self.up.Vx13 /= self.factor
211 self.down.Vx9 /= self.factor
212 self.down.Vx13 /= self.factor
214 self.factor = factor
216 self.up.Vx8 *= self.factor
217 self.down.Vx8 *= self.factor
218 if (self.parallel):
219 self.up.Vx9 *= self.factor
220 self.up.Vx13 *= self.factor
221 self.down.Vx9 *= self.factor
222 self.down.Vx13 *= self.factor
224 def __repr__(self):
225 return self.get_default_title()
227 def calc_B(self, V):
228 """
229 Args:
230 V:
231 """
232 e = scipy.constants.physical_constants['electron volt'][0]
233 R = V / 2.5e-6
234 B = R * self.n * e
235 return B
237 def remove_zero(self, columns=None):
238 """Removes the values 0.0 from up and down sweep where the lock-in
239 didn't return any values (read error will be saved as 0.0).
241 Args:
242 columns:
243 """
244 # Set Columns to remove values from
245 if columns == None:
246 if self.parallel:
247 columns = ['Vx8', 'Vx9', 'Vx13']
248 else:
249 columns = ['Vx8']
251 # Remove zero values from columns (= measurement error)
252 for v in columns:
253 self.up = self.up[self.up[v] != 0.0]
254 self.down = self.down[self.down[v] != 0.0]
256 def fit(self, cond=pd.Series()):
257 """
258 Fitting the Voltage Signal.
260 For Parallel measurement:
262 the empty cross Vx13 is subtracted from the signal Vx8/Vx9
264 For gradiometry:
266 A condition can be added to determine the amount of
267 datapoints to fit (default: B < B_min + 150mT).
269 .. code-block:: python
270 :caption: Example
272 cond = (self.up.B < self.up.B.min() + 150)
273 m.fit(cond)
275 Args:
276 cond:
277 """
278 # Set Fitting Condition
279 if (cond.empty):
280 cond = (self.up.B < self.up.B.min() + 150)
282 ### Fitting Graph
283 self.up_fitted = self.up.copy()
284 self.down_fitted = self.down.copy()
286 if (self.parallel):
287 self.up_fitted.Vx8 -= self.up_fitted.Vx13
288 self.up_fitted.Vx9 -= self.up_fitted.Vx13
289 self.down_fitted.Vx8 -= self.down_fitted.Vx13
290 self.down_fitted.Vx9 -= self.down_fitted.Vx13
291 else:
292 fit_area = self.up[cond].copy()
293 fit = scipy.stats.linregress(fit_area.B, fit_area.Vx8)
294 self.up_fitted['fit'] = (
295 self.up_fitted.B * fit.slope + fit.intercept)
296 self.down_fitted['fit'] = (
297 self.down_fitted.B * fit.slope + fit.intercept)
298 fit_area['fit'] = (fit_area.loc[:,
299 'B'].to_numpy() * fit.slope + fit.intercept)
300 self.up_fitted.Vx8 = self.up_fitted.Vx8 - self.up_fitted.fit
301 self.down_fitted.Vx8 = self.down_fitted.Vx8 - self.down_fitted.fit
303 def plot_hloop(self, ax, figtitle="", show_fitted=True,
304 show_rem=False, show_coer=False,
305 **kwargs):
306 """Plotting the hysteresis loop.
308 :param ax: Matplotlib Axis to plot on.
309 :param figtitle: can be set to a manual figure title.
310 :param show_fitted: plot the fitted curve.
311 :param show_rem: show the remanent voltage.
312 :param show_coer: show the coercive field.
313 :param show_original: plots the RAW data.
314 :param show_linear_fit: plots the linear fitting line used for the fit
315 (only gradiometry).
316 """
318 if kwargs.get('show_original'): # Drawing RAW Data
319 ax.plot(self.up.B, self.up.Vx8, label="up")
320 ax.plot(self.down.B, self.down.Vx8, label='down')
321 if (self.parallel):
322 ax.plot(self.up.B, self.up.Vx9, label="up (LI 9)")
323 ax.plot(self.down.B, self.down.Vx9, label='down (LI 9)')
324 ax.plot(self.up.B, self.up.Vx13, label="up (LI 13)")
325 ax.plot(self.down.B, self.down.Vx13, label='down (LI 13)')
327 if show_fitted: # Drawing fitted data
328 # Already fitted?
329 if not (hasattr(self, 'down_fitted')):
330 self.fit()
331 if (self.parallel):
332 ax.plot(self.up_fitted.B, self.up_fitted.Vx8,
333 label='Plusses: Up (fitted)')
334 ax.plot(self.down_fitted.B, self.down_fitted.Vx8,
335 label='Plusses: Down (fitted)')
336 ax.plot(self.up_fitted.B, self.up_fitted.Vx9,
337 label='Crosses: Up (fitted)')
338 ax.plot(self.down_fitted.B, self.down_fitted.Vx9,
339 label='Crosses: Down (fitted)')
340 else:
341 ax.plot(self.up_fitted.B, self.up_fitted.Vx8,
342 label='Up (fitted)')
343 ax.plot(self.down_fitted.B, self.down_fitted.Vx8,
344 label='Down (fitted)')
345 if kwargs.get('show_linear_fit'): # Drawing linear Fit
346 ax.plot(self.up_fitted.B, self.up_fitted.fit,
347 label='Linear Fit')
349 ### Remanent Field ###
350 if show_rem:
351 rem1, rem2 = self.get_remanence()
352 ax.plot([0, 0],
353 [self.up_fitted.Vx8.min(), self.down_fitted.Vx8.max()],
354 'r-.', linewidth='.5')
355 ax.plot(rem1['B'], rem1['Vx8'], 'bo', markersize=12)
356 ax.plot(rem2['B'], rem2['Vx8'], 'bo', markersize=12)
357 ax.annotate("$V_{rem} = %.3f \\mu V$" % rem1['Vx8'],
358 xy=(rem1['B'], rem1['Vx8']),
359 xytext=(150, -100), textcoords='offset points',
360 arrowprops={'arrowstyle': '->', 'color': 'black'})
361 ax.annotate("$V_{rem} = %.3f \\mu V$" % rem2['Vx8'],
362 xy=(rem2['B'], rem2['Vx8']),
363 xytext=(-150, 100), textcoords='offset points',
364 arrowprops={'arrowstyle': '->', 'color': 'black'})
366 ### Coercive Field
367 if show_coer:
368 mean, coer, coer2 = self.get_coercive_field()
369 ax.plot([self.up_fitted.B.min(), self.up_fitted.B.max()],
370 [mean, mean], 'r-.', linewidth='.5')
371 ax.plot(coer['B'], coer['Vx8'], 'go', markersize=12)
372 ax.plot(coer2['B'], coer2['Vx8'], 'go', markersize=12)
373 ax.annotate("$B_{coer} = %.4f T$" % coer.B,
374 xy=(coer['B'], coer['Vx8']),
375 xytext=(50, -30), textcoords='offset points',
376 arrowprops={'arrowstyle': '->', 'color': 'black'})
377 ax.annotate("$B_{coer} = %.4f T$" % coer2.B,
378 xy=(coer2['B'], coer2['Vx8']),
379 xytext=(-200, 20), textcoords='offset points',
380 arrowprops={'arrowstyle': '->', 'color': 'black'})
382 # Making a nice plot
383 ax.legend(loc='best')
384 bmin, bmax, vmin, vmax = self.get_minmax()
385 ax.set_xlim(bmin, bmax)
386 ax.set_xlabel("$B\\;[\\mathrm{mT}]$")
388 # Setting correct Y Label
389 yunit = '$V_x$'
390 if (self.factor == 1e3):
391 yunit += ' $[\\mathrm{mV}]$'
392 elif (self.factor == 1e6):
393 yunit += ' $[\\mathrm{\\mu V}]$'
395 ax.set_ylabel("%s" % yunit)
397 if (figtitle == ''):
398 figtitle = self.get_default_title()
399 ax.set_title(figtitle)
401 def calculate_strayfield(self):
402 """Calculates the strayfield of the signal and stores it in up and down
403 sweeps.
404 """
405 self.up['Bx8'] = self.calc_B(self.up.Vx8)
406 self.down['Bx8'] = self.calc_B(self.down.Vx8)
407 if (self.parallel):
408 self.up['Bx9'] = self.calc_B(self.up.Vx9)
409 self.down['Bx9'] = self.calc_B(self.down.Vx9)
410 self.up['Bx13'] = self.calc_B(self.up.Vx13)
411 self.down['Bx13'] = self.calc_B(self.down.Vx13)
413 def calculate_fitted_strayfield(self):
414 """Calculates the strayfield of the fitted signal and stores it in up
415 and down sweeps.
416 """
417 self.up_fitted['Bx8'] = self.calc_B(self.up_fitted.Vx8)
418 self.down_fitted['Bx8'] = self.calc_B(self.down_fitted.Vx8)
419 if (self.parallel):
420 self.up_fitted['Bx9'] = self.calc_B(self.up_fitted.Vx9)
421 self.down_fitted['Bx9'] = self.calc_B(self.down_fitted.Vx9)
422 self.up_fitted['Bx13'] = self.calc_B(self.up_fitted.Vx13)
423 self.down_fitted['Bx13'] = self.calc_B(self.down_fitted.Vx13)
425 def plot_strayfield(self, ax, figtitle="", **kwargs):
426 """Plots the strayfield of the data. Strayfield is calculated using the
427 electron concentration at 0 degree measured before
428 (hardcoded in __init__).
430 Args:
431 ax (matplotlib.pyplot.axis): where should we plot.
432 figtitle (str, optional): Choose your own title above the figure.
433 **kwargs:
435 Returns:
436 None.:
437 """
438 self.set_factor(kwargs.get('factor', 1e3))
439 self.calculate_strayfield()
441 if kwargs.get('show_original'): # Drawing RAW Data
442 ax.plot(self.up.B, self.up.Bx8, label="up")
443 ax.plot(self.down.B, self.down.Bx8, label='down')
445 if kwargs.get('show_fitted', True): # Drawing fitted data
446 # Already fitted?
447 if not (hasattr(self, 'down_fitted')):
448 self.fit()
449 self.calculate_fitted_strayfield()
451 if self.parallel:
452 if kwargs.get('show_plusses', True):
453 ax.plot(self.up_fitted.B, self.up_fitted.Bx8,
454 label='Plusses: Up (fitted)')
455 ax.plot(self.down_fitted.B, self.down_fitted.Bx8,
456 label='Plusses: Down (fitted)')
458 if kwargs.get('show_crosses', True):
459 ax.plot(self.up_fitted.B, self.up_fitted.Bx9,
460 label='Crosses: Up (fitted)')
461 ax.plot(self.down_fitted.B, self.down_fitted.Bx9,
462 label='Crosses: Down (fitted)')
463 else:
464 ax.plot(self.up_fitted.B, self.up_fitted.Bx8,
465 label='Up (fitted)')
466 ax.plot(self.down_fitted.B, self.down_fitted.Bx8,
467 label='Down (fitted)')
469 # Making a nice plot
470 if not (kwargs.get('nolegend')):
471 ax.legend(loc='best')
472 bmin, bmax, vmin, vmax = self.get_minmax()
473 ax.set_xlim(bmin, bmax)
474 ax.set_xlabel("$\\mu_0 H_{ext}$ $[\\mathrm{mT}]$")
476 # Setting correct Y Label
477 bhmin, bhmax = self.get_bhminmax()
478 ax.set_ylim(bhmin - .05, bhmax + .05)
479 ax.set_ylabel("$\\langle B_z \\rangle$ $[\\mathrm{mT}]$")
481 # Setting Title
482 if (figtitle == ''):
483 figtitle = self.get_default_title()
484 if (self.parallel):
485 figtitle = "M%s: $%s^\\circ$" % (self.measurement_number,
486 self.info['Angle'])
487 ax.set_title(figtitle)
489 def plot_downminusup(self, ax, figtitle=""):
490 """Plotting the difference between the down and up sweep. ax: Matplotlib
491 Axis to plot on. figtitle: can be set to a manual figure title.
493 Args:
494 ax:
495 figtitle:
496 """
497 B, vx = self.get_downminusup()
498 ax.plot(B, vx)
499 ax.set_xlabel("$B [\\mathrm{mT}]$")
500 ax.set_ylabel("$V_x [\\mathrm{\\mu V}]$")
501 if (figtitle == ''):
502 figtitle = self.get_default_title()
503 ax.set_title(figtitle)
505 def get_downminusup(self, n=1e5):
506 """Returns the magnetic field and difference between down and up sweep.
508 .. code-block:: python
509 :caption: Example
511 B, Vx = meas.get_downminusup() plt.plot(B, Vx)
513 Args:
514 n:
515 """
516 f_up = scipy.interpolate.interp1d(self.up.B, self.up.Vx8)
517 f_down = scipy.interpolate.interp1d(self.down.B, self.down.Vx8)
518 B = np.linspace(self.up.B.min(), self.up.B.max(), int(n))
519 downminusup = f_down(B) - f_up(B)
520 return B, downminusup
522 def get_downminusup_strayfield(self, n=1e5, fitted_data=False):
523 """Returns the magnetic field and difference between down and up sweep.
525 .. code-block:: python
526 :caption: Example
528 B_ext, Bx = meas.get_downminusup_strayfield() plt.plot(B_ext, Bx)
530 Args:
531 n:
532 fitted_data:
533 """
534 if fitted_data:
535 up = self.up_fitted
536 down = self.down_fitted
537 else:
538 up = self.up
539 down = self.down
540 f_up = scipy.interpolate.interp1d(up.B, up.Bx8)
541 f_down = scipy.interpolate.interp1d(down.B, down.Bx8)
542 B = np.linspace(up.B.min(), up.B.max(), int(n))
543 downminusup = f_down(B) - f_up(B)
544 return B, downminusup
546 def get_coercive_field(self):
547 """Returns the mean and coercive fields calculated.
549 .. code-block:: python
550 :caption: Example
552 mean, coer1, coer2 = meas.get_coercive_field()
553 print('Coercive Field (up sweep): (%.3f, %.3f)' % (coer1, mean))
554 print('Coercive Field (down sweep): (%.3f, %.3f)' % (coer2, mean))
555 """
556 mean = (self.up_fitted['Vx8'].min() + self.down_fitted[
557 'Vx8'].max()) / 2
558 coer = self.up_fitted.iloc[np.abs(self.up_fitted[ \
559 self.up_fitted.B.abs() < 350][
560 'Vx8'] - mean).argmin()]
561 coer2 = self.down_fitted.iloc[np.abs(self.down_fitted[ \
562 self.down_fitted.B.abs() < 350][
563 'Vx8'] - mean).argmin()]
564 return mean, coer, coer2
566 def plot_hysteresis(y="B", **kwargs):
567 fig, ax = plt.subplots(figsize=self.style.get('figsize'))
569 if y == 'B':
570 self.plot_strayfield(ax, **kwargs)
571 elif y == 'V':
572 self.plot_hloop(ax, **kwargs)
573 else:
574 self.plot_downminusup(ax, **kwargs)
576 def get_remanence(self):
577 """Returns the remanent voltage calculated.
579 .. code-block:: python
580 :caption: Example
582 rem1, rem2 = meas.get_remanence()
583 print('Remanent Voltage (up sweep): (%d, %.3f)' % (0, rem1))
584 print('Remanent Voltage (down sweep): (%d, %.3f)' % (0, rem2))
585 """
586 rem1 = self.up_fitted.iloc[self.up_fitted.B.abs().idxmin()]
587 rem2 = self.down_fitted.iloc[self.down_fitted.B.abs().idxmin()]
588 return rem1, rem2
590 def get_minmax(self):
591 """Returns the minimum and maximum of the field and voltage.
593 .. code-block:: python
594 :caption: Example
596 bmin, bmax, vmin, vmax = meas.get_minmax()
597 """
598 bmin = np.min([self.down.B.min(), self.up.B.min()])
599 bmax = np.max([self.down.B.max(), self.up.B.max()])
600 if (self.parallel):
601 max_8 = self.up.Vx8.max()
602 max_9 = self.up.Vx9.max()
603 max_13 = self.up.Vx13.max()
604 vmax = np.max([max_8, max_9, max_13])
606 min_8 = self.up.Vx8.min()
607 min_9 = self.up.Vx9.min()
608 min_13 = self.up.Vx13.min()
609 vmin = np.min([min_8, min_9, min_13])
610 else:
611 vmax = self.up.Vx8.max()
612 vmin = self.up.Vx8.min()
614 return bmin, bmax, vmin, vmax
616 def get_bhminmax(self):
617 """Returns the minimum and maximum of the measured hall voltage
618 converted to strayfield.
620 .. code-block:: python
621 :caption: Example
623 bhmin, bhmax = meas.get_bhminmax()
624 """
625 max_8 = np.max([self.up_fitted.Bx8.max(),
626 self.down_fitted.Bx8.max()])
627 min_8 = np.min([self.up_fitted.Bx8.min(),
628 self.down_fitted.Bx8.min()])
629 if (self.parallel):
630 max_9 = np.max([self.up_fitted.Bx9.max(),
631 self.down_fitted.Bx9.max()])
632 vmax = np.max([max_8, max_9])
634 min_9 = np.min([self.up_fitted.Bx9.min(),
635 self.down_fitted.Bx9.min()])
636 vmin = np.min([min_8, min_9])
637 else:
638 vmax = max_8
639 vmin = min_8
641 return vmin, vmax
643 def get_default_title(self):
644 if (self.parallel):
645 return "m%s: $%s^\\circ$" % (self.measurement_number,
646 self.info['Angle'])
647 return 'm%s: %s ($%s^\\circ$)' % (self.measurement_number,
648 self.info['Structure'],
649 self.info['Angle'])