mirror of
https://github.com/vale981/fpraktikum
synced 2025-03-06 01:51:38 -05:00
344 lines
11 KiB
Python
344 lines
11 KiB
Python
"""
|
|
Units:
|
|
* Time: nanosec
|
|
|
|
Conventions:
|
|
* Channels have a zero based index: i.e. Channel 1 = Channel 0 here
|
|
"""
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from scipy.optimize import minimize_scalar, root_scalar
|
|
from SecondaryValue import SecondaryValue
|
|
import matplotlib.ticker as ticker
|
|
from typing import Dict, Tuple, Sequence
|
|
from scipy.stats import binned_statistic
|
|
import os
|
|
from scipy.optimize import curve_fit
|
|
import pylandau
|
|
import matplotlib
|
|
from scipy import constants
|
|
|
|
###############################################################################
|
|
# Auxiliary #
|
|
###############################################################################
|
|
|
|
def load_counts(path):
|
|
"""Parses the TDC data from the text format.
|
|
|
|
:param path: path to the log
|
|
:returns: counts
|
|
"""
|
|
|
|
skip = 0
|
|
with open(path, 'r') as f:
|
|
for line in f:
|
|
if 'Summenspektrum' in line:
|
|
break
|
|
skip += 1
|
|
|
|
data = np.loadtxt(path, skiprows=skip+1, encoding='latin1',
|
|
usecols=[1], dtype=np.integer)
|
|
|
|
return data
|
|
|
|
|
|
|
|
def chan_to_time(channel, tick=1000/24, center=False, offset=0):
|
|
"""Convert channel number to time.
|
|
|
|
:param channel: channel Number
|
|
:param tick: width of the channels
|
|
:param center: wether to take the time at the channel center
|
|
:param offset: start channel
|
|
|
|
:returns: mean time of channel (center)
|
|
|
|
:rtype: float
|
|
"""
|
|
|
|
return (channel + offset) * tick + (tick/2 if center else 0)
|
|
|
|
def calculate_peak_uncertainty(peak):
|
|
"""Gives the uncertainty of the peak measurements with the osciloscope.
|
|
|
|
:param peak: peak value
|
|
:returns: uncertainty
|
|
"""
|
|
|
|
uncertainties = np.ones_like(peak) * 0.01
|
|
uncertainties[peak > 1] = 0.1
|
|
return uncertainties
|
|
|
|
m_mu, _, d_m_mu = constants.physical_constants['muon mass energy equivalent in MeV']
|
|
h, _, d_h = constants.physical_constants['Planck constant over 2 pi in eV s']
|
|
weak_constant = SecondaryValue('sqrt(192*pi^3/((1e-9*tau/(h*1e-9))*(1e-3*m)^5))',
|
|
defaults=dict(pi=np.pi, h=(h, d_h),
|
|
m=(m_mu, d_m_mu)))
|
|
|
|
###############################################################################
|
|
# Binning #
|
|
###############################################################################
|
|
|
|
def calculate_bins(peaks):
|
|
return int(np.log2(peaks.size) + 1) + 1
|
|
|
|
def plot_hist(peaks, bins, scale=1, save=None):
|
|
fig, ax = set_up_plot()
|
|
peaks = peaks*scale
|
|
ax.hist(peaks, bins, density=True, label='Peakhoehenhistogram')
|
|
|
|
hist, edges = np.histogram(peaks, bins, density=True)
|
|
mids = (edges[:-1] + edges[1:])/2
|
|
popt, _ = curve_fit(boltzmann, mids, hist, bounds=([1, .2], np.inf))
|
|
|
|
points = np.linspace(0, edges[-1], 100)
|
|
ax.plot(points, boltzmann(points, *popt),
|
|
label=fr'Landauverteilung $\mu={popt[1]:.1f},\; \eta={popt[0]:.1f}$')
|
|
ax.legend()
|
|
ax.set_ylabel('Warscheinlichkeitsdichte')
|
|
ax.set_xlabel(fr'Peakhoehe [Volt$\cdot {scale}$]')
|
|
if save:
|
|
save_fig(fig, *save)
|
|
return fig, ax
|
|
|
|
|
|
def boltzmann(x, c, mu):
|
|
return pylandau.landau_pdf(x, mu=mu, eta=c)
|
|
|
|
###############################################################################
|
|
# Plot Porn #
|
|
###############################################################################
|
|
|
|
matplotlib.rcParams.update({
|
|
'font.family': 'serif',
|
|
'text.usetex': False,
|
|
'pgf.rcfonts': False,
|
|
})
|
|
|
|
def pinmp_ticks(axis, ticks):
|
|
axis.set_major_locator(ticker.MaxNLocator(ticks))
|
|
axis.set_minor_locator(ticker.MaxNLocator(ticks*10))
|
|
return axis
|
|
|
|
def set_up_plot(ticks=10):
|
|
fig = plt.figure()
|
|
ax = fig.add_subplot(111)
|
|
|
|
pinmp_ticks(ax.xaxis, ticks)
|
|
pinmp_ticks(ax.yaxis, ticks)
|
|
|
|
ax.grid(which='minor', alpha=.3)
|
|
ax.grid(which='major', alpha=.8)
|
|
|
|
|
|
ax.tick_params(right=True, top=True, which='both')
|
|
|
|
return fig, ax
|
|
|
|
def save_fig(fig, title, folder='unsorted', size=(5, 4)):
|
|
fig.set_size_inches(*size)
|
|
fig.tight_layout()
|
|
try:
|
|
os.makedirs(f'./figs/{folder}/')
|
|
except OSError as exc:
|
|
pass
|
|
fig.savefig(f'./figs/{folder}/{title}.pdf')
|
|
fig.savefig(f'./figs/{folder}/{title}.pgf')
|
|
|
|
with open('./out/figlist.txt', 'a') as f:
|
|
f.write(r'''
|
|
\begin{figure}[H]\centering
|
|
\input{../auswertung/figs/'''
|
|
+ f'{folder}/{title}.pgf' +
|
|
r'''}
|
|
\caption{}
|
|
\label{fig:''' + folder + '-' + title + '''}
|
|
\end{figure}
|
|
''')
|
|
|
|
def plot_spectrum(counts, offset=1, errorevery=False, save=None, **pyplot_args):
|
|
fig, ax = set_up_plot()
|
|
|
|
channels = np.arange(0, counts.size) + offset
|
|
ax.step(channels, counts, **pyplot_args)
|
|
|
|
if errorevery:
|
|
ax.errorbar(channels, counts, yerr=np.sqrt(counts), linestyle='None',
|
|
errorevery=errorevery)
|
|
|
|
ax.set_xlabel('Kanal')
|
|
ax.set_ylabel('Counts')
|
|
ax.set_xlim([channels[0], channels[-1]])
|
|
ax.set_ylim(0)
|
|
|
|
if save:
|
|
save_fig(fig, *save)
|
|
|
|
return fig, ax
|
|
|
|
###############################################################################
|
|
# Maximum Likelihood #
|
|
###############################################################################
|
|
|
|
def continous(counts: np.ndarray, interval: Tuple[float, float], epsilon: float=1e-3) \
|
|
-> (float, float, int, float):
|
|
"""Maximizes the likelihood for the continous propability model.
|
|
(Method 1)
|
|
|
|
:param array counts: the time-spectrum
|
|
:param tuple interval: the interval of the time spectrum used
|
|
(inclusive, exclusive)
|
|
:param float epsilon: the desired precision
|
|
|
|
:returns: the lifetime, the deviation of the same, total counts,
|
|
total time
|
|
"""
|
|
|
|
cts = counts[interval[0]:interval[1]] # constrained counts
|
|
channels = np.arange(0, interval[1] - interval[0]) # channel indices
|
|
times = chan_to_time(channels, center=True) # mean time of channels
|
|
|
|
T = (times[1] - times[0])*len(cts) # time interval
|
|
N = cts.sum() # total count
|
|
tau_0 = np.sum(cts*times)/N # initial guess
|
|
|
|
def model(tau):
|
|
return tau_0 + T/(np.exp(T/tau) - 1)
|
|
|
|
taus = [tau_0]
|
|
def optimize(tau):
|
|
next_tau = model(tau)
|
|
taus.append(next_tau)
|
|
|
|
if np.abs(tau - next_tau) < epsilon:
|
|
return next_tau
|
|
return optimize(next_tau)
|
|
|
|
tau = optimize(tau_0)
|
|
|
|
correction_factor = 1/(1-(T/((np.exp(T/tau)-1)*tau))**2*np.exp(T/tau))
|
|
delta_tau = np.sqrt(np.sum(cts*times**2))/N*correction_factor
|
|
|
|
return tau, delta_tau, N, T, taus, correction_factor
|
|
|
|
|
|
def binned_likelihood(counts, interval):
|
|
"""Generates the -2*ln(likelihood) functions as closures under the
|
|
assumption of poisson and gaussian statistics.
|
|
|
|
:param array counts: the time-spectrum
|
|
:param tuple interval: the interval of the time spectrum used
|
|
(inclusive, exclusive)
|
|
|
|
:returns: poisson, gauss likelihood funcions, total N
|
|
"""
|
|
|
|
cts = counts[interval[0]:interval[1]][:, None] # constrained counts
|
|
channels = np.arange(interval[0], interval[1]) # channel indices
|
|
times = chan_to_time(channels)[:, None]
|
|
tick = times[1][0] - times[0][0]
|
|
N = cts.sum()
|
|
|
|
def N_0(tau):
|
|
return N/(np.exp(-1*times[0][0]/tau) - np.exp(-1*(times[-1][0] + tick)/tau))
|
|
|
|
def f(tau):
|
|
return N_0(tau)/tau*np.exp(-(times + tick/2)/tau)*tick
|
|
|
|
def convert_tau(tau):
|
|
"""Move tau to the second first dimension."""
|
|
return tau[None,:] if isinstance(tau, np.ndarray) else np.array([[tau]])
|
|
|
|
def ln_poisson_likelihood(tau):
|
|
tau = convert_tau(tau)
|
|
return -2*np.sum(cts*np.log(f(tau)), axis=0)
|
|
|
|
def ln_gauss_likelihood(tau):
|
|
tau = convert_tau(tau)
|
|
fi = f(tau)
|
|
return np.sum((cts-fi)**2/fi, axis=0)
|
|
|
|
# for cross checking
|
|
def ln_exp_likelihood(tau):
|
|
tau = convert_tau(tau)
|
|
mod_times = times - times[0][0] + tick/2
|
|
T = tick * len(cts)
|
|
return 2*np.sum(cts*(mod_times/tau+np.log(tau)+np.log(1-np.exp(-T/tau))), axis=0)
|
|
|
|
return ln_poisson_likelihood, ln_gauss_likelihood, ln_exp_likelihood, N, N_0
|
|
|
|
def maximize_likelihood(likelihood, tau_range, epsilon=1e-3):
|
|
"""Minizizes the -2ln(likelihood) function thus maximizing the
|
|
likelihood.
|
|
|
|
:param likelihood: the -2*ln(likelihood) function
|
|
:param tau_range: the range in which to look for the lifetime
|
|
:param epsilon: the desired precision
|
|
|
|
:returns: tau, (sigma minus, sigma plus), -2*ln(likelihood) at minimum,
|
|
(gf, delta_gf)
|
|
"""
|
|
|
|
result = minimize_scalar(likelihood, bounds=tau_range, bracket=tau_range,
|
|
tol=epsilon)
|
|
tau = result.x[0]
|
|
l = likelihood(tau)[0]
|
|
|
|
# one should check for errors here, but this is a one-off, so i
|
|
# dont bother
|
|
|
|
def find_sigma(right=False):
|
|
return np.abs(tau - root_scalar(lambda t: likelihood(t) - (l + 1),
|
|
bracket=[tau, tau_range[1]] \
|
|
if right else [tau_range[0], tau],
|
|
x0=tau,
|
|
x1=tau + 100 if right else tau - 100).root)
|
|
sigma_minus = find_sigma()
|
|
sigma_plus = find_sigma(right=True)
|
|
max_sigma = np.max([sigma_plus, sigma_minus])
|
|
return tau, (sigma_minus, sigma_plus), l, weak_constant(tau=(tau, max_sigma))
|
|
|
|
|
|
# i could just use *args **kwargs for this wrapper, but i won't for
|
|
# the sake of clarity
|
|
def maximize_and_plot_likelihood(likelihood, tau_range, epsilon=1e-5, save=None):
|
|
"""Minizizes the -2ln(likelihood) function thus maximizing the
|
|
likelihood.
|
|
|
|
:param likelihood: the -2*ln(likelihood) function
|
|
:param tau_range: the range in which to look for the lifetime
|
|
:param epsilon: the desired precision
|
|
|
|
:returns: (fig, ax), tau, (sigma minus, sigma plus),
|
|
-2*ln(likelihood) at minimum, (gf, delta_gf)
|
|
"""
|
|
|
|
tau, sigma, l, gf = maximize_likelihood(likelihood,
|
|
tau_range, epsilon)
|
|
|
|
rng = np.max(sigma) * 3
|
|
taus = np.linspace(tau - rng, tau + rng, 1000)
|
|
fig, ax = set_up_plot()
|
|
|
|
ax.plot(taus, likelihood(taus), label='$-2\ln(L)$')
|
|
ax.plot([(tau - sigma[0]), (tau + sigma[1])], [(l + 1), (l + 1)],
|
|
linestyle='dotted', color='gray', label='Deviation from +1 Unit')
|
|
ax.plot([(tau - sigma[0]), (tau - sigma[0])], [(l + 1), l],
|
|
linestyle='dotted', color='gray')
|
|
ax.plot([(tau + sigma[1]), (tau + sigma[1])], [(l + 1), l],
|
|
linestyle='dotted', color='gray')
|
|
|
|
ax.set_xlim((taus[0], taus[-1]))
|
|
ax.axvline(tau, linestyle='--', color='black', label=r'$\tau$')
|
|
ax.errorbar(tau, l, marker='x', markersize=10, xerr=np.array([sigma]).T,
|
|
label='Minimum', capsize=10)
|
|
|
|
ax.set_xlabel(r'$\tau$ [ns]')
|
|
ax.set_ylabel(r'$-2\ln(L)$')
|
|
ax.legend()
|
|
|
|
|
|
if save:
|
|
save_fig(fig, *save)
|
|
return (fig, ax), tau, sigma, l, gf
|