summaryrefslogtreecommitdiff
path: root/doc/source
AgeCommit message (Expand)Author
2021-05-09Add dependency on libatlas so that numpy will compileStan Seibert
2021-05-09Nope, Chroma really does require Numpy 1.6 or later. Putting dependency back...Stan Seibert
2021-05-09Fix documentation bug regarding location of GEANT4 dataStan Seibert
2021-05-09I give up. We have to install pyublas by hand to be able to compile any exte...Stan Seibert
2021-05-09More fixes to installation guide.Stan Seibert
2021-05-09Update ROOT version number and make the numbers consistent. (Hat tip to mast...Stan Seibert
2021-05-09Minor tweaks to install instructonsStan Seibert
2021-05-09Add authors and Chroma PDF to index pageStan Seibert
2021-05-09Update index page in documentation to also work as website.Stan Seibert
2021-05-09Tell people to grab Chroma from the Bitbucket repository.Stan Seibert
2021-05-09Update version number to 0.1, switch to sphinxdoc style (lighter shades!) and...Stan Seibert
2011-09-19Chroma installation guide.Stan Seibert
2011-09-09update sphinx documentation.Anthony LaTorre
2011-09-06add Mesh class to sphinx documentation.Anthony LaTorre
2011-09-03add mesh_from_stl() to sphinx documentation.Anthony LaTorre
2011-09-03reorder members in chroma.make sphinx documentation.Anthony LaTorre
2011-09-03add mesh modeling tools to sphinx documentation.Anthony LaTorre
2011-09-03add intro to sphinx documentation from the bitbucket wiki and add some entrie...Anthony LaTorre
2011-09-03initial sphinx documentation.Anthony LaTorre
155'>155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
#!/usr/bin/env python
# Copyright (c) 2019, Anthony Latorre <tlatorre at uchicago>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.

from __future__ import print_function, division
import yaml
import numpy as np
from scipy.stats import iqr
from matplotlib.lines import Line2D

# on retina screens, the default plots are way too small
# by using Qt5 and setting QT_AUTO_SCREEN_SCALE_FACTOR=1
# Qt5 will scale everything using the dpi in ~/.Xresources
import matplotlib
matplotlib.use("Qt5Agg")

matplotlib.rc('font', size=22)

IDP_E_MINUS  =    20
IDP_MU_MINUS =    22

SNOMAN_MASS = {
    20: 0.511,
    21: 0.511,
    22: 105.658,
    23: 105.658
}

def plot_hist(x, label=None):
    # determine the bin width using the Freedman Diaconis rule
    # see https://en.wikipedia.org/wiki/Freedman%E2%80%93Diaconis_rule
    h = 2*iqr(x)/len(x)**(1/3)
    n = max(int((np.max(x)-np.min(x))/h),10)
    bins = np.linspace(np.min(x),np.max(x),n)
    plt.hist(x, bins=bins, histtype='step', label=label)

def plot_legend(n):
    plt.figure(n)
    ax = plt.gca()
    handles, labels = ax.get_legend_handles_labels()
    new_handles = [Line2D([],[],c=h.get_edgecolor()) for h in handles]
    plt.legend(handles=new_handles,labels=labels)

def get_stats(x):
    """
    Returns a tuple (mean, error mean, std, error std) for the values in x.

    The formula for the standard error on the standard deviation comes from
    https://stats.stackexchange.com/questions/156518.
    """
    mean = np.mean(x)
    std = np.std(x)
    n = len(x)
    u4 = np.mean((x-mean)**4)
    error = np.sqrt((u4-(n-3)*std**4/(n-1))/n)/(2*std)
    return mean, std/np.sqrt(n), std, error

if __name__ == '__main__':
    import argparse
    import matplotlib.pyplot as plt
    import numpy as np

    parser = argparse.ArgumentParser("plot fit results")
    parser.add_argument("filenames", nargs='+', help="input files")
    args = parser.parse_args()

    events = []

    for filename in args.filenames:
        print(filename)
        with open(filename) as f:
            data = yaml.load(f.read())

        a = np.ma.empty(len(data['data']),
                        dtype=[('id',np.int),       # particle id
                               ('T', np.double),    # true energy
                               ('dx',np.double),    # dx
                               ('dy',np.double),    # dy
                               ('dz',np.double),    # dz
                               ('dT',np.double),    # dT
                               ('theta',np.double), # theta
                               ('ratio',np.double), # likelihood ratio
                               ('te',np.double),    # time electron
                               ('tm',np.double),    # time muon
                               ('Te',np.double)]    # electron energy
                    )

        for i, event in enumerate(data['data']):
            # get the particle ID
            id = event['mcgn'][0]['id']

            a[i]['id'] = id

            if 'fit' not in event['ev'][0]:
                # if nhit < 100 we don't fit the event
                continue

            if id not in event['ev'][0]['fit']:
                a[i] = np.ma.masked
                continue

            mass = SNOMAN_MASS[id]
            # for some reason it's the *second* track which seems to contain the
            # initial energy
            true_energy = event['mcgn'][0]['energy']
            # The MCTK bank has the particle's total energy (except for neutrons)
            # so we need to convert it into kinetic energy
            ke = true_energy - mass

            fit = event['ev'][0]['fit']

            a[i]['T'] = ke
            energy = fit[id]['energy']
            a[i]['dT'] = energy-ke

            # store the fit position residuals
            true_posx = event['mcgn'][0]['posx']
            posx = fit[id]['posx']
            a[i]['dx'] = posx-true_posx
            true_posy = event['mcgn'][0]['posy']
            posy = fit[id]['posy']
            a[i]['dy'] = posy-true_posy
            true_posz = event['mcgn'][0]['posz']
            posz = fit[id]['posz']
            a[i]['dz'] = posz-true_posz

            # compute the angle between the fit direction and the true
            # direction
            dirx = event['mcgn'][0]['dirx']
            diry = event['mcgn'][0]['diry']
            dirz = event['mcgn'][0]['dirz']
            true_dir = [dirx,diry,dirz]
            true_dir = np.array(true_dir)/np.linalg.norm(true_dir)
            theta = fit[id]['theta']
            phi = fit[id]['phi']
            dir = [np.sin(theta)*np.cos(phi),np.sin(theta)*np.sin(phi),np.cos(theta)]
            dir = np.array(dir)/np.linalg.norm(dir)
            a[i]['theta'] = np.degrees(np.arccos(np.dot(true_dir,dir)))

            # compute the log likelihood ratio
            if IDP_E_MINUS in fit and IDP_MU_MINUS in fit:
                fmin_electron = fit[IDP_E_MINUS]['fmin']
                fmin_muon = fit[IDP_MU_MINUS]['fmin']
                a[i]['ratio'] = fmin_muon-fmin_electron
            else:
                a[i]['ratio'] = np.ma.masked

            # store the time taken for electron and muon fits
            if IDP_E_MINUS in fit:
                a[i]['te'] = fit[IDP_E_MINUS]['time']
                a[i]['Te'] = fit[IDP_E_MINUS]['energy']
            else:
                a[i]['te'] = np.ma.masked
                a[i]['Te'] = np.ma.masked
            if IDP_MU_MINUS in fit:
                a[i]['tm'] = fit[IDP_MU_MINUS]['time']
            else:
                a[i]['tm'] = np.ma.masked

        events.append(a)

    a = np.ma.concatenate(events)

    bins = np.arange(50,1000,100)

    stats_array = np.ma.empty(len(bins)-1,
                     dtype=[('T',             np.double),
                            ('dT',            np.double),
                            ('dT_err',        np.double),
                            ('dT_std',        np.double),
                            ('dT_std_err',    np.double),
                            ('dx',            np.double),
                            ('dx_err',        np.double),
                            ('dx_std',        np.double),
                            ('dx_std_err',    np.double),
                            ('dy',            np.double),
                            ('dy_err',        np.double),
                            ('dy_std',        np.double),
                            ('dy_std_err',    np.double),
                            ('dz',            np.double),
                            ('dz_err',        np.double),
                            ('dz_std',        np.double),
                            ('dz_std_err',    np.double),
                            ('theta',         np.double),
                            ('theta_err',     np.double),
                            ('theta_std',     np.double),
                            ('theta_std_err', np.double)])

    stats = {IDP_E_MINUS: stats_array, IDP_MU_MINUS: stats_array.copy()}

    for id in stats:
        electron_events = a[a['id'] == id]

        for i, (ablah, b) in enumerate(zip(bins[:-1], bins[1:])):
            events = electron_events[(electron_events['T'] >= ablah) & (electron_events['T'] < b)]

            if len(events) < 2:
                stats[id][i] = np.ma.masked
                continue

            stats[id][i]['T'] = (ablah+b)/2
            mean, mean_error, std, std_error = get_stats(events['dT'].compressed())
            stats[id][i]['dT'] = mean
            stats[id][i]['dT_err'] = mean_error
            stats[id][i]['dT_std'] = std
            stats[id][i]['dT_std_err'] = std_error
            mean, mean_error, std, std_error = get_stats(events['dx'].compressed())
            stats[id][i]['dx'] = mean
            stats[id][i]['dx_err'] = mean_error
            stats[id][i]['dx_std'] = std
            stats[id][i]['dx_std_err'] = std_error
            mean, mean_error, std, std_error = get_stats(events['dy'].compressed())
            stats[id][i]['dy'] = mean
            stats[id][i]['dy_err'] = mean_error
            stats[id][i]['dy_std'] = std
            stats[id][i]['dy_std_err'] = std_error
            mean, mean_error, std, std_error = get_stats(events['dz'].compressed())
            stats[id][i]['dz'] = mean
            stats[id][i]['dz_err'] = mean_error
            stats[id][i]['dz_std'] = std
            stats[id][i]['dz_std_err'] = std_error
            mean, mean_error, std, std_error = get_stats(events['theta'].compressed())
            stats[id][i]['theta'] = mean
            stats[id][i]['theta_err'] = mean_error
            stats[id][i]['theta_std'] = std
            stats[id][i]['theta_std_err'] = std_error

    for id in stats:
        label = 'Muon' if id == IDP_MU_MINUS else 'Electron'

        T = stats[id]['T']
        dT = stats[id]['dT']
        dT_err = stats[id]['dT_err']
        std_dT = stats[id]['dT_std']
        std_dT_err = stats[id]['dT_std_err']
        dx = stats[id]['dx']
        dx_err = stats[id]['dx_err']
        std_dx = stats[id]['dx_std']
        std_dx_err = stats[id]['dx_std_err']
        dy = stats[id]['dy']
        dy_err = stats[id]['dy_err']
        std_dy = stats[id]['dy_std']
        std_dy_err = stats[id]['dy_std_err']
        dz = stats[id]['dz']
        dz_err = stats[id]['dz_err']
        std_dz = stats[id]['dz_std']
        std_dz_err = stats[id]['dz_std_err']
        theta = stats[id]['theta']
        theta_err = stats[id]['theta_err']
        std_theta = stats[id]['theta_std']
        std_theta_err = stats[id]['theta_std_err']

        plt.figure(1)
        plt.errorbar(T,dT*100/T,yerr=dT_err*100/T,fmt='o',label=label)
        plt.xlabel("Kinetic Energy (MeV)")
        plt.ylabel("Energy bias (%)")
        plt.title("Energy Bias")
        plt.legend()

        plt.figure(2)
        plt.errorbar(T,std_dT*100/T,yerr=std_dT_err*100/T,fmt='o',label=label)
        plt.xlabel("Kinetic Energy (MeV)")
        plt.ylabel("Energy resolution (%)")
        plt.title("Energy Resolution")
        plt.legend()

        plt.figure(3)
        plt.errorbar(T,dx,yerr=dx_err,fmt='o',label='%s (x)' % label)
        plt.errorbar(T,dy,yerr=dy_err,fmt='o',label='%s (y)' % label)
        plt.errorbar(T,dz,yerr=dz_err,fmt='o',label='%s (z)' % label)
        plt.xlabel("Kinetic Energy (MeV)")
        plt.ylabel("Position bias (cm)")
        plt.title("Position Bias")
        plt.legend()

        plt.figure(4)
        plt.errorbar(T,std_dx,yerr=std_dx_err,fmt='o',label='%s (x)' % label)
        plt.errorbar(T,std_dy,yerr=std_dy_err,fmt='o',label='%s (y)' % label)
        plt.errorbar(T,std_dz,yerr=std_dz_err,fmt='o',label='%s (z)' % label)
        plt.xlabel("Kinetic Energy (MeV)")
        plt.ylabel("Position resolution (cm)")
        plt.title("Position Resolution")
        plt.ylim((0,plt.gca().get_ylim()[1]))
        plt.legend()

        plt.figure(5)
        plt.errorbar(T,std_theta,yerr=std_theta_err,fmt='o',label=label)
        plt.xlabel("Kinetic Energy (MeV)")
        plt.ylabel("Angular resolution (deg)")
        plt.title("Angular Resolution")
        plt.ylim((0,plt.gca().get_ylim()[1]))
        plt.legend()

        plt.figure(6)
        plt.scatter(a[a['id'] == id]['Te'],a[a['id'] == id]['ratio'],label=label)
        plt.xlabel("Reconstructed Electron Energy (MeV)")
        plt.ylabel(r"Log Likelihood Ratio (e/$\mu$)")
        plt.title("Log Likelihood Ratio vs Reconstructed Electron Energy")
        plt.legend()
    plt.show()