Newer
Older
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
lorenzennio
committed
This class computes mean and error on the detector output.
It then fits the exact equations of motions for a particle
in a magnetic field to the input data.
The results can then be visualized with plots.
"""
The class is initialized with the angles from the detector results
and then automatically deletes 'None' entries and computes the minimum
range of angles and the error.
"""
...
def fill(self, bounds):
self.xBounds = pd.Series({'High' : bounds[:,0,0],
'Low' : bounds[:,0,1]})
self.yBounds = pd.Series({'High' : bounds[:,1,0],
'Low' : bounds[:,1,1]})
self.results = pd.DataFrame({'w' : np.zeros(2),
'vx' : np.zeros(2),
'vy' : np.zeros(2),
'vz' : np.zeros(2)})
lorenzennio
committed
self.xPoints = pd.DataFrame({'Mean': (bounds[1:,0,0] + bounds[1:,0,1])/2.,
'Error': (bounds[1:,0,0] - bounds[1:,0,1])/np.sqrt(12.)})
lorenzennio
committed
self.yPoints = pd.DataFrame({'Mean': (bounds[1:,1,0] + bounds[1:,1,1])/2.,
lorenzennio
committed
'Error': (bounds[1:,1,0] - bounds[1:,1,1])/np.sqrt(12.)})
lorenzennio
committed
self.zPoints = pd.DataFrame({'Mean': (bounds[1:,2,0] + bounds[1:,2,1])/2.,
lorenzennio
committed
'Error': (bounds[1:,2,0] - bounds[1:,2,1])/np.sqrt(12.)})
self.tPoints = pd.DataFrame({'Mean': bounds[1:,3,0],
'Error': 1e-9})
print(self.xPoints)
print(self.yPoints)
print(self.zPoints)
lorenzennio
committed
print(self.tPoints)
Fill the angle data into the class variables.
#print(self.xBounds['High'])
self.xBounds['High'] = np.array([x for x in self.xBounds['High'] if x is not None])
self.xBounds['Low'] = np.array([x for x in self.xBounds['Low'] if x is not None])
self.yBounds['High'] = np.array([x for x in self.yBounds['High'] if x is not None])
self.yBounds['Low'] = np.array([x for x in self.yBounds['Low'] if x is not None])
lorenzennio
committed
"""----------------------------------------------------------------------------
FITTING
Here we fit the data. As a fitting procedure we use ORTHOGONAL DISTANCE REGRESSION.
We fit all three spacial dimensions with respect to time, which is the same as fitting
path length for non-relativistic paricles.
The fitting functions / equations of motion are xEOM, yEOM, zEOM.
The actual fits are performed by the functions xFit, yFit, zFit.
The fitting functions are called by the function Fit. It also collects
all the results and fills them into the results dataframe.
"""
def Fit(self):
lorenzennio
committed
lorenzennio
committed
xResults = self.xFit()
yResults = self.yFit()
zResults = self.zFit()
#check sign of w, as it is sometimes added to other fitting parameters
sign = 1
ySgn = np.sign(yResults.beta)
zSgn = np.sign(zResults.beta)
if np.amin(ySgn) == -1 and np.amin(zSgn) ==-1:
sign = -1
lorenzennio
committed
# do we need this?
if np.sum(ySgn) < 1 or np.sum(zSgn) < 1:
raise Exception('A fitting error occured. Try again.')
self.results['vx'] = np.array([xResults.beta[0], xResults.sd_beta[0]])
lorenzennio
committed
w = (np.abs(yResults.beta[0]) + np.abs(zResults.beta[0]))/2.
wStd = np.sqrt(yResults.sd_beta[0]**2 + zResults.sd_beta[0]**2)
lorenzennio
committed
vy = (np.abs(yResults.beta[1]) + np.abs(zResults.beta[1]))/2.
vyStd = np.sqrt(yResults.sd_beta[1]**2 + zResults.sd_beta[1]**2)
lorenzennio
committed
vz = (np.abs(yResults.beta[2]) + np.abs(zResults.beta[2]))/2.
vzStd = np.sqrt(yResults.sd_beta[2]**2 + zResults.sd_beta[2]**2)
lorenzennio
committed
def xEOM(self, B, t):
lorenzennio
committed
B = [Vx]
lorenzennio
committed
x = B[0]*t
return x
lorenzennio
committed
def yEOM(self, B, t):
"""
B = [w, Vy, Vz]
"""
#y = B[1] + np.sqrt(B[0]**2 - (z-B[2])**2)
y = (1/B[0])*(B[2]*(1-np.cos(t*B[0])) + B[1]*np.sin(t*B[0]))
return y
lorenzennio
committed
def zEOM(self, B, t):
"""
B= [w, Vy, Vz]
"""
z = (1/B[0])*(-B[1]*(1-np.cos(t*B[0])) + B[2]*np.sin(t*B[0]))
return z
lorenzennio
committed
def pol(self, B, x):
return B[0] + B[1]*x + B[2]*(x**2) + B[3]*(x**3)
lorenzennio
committed
lorenzennio
committed
xModel = odr.Model(self.xEOM)
xData = odr.RealData(self.tPoints['Mean'],
lorenzennio
committed
self.xPoints['Mean'],
lorenzennio
committed
sx = self.tPoints['Error'],
lorenzennio
committed
sy = self.xPoints['Error'])
lorenzennio
committed
xODR = odr.ODR(xData, xModel,beta0=[10])# beta0=[1, 100, 100, 100])
lorenzennio
committed
#xOUT.pprint()
print('The x fit was performend with chi2 = ', xOUT.res_var)
#print(xOUT.sd_beta)
return xOUT
"""
lorenzennio
committed
print('x ', xOUT.res_var)
lorenzennio
committed
plt.plot(self.tPoints['Mean'],self.xEOM(xOUT.beta, self.tPoints['Mean']))
plt.plot(self.tPoints['Mean'], self.xPoints['Mean'], 'o')
plt.xlabel('t')
lorenzennio
committed
plt.ylabel('x')
lorenzennio
committed
plt.savefig('x.pdf', format='pdf')
lorenzennio
committed
plt.gcf().clear()
lorenzennio
committed
lorenzennio
committed
yModel = odr.Model(self.yEOM)
yData = odr.RealData(self.tPoints['Mean'],
lorenzennio
committed
sx = self.tPoints['Error'],
lorenzennio
committed
yODR = odr.ODR(yData, yModel, beta0=[1, 10, 100])
lorenzennio
committed
print('The y fit was performend with chi2 = ', yOUT.res_var)
#yOUT.pprint()
return yOUT
"""
lorenzennio
committed
print('y :', yOUT.res_var)
lorenzennio
committed
plt.plot(self.tPoints['Mean'], self.yEOM(yOUT.beta, self.tPoints['Mean']))
plt.plot(self.tPoints['Mean'], self.yPoints['Mean'], 'o')
plt.xlabel('t')
lorenzennio
committed
plt.ylabel('y')
lorenzennio
committed
plt.savefig('y.pdf', format='pdf')
lorenzennio
committed
plt.gcf().clear()
lorenzennio
committed
def zFit(self):
#ODR
zModel = odr.Model(self.zEOM)
zData = odr.RealData(self.tPoints['Mean'],
self.zPoints['Mean'],
sx = self.tPoints['Error'],
sy = self.zPoints['Error'])
zODR = odr.ODR(zData, zModel, beta0=[1, 10, 100])
zOUT = zODR.run()
print('The z fit was performend with chi2 = ', zOUT.res_var)
#zOUT.pprint()
#print('z :', zOUT.res_var)
return zOUT
"""
plt.plot(self.tPoints['Mean'], self.zEOM(zOUT.beta, self.tPoints['Mean']))
plt.plot(self.tPoints['Mean'], self.zPoints['Mean'], 'o')
plt.xlabel('t')
plt.ylabel('z')
plt.savefig('z.pdf', format='pdf')
plt.gcf().clear()
def Bounds(self, high, low, call=0):
Algorithm to compute the minimum possible range of angles.
The upper and lower angles must be given as a argument and also
the 'call' variable secifies the plane you are working in
(x-z or y-z plane).
The algorithim loops over the angles from the last detector layer
to the first, and replaces the upper and lower angle bounds, if
a certain layer restricts the range further.
The mean is calculated as the arithmetic mean of the resulting
angles and the error is the difference from the mean to the angle
bounds.
highRev = high[::-1]
lowRev = low[::-1]
highBound = highRev[0]
lowBound = lowRev[0]
for a,b in zip(highRev[1:], lowRev[1:]):
if a < highBound and a > lowBound:
highBound = a
if b < highBound and b > lowBound:
lowBound = b
error = (highBound - lowBound)/2
mean = highBound - error
self.results['Mean'][call] = mean
self.results['Error'][call] = error