m-thesis-introduction/simulations/airshower_beacon_simulation/lib/figlib.py

213 lines
6.9 KiB
Python

import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
from itertools import zip_longest
def phase_comparison_figure(
measured_phases,
true_phases,
plot_residuals=True,
f_beacon=None,
hist_kwargs={},
sc_kwargs={},
colors=['blue', 'orange'],
legend_on_scatter=True,
secondary_axis='time',
fit_gaussian=False,
**fig_kwargs
):
"""
Create a figure comparing measured_phase against true_phase
by both plotting the values, and the residuals.
"""
default_fig_kwargs = dict(sharex=True)
fig_kwargs = {**default_fig_kwargs, **fig_kwargs}
do_hist_plot = hist_kwargs is not False
do_scatter_plot = sc_kwargs is not False
fig, axs = plt.subplots(0+do_hist_plot+do_scatter_plot, 1, **fig_kwargs)
if not hasattr(axs, '__len__'):
axs = [axs]
if f_beacon and secondary_axis in ['phase', 'time']:
phase2time = lambda x: x/(2*np.pi*f_beacon)
time2phase = lambda x: 2*np.pi*x*f_beacon
if secondary_axis == 'time':
functions = (phase2time, time2phase)
label = 'Time $\\varphi/(2\\pi f_{beac})$ [ns]'
else:
functions = (time2phase, phase2time)
label = 'Phase $2\\pi t f_{beac}$ [rad]'
secax = axs[0].secondary_xaxis('top', functions=functions)
# Histogram
if do_hist_plot:
i=0
default_hist_kwargs = dict(bins='sqrt', density=False, alpha=0.8, histtype='step')
hist_kwargs = {**default_hist_kwargs, **hist_kwargs}
axs[i].set_ylabel("#")
_counts, _bins, _patches = axs[i].hist(measured_phases, color=colors[0], label='Measured', ls='solid', **hist_kwargs)
if not plot_residuals: # also plot the true clock phases
axs[i].hist(true_phases, color=colors[1], label='Actual', ls='dashed', **{**hist_kwargs, **dict(bins=_bins)})
if fit_gaussian:# Fit a gaussian to the histogram
gauss_kwargs = dict(color=colors[0], ls='dotted')
xs = np.linspace(min(_bins), max(_bins))
mean, std = norm.fit(measured_phases)
dx = _bins[1] - _bins[0]
scale = len(measured_phases)*dx
fit_ys = norm.pdf(xs, mean, std) * scale
axs[i].axvline(mean, ls='dotted', color='k', lw=2)
axs[i].plot( xs, fit_ys, label='fit', **gauss_kwargs)
# put mean, sigma and chisq on plot
text_str = f"$\\mu$ = {mean: .2e}\n$\\sigma$ = {std: .2e}"
text_kwargs = dict( transform=axs[i].transAxes, fontsize=14, verticalalignment='top')
axs[i].text(0.02, 0.95, text_str, **text_kwargs)
# Scatter plot
if do_scatter_plot:
i=1
default_sc_kwargs = dict(alpha=0.6, ls='none')
sc_kwargs = {**default_sc_kwargs, **sc_kwargs}
axs[i].set_ylabel("Antenna no.")
axs[i].plot(measured_phases, np.arange(len(measured_phases)), marker='x' if plot_residuals else '3', color=colors[0], label='Measured', **sc_kwargs)
if not plot_residuals: # also plot the true clock phases
axs[i].plot(true_phases, np.arange(len(true_phases)), marker='4', color=colors[1], label='Actual', **sc_kwargs)
if not plot_residuals and legend_on_scatter:
axs[i].legend()
fig.tight_layout()
return fig
def fitted_histogram_figure(
amplitudes,
fit_distr = None,
calc_chisq = True,
text_kwargs={},
hist_kwargs={},
mean_snr = None,
ax = None,
**fig_kwargs
):
"""
Create a figure showing $amplitudes$ as a histogram.
If fit_distr is a (list of) string, also fit the respective
distribution function and show the parameters on the figure.
"""
default_hist_kwargs = dict(bins='sqrt', density=False, alpha=0.8, histtype='step', label='hist')
default_text_kwargs = dict(fontsize=14, verticalalignment='top')
if isinstance(fit_distr, str):
fit_distr = [fit_distr]
hist_kwargs = {**default_hist_kwargs, **hist_kwargs}
text_kwargs = {**default_text_kwargs, **text_kwargs}
if ax is None:
fig, ax = plt.subplots(1,1, **fig_kwargs)
else:
fig = ax.get_figure()
text_kwargs['transform'] = ax.transAxes
counts, bins, _patches = ax.hist(amplitudes, **hist_kwargs)
fit_info = []
if fit_distr:
min_x = min(amplitudes)
max_x = max(amplitudes)
dx = bins[1] - bins[0]
scale = len(amplitudes) * dx
xs = np.linspace(min_x, max_x)
for distr in fit_distr:
param_manipulate = lambda x: x
if 'rice' == distr:
name = "Rice"
param_names = [ "$\\nu$", "$\\sigma$" ]
distr_func = stats.rice
fit_params2text_params = lambda x: (x[0]*x[1], x[1])
elif 'gaussian' == distr:
name = "Norm"
param_names = [ "$\\mu$", "$\\sigma$" ]
distr_func = stats.norm
elif 'rayleigh' == distr:
name = "Rayleigh"
param_names = [ "$\\sigma$" ]
distr_func = stats.rayleigh
fit_params2text_params = lambda x: (x[0]+x[1]/2,)
else:
raise ValueError('Unknown distribution function '+distr)
label = name +"(" + ','.join(param_names) + ')'
fit_params = distr_func.fit(amplitudes)
fit_ys = distr_func.pdf(xs, *fit_params)
ax.plot(xs, fit_ys*scale, label=label)
chisq_strs = []
if calc_chisq:
ct = np.diff(distr_func.cdf(bins, *fit_params))*np.sum(counts)
c2t = stats.chisquare(counts, ct, ddof=len(fit_params))
chisq_strs = [
f"$\\chi^2$/dof = {c2t[0]: .2g}/{len(fit_params)}"
]
# change parameters if needed
text_fit_params = fit_params2text_params(fit_params)
text_str = "\n".join(
[label]
+
[ f"{param} = {value: .2e}" for param, value in zip_longest(param_names, text_fit_params, fillvalue='?') ]
+
chisq_strs
)
this_info = {
'name': name,
'param_names': param_names,
'param_values': text_fit_params,
'text_str': text_str,
}
if chisq_strs:
this_info['chisq'] = c2t[0]
this_info['dof'] = len(fit_params)
fit_info.append(this_info)
loc = (0.02, 0.95)
ax.text(*loc, "\n\n".join([info['text_str'] for info in fit_info]), **{**text_kwargs, **dict(ha='left')})
if mean_snr:
text_str = f"$\\langle SNR \\rangle$ = {mean_snr: .1e}"
loc = (0.98, 0.95)
ax.text(*loc, text_str, **{**text_kwargs, **dict(ha='right')})
return fig, fit_info