| import argparse |
| import glob |
| import os |
| import sys |
| from pathlib import Path |
|
|
| import awkward as ak |
| import uproot |
|
|
| REPO_ROOT = Path(__file__).resolve().parents[1] |
| sys.path.insert(0, str(REPO_ROOT)) |
|
|
| from root_gnn_base import utils |
| from root_gnn_base.dataset import selection_branches, check_selection, print_cutflow, init_cutflow |
|
|
|
|
| def get_branches(dataset_config): |
| args = dataset_config["args"] |
| branches = [] |
| for feat in args.get("node_branch_names", []): |
| if isinstance(feat, list): |
| for branch in feat: |
| if isinstance(branch, str) and branch != "CALC_E": |
| branches.append(branch) |
| for feat in args.get("global_features", []): |
| if isinstance(feat, str): |
| branches.append(feat) |
| for feat in args.get("tracking_info", []): |
| if isinstance(feat, str): |
| branches.append(feat) |
| for selection in dataset_config.get("selections", []): |
| branches.extend(selection_branches(selection)) |
| return sorted(set(branches)) |
|
|
|
|
| def load_arrays(dataset_config): |
| args = dataset_config["args"] |
| raw_dir = args["raw_dir"] |
| file_names = args["file_names"] |
| tree_name = args.get("tree_name", "nominal_Loose") |
|
|
| files = [] |
| if isinstance(file_names, str): |
| files = glob.glob(os.path.join(raw_dir, file_names)) |
| else: |
| for file_name in file_names: |
| files.extend(glob.glob(os.path.join(raw_dir, file_name))) |
|
|
| branches = get_branches(dataset_config) |
| arrays = [] |
| for file in files: |
| with uproot.open(file) as f: |
| arrays.append(f[tree_name].arrays(branches, library="ak")) |
| if not arrays: |
| raise FileNotFoundError(f"No files found in {os.path.join(raw_dir, str(file_names))}") |
| return ak.concatenate(arrays, axis=0), branches |
|
|
|
|
| def vectorized_cutflow(data, selections): |
| cutflow = init_cutflow(selections) |
| first_field = data.fields[0] if len(data.fields) > 0 else None |
| cutflow["total"] = len(data[first_field]) if first_field is not None else 0 |
| mask = ak.ones_like(data[first_field], dtype=bool) if first_field is not None else None |
|
|
| for i, selection in enumerate(selections): |
| if isinstance(selection, str): |
| current_mask = eval(selection, {"__builtins__": {}}, data) |
| else: |
| current_mask = check_selection(data, selection) |
| current_mask = ak.to_numpy(current_mask) |
| if mask is None: |
| mask = current_mask |
| else: |
| mask = mask & current_mask |
| cutflow["counts"][i] = int(ak.sum(mask)) |
| return cutflow |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser(description="Fast selection tester and cutflow printer") |
| parser.add_argument("--config", required=True, help="Path to YAML config file") |
| args = parser.parse_args() |
|
|
| config = utils.load_config(args.config) |
| for dataset_name, dataset_config in config["Datasets"].items(): |
| selections = dataset_config.get("selections", []) |
| print(f"\n== Dataset: {dataset_name} ==") |
|
|
| data, branches = load_arrays(dataset_config) |
|
|
| for selection in selections: |
| try: |
| _ = eval(selection, {"__builtins__": {}}, data) if isinstance(selection, str) else check_selection({b: data[b] for b in branches}, selection) |
| print(f"OK: {selection}") |
| except Exception: |
| print(f"FAILED: {selection}") |
| raise |
|
|
| cutflow = vectorized_cutflow(data, selections) |
| print_cutflow(cutflow, title=f"Cutflow for {dataset_name}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|