def normcheckpath(path, checkdir=False): """Normalize a path and check that it exists. Useful e.g. so that different path specifications that resolve to the same location will resolve to the same string. Parameters ---------- path : string Path to check checkdir : bool Whether `path` is expected to be a directory Returns ------- normpath : string Normalized path """ normpath = find_resource(path) if checkdir: kind = 'dir' check = isdir else: kind = 'file' check = isfile if not check(normpath): raise IOError('Path "%s" which resolves to "%s" is not a %s.' % (path, normpath, kind)) return normpath
def read(self, filenames, encoding=None): """Override `read` method to interpret `filenames` as PISA resource locations, then call overridden `read` method. Also, IOError fails here, whereas it is ignored in RawConfigParser. For further help on this method and its arguments, see :method:`~backports.configparser.configparser.read` """ if isinstance(filenames, str): filenames = [filenames] resource_locations = [] for filename in filenames: resource_location = find_resource(filename) if not isfile(resource_location): raise ValueError('"%s" is not a file or could not be located' % filename) resource_locations.append(resource_location) filenames = resource_locations # NOTE: From here on, most of the `read` method is copied, but # ignoring IOError exceptions is removed here. Python copyrights apply. if isinstance(filenames, str): filenames = [filenames] read_ok = [] for filename in filenames: with open(filename, encoding=encoding) as fp: self._read(fp, filename) read_ok.append(filename) return read_ok
def save(self, fpath, ver=None, **kwargs): """Save cross sections (and the energy specification) to a file at `fpath`.""" if ver is None: if self._ver is None: raise ValueError( 'Either a ver must be specified in call to `save` or it ' 'must have been set prior to the invocation of `save`.' ) ver = self._ver else: assert ver == self._ver try: fpath = find_resource(fpath) except IOError: pass fpath = os.path.expandvars(os.path.expanduser(fpath)) all_xs = {} # Get any existing data from file if os.path.exists(fpath): all_xs = from_file(fpath) # Validate existing data by instantiating objects from each for v, d in all_xs.items(): CrossSections(ver=v, energy=d['energy'], xsec=d['xsec']) if ver in all_xs: logging.warning('Overwriting existing version "' + ver + '" in file ' + fpath) all_xs[ver] = {'xsec':self, 'energy':self.energy} to_file(all_xs, fpath, **kwargs)
def __init__( self, events_file, files_per_flavor, output_names, reco, keys, cuts, track_E_cut=None, **std_kwargs, ): # instantiation args that should not change self.events_file = find_resource(events_file) # init base class super().__init__( expected_params=(), **std_kwargs, ) self.output_names = output_names self.reco = reco self.files_per_flavor = split(files_per_flavor) self.track_E_cut = track_E_cut self.keys = split(keys) self.cuts = eval(cuts)
def setup_function(self): # load MCeq tables spline_tables_dict = pickle.load( BZ2File(find_resource(self.params.table_file.value))) self.data.data_specs = self.calc_specs for container in self.data: container['sys_flux'] = np.empty((container.size, 2), dtype=FTYPE) if self.calc_mode == 'binned': # speed up calculation by adding links # as layers don't care about flavour self.data.link_containers('nu', [ 'nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc', 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc' ]) for container in self.data: # evaluate the splines (flux and deltas) for each E/CZ point # at the moment this is done on CPU, therefore we force 'host' for key in spline_tables_dict.keys(): logging.info( 'Evaluating MCEq splines for %s for Barr parameter %s' % (container.name, key)) container['barr_' + key] = np.empty((container.size, 8), dtype=FTYPE) self.eval_spline(container['true_energy'].get('host'), container['true_coszen'].get('host'), spline_tables_dict[key], out=container['barr_' + key].get('host')) container['barr_' + key].mark_changed('host') self.data.unlink_containers()
def setup_function(self): # object for oscillation parameters self.osc_params = OscParams() # setup the layers #if self.params.earth_model.value is not None: earth_model = find_resource(self.params.earth_model.value) YeI = self.params.YeI.value.m_as('dimensionless') YeO = self.params.YeO.value.m_as('dimensionless') YeM = self.params.YeM.value.m_as('dimensionless') prop_height = self.params.prop_height.value.m_as('km') detector_depth = self.params.detector_depth.value.m_as('km') self.layers = Layers(earth_model, detector_depth, prop_height) self.layers.setElecFrac(YeI, YeO, YeM) # set the correct data mode self.data.data_specs = self.calc_specs # --- calculate the layers --- if self.calc_mode == 'binned': # speed up calculation by adding links # as layers don't care about flavour self.data.link_containers('nu', [ 'nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc', 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc' ]) for container in self.data: self.layers.calcLayers(container['true_coszen'].get('host')) container['densities'] = self.layers.density.reshape( (container.size, self.layers.max_layers)) container['distances'] = self.layers.distance.reshape( (container.size, self.layers.max_layers)) # don't forget to un-link everything again self.data.unlink_containers() # --- setup empty arrays --- if self.calc_mode == 'binned': self.data.link_containers('nu', [ 'nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc' ]) self.data.link_containers('nubar', [ 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc' ]) for container in self.data: container['probability'] = np.empty((container.size, 3, 3), dtype=FTYPE) self.data.unlink_containers() # setup more empty arrays for container in self.data: container['prob_e'] = np.empty((container.size), dtype=FTYPE) container['prob_mu'] = np.empty((container.size), dtype=FTYPE)
def setup_function(self): # setup Earth model if self.params.earth_model.value is not None: earth_model = find_resource(self.params.earth_model.value) YeI = self.params.YeI.value.m_as('dimensionless') YeO = self.params.YeO.value.m_as('dimensionless') YeM = self.params.YeM.value.m_as('dimensionless') else: earth_model = None # setup the layers prop_height = self.params.prop_height.value.m_as('km') detector_depth = self.params.detector_depth.value.m_as('km') self.layers = Layers(earth_model, detector_depth, prop_height) if earth_model is not None: self.layers.setElecFrac(YeI, YeO, YeM) # set the correct data mode self.data.representation = self.calc_mode # --- calculate the layers --- if self.data.is_map: # speed up calculation by adding links # as layers don't care about flavour self.data.link_containers('nu', ['nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc', 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc']) for container in self.data: if self.params.earth_model.value is not None: self.layers.calcLayers(container['true_coszen']) container['densities'] = self.layers.density.reshape((container.size, self.layers.max_layers)) container['distances'] = self.layers.distance.reshape((container.size, self.layers.max_layers)) else: self.layers.calcPathLength(container['true_coszen']) container['distances'] = self.layers.distance # don't forget to un-link everything again self.data.unlink_containers() # --- setup empty arrays --- if self.data.is_map: self.data.link_containers('nu', ['nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc']) self.data.link_containers('nubar', ['nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc']) for container in self.data: container['probability'] = np.empty((container.size, 3, 3), dtype=FTYPE) self.data.unlink_containers() # setup more empty arrays for container in self.data: container['prob_e'] = np.empty((container.size), dtype=FTYPE) container['prob_mu'] = np.empty((container.size), dtype=FTYPE)
def __load(self, fname): fpath = resources.find_resource(fname) with h5py.File(fpath, 'r') as open_file: meta = dict(open_file.attrs) for k, v in meta.items(): if hasattr(v, 'tolist'): meta[k] = v.tolist() data = hdf.from_hdf(open_file) self.validate(data) return data, meta
def from_file(fname, fmt=None, **kwargs): """Dispatch correct file reader based on `fmt` (if specified) or guess based on file name's extension. Parameters ---------- fname : string File path / name from which to load data. fmt : None or string If string, for interpretation of the file according to this format. If None, file format is deduced by an extension found in `fname`. **kwargs All other arguments are passed to the function dispatched to read the file. Returns ------- Object instantiated from the file (string, dictionary, ...). Each format is interpreted differently. Raises ------ ValueError If extension is not recognized """ if fmt is None: rootname, ext = os.path.splitext(fname) ext = ext.replace('.', '').lower() else: rootname = fname ext = fmt.lower() if ext in ZIP_EXTS or ext in XOR_EXTS: rootname, inner_ext = os.path.splitext(rootname) inner_ext = inner_ext.replace('.', '').lower() ext = inner_ext fname = resources.find_resource(fname) if ext in jsons.JSON_EXTS: return jsons.from_json(fname, **kwargs) if ext in hdf.HDF5_EXTS: return hdf.from_hdf(fname, **kwargs) if ext in PKL_EXTS: return from_pickle(fname, **kwargs) if ext in CFG_EXTS: return from_cfg(fname, **kwargs) if ext in TXT_EXTS: return from_txt(fname, **kwargs) errmsg = 'File "%s": unrecognized extension "%s"' % (fname, ext) log.logging.error(errmsg) raise ValueError(errmsg)
def __init__(self, fp, fpname, fpath=None): self._iter_stack = [] """Stack for storing dicts with 'fp', 'fpname', 'fpath', 'lineno', and 'line' for keeping track of the hierarchy of master config & included configs""" # It's ok to not find the fpname / fpname to not be a file for the # *master* config, since this could e.g. be a io.StringIO file-like # object (`read_string`) which comes from no actual file/resource on # disk. if not fpname and hasattr(fp, 'name'): fpname = fp.name if fpath is None: try: resource = find_resource(fpname) except IOError: pass else: if isfile(resource): fpath = abspath(expanduser(expandvars(fpname))) if fpath is None: try: resource = find_resource(fpname) except IOError: pass else: if isfile(resource): fpath = resource if fpath is None: self.fpaths_processed = [] else: self.fpaths_processed = [fpath] self.fps_processed = [fp] record = dict(fp=fp, fpname=fpname, fpath=fpath, lineno=0, line='') self._iter_stack.append(record) self.file_hierarchy = OrderedDict([(fpname, OrderedDict())])
def __init__(self, airs_spline, **std_kwargs): _airs_spline_loc = find_resource(airs_spline) self.airs_spline = photospline.SplineTable(_airs_spline_loc) expected_params = [ "airs_scale", ] super().__init__( expected_params=expected_params, **std_kwargs, )
def normcheckpath(path, checkdir=False): normpath = find_resource(path) if checkdir: kind = 'dir' check = os.path.isdir else: kind = 'file' check = os.path.isfile if not check(normpath): raise IOError('Path "%s" which resolves to "%s" is not a %s.' % (path, normpath, kind)) return normpath
def __init__( self, in_files, lic_files, output_names, n_files: int, diff_nu_cc_xs="dsdxdy_nu_CC_iso.fits", diff_nubar_cc_xs="dsdxdy_nubar_CC_iso.fits", diff_nu_nc_xs="dsdxdy_nu_NC_iso.fits", diff_nubar_nc_xs="dsdxdy_nubar_NC_iso.fits", **std_kwargs ): if isinstance(lic_files, str): self._lic_files_paths = [ find_resource(lic_files), ] elif isinstance(lic_files, (list, tuple)): self._lic_files_paths = [find_resource(lic_file) for lic_file in lic_files] else: raise TypeError("Unknown lic_file datatype {}".format(type(lic_files))) if isinstance(in_files, str): self.in_files = [ find_resource(in_files), ] elif isinstance(in_files, (tuple, list)): self.in_files = [find_resource(in_file) for in_file in in_files] else: raise TypeError("Unknown in_files datatype {}".format(type(in_files))) # load the lic files! self.lic_files = [ LW.MakeGeneratorsFromLICFile(name) for name in self._lic_files_paths ] self.xs_obj = LW.CrossSectionFromSpline( find_resource(diff_nu_cc_xs), find_resource(diff_nubar_cc_xs), find_resource(diff_nu_nc_xs), find_resource(diff_nubar_nc_xs), ) # the target containers! self.output_names = output_names # with this, we just need to multiply the weight by the actual flux. Then it'll work! self._one_weighter = LW.Weighter( LW.ConstantFlux(1.0 / n_files), self.xs_obj, self.lic_files )
def _compute_nominal_outputs(self): ''' load events, perform sanity check and put them into histograms, if alt_bg file is specified, also put these events into separate histograms, that are normalized to the nominal ones (we are only interested in the shape difference) ''' # get params icc_bg_file = self.params.icc_bg_file.value if 'shape' in self.error_method: alt_icc_bg_file = self.params.alt_icc_bg_file.value else: alt_icc_bg_file = None sim_ver = self.params.sim_ver.value use_def1 = self.params.use_def1.value bdt_cut = self.params.bdt_cut.m_as('dimensionless') self.bin_names = self.output_binning.names self.bin_edges = [] for name in self.bin_names: if 'energy' in name: bin_edges = self.output_binning[name].bin_edges.to('GeV').magnitude else: bin_edges = self.output_binning[name].bin_edges.magnitude self.bin_edges.append(bin_edges) # the rest of this function is PISA v2 legacy code... logging.info('Initializing BackgroundServiceICC...') logging.info('Opening file: %s'%(icc_bg_file)) try: bg_file = h5py.File(find_resource(icc_bg_file),'r') if alt_icc_bg_file is not None: alt_bg_file = h5py.File(find_resource(alt_icc_bg_file),'r') except IOError,e: logging.error("Unable to open icc_bg_file %s"%icc_bg_file) logging.error(e) sys.exit(1)
def setup_function(self): import ROOT # setup the layers earth_model = find_resource(self.earth_model) self.layers = Layers(earth_model, self.detector_depth, self.prop_height) # This is a bit hacky, but setting the electron density to 1. # gives us the total density of matter, which is what we want. self.layers.setElecFrac(1., 1., 1.) # setup cross-sections self.xsroot = ROOT.TFile(self.xsec_file) # set the correct data mode self.data.data_specs = self.calc_specs # --- calculate the layers --- if self.calc_mode == 'binned': # layers don't care about flavor self.data.link_containers('nu', [ 'nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc', 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc' ]) for container in self.data: self.layers.calcLayers(container['true_coszen'].get(WHERE)) container['densities'] = self.layers.density.reshape( (container.size, self.layers.max_layers)) container['distances'] = self.layers.distance.reshape( (container.size, self.layers.max_layers)) container['rho_int'] = np.empty((container.size), dtype=FTYPE) # don't forget to un-link everything again self.data.unlink_containers() # --- setup cross section and survival probability --- if self.calc_mode == 'binned': # The cross-sections do not depend on nc/cc, so we can at least link those containers self.data.link_containers('nue', ['nue_cc', 'nue_nc']) self.data.link_containers('nuebar', ['nuebar_cc', 'nuebar_nc']) self.data.link_containers('numu', ['numu_cc', 'numu_nc']) self.data.link_containers('numubar', ['numubar_cc', 'numubar_nc']) self.data.link_containers('nutau', ['nutau_cc', 'nutau_nc']) self.data.link_containers('nutaubar', ['nutaubar_cc', 'nutaubar_nc']) for container in self.data: container['xsection'] = np.empty((container.size), dtype=FTYPE) container['survival_prob'] = np.empty((container.size), dtype=FTYPE) self.data.unlink_containers()
def __init__( self, events_file, **std_kwargs, ): # instantiation args that should not change self.events_file = find_resource(events_file) expected_params = ('atm_muon_scale', ) # init base class super().__init__( expected_params=expected_params, **std_kwargs, )
def setup_function(self): scale_file = find_resource(self.scale_file) logging.info("Loading scaling factors from : %s", scale_file) scaling_dict = from_json(scale_file) scale_binning = MultiDimBinning( **scaling_dict[self.variable]["binning"]) scale_factors = np.array(scaling_dict[self.variable]["scales"], dtype=FTYPE) logging.info(f"Binning for ad-hoc systematic: \n {str(scale_binning)}") logging.info( f"scaling factors of ad-hoc systematic:\n {str(scale_factors)}") self.data.representation = scale_binning for container in self.data: container["adhoc_scale_factors"] = scale_factors
def setup_function(self): sys.path.append(self.globes_wrapper) import GLoBES ### you need to start GLoBES from the folder containing a dummy experiment # therefore we go to the folder, load GLoBES and then go back curdir = os.getcwd() os.chdir(self.globes_wrapper) self.globes_calc = GLoBES.GLoBESCalculator("calc") os.chdir(curdir) self.globes_calc.InitSteriles(2) # object for oscillation parameters self.osc_params = OscParams() earth_model = find_resource(self.earth_model) prop_height = self.prop_height.m_as('km') detector_depth = self.detector_depth.m_as('km') self.layers = Layers(earth_model, detector_depth, prop_height) # The electron fractions are taken into account internally by GLoBES/SNU. # See the SNU patch for details. It uses the density to decide # whether it is in the core or in the mantle. Therefore, we just multiply by # one to give GLoBES the raw densities. self.layers.setElecFrac(1., 1., 1.) # set the correct data mode self.data.data_specs = self.calc_specs # --- calculate the layers --- if self.calc_mode == 'binned': # speed up calculation by adding links # as layers don't care about flavour self.data.link_containers('nu', ['nue_cc', 'numu_cc', 'nutau_cc', 'nue_nc', 'numu_nc', 'nutau_nc', 'nuebar_cc', 'numubar_cc', 'nutaubar_cc', 'nuebar_nc', 'numubar_nc', 'nutaubar_nc']) for container in self.data: self.layers.calcLayers(container['true_coszen'].get('host')) container['densities'] = self.layers.density.reshape((container.size, self.layers.max_layers)) container['distances'] = self.layers.distance.reshape((container.size, self.layers.max_layers)) # don't forget to un-link everything again self.data.unlink_containers() # setup probability containers for container in self.data: container['prob_e'] = np.empty((container.size), dtype=FTYPE) container['prob_mu'] = np.empty((container.size), dtype=FTYPE) container['prob_nonsterile'] = np.empty((container.size), dtype=FTYPE)
def __init__( self, events_file, output_names, **std_kwargs, ): # instantiation args that should not change self.events_file = find_resource(events_file) # init base class super().__init__( expected_params=(), **std_kwargs, ) self.output_names = output_names
def read(self, filenames, encoding=None): """Override `read` method to interpret `filenames` as PISA resource locations, then call overridden `read` method. Also, IOError fails here, whereas it is ignored in RawConfigParser. For further help on this method and its arguments, see :method:`~backports.configparser.configparser.read` """ if isinstance(filenames, basestring): filenames = [filenames] resource_locations = [] for filename in filenames: resource_location = find_resource(filename) if not isfile(resource_location): raise ValueError('"%s" is not a file or could not be located' % filename) resource_locations.append(resource_location) filenames = resource_locations # NOTE: From here on, most of the `read` method is copied, but # ignoring IOError exceptions is removed here. Python copyrights apply. if PY2 and isinstance(filenames, bytes): # we allow for a little unholy magic for Python 2 so that # people not using unicode_literals can still use the library # conveniently warnings.warn( "You passed a bytestring as `filenames`. This will not work" " on Python 3. Use `cp.read_file()` or switch to using Unicode" " strings across the board.", DeprecationWarning, stacklevel=2, ) filenames = [filenames] elif isinstance(filenames, str): filenames = [filenames] read_ok = [] for filename in filenames: with c_open(filename, encoding=encoding) as fp: self._read(fp, filename) read_ok.append(filename) return read_ok
def __init__(self, run_settings, detector=None): super().__init__() if isinstance(run_settings, str): rsd = fileio.from_file(resources.find_resource(run_settings)) elif isinstance(run_settings, dict): rsd = run_settings else: raise TypeError('Unhandled run_settings type passed in arg: ' + type(run_settings)) if detector: detector = str(detector).strip() self.detector = detector # Determine how deeply nested runs are in the dict to allow for # user to specify a dict that has multiple detectors in it OR # a dict with just a single detector in it if 'flavints' in rsd.values()[0]: runs_d = rsd elif 'flavints' in rsd.values()[0].values()[0]: if self.detector is None: if len(rsd) == 1: runs_d = rsd.values()[0] else: raise ValueError('Must specify which detector; detectors ' 'found: ' + str(rsd.keys())) else: runs_d = rsd[self.detector.strip()] else: raise Exception('dict must either be 3 levels: ' '{DET:{RUN:{...}}}; or 2 levels: {RUN:{...}}') # Force run numbers to be strings (JSON files cannot have an int as # a key, so it is a string upon import, and it's safest to keep it as # a string considering how non-standardized naming is in IceCube) and # convert actual run settings dict to MCSimRunSettings instances runs_d = {str(k): MCSimRunSettings(v) for k, v in runs_d.items()} # Save the runs_d to this object instance, which behaves like a dict self.update(runs_d)
def __init__(self, detector, geom, proc_ver, pid_spec_ver=1, pid_specs=None): geom = str(geom) proc_ver = str(proc_ver) pid_spec_ver = str(pid_spec_ver) if pid_specs is None: pid_specs = 'pid/pid_specifications.json' if isinstance(pid_specs, str): pid_specs = from_json(resources.find_resource(pid_specs)) elif isinstance(pid_specs, collections.Mapping): pass else: raise TypeError('Unhandled `pid_specs` type: "%s"' % type(data_proc_params)) self.detector = detector self.proc_ver = proc_ver self.pid_spec_ver = str(pid_spec_ver) d = pid_specs all_k = [] for wanted_key in [detector, geom, proc_ver, pid_spec_ver]: wanted_key = wanted_key.replace("'", "").lower() for orig_dict_key, subdict in d.items(): dict_key = orig_dict_key.replace("'", "").lower() if (dict_key == wanted_key): d = subdict all_k.append(orig_dict_key) if len(all_k) != 4: raise ValueError('Could not find %s' % str([detector, geom, proc_ver, pid_spec_ver])) self.pid_spec = pid_specs[all_k[0]][all_k[1]][all_k[2]][all_k[3]] # Enforce rules on PID spec: self.validatePIDSpec(self.pid_spec)
def load_events(self, events): """Load events from path given by `events`. Stored as `self.events`. Parameters ---------- events : string or Events object If string, load events from that location. If Events object, deepcopy to obtain `self.events` """ if isinstance(events, Param): events = events.value elif isinstance(events, basestring): events = find_resource(events) this_hash = hash_obj(events, full_hash=self.full_hash) if self._events_hash is not None and this_hash == self._events_hash: return logging.debug("Extracting events from Events obj or file: %s", events) events_obj = Events(events) events_hash = this_hash self.events = events_obj self._events_hash = events_hash
def _compute_nominal_outputs(self): ''' load events, perform sanity check and put them into histograms, if alt_bg file is specified, also put these events into separate histograms, that are normalized to the nominal ones (we are only interested in the shape difference) ''' # get params icc_bg_file = self.params.icc_bg_file.value if 'shape' in self.error_method: alt_icc_bg_file = self.params.alt_icc_bg_file.value else: alt_icc_bg_file = None sim_ver = self.params.sim_ver.value use_def1 = self.params.use_def1.value bdt_cut = self.params.bdt_cut.m_as('dimensionless') self.bin_names = self.output_binning.names self.bin_edges = [] for name in self.bin_names: if 'energy' in name: bin_edges = self.output_binning[name].bin_edges.to( 'GeV').magnitude else: bin_edges = self.output_binning[name].bin_edges.magnitude self.bin_edges.append(bin_edges) # the rest of this function is PISA v2 legacy code... logging.info('Initializing BackgroundServiceICC...') logging.info('Opening file: %s', icc_bg_file) try: bg_file = h5py.File(find_resource(icc_bg_file), 'r') if alt_icc_bg_file is not None: alt_bg_file = h5py.File(find_resource(alt_icc_bg_file), 'r') except IOError as e: logging.error("Unable to open icc_bg_file %s", icc_bg_file) logging.error(e) sys.exit(1) # sanity check santa_doms = bg_file['IC86_Dunkman_L6_SANTA_DirectDOMs']['value'] l3 = bg_file['IC86_Dunkman_L3']['value'] l4 = bg_file['IC86_Dunkman_L4']['result'] l5 = bg_file['IC86_Dunkman_L5']['bdt_score'] l6 = bg_file['IC86_Dunkman_L6'] if use_def1: l4_pass = np.all(l4 == 1) else: if sim_ver in ['5digit', 'dima']: l4_invVICH = bg_file['IC86_Dunkman_L4']['result_invertedVICH'] l4_pass = np.all(np.logical_or(l4 == 1, l4_invVICH == 1)) else: logging.info( 'For the old simulation, def.2 background not done yet,' ' so still use def1 for it.') l4_pass = np.all(l4 == 1) assert (np.all(santa_doms >= 3) and np.all(l3 == 1) and l4_pass and np.all(l5 >= 0.1)) corridor_doms_over_threshold = l6['corridor_doms_over_threshold'] inverted_corridor_cut = corridor_doms_over_threshold > 1 assert (np.all(inverted_corridor_cut) and np.all(l6['santa_direct_doms'] >= 3) and np.all(l6['mn_start_contained'] == 1.) and np.all(l6['mn_stop_contained'] == 1.)) #load events if sim_ver == '4digit': variable = 'IC86_Dunkman_L6_MultiNest8D_PDG_Neutrino' elif sim_ver in ['5digit', 'dima']: variable = 'IC86_Dunkman_L6_PegLeg_MultiNest8D_NumuCC' else: raise ValueError('Only allow sim_ver 4digit, 5 digit or dima!') reco_energy_all = np.array(bg_file[variable]['energy']) reco_coszen_all = np.array(np.cos(bg_file[variable]['zenith'])) pid_all = np.array(bg_file['IC86_Dunkman_L6']['delta_LLH']) if alt_icc_bg_file is not None: alt_reco_energy_all = np.array(alt_bg_file[variable]['energy']) alt_reco_coszen_all = np.array( np.cos(alt_bg_file[variable]['zenith'])) alt_pid_all = np.array(alt_bg_file['IC86_Dunkman_L6']['delta_LLH']) alt_l5 = alt_bg_file['IC86_Dunkman_L5']['bdt_score'] # Cut: Only keep bdt score >= 0.2 (from MSU latest result, make data/MC # agree much better) cut_events = {} cut = l5 >= bdt_cut cut_events['reco_energy'] = reco_energy_all[cut] cut_events['reco_coszen'] = reco_coszen_all[cut] cut_events['pid'] = pid_all[cut] if alt_icc_bg_file is not None: # Cut: Only keep bdt score >= 0.2 (from MSU latest result, make # data/MC agree much better) alt_cut_events = {} alt_cut = alt_l5 >= bdt_cut alt_cut_events['reco_energy'] = alt_reco_energy_all[alt_cut] alt_cut_events['reco_coszen'] = alt_reco_coszen_all[alt_cut] alt_cut_events['pid'] = alt_pid_all[alt_cut] logging.info("Creating a ICC background hists...") # make histo if self.params.kde_hist.value: self.icc_bg_hist = self.kde_histogramdd( np.array([cut_events[bin_name] for bin_name in self.bin_names]).T, binning=self.output_binning, coszen_name='reco_coszen', use_cuda=True, bw_method='silverman', alpha=0.3, oversample=10, coszen_reflection=0.5, adaptive=True) else: self.icc_bg_hist, _ = np.histogramdd(sample=np.array( [cut_events[bin_name] for bin_name in self.bin_names]).T, bins=self.bin_edges) conversion = self.params.atm_muon_scale.value.m_as( 'dimensionless') / ureg('common_year').to('seconds').m logging.info('nominal ICC rate at %.6E Hz', self.icc_bg_hist.sum() * conversion) if alt_icc_bg_file is not None: if self.params.kde_hist.value: self.alt_icc_bg_hist = self.kde_histogramdd( np.array([ alt_cut_events[bin_name] for bin_name in self.bin_names ]).T, binning=self.output_binning, coszen_name='reco_coszen', use_cuda=True, bw_method='silverman', alpha=0.3, oversample=10, coszen_reflection=0.5, adaptive=True) else: self.alt_icc_bg_hist, _ = np.histogramdd(sample=np.array([ alt_cut_events[bin_name] for bin_name in self.bin_names ]).T, bins=self.bin_edges) # only interested in shape difference, not rate scale = self.icc_bg_hist.sum() / self.alt_icc_bg_hist.sum() self.alt_icc_bg_hist *= scale
def switch_to_file(self, fp=None, fpname=None): """Switch iterator to a new resource location to continue processing. Parameters ---------- fp : None or file-like object If `fp` is specified, this takes precedence over `fpname`. fpname : None or string Path of the file or resource to read from. This resource will be located and opened if `fp` is None. encoding Argument is passed to the builtin ``open`` function for opening the file. """ fpath = None if fp is None: assert fpname resource = find_resource(fpname) if isfile(resource): fpath = abspath(expanduser(expandvars(resource))) if fpath in self.fpaths_processed: self._cleanup() raise ValueError( 'Circular reference; already processed "%s" at path' ' "%s"' % (fpname, fpath)) else: self._cleanup() raise ValueError('`fpname` "%s" is not a file') fp_ = c_open(fpath, encoding=None) else: fp_ = fp if fpname is None: if hasattr(fp_, 'name'): fpname = fp_.name else: fpname = '' try: resource = find_resource(fpname) except IOError: pass else: if isfile(resource): fpath = resource if fp in self.fps_processed: self._cleanup() raise ValueError( 'Circular reference; already processed file pointer "%s"' ' at path "%s"' % (fp_, fpname)) if fpath is not None: if fpath in self.fpaths_processed: self._cleanup() raise ValueError( 'Circular reference; already processed "%s" at path' ' "%s"' % (fpname, fpath)) self.fpaths_processed.append(fpath) self.fps_processed.append(fp) if fpath is not None: self.fpaths_processed.append(fpath) logging.trace('Switching to "%s" at path "%s"' % (fpname, fpath)) record = dict(fp=fp_, fpname=fpname, fpath=fpath, lineno=0, line='') self._iter_stack.append(record)
def make_toy_events(outdir, num_events, energy_range, spectral_index, coszen_range, num_sets, first_set, aeff_energy_param, aeff_coszen_param, reco_param, pid_param, pid_dist): """Make toy events and store to a file. Parameters ---------- outdir : string num_events : int energy_range : 2-tuple of floats spectral_index : float coszen_range : 2-tuple of floats num_sets : int first_set : int aeff_energy_param : string aeff_coszen_param : string reco_param : string pid_param : string pid_dist : string Returns ------- events : :class:`pisa.core.events.Events` """ energy_range = sorted(energy_range) coszen_range = sorted(coszen_range) # Validation of args assert energy_range[0] > 0 and energy_range[1] < 1e9 assert coszen_range[0] >= -1 and coszen_range[1] <= 1 assert np.diff(energy_range)[0] > 0, str(energy_range) assert np.diff(coszen_range)[0] > 0, str(coszen_range) assert spectral_index >= 0, str(spectral_index) assert first_set >= 0, str(first_set) assert num_sets >= 1, str(first_set) # Make sure resources specified actually exist for arg in [aeff_energy_param, aeff_coszen_param, reco_param, pid_param]: find_resource(arg) mkdir(outdir, warn=False) set_indices = list(range(first_set, first_set + num_sets)) # The following loop is for validation only for num, index in product(num_events, set_indices): mcgen_random_state(num_events=num, set_index=index) for num, set_index in product(num_events, set_indices): mcevts_fname = FNAME_TEMPLATE.format( file_type='events', detector='vlvnt', e_min=format_num(energy_range[0]), e_max=format_num(energy_range[1]), spectral_index=format_num(spectral_index, sigfigs=2, trailing_zeros=True), cz_min=format_num(coszen_range[0]), cz_max=format_num(coszen_range[1]), num_events=format_num(num, sigfigs=3, sci_thresh=(1, -1)), set_index=format_num(set_index, sci_thresh=(10, -10)), extension='hdf5') mcevts_fpath = os.path.join(outdir, mcevts_fname) if os.path.isfile(mcevts_fpath): logging.warn('File already exists, skipping: "%s"', mcevts_fpath) continue logging.info('Working on set "%s"', mcevts_fname) # TODO: pass filepaths / resource locations via command line args # Create a single random state object to pass from function to function random_state = mcgen_random_state(num_events=num, set_index=set_index) mc_events = generate_mc_events( num_events=num, energy_range=energy_range, coszen_range=coszen_range, spec_ind=spectral_index, aeff_energy_param_source=aeff_energy_param, aeff_coszen_param_source=aeff_coszen_param, random_state=random_state) populate_reco_observables(mc_events=mc_events, param_source=reco_param, random_state=random_state) populate_pid(mc_events=mc_events, param_source=pid_param, random_state=random_state, dist=pid_dist) to_file(mc_events, mcevts_fpath) return mc_events
def _compute_nominal_outputs(self): """load the evnts from file, perform sanity checks and histogram them (into final MapSet) """ # get params data_file_name = self.params.data_file.value sim_version = self.params.sim_ver.value bdt_cut = self.params.bdt_cut.value.m_as('dimensionless') self.bin_names = self.output_binning.names # TODO: convert units using e.g. `comp_units` in stages/reco/hist.py self.bin_edges = [] for name in self.bin_names: if 'energy' in name: bin_edges = self.output_binning[name].bin_edges.to('GeV').magnitude else: bin_edges = self.output_binning[name].bin_edges.magnitude self.bin_edges.append(bin_edges) # the rest of this function is PISA v2 legacy code... # right now only use burn sample with sim_version = '4digit' #print "sim_version == ", sim_version if sim_version == "4digit": Reco_Neutrino_Name = 'IC86_Dunkman_L6_MultiNest8D_PDG_Neutrino' Reco_Track_Name = 'IC86_Dunkman_L6_MultiNest8D_PDG_Track' elif sim_version == "5digit" or sim_version=="dima": Reco_Neutrino_Name = 'IC86_Dunkman_L6_PegLeg_MultiNest8D_NumuCC' Reco_Track_Name = 'IC86_Dunkman_L6_PegLeg_MultiNest8D_Track' else: raise ValueError( 'only allow 4digit, 5digit(H2 model for hole ice) or' ' dima (dima p1 and p2 for hole ice)!' ) data_file = h5py.File(find_resource(data_file_name), 'r') L6_result = np.array(data_file['IC86_Dunkman_L6']['result']) dLLH = np.array(data_file['IC86_Dunkman_L6']['delta_LLH']) reco_energy_all = np.array(data_file[Reco_Neutrino_Name]['energy']) reco_coszen_all = np.array(np.cos( data_file[Reco_Neutrino_Name]['zenith'] )) reco_trck_len_all = np.array(data_file[Reco_Track_Name]['length']) #print "before L6 cut, no. of burn sample = ", len(reco_coszen_all) # sanity check santa_doms = data_file['IC86_Dunkman_L6_SANTA_DirectDOMs']['value'] l3 = data_file['IC86_Dunkman_L3']['value'] l4 = data_file['IC86_Dunkman_L4']['result'] l5 = data_file['IC86_Dunkman_L5']['bdt_score'] assert(np.all(santa_doms>=3) and np.all(l3 == 1) and np.all(l5 >= 0.1)) # l4==1 was not applied when i3 files were written to hdf5 files, so do # it here dLLH = dLLH[l4==1] reco_energy_all = reco_energy_all[l4==1] reco_coszen_all = reco_coszen_all[l4==1] l5 = l5[l4==1] L6_result = L6_result[l4==1] data_file.close() dLLH_L6 = dLLH[L6_result==1] l5 = l5[L6_result==1] reco_energy_L6 = reco_energy_all[L6_result==1] reco_coszen_L6 = reco_coszen_all[L6_result==1] #print "after L6 cut, no. of burn sample = ", len(reco_coszen_L6) # Cut: Only keep bdt score >= 0.2 (from MSU latest result, make data/MC # agree much better); if use no such further cut, use bdt_cut = 0.1 logging.info( "Cut2, removing events with bdt_score < %s i.e. only keep bdt > %s" %(bdt_cut, bdt_cut) ) cut_events = {} cut = l5>=bdt_cut cut_events['reco_energy'] = reco_energy_L6[cut] cut_events['reco_coszen'] = reco_coszen_L6[cut] cut_events['pid'] = dLLH_L6[cut] hist, _ = np.histogramdd(sample = np.array( [cut_events[bin_name] for bin_name in self.bin_names] ).T, bins=self.bin_edges) maps = [Map(name=self.output_names[0], hist=hist, binning=self.output_binning)] self.template = MapSet(maps, name='data')
def test_CrossSections(outdir=None): """Unit tests for CrossSections class""" from shutil import rmtree from tempfile import mkdtemp remove_dir = False if outdir is None: remove_dir = True outdir = mkdtemp() try: # "Standard" location of cross sections file in PISA; retrieve 2.6.4 for # testing purposes pisa_xs_file = 'cross_sections/cross_sections.json' xs = CrossSections(ver='genie_2.6.4', xsec=pisa_xs_file) # Location of the root file to use (not included in PISA at the moment) test_dir = expand(os.path.join('/tmp', 'pisa_tests', 'cross_sections')) #root_xs_file = os.path.join(test_dir, 'genie_2.6.4_simplified.root') root_xs_file = find_resource(os.path.join( #'tests', 'data', 'xsec', 'genie_2.6.4_simplified.root' 'cross_sections', 'genie_xsec_H2O.root' )) # Make sure that the XS newly-imported from ROOT match those stored in # PISA if os.path.isfile(root_xs_file): xs_from_root = CrossSections.new_from_root(root_xs_file, ver='genie_2.6.4') logging.info('Found and loaded ROOT source cross sections file %s', root_xs_file) #assert xs_from_root.allclose(xs, rtol=1e-7) # Check XS ratio for numu_cc to numu_cc + numu_nc (user must inspect) kg0 = NuFlavIntGroup('numu_cc') kg1 = NuFlavIntGroup('numu_nc') logging.info( r'\int_1^80 xs(numu_cc) E^{-1} dE = %e', xs.get_xs_ratio_integral(kg0, None, e_range=[1, 80], gamma=1) ) logging.info( '(int E^{-gamma} * (sigma_numu_cc)/int(sigma_(numu_cc+numu_nc)) dE)' ' / (int E^{-gamma} dE) = %e', xs.get_xs_ratio_integral(kg0, kg0+kg1, e_range=[1, 80], gamma=1, average=True) ) # Check that XS ratio for numu_cc+numu_nc to the same is 1.0 int_val = xs.get_xs_ratio_integral(kg0+kg1, kg0+kg1, e_range=[1, 80], gamma=1, average=True) if not recursiveEquality(int_val, 1): raise ValueError('Integral of nc + cc should be 1.0; get %e' ' instead.' % int_val) # Check via plot that the # Plot all cross sections stored in PISA xs file try: alldata = from_file(pisa_xs_file) xs_versions = alldata.keys() for ver in xs_versions: xs = CrossSections(ver=ver, xsec=pisa_xs_file) xs.plot(save=os.path.join( outdir, 'pisa_' + ver + '_nuxCCNC_H2O_cross_sections.pdf' )) except ImportError as exc: logging.debug('Could not plot; possible that matplotlib not' 'installed. ImportError: %s', exc) finally: if remove_dir: rmtree(outdir)
def main(): """Do the conversion.""" args = parse_args() in_fpath = os.path.expanduser(os.path.expandvars(args.config[0])) out_fpath = in_fpath + '.new' with open(in_fpath, 'r') as infile: orig_contents = infile.readlines() osc_stage_header_line = None section_names_with_colons = {} new_contents = [] for lineno, orig_line in enumerate(orig_contents, start=1): # Remove trailing whitespace, including newline character(s) new_line = orig_line.rstrip() # Empty (or whitespace-only) line is trivial if new_line == '': new_contents.append(new_line) continue # Replace text substitution ("config file variables") syntax new_line = OLD_SUB_RE.sub(repl=replace_substitution, string=new_line) # Replace stage headers. E.g. # `` [ stage :stage_name ]`` # is replaced by # `` [stage.stage_name]`` # I.e. retain any whitespace before (and after... though this is # already removed) the brackets but swap colon for period and remove # whitespace within the brackets. new_line = OLD_STAGE_SECTION_RE.sub( repl=lambda m: '[stage.%s]' % m.groups(), string=new_line) # Replace stage:key variables. E.g. what should now look like # `` ${ stage : key } `` # should look like # `` ${stage.key} `` new_line = OLD_STAGE_VARIABLE_RE.sub( repl=lambda m: '${stage.%s}' % m.groups(), string=new_line) stripped = new_line.strip() # Replace order string if stripped.startswith('order'): new_line = OLD_ORDER_RE.sub(repl=replace_order, string=new_line) # Record line on which the [stage.osc] section occurs (if any) elif stripped == '[stage.osc]': osc_stage_header_line = lineno - 1 # Convert ``#include x`` to ``#include x as y``, where appropriate new_line = PISAConfigParser.INCLUDE_RE.sub(repl=append_include_as, string=new_line) # Convert JSON filenames to .json.bz2 that are now bzipped if '.json' in new_line: for json_re in JSON_NO_BZ2_RE_LIST: new_line = json_re.sub(repl='.json.bz2', string=new_line) # Replace changed names for orig_name, new_name in NAMES_CHANGED_MAP.items(): new_line = new_line.replace(orig_name, new_name) # Search for any colons used in section names. This is illegal, as a # section name can be used as a variable where the syntax is # ``${section_name:key}`` # so any colons in section_name will make the parser choke. for match in SECTION_NAME_WITH_COLON_RE.finditer(new_line): section_name_with_colons = match.groups()[0] if NEW_VARIABLE_RE.match(section_name_with_colons): if section_name_with_colons.count(':') > 1: raise ValueError( 'Multiple colons in new-style variable, line %d:\n' '>> Original line:\n%s\n>> New line:\n%s\n' % (lineno, orig_line, new_line)) else: continue section_name_without_colons = section_name_with_colons.replace( ':', OTHER_SECTION_NAME_SEPARATOR) section_names_with_colons[section_name_with_colons] = ( section_name_without_colons) new_contents.append(new_line) #for item in section_names_with_colons.items(): # print '%s --> %s' % item # Go back through and replace colon-sparated section names with # ``OTHER_SECTION_NAME_SEPARATOR``-separated section names all_names_to_replace = section_names_with_colons.keys() def replace_var(match): """Closure to replace variable names""" whole_string, var_name = match.groups() if var_name in all_names_to_replace: return '${%s}' % section_names_with_colons[var_name] return whole_string def replace_section_name(match): """Closure to replace section names""" whole_string, section_name = match.groups() if section_name in all_names_to_replace: return whole_string.replace( section_name, section_names_with_colons[section_name]) return whole_string for lineno, new_line in enumerate(new_contents, start=1): if not new_line: continue new_line = NEW_VARIABLE_RE.sub(repl=replace_var, string=new_line) new_line = NEW_SECTION_RE.sub(repl=replace_section_name, string=new_line) #new_line = NEW_SECTION_RE.sub(repl=replace_colon_names, # string=new_line) #for with_colons, without_colons in section_names_with_colons: # new_line = new_line.replace(with_colons, without_colons) # Check for multiple colons in a variable (which is illegal) if MULTI_COLON_VAR_RE.findall(new_line): raise ValueError( 'Multiple colons in variable, line %d:\n>> Original' ' line:\n%s\n>> New line:\n%s\n' % (lineno, orig_contents[lineno - 1], new_line)) new_contents[lineno - 1] = new_line # Parse the new config file with the PISAConfigParser to see if NSI # parameters are defined in the `stage.osc` section (if the latter is # present). If needed, insert appropriate #include in the section pcp = PISAConfigParser() missing_section_header = False try: pcp.read_string(('\n'.join(new_contents) + '\n').decode('utf-8')) except MissingSectionHeaderError: missing_section_header = True pcp.read_string(('\n'.join(['[dummy section header]'] + new_contents) + '\n').decode('utf-8')) if 'stage.osc' in pcp: keys_containing_eps = [ k for k in pcp['stage.osc'].keys() if '.eps_'.encode('utf-8') in k ] nsi_params_present = [] nsi_params_missing = [] for nsi_param, nsi_param_re in NSI_PARAM_RE_MAP.items(): found = None for key_idx, key in enumerate(keys_containing_eps): if nsi_param_re.match(key): found = key_idx nsi_params_present.append(nsi_param) if found is None: nsi_params_missing.append(nsi_param) else: # No need to search this key again keys_containing_eps.pop(found) if set(nsi_params_present) == set(NSI_PARAM_RE_MAP.keys()): all_nsi_params_defined = True elif set(nsi_params_missing) == set(NSI_PARAM_RE_MAP.keys()): all_nsi_params_defined = False else: raise ValueError( 'Found a subset of NSI params defined; missing %s' % str(nsi_params_missing)) # NOTE: since for now the contents of nsi_null.cfg are commented out # (until merging NSI branch), the above check will say NSI params are # missing if the #include statement was made. So check to see if # settings/osc/nsi_null.cfg _has_ been included (we can't tell what # section it is in, but we'll have to just accept that). # # We will probably want to remove this stanza as soon as NSI brnach is # merged, since this is imprecise and can introduce other weird corner # cases. rsrc_loc = find_resource('settings/osc/nsi_null.cfg') for file_iter in pcp.file_iterators: if rsrc_loc in file_iter.fpaths_processed: all_nsi_params_defined = True if not all_nsi_params_defined and osc_stage_header_line is None: raise ValueError( "Found a stage.osc section without NSI params defined (using" " PISAConfigParser) but could not find the line of the" " `[stage.osc]` header. This could occur if `[stage.osc]` came" " from an `#include`'d file. You can manually define the NSI" " parameters in this file or in the included file e.g. as" " found in `settings/osc/nsi_null.cfg` or simply add the" " statement ``#include settings/osc/nsi_null.cfg`` to either" " file (so long as that statement it falls within the" " stage.osc section).") # Add ``#include settings/osc/nsi_null.cfg`` at top of stage.osc # section if a stage.osc section is present and no NSI params were # specified in that section if not all_nsi_params_defined: # Add an #include to set all NSI parameters to 0 new_contents.insert(osc_stage_header_line + 1, '#include settings/osc/nsi_null.cfg') # Add an extra blank line after the #include line new_contents.insert(osc_stage_header_line + 2, '') if not new_contents: raise ValueError('Empty file after conversion; quitting.') # Note that newlines are added but no join is performed for comparison # against `orig_contents` new_contents = [line + '\n' for line in new_contents] # Now for validation, try to parse the new config file with the # PISAConfigParser pcp = PISAConfigParser() if missing_section_header: pcp.read_string((''.join(['[dummy section header]\n'] + new_contents) + '\n').decode('utf-8')) else: pcp.read_string((''.join(new_contents)).decode('utf-8')) if new_contents == orig_contents: sys.stdout.write('Nothing modified in the original file (ok!).\n') return if args.validate_only: raise ValueError( 'Original config file "%s" would be modfied (and so may be' ' invalid). Re-run this script without the --validate-only flag to' ' produce an appropriately-converted config file.' % args.config[0]) sys.stdout.write('Writing modified config file to "%s"\n' % out_fpath) with open(out_fpath, 'w') as outfile: outfile.writelines(new_contents)
def __init__(self, detector, proc_ver, data_proc_params=None): super().__init__() if data_proc_params is None: data_proc_params = 'events/data_proc_params.json' if isinstance(data_proc_params, str): ps = jsons.from_json(resources.find_resource(data_proc_params)) elif isinstance(data_proc_params, dict): ps = data_proc_params else: raise TypeError('Unhandled data_proc_params type passed in arg: ' + type(data_proc_params)) self.detector = detector self.proc_ver = str(proc_ver) self.det_key = [k for k in ps.keys() if k.lower() == self.detector.lower()][0] for key in ps[self.det_key].keys(): lk = key.lower() lpv = self.proc_ver.lower() if lk == lpv or ('v'+lk == lpv) or (lk == 'v'+lpv): self.procver_key = key # This works for PINGU elif ('msu_'+lk == lpv) or (lk == 'msu_'+lpv): self.procver_key = key elif ('nbi_'+lk == lpv) or (lk == 'nbi_'+lpv): self.procver_key = key # Generalising for DeepCore and different selections ps = ps[self.det_key][self.procver_key] self.update(ps) self.trans_nu_code = False if 'nu_code_to_pdg_map' in self: self.trans_nu_code = True try: self.nu_code_to_pdg_map = { int(code): pdg for code, pdg in self['nu_code_to_pdg_map'].items() } except: self.nu_code_to_pdg_map = self['nu_code_to_pdg_map'] # NOTE: the keys are strings so the particular string formatting is # important for indexing into the dict! # Add generic cuts self['cuts'].update({ # Cut for particles only (no anti-particles) str(NuFlav(12).bar_code).lower(): {'fields': ['nu_code'], 'pass_if': 'nu_code > 0'}, # Cut for anti-particles only (no particles) str(NuFlav(-12).bar_code).lower(): {'fields': ['nu_code'], 'pass_if': 'nu_code < 0'}, # Cut for charged-current interactions only str(IntType('cc')).lower(): {'fields': ['interaction_type'], 'pass_if': 'interaction_type == 1'}, # Cut for neutral-current interactions only str(IntType('nc')).lower(): {'fields': ['interaction_type'], 'pass_if': 'interaction_type == 2'}, # True-upgoing cut usinng the zenith field 'true_upgoing_zen': {'fields': ['true_zenith'], 'pass_if': 'true_zenith > pi/2'}, # True-upgoing cut usinng the cosine-zenith field 'true_upgoing_coszen': {'fields': ['true_coszen'], 'pass_if': 'true_coszen < 0'}, }) # Enforce rules on cuts: self.validate_cut_spec(self['cuts'])