Hide keyboard shortcuts

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 

4 

5""" 

6Evaluates Hysteresis Loops. 

7""" 

8 

9from .single import SingleM 

10 

11import logging 

12import re 

13from glob import glob 

14 

15import numpy as np 

16import pandas as pd 

17import scipy.interpolate 

18import scipy.constants 

19import scipy.stats 

20 

21 

22class Hloop(SingleM): 

23 """This class represents the measurement of a hysteresis loop. It evaluates 

24 and plots the data. 

25 

26 It collects all files that match the following regex: 

27 

28 .. code-block:: python 

29 

30 files = glob("data/Hloop/[mM]%s_*.dat" % measurement_number) 

31 

32 The files should contain a file that contains the word "up" or "down" 

33 indicating the direction of the field sweep. 

34 

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 """ 

40 

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 

62 

63 self.n = 1369288448319507.5 # Carrier Concentration calculated from perp. sweep 

64 

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) 

74 

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) 

123 

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) 

128 

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() 

134 

135 if fname.find('b.dat') > 0: 

136 m_dir = 'up' 

137 

138 

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 

150 

151 if (kwargs.get('down_only')): 

152 self.up = self.down 

153 if (kwargs.get('up_only')): 

154 self.down = self.up 

155 

156 self.measurement_number = measurement_number 

157 self.factor = 1 

158 

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 

165 

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': ''} 

194 

195 # Update data from kwargs 

196 for key, value in kwargs.items(): 

197 if key in self.info.keys(): 

198 self.info[key] = value 

199 

200 def set_factor(self, factor): 

201 """Multiplying the Voltage by a Factor 

202 

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 

213 

214 self.factor = factor 

215 

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 

223 

224 def __repr__(self): 

225 return self.get_default_title() 

226 

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 

236 

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). 

240 

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'] 

250 

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] 

255 

256 def fit(self, cond=pd.Series()): 

257 """ 

258 Fitting the Voltage Signal. 

259 

260 For Parallel measurement: 

261 

262 the empty cross Vx13 is subtracted from the signal Vx8/Vx9 

263 

264 For gradiometry: 

265 

266 A condition can be added to determine the amount of 

267 datapoints to fit (default: B < B_min + 150mT). 

268 

269 .. code-block:: python 

270 :caption: Example 

271 

272 cond = (self.up.B < self.up.B.min() + 150) 

273 m.fit(cond) 

274 

275 Args: 

276 cond: 

277 """ 

278 # Set Fitting Condition 

279 if (cond.empty): 

280 cond = (self.up.B < self.up.B.min() + 150) 

281 

282 ### Fitting Graph 

283 self.up_fitted = self.up.copy() 

284 self.down_fitted = self.down.copy() 

285 

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 

302 

303 def plot_hloop(self, ax, figtitle="", show_fitted=True, 

304 show_rem=False, show_coer=False, 

305 **kwargs): 

306 """Plotting the hysteresis loop. 

307 

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 """ 

317 

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)') 

326 

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') 

348 

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'}) 

365 

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'}) 

381 

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}]$") 

387 

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}]$' 

394 

395 ax.set_ylabel("%s" % yunit) 

396 

397 if (figtitle == ''): 

398 figtitle = self.get_default_title() 

399 ax.set_title(figtitle) 

400 

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) 

412 

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) 

424 

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__). 

429 

430 Args: 

431 ax (matplotlib.pyplot.axis): where should we plot. 

432 figtitle (str, optional): Choose your own title above the figure. 

433 **kwargs: 

434 

435 Returns: 

436 None.: 

437 """ 

438 self.set_factor(kwargs.get('factor', 1e3)) 

439 self.calculate_strayfield() 

440 

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') 

444 

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() 

450 

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)') 

457 

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)') 

468 

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}]$") 

475 

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}]$") 

480 

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) 

488 

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. 

492 

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) 

504 

505 def get_downminusup(self, n=1e5): 

506 """Returns the magnetic field and difference between down and up sweep. 

507 

508 .. code-block:: python 

509 :caption: Example 

510 

511 B, Vx = meas.get_downminusup() plt.plot(B, Vx) 

512 

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 

521 

522 def get_downminusup_strayfield(self, n=1e5, fitted_data=False): 

523 """Returns the magnetic field and difference between down and up sweep. 

524 

525 .. code-block:: python 

526 :caption: Example 

527 

528 B_ext, Bx = meas.get_downminusup_strayfield() plt.plot(B_ext, Bx) 

529 

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 

545 

546 def get_coercive_field(self): 

547 """Returns the mean and coercive fields calculated. 

548 

549 .. code-block:: python 

550 :caption: Example 

551 

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 

565 

566 def plot_hysteresis(y="B", **kwargs): 

567 fig, ax = plt.subplots(figsize=self.style.get('figsize')) 

568 

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) 

575 

576 def get_remanence(self): 

577 """Returns the remanent voltage calculated. 

578 

579 .. code-block:: python 

580 :caption: Example 

581 

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 

589 

590 def get_minmax(self): 

591 """Returns the minimum and maximum of the field and voltage. 

592 

593 .. code-block:: python 

594 :caption: Example 

595 

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]) 

605 

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() 

613 

614 return bmin, bmax, vmin, vmax 

615 

616 def get_bhminmax(self): 

617 """Returns the minimum and maximum of the measured hall voltage 

618 converted to strayfield. 

619 

620 .. code-block:: python 

621 :caption: Example 

622 

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]) 

633 

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 

640 

641 return vmin, vmax 

642 

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'])