aboutsummaryrefslogtreecommitdiff
path: root/src
AgeCommit message (Expand)Author
2018-10-19add MIN_RATIO_FAST to speed up the "fast" likelihood calculationtlatorre
2018-10-19speed up get_total_charge_approx()tlatorre
2018-10-19epsrel -> npointstlatorre
2018-10-19add interp2d() for fast bilinear 2D interpolationtlatorre
2018-10-19speed up sin_theta calculation by replacing sin(acos(cos_theta)) with sqrt(1-...tlatorre
2018-10-19update path integral to use a fixed number of pointstlatorre
2018-10-18fix a bug in get_total_charge_approx()tlatorre
2018-10-18update path_init() to check for a divide by zerotlatorre
2018-10-18make sure that the kinetic energy is zero at the last steptlatorre
2018-10-18hardcode the density when computing dE/dxtlatorre
2018-10-18fix the likelihood function to return the *negative* log likelihood of the pa...tlatorre
2018-10-18update theta0 calculation to use the radiation length in light watertlatorre
2018-10-18update fit to fit for electrons and protonstlatorre
2018-10-17fix a bug in the theta0 calculation for a pathtlatorre
2018-10-12skip PMTs which weren't hit for the fast likelihood calculationtlatorre
2018-10-06add media_qoca_d2o_20060110.cmdtlatorre
2018-10-06prune output of mcvx and mctk bankstlatorre
2018-10-05epsrel = 1e-2 to speed up fittlatorre
2018-10-03update absorption coefficients from media_qoca_d2o_20060110.cmdtlatorre
2018-10-03add \ntlatorre
2018-10-03move python scripts into utils/ directorytlatorre
2018-10-03add a script to plot fit resultstlatorre
2018-10-03make sure wavelengths are uniformly spaced since we're using interp1d()tlatorre
2018-10-02update get_quantum_efficiency() to use interp1d()tlatorre
2018-10-02update MIN_THETA0 to 0.02tlatorre
2018-10-02update charge fraction to 0.5tlatorre
2018-10-02update optics to use LETA constants for the D2O phasetlatorre
2018-10-01flag PMTs with the KPF_DIS bit settlatorre
2018-10-01fall back to old scattering rms calculation when n = 0tlatorre
2018-10-01update negative log likelihood for path coefficientstlatorre
2018-10-01loop over all normal PMTs when calculating the expected number of photonstlatorre
2018-10-01use the PMT response table to calculate the amount of reflected lighttlatorre
2018-10-01add absorption length for acrylictlatorre
2018-10-01fix a bug when computing the absorption length in H2O and D2Otlatorre
2018-09-26speed up fast likelihood calculationtlatorre
2018-09-26speed up fast likelihood calculationtlatorre
2018-09-26increase number of points in cos(theta) interpolation to 1000tlatorre
2018-09-25update indirect scattering PDF start timetlatorre
2018-09-25update likelihood calculation to use PMT_TTS macrotlatorre
2018-09-25update integration bounds in likelihood calculationtlatorre
2018-09-25increase maxeval to 20 for the "quick" minimization phasetlatorre
2018-09-21update likelihood function to include the probability of the path coefficientstlatorre
2018-09-21split up the track integral into two intervalstlatorre
2018-09-21increase MAX_PE to 10000tlatorre
2018-09-20delete unused variable distancetlatorre
2018-09-20don't include the OWL PMTs in the likelihood calculationtlatorre
2018-09-20make sure direction vector is normalized in path_eval()tlatorre
2018-09-20add particle id code to output filetlatorre
2018-09-20add git SHA1 hash to output filetlatorre
2018-09-20add time elapsed to the output filetlatorre
t: bold } /* Literal.Number.Integer.Long */
#!/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/>.
"""
Script to combine the fit results from jobs submitted to the grid.

This script first runs zdab-cat on the zdab file to get the data cleaning words
and SNOMAN fitter results for every event in the file. It then adds any fit
results from the other files listed on the command line and prints the results
as YAML to stdout.

Example:

    $ cat-grid-jobs ~/mc_atm_nu_no_osc_genie_010000_0.mcds ~/grid_job_results/*.txt > output.txt

"""

from __future__ import print_function, division
import yaml
try:
    from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
    from yaml import Loader, Dumper
import os
import sys

# Check that a given file can be accessed with the correct mode.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def _access_check(fn, mode):
    return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn))

