Skip to content
Snippets Groups Projects
analysis.py 8.37 KiB
Newer Older
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy import odr
class Analysis:
lorenzennio's avatar
lorenzennio committed
    """
    Analysis class:
        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.
lorenzennio's avatar
lorenzennio committed
    """
    def __init__(self):
lorenzennio's avatar
lorenzennio committed
        """
        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):
lorenzennio's avatar
lorenzennio committed
        """
        """
        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),
lorenzennio's avatar
lorenzennio committed
                                    'vx'    : np.zeros(2),
                                    'vy'    : np.zeros(2),
                                    'vz'    : np.zeros(2)})
lorenzennio's avatar
lorenzennio committed
        #Determine mean and std
        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.)})
        self.yPoints = pd.DataFrame({'Mean': (bounds[1:,1,0] + bounds[1:,1,1])/2.,
                                    'Error': (bounds[1:,1,0] - bounds[1:,1,1])/np.sqrt(12.)})
        self.zPoints = pd.DataFrame({'Mean': (bounds[1:,2,0] + bounds[1:,2,1])/2.,
                                    'Error': (bounds[1:,2,0] - bounds[1:,2,1])/np.sqrt(12.)})
        self.tPoints = pd.DataFrame({'Mean': bounds[1:,3,0],
                                     'Error': 1e-9})
lorenzennio's avatar
lorenzennio committed
        """ 
        print(self.xPoints)
        print(self.yPoints)
        print(self.zPoints)
lorenzennio's avatar
lorenzennio committed
        """
    def rmNone(self):
lorenzennio's avatar
lorenzennio committed
        """
        Fill the angle data into the class variables.
lorenzennio's avatar
lorenzennio committed
        """
        #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])

    """----------------------------------------------------------------------------
    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):
        xResults = self.xFit()
        yResults = self.yFit()
        zResults = self.zFit()

        #check sign of w, as it is sometimes added to other fitting parameters
lorenzennio's avatar
lorenzennio committed
        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's avatar
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]])
        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's avatar
lorenzennio committed
        self.results['w'] = np.array([ sign * w , wStd])

        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's avatar
lorenzennio committed
        self.results['vy'] = np.array([vy,vyStd])

        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's avatar
lorenzennio committed
        self.results['vz'] = np.array([vz,vzStd])
    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
    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's avatar
lorenzennio committed
    def pol(self, B, x):
        return B[0] + B[1]*x + B[2]*(x**2) + B[3]*(x**3)
    def xFit(self):
        xModel  = odr.Model(self.xEOM)
        xData   = odr.RealData(self.tPoints['Mean'],
        xODR = odr.ODR(xData, xModel,beta0=[10])# beta0=[1, 100, 100, 100])
        xOUT = xODR.run()
        #xOUT.pprint()
        print('The x fit was performend with chi2 = ', xOUT.res_var)
        #print(xOUT.sd_beta)
        return xOUT
        """
        plt.plot(self.tPoints['Mean'],self.xEOM(xOUT.beta, self.tPoints['Mean']))
        plt.plot(self.tPoints['Mean'], self.xPoints['Mean'], 'o')
        plt.xlabel('t')
    def yFit(self):
        #ODR
        yModel  = odr.Model(self.yEOM)
        yData   = odr.RealData(self.tPoints['Mean'],
                               self.yPoints['Mean'],
                               sy = self.yPoints['Error'])
        yODR = odr.ODR(yData, yModel, beta0=[1, 10, 100])
        yOUT = yODR.run()
        print('The y fit was performend with chi2 = ', yOUT.res_var)
        #yOUT.pprint()
        return yOUT
        """
        plt.plot(self.tPoints['Mean'], self.yEOM(yOUT.beta, self.tPoints['Mean']))
        plt.plot(self.tPoints['Mean'], self.yPoints['Mean'], 'o')
        plt.xlabel('t')

    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()
lorenzennio's avatar
lorenzennio committed
    """
    def Bounds(self, high, low, call=0):
lorenzennio's avatar
lorenzennio committed
        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
lorenzennio's avatar
lorenzennio committed
    """
    def Plot(self):
        ...