diff --git a/QTracker_training/data/multi_track/gen_residual_training.py b/QTracker_training/data/multi_track/gen_residual_training.py new file mode 100644 index 0000000..9bc0444 --- /dev/null +++ b/QTracker_training/data/multi_track/gen_residual_training.py @@ -0,0 +1,164 @@ +""" +Generate residual training data for fine-tuning the autoregressive track finder. + +For each multi-track event with K pairs: +- Iteration 0: original hit matrix → GT pair 0 +- Iteration 1: hit matrix minus pair 0's hits → GT pair 1 +- ... +- Iteration K-1: hit matrix minus pairs 0..K-2 → GT pair K-1 + +Output: single-track format ROOT file where each row is one (residual_matrix, single_pair) sample. +""" + +import ROOT +import numpy as np +from array import array +import argparse +import os + +NUM_DETECTORS = 62 +NUM_ELEMENT_IDS = 201 + + +def generate_residual_data(input_file, output_file, max_pairs): + f_in = ROOT.TFile.Open(input_file, "READ") + tree = f_in.Get("tree") + if not tree: + raise RuntimeError(f"Tree not found in {input_file}") + + if os.path.exists(output_file): + os.remove(output_file) + + f_out = ROOT.TFile.Open(output_file, "RECREATE", "", ROOT.kLZMA) + f_out.SetCompressionLevel(5) + out_tree = ROOT.TTree("tree", "Residual training data for autoregressive finder") + out_tree.SetAutoSave(0) + + # Output branches: single-track format + eventID = array("i", [0]) + iterationID = array("i", [0]) + + element_id = ROOT.std.vector("int")() + detector_id = ROOT.std.vector("int")() + element_id_clean = ROOT.std.vector("int")() + detector_id_clean = ROOT.std.vector("int")() + drift_distance = ROOT.std.vector("double")() + tdc_time = ROOT.std.vector("double")() + + HitArray_mup = np.zeros(NUM_DETECTORS, dtype=np.int32) + HitArray_mum = np.zeros(NUM_DETECTORS, dtype=np.int32) + + out_tree.Branch("eventID", eventID, "eventID/I") + out_tree.Branch("iterationID", iterationID, "iterationID/I") + out_tree.Branch("elementID", element_id) + out_tree.Branch("detectorID", detector_id) + out_tree.Branch("elementIDClean", element_id_clean) + out_tree.Branch("detectorIDClean", detector_id_clean) + out_tree.Branch("driftDistance", drift_distance) + out_tree.Branch("tdcTime", tdc_time) + out_tree.Branch("HitArray_mup", HitArray_mup, f"HitArray_mup[{NUM_DETECTORS}]/I") + out_tree.Branch("HitArray_mum", HitArray_mum, f"HitArray_mum[{NUM_DETECTORS}]/I") + + # Bind input multi-track hit arrays + leaf_mup = tree.GetLeaf("HitArray_mup") + total_len = leaf_mup.GetLen() if leaf_mup else 0 + input_max_pairs = total_len // NUM_DETECTORS if total_len > 0 else max_pairs + + input_mup = np.zeros((input_max_pairs, NUM_DETECTORS), dtype=np.int32) + input_mum = np.zeros((input_max_pairs, NUM_DETECTORS), dtype=np.int32) + tree.SetBranchAddress("HitArray_mup", input_mup) + tree.SetBranchAddress("HitArray_mum", input_mum) + + fills = 0 + for ev in range(tree.GetEntries()): + tree.GetEntry(ev) + + n_active = int(tree.nPairs) if hasattr(tree, "nPairs") else input_max_pairs + + # Build the full hit set for this event + all_elem = list(tree.elementID) + all_det = list(tree.detectorID) + all_drift = list(tree.driftDistance) + all_tdc = list(tree.tdcTime) + + all_elem_clean = ( + list(tree.elementIDClean) if hasattr(tree, "elementIDClean") else all_elem + ) + all_det_clean = ( + list(tree.detectorIDClean) if hasattr(tree, "detectorIDClean") else all_det + ) + + # Track which hits have been "consumed" by previous iterations + removed_positions = set() # (det, elem) tuples + + for it in range(n_active): + # Check if this pair is actually active (nonzero) + if np.all(input_mup[it] == 0) and np.all(input_mum[it] == 0): + continue + + eventID[0] = ev + iterationID[0] = it + + # Write residual hit vectors (excluding removed positions) + element_id.clear() + detector_id.clear() + element_id_clean.clear() + detector_id_clean.clear() + drift_distance.clear() + tdc_time.clear() + + for elem, det, drift, tdc in zip(all_elem, all_det, all_drift, all_tdc): + if (int(det), int(elem)) not in removed_positions: + element_id.push_back(int(elem)) + detector_id.push_back(int(det)) + drift_distance.push_back(float(drift)) + tdc_time.push_back(float(tdc)) + + for elem, det in zip(all_elem_clean, all_det_clean): + if (int(det), int(elem)) not in removed_positions: + element_id_clean.push_back(int(elem)) + detector_id_clean.push_back(int(det)) + + # GT for this iteration: the current pair + for d in range(NUM_DETECTORS): + HitArray_mup[d] = input_mup[it, d] + HitArray_mum[d] = input_mum[it, d] + + out_tree.Fill() + fills += 1 + + # Mark this pair's hits as removed for next iteration + for d in range(NUM_DETECTORS): + if input_mup[it, d] > 0: + removed_positions.add((d + 1, int(input_mup[it, d]))) + if input_mum[it, d] > 0: + removed_positions.add((d + 1, int(input_mum[it, d]))) + + print( + f"Generated {fills} residual training samples from {tree.GetEntries()} events." + ) + + out_tree.Write("", ROOT.TObject.kOverwrite) + f_out.Close() + f_in.Close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate residual training data for autoregressive track finder." + ) + parser.add_argument("input_file", type=str, help="Multi-track mc_events ROOT file.") + parser.add_argument( + "--output", + type=str, + default="residual_training.root", + help="Output single-track-format ROOT file.", + ) + parser.add_argument( + "--max_pairs", + type=int, + default=5, + help="Max pairs in the input file.", + ) + args = parser.parse_args() + generate_residual_data(args.input_file, args.output, args.max_pairs) diff --git a/QTracker_training/eval_autoregressive.py b/QTracker_training/eval_autoregressive.py new file mode 100644 index 0000000..ea77be4 --- /dev/null +++ b/QTracker_training/eval_autoregressive.py @@ -0,0 +1,451 @@ +""" +Evaluate autoregressive multi-track predictions against ground truth. + +Loads the .npz output from AutoregressiveTrackFinder.py and computes: +- Pair-detection confusion matrices (global + per extraction rank) +- Per extraction-rank raw and refined hit accuracy and residual tables +- Residual plots +""" + +# ruff: noqa: E402 + +import os +import argparse + +os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" + +import numpy as np +import matplotlib.pyplot as plt +from scipy.optimize import linear_sum_assignment +import ROOT # noqa: F401 + +import QTracker +from refine import refine_hit_arrays + + +def plot_residuals(det_ids, res_plus, res_minus, predictions_file, stage_label): + mean_p = np.nanmean(np.abs(res_plus), axis=0) + std_p = np.nanstd(np.abs(res_plus), axis=0) + mean_m = np.nanmean(np.abs(res_minus), axis=0) + std_m = np.nanstd(np.abs(res_minus), axis=0) + + plt.figure(figsize=(10, 5)) + plt.errorbar(det_ids, mean_p, yerr=std_p, marker="o", label="μ+ mean±σ") + plt.errorbar(det_ids, mean_m, yerr=std_m, marker="s", label="μ- mean±σ") + plt.axhline(0, linestyle="--", linewidth=1) + plt.xlabel("Detector Layer (skipping masked slots)") + plt.ylabel("Absolute Residual (predicted − true)") + plt.title(f"Per-layer Absolute Residual ({stage_label})") + plt.legend() + plt.tight_layout() + + base = os.path.splitext(os.path.basename(predictions_file))[0] + fname = f"{base}_{stage_label}_residuals.png" + plot_dir = os.path.join(os.path.dirname(__file__), "plots") + os.makedirs(plot_dir, exist_ok=True) + plt.savefig(os.path.join(plot_dir, fname)) + plt.close() + + +def plot_confusion_matrix(tp, tn, fp, fn, predictions_file, stage_label): + """Save a 2x2 pair-existence confusion matrix heatmap.""" + matrix = np.array([[tp, fn], [fp, tn]], dtype=np.int64) + labels = np.array([["TP", "FN"], ["FP", "TN"]], dtype=object) + + fig, ax = plt.subplots(figsize=(5, 4)) + im = ax.imshow(matrix, cmap="Blues") + ax.set_xticks([0, 1]) + ax.set_yticks([0, 1]) + ax.set_xticklabels(["Predicted +", "Predicted -"]) + ax.set_yticklabels(["GT +", "GT -"]) + ax.set_title(f"Pair Existence ({stage_label})") + + for i in range(2): + for j in range(2): + color = "white" if matrix[i, j] > matrix.max() / 2 else "black" + ax.text( + j, + i, + f"{labels[i, j]}\n{matrix[i, j]:,}", + ha="center", + va="center", + color=color, + ) + + fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04) + plt.tight_layout() + + base = os.path.splitext(os.path.basename(predictions_file))[0] + fname = f"{base}_{stage_label}_confusion_matrix.png" + plot_dir = os.path.join(os.path.dirname(__file__), "plots") + os.makedirs(plot_dir, exist_ok=True) + plt.savefig(os.path.join(plot_dir, fname)) + plt.close() + + +def print_confusion_matrix(label, tp, tn, fp, fn, predictions_file, stage_label): + """Print pair-existence confusion matrix and derived metrics.""" + total = tp + tn + fp + fn + print(f"\n--- {label} Pair Existence Confusion Matrix ---") + print(f"{'':>20} | {'Predicted +':>11} | {'Predicted -':>11}") + print(f"{'GT +':>20} | {tp:11,d} | {fn:11,d}") + print(f"{'GT -':>20} | {fp:11,d} | {tn:11,d}") + print(f"\nTotal events/slots: {total:,}") + + accuracy = (tp + tn) / total if total > 0 else 0.0 + precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0 + recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0 + specificity = tn / (tn + fp) if (tn + fp) > 0 else 0.0 + f1 = ( + 2 * precision * recall / (precision + recall) + if (precision + recall) > 0 + else 0.0 + ) + + print(f"Accuracy : {accuracy:.4f}") + print(f"Precision : {precision:.4f}") + print(f"Recall : {recall:.4f}") + print(f"Specificity: {specificity:.4f}") + print(f"F1 Score : {f1:.4f}") + + plot_confusion_matrix(tp, tn, fp, fn, predictions_file, stage_label) + + +def print_global_pair_detection(tp, fn, fp): + """Print dataset-level pair counts after Hungarian matching.""" + total_gt = tp + fn + total_pred = tp + fp + + print("\n--- Global Pair Detection (Hungarian matching) ---") + print(f"Total ground-truth pairs : {total_gt:,}") + print(f"Total predicted pairs : {total_pred:,}") + print(f"Matched pairs (TP) : {tp:,}") + print(f"Unmatched GT (FN) : {fn:,}") + print(f"Unmatched pred (FP) : {fp:,}") + + precision = tp / total_pred if total_pred > 0 else 0.0 + recall = tp / total_gt if total_gt > 0 else 0.0 + f1 = ( + 2 * precision * recall / (precision + recall) + if (precision + recall) > 0 + else 0.0 + ) + + print(f"\nPair detection precision : {precision:.4f}") + print(f"Pair detection recall : {recall:.4f}") + print(f"Pair detection F1 : {f1:.4f}") + + +def hungarian_match_predictions(y_gt, y_pred, n_gt, n_pred): + """Match predicted pairs to GT pairs using Hungarian algorithm.""" + if n_gt == 0 or n_pred == 0: + return np.array([], dtype=int), np.array([], dtype=int) + + cost = np.zeros((n_pred, n_gt)) + for i in range(n_pred): + for j in range(n_gt): + cost[i, j] = np.sum(np.abs(y_pred[i] - y_gt[j])) + + row_ind, col_ind = linear_sum_assignment(cost) + n = min(n_gt, n_pred) + return col_ind[:n], row_ind[:n] + + +def compute_true_counts(y_gt, max_pairs, num_events): + """Count how many non-zero GT pairs each event has.""" + true_counts = np.zeros(num_events, dtype=np.int32) + for ev in range(num_events): + for k in range(max_pairs): + if np.any(y_gt[ev, k, :, :] != 0): + true_counts[ev] = k + 1 + return true_counts + + +def print_summary(label, gt_p, gt_m, pred_p, pred_m, mask): + """Print summary metrics matching evaluate.py output format.""" + res_p = gt_p - pred_p + res_m = gt_m - pred_m + + acc_p = np.mean(np.abs(res_p[:, mask]) == 0) + acc_m = np.mean(np.abs(res_m[:, mask]) == 0) + print(f"\n{label} μ+ accuracy: {acc_p:.4f}") + print(f"{label} μ- accuracy: {acc_m:.4f}") + + w2_p = np.mean(np.abs(res_p[:, mask]) <= 2) + w2_m = np.mean(np.abs(res_m[:, mask]) <= 2) + print(f"\n{label} μ+ within-2 accuracy: {w2_p:.4f}") + print(f"{label} μ- within-2 accuracy: {w2_m:.4f}") + + m_p = np.mean(np.abs(res_p[:, mask])) + s_p = np.std(np.abs(res_p[:, mask])) + m_m = np.mean(np.abs(res_m[:, mask])) + s_m = np.std(np.abs(res_m[:, mask])) + print(f"\n--- {label} Absolute Residuals ---") + print("μ+ mean | μ+ std | μ- mean | μ- std") + print(f"{m_p:8.3f} | {s_p:8.3f} | {m_m:8.3f} | {s_m:8.3f}") + + +def print_per_detector_table(label, res_p, res_m, mask): + """Print per-detector residual table matching evaluate.py.""" + print(f"\n--- {label} Residuals ---") + print("Det | μ+ mean | μ+ std | μ- mean | μ- std") + for det in np.where(mask)[0]: + m_p = np.mean(np.abs(res_p[:, det])) + s_p = np.std(np.abs(res_p[:, det])) + m_m = np.mean(np.abs(res_m[:, det])) + s_m = np.std(np.abs(res_m[:, det])) + print(f"{det + 1:3d} | {m_p:8.3f} | {s_p:8.3f} | {m_m:8.3f} | {s_m:8.3f}") + + +def refine_matched_pairs(pred_p, pred_m, event_indices, detector_ids, element_ids): + """Refine matched pair predictions using each source event's recorded hits.""" + ref_p = np.zeros_like(pred_p) + ref_m = np.zeros_like(pred_m) + by_event: dict[int, list[int]] = {} + for i, ev in enumerate(event_indices): + by_event.setdefault(int(ev), []).append(i) + + for ev, indices in by_event.items(): + idx = np.array(indices) + rp, rm = refine_hit_arrays( + pred_p[idx], + pred_m[idx], + [detector_ids[ev]] * len(idx), + [element_ids[ev]] * len(idx), + ) + ref_p[idx] = rp + ref_m[idx] = rm + + return ref_p, ref_m + + +def evaluate_hit_quality( + label, + gt_p, + gt_m, + pred_p, + pred_m, + event_indices, + detector_ids, + element_ids, + mask, + predictions_file, + plot_label, +): + """Print and plot raw and refined hit-quality metrics for matched pairs.""" + ref_p, ref_m = refine_matched_pairs( + pred_p, pred_m, event_indices, detector_ids, element_ids + ) + + raw_res_p = gt_p - pred_p + raw_res_m = gt_m - pred_m + ref_res_p = gt_p - ref_p + ref_res_m = gt_m - ref_m + + dets_used = np.where(mask)[0] + 1 + + print_per_detector_table(f"{label} Raw", raw_res_p, raw_res_m, mask) + plot_residuals( + dets_used, + raw_res_p[:, mask], + raw_res_m[:, mask], + predictions_file, + f"{plot_label}_raw", + ) + print_summary(f"{label} Raw", gt_p, gt_m, pred_p, pred_m, mask) + + print_per_detector_table(f"{label} Refined", ref_res_p, ref_res_m, mask) + plot_residuals( + dets_used, + ref_res_p[:, mask], + ref_res_m[:, mask], + predictions_file, + f"{plot_label}_refined", + ) + print_summary(f"{label} Refined", gt_p, gt_m, ref_p, ref_m, mask) + + +def evaluate(args): + data = np.load(args.predictions_file) + + all_mup = data["all_mup"] # (max_pairs, num_events, 62) + all_mum = data["all_mum"] + n_pairs = data["n_pairs"] # (num_events,) + y_muPlus = data["y_muPlus"] # (num_events, max_pairs, 62) or (num_events, 62) + y_muMinus = data["y_muMinus"] + + max_pairs = all_mup.shape[0] + num_events = all_mup.shape[1] + + if len(y_muPlus.shape) == 2: + y_muPlus = y_muPlus[:, np.newaxis, :] + y_muMinus = y_muMinus[:, np.newaxis, :] + + if y_muPlus.shape[1] != max_pairs: + raise ValueError( + f"GT pair slots ({y_muPlus.shape[1]}) != prediction ranks ({max_pairs})" + ) + + y_gt = np.stack([y_muPlus, y_muMinus], axis=2) # (num_events, max_pairs, 2, 62) + + detector_ids, element_ids, _, _, _ = QTracker.load_detector_element_data( + args.root_file + ) + if len(detector_ids) != num_events: + raise ValueError( + f"ROOT events ({len(detector_ids)}) != predictions ({num_events}). " + "Use the same ROOT file passed to AutoregressiveTrackFinder.py." + ) + + mask = np.ones(62, dtype=bool) + mask[6:12] = False + mask[54:62] = False + + true_counts = compute_true_counts(y_gt, max_pairs, num_events) + + print(f"\n{'=' * 70}") + print("Autoregressive Multi-Track Evaluation") + print(f"Events: {num_events}, Max pairs: {max_pairs}") + print(f"{'=' * 70}") + + gt_exists = np.any(y_gt != 0, axis=(2, 3)) + pred_exists = np.zeros((num_events, max_pairs), dtype=bool) + for ev in range(num_events): + for k in range(int(n_pairs[ev])): + pred_exists[ev, k] = np.any(all_mup[k, ev] != 0) or np.any( + all_mum[k, ev] != 0 + ) + + for k in range(max_pairs): + gt_k = gt_exists[:, k] + pred_k = pred_exists[:, k] + tp = int(np.sum(gt_k & pred_k)) + tn = int(np.sum(~gt_k & ~pred_k)) + fp = int(np.sum(~gt_k & pred_k)) + fn = int(np.sum(gt_k & ~pred_k)) + + print(f"\n{'=' * 70}") + print(f"Rank {k} Pair Existence") + print(f"{'=' * 70}") + print_confusion_matrix( + f"Rank {k}", + tp, + tn, + fp, + fn, + args.predictions_file, + f"rank{k}_existence", + ) + + # Hungarian-match predictions to GT and bucket by extraction rank + rank_gt_p = [[] for _ in range(max_pairs)] + rank_gt_m = [[] for _ in range(max_pairs)] + rank_pred_p = [[] for _ in range(max_pairs)] + rank_pred_m = [[] for _ in range(max_pairs)] + rank_event_idx = [[] for _ in range(max_pairs)] + total_matched_pairs = 0 + + for ev in range(num_events): + n_gt_ev = int(true_counts[ev]) + n_pred_ev = int(n_pairs[ev]) + + if n_gt_ev == 0 or n_pred_ev == 0: + continue + + pred_ev = np.stack( + [all_mup[:n_pred_ev, ev, :], all_mum[:n_pred_ev, ev, :]], + axis=1, + ) + gt_ev = y_gt[ev, :n_gt_ev, :, :] + + gt_order, pred_order = hungarian_match_predictions( + gt_ev, pred_ev, n_gt_ev, n_pred_ev + ) + total_matched_pairs += len(gt_order) + + for gi, pi in zip(gt_order, pred_order): + rank_gt_p[pi].append(gt_ev[gi, 0, :]) + rank_gt_m[pi].append(gt_ev[gi, 1, :]) + rank_pred_p[pi].append(pred_ev[pi, 0, :]) + rank_pred_m[pi].append(pred_ev[pi, 1, :]) + rank_event_idx[pi].append(ev) + + total_gt_pairs = int(np.sum(true_counts)) + total_pred_pairs = int(np.sum(n_pairs)) + global_fn = total_gt_pairs - total_matched_pairs + global_fp = total_pred_pairs - total_matched_pairs + + print(f"\n{'=' * 70}") + print("Global Pair Detection") + print(f"{'=' * 70}") + print_global_pair_detection(total_matched_pairs, global_fn, global_fp) + + # Per-rank hit-quality evaluation (raw + refined) + for k in range(max_pairs): + n_match = len(rank_gt_p[k]) + if n_match == 0: + continue + + print(f"\n{'=' * 70}") + print(f"Rank {k} ({n_match} matched pairs)") + print(f"{'=' * 70}") + + evaluate_hit_quality( + f"Rank {k}", + np.array(rank_gt_p[k]), + np.array(rank_gt_m[k]), + np.array(rank_pred_p[k]), + np.array(rank_pred_m[k]), + rank_event_idx[k], + detector_ids, + element_ids, + mask, + args.predictions_file, + f"rank{k}", + ) + + # Combined across all ranks + all_gt_p = [v for rank in rank_gt_p for v in rank] + all_gt_m = [v for rank in rank_gt_m for v in rank] + all_pred_p = [v for rank in rank_pred_p for v in rank] + all_pred_m = [v for rank in rank_pred_m for v in rank] + all_event_idx = [v for rank in rank_event_idx for v in rank] + + if all_gt_p: + print(f"\n{'=' * 70}") + print(f"All Ranks Combined ({len(all_gt_p)} matched pairs)") + print(f"{'=' * 70}") + + evaluate_hit_quality( + "All Ranks", + np.array(all_gt_p), + np.array(all_gt_m), + np.array(all_pred_p), + np.array(all_pred_m), + all_event_idx, + detector_ids, + element_ids, + mask, + args.predictions_file, + "all_ranks", + ) + + print() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Evaluate autoregressive multi-track predictions." + ) + parser.add_argument( + "predictions_file", + type=str, + help="Path to .npz file from AutoregressiveTrackFinder.py", + ) + parser.add_argument( + "root_file", + type=str, + help="Path to the ROOT file used to generate predictions (for hit refinement).", + ) + args = parser.parse_args() + evaluate(args) diff --git a/QTracker_training/models/AutoregressiveTrackFinder.py b/QTracker_training/models/AutoregressiveTrackFinder.py new file mode 100644 index 0000000..6c7714d --- /dev/null +++ b/QTracker_training/models/AutoregressiveTrackFinder.py @@ -0,0 +1,280 @@ +""" +Autoregressive Multi-Track Finder. + +Uses a trained single-track TrackFinder iteratively: +1. Predict the highest-confidence dimuon pair. +2. Remove predicted hits from the input matrix. +3. Repeat until confidence drops below threshold or max_pairs is reached. +""" + +# ruff: noqa: E402 + +import argparse +import os +import sys + +os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" + +sys.path.insert(0, os.path.dirname(__file__)) +import numpy as np +import ROOT # noqa: F401 +import tensorflow as tf +from layers import AxialAttention +from data_loader import load_data + +NUM_DETECTORS = 62 +NUM_ELEMENT_IDS = 201 +INFERENCE_CHUNK_SIZE = 128 + + +def compute_confidence(softmax_mup, softmax_mum): + """ + Compute a confidence score for the predicted pair. + + Uses the mean max-probability across all detectors for both mu+ and mu-. + High confidence means the model is certain about its element ID picks. + Low confidence (close to 1/201 ≈ 0.005) means the model is guessing. + + Args: + softmax_mup: (num_events, 62, 201) softmax output for mu+ + softmax_mum: (num_events, 62, 201) softmax output for mu- + + Returns: + confidence: (num_events,) scalar confidence per event + """ + max_prob_mup = np.max(softmax_mup, axis=-1) # (num_events, 62) + max_prob_mum = np.max(softmax_mum, axis=-1) # (num_events, 62) + + # Average across detectors (excluding masked detectors 7-12, 55-62) + mask = np.ones(62, dtype=bool) + mask[6:12] = False + mask[54:62] = False + + conf_mup = np.mean(max_prob_mup[:, mask], axis=-1) # (num_events,) + conf_mum = np.mean(max_prob_mum[:, mask], axis=-1) + + return (conf_mup + conf_mum) / 2.0 + + +def remove_predicted_hits(hit_matrix, hit_array_mup, hit_array_mum): + """ + Remove predicted hits from the input hit matrix. + + For each detector where the model predicted a nonzero element ID, + set that position in the hit matrix to 0. + + Args: + hit_matrix: (num_events, 62, 201, 1) binary hit matrix + hit_array_mup: (num_events, 62) predicted mu+ element IDs + hit_array_mum: (num_events, 62) predicted mu- element IDs + + Returns: + hit_matrix_residual: (num_events, 62, 201, 1) with predicted hits removed + """ + residual = hit_matrix.copy() + num_events = hit_matrix.shape[0] + + for i in range(num_events): + for det in range(NUM_DETECTORS): + elem_p = hit_array_mup[i, det] + elem_m = hit_array_mum[i, det] + if 0 < elem_p < NUM_ELEMENT_IDS: + residual[i, det, elem_p, 0] = 0 + if 0 < elem_m < NUM_ELEMENT_IDS: + residual[i, det, elem_m, 0] = 0 + + return residual + + +def predict_single_pass(model, X): + """ + Run the single-track model on input hit matrices. + + Args: + model: loaded single-track TrackFinder model + X: (num_events, 62, 201, 1) input hit matrices + + Returns: + hit_array_mup: (num_events, 62) predicted mu+ element IDs + hit_array_mum: (num_events, 62) predicted mu- element IDs + softmax_mup: (num_events, 62, 201) softmax probabilities for mu+ + softmax_mum: (num_events, 62, 201) softmax probabilities for mu- + """ + preds = [] + + for i in range(0, len(X), INFERENCE_CHUNK_SIZE): + X_chunk = tf.cast(X[i : i + INFERENCE_CHUNK_SIZE], tf.float32) + y_chunk = model.predict(X_chunk, verbose=0) + preds.append(y_chunk[1]) # segment output + + predictions = np.concatenate(preds, axis=0) + + softmax_mup = predictions[:, 0, :, :] # (num_events, 62, 201) + softmax_mum = predictions[:, 1, :, :] + hit_array_mup = np.argmax(softmax_mup, axis=-1).astype(np.int32) + hit_array_mum = np.argmax(softmax_mum, axis=-1).astype(np.int32) + + return hit_array_mup, hit_array_mum, softmax_mup, softmax_mum + + +def autoregressive_predict(model, X, max_pairs=5, confidence_threshold=0.5): + """ + Iterative multi-track prediction. + + For each event, repeatedly run the single-track model: + 1. Predict one pair from the current hit matrix. + 2. Compute confidence. If below threshold, stop for this event. + 3. Remove predicted hits from the hit matrix. + 4. Repeat up to max_pairs times. + + Args: + model: loaded single-track TrackFinder model + X: (num_events, 62, 201, 1) input hit matrices + max_pairs: maximum dimuon pairs to extract (and GT slots in ROOT data) + confidence_threshold: stop when confidence drops below this + + Returns: + all_pairs_mup: list of (num_events, 62) arrays, one per iteration + all_pairs_mum: list of (num_events, 62) arrays, one per iteration + all_confidences: list of (num_events,) arrays, one per iteration + n_pairs_per_event: (num_events,) number of valid pairs found per event + """ + num_events = X.shape[0] + residual = X.copy() + + all_pairs_mup = [] + all_pairs_mum = [] + all_confidences = [] + + # Track which events are still active (haven't stopped) + active = np.ones(num_events, dtype=bool) + n_pairs_per_event = np.zeros(num_events, dtype=np.int32) + + for iteration in range(max_pairs): + print(f" Pair {iteration + 1}/{max_pairs} — {np.sum(active)} active events") + + if not np.any(active): + break + + # Only run inference on active events + active_idx = np.where(active)[0] + + a_mup, a_mum, a_smax_mup, a_smax_mum = predict_single_pass( + model, residual[active_idx] + ) + a_conf = compute_confidence(a_smax_mup, a_smax_mum) + + passes_threshold = a_conf >= confidence_threshold + + # Full-size output arrays (zeros for inactive events) + mup = np.zeros((num_events, NUM_DETECTORS), dtype=np.int32) + mum = np.zeros((num_events, NUM_DETECTORS), dtype=np.int32) + confidence = np.zeros(num_events) + + newly_active_idx = active_idx[passes_threshold] + mup[newly_active_idx] = a_mup[passes_threshold] + mum[newly_active_idx] = a_mum[passes_threshold] + confidence[active_idx] = a_conf + + all_pairs_mup.append(mup.copy()) + all_pairs_mum.append(mum.copy()) + all_confidences.append(confidence.copy()) + + # Update pair counts for newly found pairs + n_pairs_per_event[newly_active_idx] += 1 + + # Remove predicted hits for active events + residual = remove_predicted_hits(residual, mup, mum) + + # Update active mask + active = np.zeros(num_events, dtype=bool) + active[newly_active_idx] = True + + return all_pairs_mup, all_pairs_mum, all_confidences, n_pairs_per_event + + +def main(args): + """Main entry point for autoregressive multi-track finding.""" + + # Load data + print(f"Loading data from {args.root_file}...") + X, y_muPlus, y_muMinus = load_data( + args.root_file, multi_track=True, max_pairs=args.max_pairs + ) + if X is None: + print("Error loading data.") + return + + print(f"Loaded {len(X)} events.") + + # Load model + print(f"Loading model from {args.model_path}...") + custom_objects = {"AxialAttention": AxialAttention} + model = tf.keras.models.load_model( + args.model_path, compile=False, custom_objects=custom_objects + ) + + # Run autoregressive prediction + print( + f"Running autoregressive prediction (max_pairs={args.max_pairs}, " + f"threshold={args.confidence_threshold})..." + ) + all_mup, all_mum, all_conf, n_pairs = autoregressive_predict( + model, + X, + max_pairs=args.max_pairs, + confidence_threshold=args.confidence_threshold, + ) + + # Print summary statistics + print(f"\n{'=' * 60}") + print("Autoregressive Prediction Summary") + print(f"{'=' * 60}") + for k in range(1, args.max_pairs + 1): + count = np.sum(n_pairs >= k) + print( + f" Events with >= {k} pairs found: {count} ({100 * count / len(X):.1f}%)" + ) + + # Save predictions to a numpy file for downstream evaluation + output_path = args.output if args.output else "autoregressive_predictions.npz" + np.savez_compressed( + output_path, + all_mup=np.array(all_mup), # (max_pairs, num_events, 62) + all_mum=np.array(all_mum), + all_conf=np.array(all_conf), # (max_pairs, num_events) + n_pairs=n_pairs, # (num_events,) + y_muPlus=y_muPlus, # GT + y_muMinus=y_muMinus, + ) + print(f"\nPredictions saved to {output_path}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Autoregressive multi-track finding using iterative single-track prediction." + ) + parser.add_argument("root_file", type=str, help="Path to multi-track ROOT file.") + parser.add_argument( + "model_path", type=str, help="Path to trained single-track model." + ) + parser.add_argument( + "--confidence_threshold", + type=float, + default=0.15, + help="Stop when mean softmax confidence drops below this value.", + ) + parser.add_argument( + "--max_pairs", + type=int, + default=5, + help="Max dimuon pairs per event (extraction limit and ROOT GT layout).", + ) + parser.add_argument( + "--output", + type=str, + default=None, + help="Output .npz file path for predictions.", + ) + args = parser.parse_args() + main(args) diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_raw_residuals.png new file mode 100644 index 0000000..80e687e Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_refined_residuals.png new file mode 100644 index 0000000..a2c35e8 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_residuals.png new file mode 100644 index 0000000..4dc6ac0 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..cd6f302 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_raw_residuals.png new file mode 100644 index 0000000..ae4a3d0 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_refined_residuals.png new file mode 100644 index 0000000..91049b7 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_residuals.png new file mode 100644 index 0000000..26520de Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..fceb8e3 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_raw_residuals.png new file mode 100644 index 0000000..e594318 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_refined_residuals.png new file mode 100644 index 0000000..67b4a2d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_residuals.png new file mode 100644 index 0000000..44d63c7 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..735b4d5 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_raw_residuals.png new file mode 100644 index 0000000..11925d7 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_refined_residuals.png new file mode 100644 index 0000000..d270c6a Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.3_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_residuals.png new file mode 100644 index 0000000..138b18e Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.3_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_raw_residuals.png new file mode 100644 index 0000000..5457cb6 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_refined_residuals.png new file mode 100644 index 0000000..7cc2428 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_residuals.png new file mode 100644 index 0000000..a17ab54 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..abb761c Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_raw_residuals.png new file mode 100644 index 0000000..4a557df Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_refined_residuals.png new file mode 100644 index 0000000..379d758 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_residuals.png new file mode 100644 index 0000000..c5ca491 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..2cfc328 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_raw_residuals.png new file mode 100644 index 0000000..b34d0f1 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_refined_residuals.png new file mode 100644 index 0000000..4102c1e Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_residuals.png new file mode 100644 index 0000000..6d18628 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..6dc925a Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_raw_residuals.png new file mode 100644 index 0000000..e4e3c4c Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_refined_residuals.png new file mode 100644 index 0000000..740917f Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.4_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_residuals.png new file mode 100644 index 0000000..f24dd93 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.4_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_raw_residuals.png new file mode 100644 index 0000000..f940ac7 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_refined_residuals.png new file mode 100644 index 0000000..4b2a739 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_residuals.png new file mode 100644 index 0000000..470afd6 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..4fe7dd8 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_raw_residuals.png new file mode 100644 index 0000000..3ad4faa Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_refined_residuals.png new file mode 100644 index 0000000..095e3f8 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_residuals.png new file mode 100644 index 0000000..ed3b1de Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..8831cae Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_raw_residuals.png new file mode 100644 index 0000000..c849d18 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_refined_residuals.png new file mode 100644 index 0000000..c502e48 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_residuals.png new file mode 100644 index 0000000..90f72f6 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..ca6f616 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_raw_residuals.png new file mode 100644 index 0000000..2746a91 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_refined_residuals.png new file mode 100644 index 0000000..bf39cea Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.5_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_residuals.png new file mode 100644 index 0000000..7722845 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.5_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_raw_residuals.png new file mode 100644 index 0000000..3215369 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_refined_residuals.png new file mode 100644 index 0000000..6e949b4 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_residuals.png new file mode 100644 index 0000000..085328d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..df45420 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_raw_residuals.png new file mode 100644 index 0000000..5ebd361 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_refined_residuals.png new file mode 100644 index 0000000..69a7c93 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_residuals.png new file mode 100644 index 0000000..990eab6 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..aeb9d7c Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_raw_residuals.png new file mode 100644 index 0000000..e871cd7 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_refined_residuals.png new file mode 100644 index 0000000..f56920f Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_residuals.png new file mode 100644 index 0000000..d29892d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..974743d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_raw_residuals.png new file mode 100644 index 0000000..99ce6eb Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_refined_residuals.png new file mode 100644 index 0000000..ad3789d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.6_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_residuals.png new file mode 100644 index 0000000..0b5c2c1 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.6_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_raw_residuals.png new file mode 100644 index 0000000..1b4e13c Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_refined_residuals.png new file mode 100644 index 0000000..b08fe68 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_residuals.png new file mode 100644 index 0000000..af6c583 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..5e04124 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_raw_residuals.png new file mode 100644 index 0000000..3a00fef Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_refined_residuals.png new file mode 100644 index 0000000..6626f8d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_residuals.png new file mode 100644 index 0000000..c7d064d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..f0ee3ad Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_raw_residuals.png new file mode 100644 index 0000000..cdcf9c6 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_refined_residuals.png new file mode 100644 index 0000000..5b7d86e Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_residuals.png new file mode 100644 index 0000000..d56acc9 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..22f2e5a Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_raw_residuals.png new file mode 100644 index 0000000..6ec979d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_refined_residuals.png new file mode 100644 index 0000000..40d956a Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.7_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_residuals.png new file mode 100644 index 0000000..d18d6c9 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.7_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_raw_residuals.png new file mode 100644 index 0000000..13bcd05 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_refined_residuals.png new file mode 100644 index 0000000..340b37d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_residuals.png new file mode 100644 index 0000000..bcc0060 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..6748bfa Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_raw_residuals.png new file mode 100644 index 0000000..538e028 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_refined_residuals.png new file mode 100644 index 0000000..9b33064 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_residuals.png new file mode 100644 index 0000000..9a0730e Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..2204785 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_raw_residuals.png new file mode 100644 index 0000000..4a06bd3 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_refined_residuals.png new file mode 100644 index 0000000..703d019 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_residuals.png new file mode 100644 index 0000000..3cf94ce Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..e156a23 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank2_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_raw_residuals.png new file mode 100644 index 0000000..bb29223 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank2_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_refined_residuals.png new file mode 100644 index 0000000..d87d516 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.8_rank2_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_residuals.png new file mode 100644 index 0000000..1454e3a Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.8_rank2_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_raw_residuals.png new file mode 100644 index 0000000..3f5872d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_refined_residuals.png new file mode 100644 index 0000000..f4248ca Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_residuals.png new file mode 100644 index 0000000..073ca92 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_all_ranks_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank0_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_existence_confusion_matrix.png new file mode 100644 index 0000000..93feabd Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank0_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_raw_residuals.png new file mode 100644 index 0000000..b51e529 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank0_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_refined_residuals.png new file mode 100644 index 0000000..50ee290 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank0_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_residuals.png new file mode 100644 index 0000000..49c500d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank0_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank1_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_existence_confusion_matrix.png new file mode 100644 index 0000000..b95d665 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_existence_confusion_matrix.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank1_raw_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_raw_residuals.png new file mode 100644 index 0000000..33e4580 Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_raw_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank1_refined_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_refined_residuals.png new file mode 100644 index 0000000..d5a468f Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_refined_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank1_residuals.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_residuals.png new file mode 100644 index 0000000..934299d Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank1_residuals.png differ diff --git a/QTracker_training/plots/autoregressive_predictions_0.9_rank2_existence_confusion_matrix.png b/QTracker_training/plots/autoregressive_predictions_0.9_rank2_existence_confusion_matrix.png new file mode 100644 index 0000000..43f3dfe Binary files /dev/null and b/QTracker_training/plots/autoregressive_predictions_0.9_rank2_existence_confusion_matrix.png differ diff --git a/QTracker_training/scripts/eval_autoregressive.slurm b/QTracker_training/scripts/eval_autoregressive.slurm new file mode 100644 index 0000000..762eb80 --- /dev/null +++ b/QTracker_training/scripts/eval_autoregressive.slurm @@ -0,0 +1,50 @@ +#!/bin/bash +#SBATCH -A spinquest_standard +#SBATCH -p gpu +#SBATCH --gres=gpu:1 +#SBATCH -c 4 +#SBATCH -t 18:00:00 +#SBATCH -J autoregressive-eval +#SBATCH -o Slurm_Files/autoregressive-eval.out +#SBATCH -e Slurm_Files/autoregressive-eval.err +#SBATCH --mem=256000 + +module purge + +# For apptainer exec +APPTAINER=/apps/software/standard/core/apptainer/1.4.5/bin/apptainer +IMAGE=/project/ptgroup/spinquest/David/TfRootBuild.sif +CODEDIR=/project/ptgroup/spinquest/Donghwa/Qtracker_basic/.worktrees/autoregressive_track_finder/QTracker_training/ + +### Main Body ### +MAX_PAIRS=3 +THRESHOLDS="0.3 0.4 0.5 0.6 0.7 0.8 0.9" + +# Single-track model used as the base finder +MODEL=/mnt/code/checkpoints/track_finder_64.keras + +# Multi-track validation data +VAL_DATA=/mnt/code/data/multi_track/processed_files/mc_events_val.root + +for THRESH in $THRESHOLDS; do + echo "" + echo "######################################################################" + echo "# THRESHOLD = ${THRESH}" + echo "######################################################################" + echo "" + + OUTPUT=/mnt/code/checkpoints/autoregressive_predictions_${THRESH}.npz + + # Step 1: Run autoregressive prediction + ${APPTAINER} exec --nv --bind ${CODEDIR}:/mnt/code "${IMAGE}" \ + python3 /mnt/code/models/AutoregressiveTrackFinder.py \ + ${VAL_DATA} \ + ${MODEL} \ + --max_pairs $MAX_PAIRS \ + --confidence_threshold $THRESH \ + --output ${OUTPUT} + + # Step 2: Evaluate + ${APPTAINER} exec --nv --bind ${CODEDIR}:/mnt/code "${IMAGE}" \ + python3 /mnt/code/eval_autoregressive.py ${OUTPUT} ${VAL_DATA} +done