def which(cmd, mode=os.F_OK | os.X_OK, path=None):
    """Given a command, mode, and a PATH string, return the path which
    conforms to the given mode on the PATH, or None if there is no such
    file.
    `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
    of os.environ.get("PATH"), or can be overridden with a custom search
    path.
    """
    # If we're given a path with a directory part, look it up directly rather
    # than referring to PATH directories. This includes checking relative to the
    # current directory, e.g. ./script
    if os.path.dirname(cmd):
        if _access_check(cmd, mode):
            return cmd
        return None

    if path is None:
        path = os.environ.get("PATH", None)
        if path is None:
            try:
                path = os.confstr("CS_PATH")
            except (AttributeError, ValueError):
                # os.confstr() or CS_PATH is not available
                path = os.defpath
        # bpo-35755: Don't use os.defpath if the PATH environment variable is
        # set to an empty string

    # PATH='' doesn't match, whereas PATH=':' looks in the current directory
    if not path:
        return None

    path = path.split(os.pathsep)

    if sys.platform == "win32":
        # The current directory takes precedence on Windows.
        curdir = os.curdir
        if curdir not in path:
            path.insert(0, curdir)

        # PATHEXT is necessary to check on Windows.
        pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
        # See if the given file matches any of the expected path extensions.
        # This will allow us to short circuit when given "python.exe".
        # If it does match, only test that one, otherwise we have to try
        # others.
        if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
            files = [cmd]
        else:
            files = [cmd + ext for ext in pathext]
    else:
        # On other platforms you don't have things like PATHEXT to tell you
        # what file suffixes are executable, so just pass on cmd as-is.
        files = [cmd]

    seen = set()
    for dir in path:
        normdir = os.path.normcase(dir)
        if not normdir in seen:
            seen.add(normdir)
            for thefile in files:
                name = os.path.join(dir, thefile)
                if _access_check(name, mode):
                    return name
    return None

# from https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-terminal-in-python
class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def print_warning(msg):
    print(bcolors.WARNING + msg + bcolors.ENDC,file=sys.stderr)

warned = False

def print_warning_once(msg):
    global warned
    if not warned:
        print_warning(msg)
        print("skipping further warnings")
        warned = True

def print_fail(msg):
    print(bcolors.FAIL + msg + bcolors.ENDC,file=sys.stderr)

if __name__ == '__main__':
    import argparse
    import matplotlib.pyplot as plt
    import numpy as np
    from subprocess import check_call
    from os.path import join, split
    import os
    import sys
    import h5py
    import glob

    parser = argparse.ArgumentParser("concatenate fit results from grid jobs into a single file")
    parser.add_argument("zdab", help="zdab input file")
    parser.add_argument("directory", help="directory with grid results")
    parser.add_argument("-o", "--output", type=str, help="output filename", required=True)
    args = parser.parse_args()

    zdab_cat = which("zdab-cat")

    if zdab_cat is None:
        print("couldn't find zdab-cat in path!",file=sys.stderr)
        sys.exit(1)

    # First we get the full event list along with the data cleaning word, FTP
    # position, FTK, and RSP energy from the original zdab and then add the fit
    # results.
    #
    # Note: We send stderr to /dev/null since there can be a lot of warnings
    # about PMT types and fit results
    with open(os.devnull, 'w') as f:
        check_call([zdab_cat,args.zdab,"-o",args.output],stderr=f)

    total_events = 0
    events_with_fit = 0
    total_fits = 0

    with h5py.File(args.output,"a") as fout:
        total_events = fout['ev'].shape[0]
        for filename in glob.glob(join(args.directory,'*.hdf5')):
            with h5py.File(filename) as f:
                if 'git_sha1' not in f.attrs:
                    print_fail("No git sha1 found for %s. Skipping..." % tail)
                    continue
                # Check to see if the git sha1 match
                if fout.attrs['git_sha1'] != f.attrs['git_sha1']:
                    head, tail = split(filename)
                    print_warning_once("git_sha1 is %s for current version but %s for %s" % (fout.attrs['git_sha1'],f.attrs['git_sha1'],tail))
                # get fits which match up with the events
                valid_fits = f['fits'][np.isin(f['fits'][:][['run','gtid']],fout['ev'][:][['run','gtid']])]
                # Add the fit results
                fout['fits'].resize((fout['fits'].shape[0]+valid_fits.shape[0],))
                fout['fits'][-valid_fits.shape[0]:] = valid_fits
                events_with_fit += len(np.unique(valid_fits[['run','gtid']]))
                total_fits += len(np.unique(f['fits']['run','gtid']))

    # Print out number of fit results that were added. Hopefully, this will
    # make it easy to catch an error if, for example, this gets run with a
    # mismatching zdab and fit results
    print("added %i/%i fit results to a total of %i events" % (events_with_fit, total_fits, total_events),file=sys.stderr)