Coverage for tcvx21/analysis/fit_eich_profile_m.py: 92%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Routines to fit Eich-type heat flux profiles
3"""
4import numpy as np
5import matplotlib.pyplot as plt
6import scipy.special
7from scipy.optimize import curve_fit
8from tcvx21 import Quantity
9import warnings
12def erfc(x):
13 """
14 Complimentary error function, with handling of Quantity objects
15 Only Dimensionless quantities can be handled
16 erfc(x) = 1 - erf(x)
17 """
18 if isinstance(x, Quantity):
19 return scipy.special.erfc(x.to('').magnitude)
20 else:
21 return scipy.special.erfc(x)
24def eich_profile(position, peak_heat_flux, lambda_q, spreading_factor, background_heat_flux, shift):
25 """
26 Fits an Eich profile to heat flux data, using upstream-mapped
27 distance to remove the explicit flux-expansion factor
29 See Eich Nuclear Fusion 2013. Using the OMP-mapped distance as position
30 means that you should set 'f_x' = 1
32 You can pass the parameters to the profile either all as dimensional
33 Quantity objects, or all as dimensionless raw arrays/floats
35 position: OMP-mapped radial distance [mm]
36 peak_heat_flux: peak heat flux [MW/m^2]
37 lambda_q: heat flux decay width [mm]
38 spreading_factor: spreading factor [mm]
39 background_heat_flux: background heat-flux [MW/m^2]
40 """
41 with warnings.catch_warnings():
42 warnings.simplefilter("ignore", category=RuntimeWarning)
43 return peak_heat_flux / 2.0 \
44 * np.exp((spreading_factor / (2.0 * lambda_q)) ** 2 - (position - shift) / lambda_q) \
45 * erfc(spreading_factor / (2.0 * lambda_q) - (position - shift) / spreading_factor) \
46 + background_heat_flux
49def fit_eich_profile(position, heat_flux, errors=None, plot=False):
50 """
51 Uses non-linear least-squares to fit an Eich profile to heat flux data
53 Should pass
54 position: OMP-mapped radial distance [mm]
55 heat_flux: total heat flux [MW/m^2]
57 We use the Levenberg-Marquardt algorithm (default) for curve fitting
59 Note that the algorithm takes into account the error if provided
60 """
62 if errors is None:
63 errors = np.ones_like(position)
64 has_errors = False
65 else:
66 errors = errors.to('MW/m^2').magnitude
67 has_errors = True
69 heat_flux = heat_flux.to('MW/m^2').magnitude
71 mask = np.logical_and.reduce((~np.isnan(heat_flux), ~np.isnan(errors), errors/heat_flux > 1E-2))
73 position = position.to('mm').magnitude[mask]
74 heat_flux = heat_flux[mask]
75 errors = errors[mask]
77 popt, pcov = curve_fit(eich_profile,
78 xdata=position,
79 ydata=heat_flux,
80 sigma=errors,
81 absolute_sigma=has_errors,
82 p0=(np.max(heat_flux), 5, 2, np.mean((heat_flux[0], heat_flux[-1])), 0.0),
83 method='lm',
84 xtol=1E-6
85 )
87 peak_heat_flux, lambda_q, spreading_factor, background_heat_flux, shift = popt
88 perr = np.sqrt(np.diag(pcov))
89 peak_heat_flux_err, lambda_q_err, spreading_err, background_heat_flux_err, shift_err = perr
91 if plot:
92 plt.plot(Quantity(position, 'mm'), Quantity(heat_flux, 'MW/m^2'), label='signal')
93 if has_errors:
94 plt.fill_between(Quantity(position, 'mm'), Quantity(heat_flux + errors, 'MW/m^2'), Quantity(heat_flux - errors, 'MW/m^2'))
95 plt.plot(Quantity(position, 'mm'), Quantity(eich_profile(position, *popt), 'MW/m^2'), label='eich')
96 plt.legend()
97 plt.title("Eich profile fitting")
99 return Quantity((peak_heat_flux, peak_heat_flux_err), 'MW/m^2'), \
100 Quantity((lambda_q, lambda_q_err), 'mm'), \
101 Quantity((spreading_factor, spreading_err), 'mm'), \
102 Quantity((background_heat_flux, background_heat_flux_err), 'MW/m^2'), \
103 Quantity((shift, shift_err), 'mm')