|
| 1 | +"""timeseries_diff.py |
| 2 | +import sys,os; sys.path.insert(1, os.environ[f"O2DPG"]+"/UTILS/TimeSeries"); |
| 3 | +from timeseries_diff import * |
| 4 | +
|
| 5 | +Utility helpers for time‑series comparison scripts. |
| 6 | +keeping their ROOT files alive. |
| 7 | +""" |
| 8 | + |
| 9 | +import os |
| 10 | +import pathlib |
| 11 | +from typing import List, Tuple, Optional |
| 12 | + |
| 13 | +import ROOT # PyROOT |
| 14 | + |
| 15 | +# --------------------------------------------------------------------------- |
| 16 | +# Helper: open many ROOT files and keep them alive |
| 17 | +# --------------------------------------------------------------------------- |
| 18 | + |
| 19 | +def read_time_series(listfile: str = "o2_timeseries_tpc.list",treename: str = "timeSeries",) -> List[Tuple[ROOT.TFile, Optional[ROOT.TTree]]]: |
| 20 | + """Read *listfile* containing one ROOT path per line and return a list |
| 21 | + of ``(TFile, TTree | None)`` tuples. |
| 22 | + The TFile objects are **kept open** (and returned) so the TTrees remain |
| 23 | + valid for the caller. Blank lines and lines starting with "#" are |
| 24 | + ignored. Environment variables in paths are expanded. |
| 25 | + Parameters |
| 26 | + ---------- |
| 27 | + listfile : str |
| 28 | + Text file with ROOT filenames. |
| 29 | + treename : str, default "timeSeries" |
| 30 | + Name of the tree to retrieve from each file. |
| 31 | + Returns |
| 32 | + ------- |
| 33 | + list of tuples |
| 34 | + ``[(f1, tree1), (f2, tree2), ...]`` where *tree* is ``None`` if |
| 35 | + the file or tree could not be opened. |
| 36 | + """ |
| 37 | + files_and_trees: List[Tuple[ROOT.TFile, Optional[ROOT.TTree]]] = [] |
| 38 | + |
| 39 | + with open(listfile, "r") as fh: |
| 40 | + paths = [ln.strip() for ln in fh if ln.strip() and not ln.startswith("#")] |
| 41 | + |
| 42 | + for raw_path in paths: |
| 43 | + path = os.path.expandvars(raw_path) |
| 44 | + if not pathlib.Path(path).is_file(): |
| 45 | + print(f"[read_time_series] warning: file not found -> {path}") |
| 46 | + files_and_trees.append((None, None)) |
| 47 | + continue |
| 48 | + try: |
| 49 | + froot = ROOT.TFile.Open(path) |
| 50 | + if not froot or froot.IsZombie(): |
| 51 | + raise RuntimeError("file could not be opened") |
| 52 | + tree = froot.Get(treename) |
| 53 | + if not tree: |
| 54 | + print(f"[read_time_series] warning: tree '{treename}' missing in {path}") |
| 55 | + files_and_trees.append((froot, tree)) |
| 56 | + except Exception as e: |
| 57 | + print(f"[read_time_series] error: cannot open {path}: {e}") |
| 58 | + files_and_trees.append((None, None)) |
| 59 | + |
| 60 | + return files_and_trees |
| 61 | + |
| 62 | +def makeAliases(trees): |
| 63 | + for tree in trees: tree[1].AddFriend(trees[0][1],"F") |
| 64 | + |
| 65 | + |
| 66 | +def setStyle(): |
| 67 | + ROOT.gStyle.SetOptStat(0) |
| 68 | + ROOT.gStyle.SetOptTitle(0) |
| 69 | + ROOT.gStyle.SetPalette(ROOT.kRainBow) |
| 70 | + ROOT.gStyle.SetPaintTextFormat(".2f") |
| 71 | + ROOT.gStyle.SetTextFont(42) |
| 72 | + ROOT.gStyle.SetTextSize(0.04) |
| 73 | + ROOT.gROOT.ForceStyle() |
| 74 | + ROOT.gROOT.SetBatch(True) |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | +# --------------------------------------------------------------------------- |
| 82 | +# make_ratios ---------------------------------------------------------------- |
| 83 | +# --------------------------------------------------------------------------- |
| 84 | + |
| 85 | +def make_ratios(trees: list, outdir: str = "fig", pdf_name: str = "ratios.pdf") -> ROOT.TCanvas: |
| 86 | + """Create ratio plots *log(var/F.var) vs Iteration$* for each input tree. |
| 87 | + * A PNG for every variable / tree is saved to *outdir* |
| 88 | + * All canvases are also appended to a multi‑page PDF *pdf_name* |
| 89 | + * Vertical guide‑lines mark the logical regions (isector, itgl, iqpt, occu) |
| 90 | +
|
| 91 | + """ |
| 92 | + outdir = pathlib.Path(outdir) |
| 93 | + outdir.mkdir(parents=True, exist_ok=True) |
| 94 | + pdf_path = outdir / pdf_name |
| 95 | + |
| 96 | + # ------- style / helpers ---------------------------------------------- |
| 97 | + ROOT.gStyle.SetOptTitle(1) |
| 98 | + canvas = ROOT.TCanvas("c_ratio", "ratio plots", 1200, 600) |
| 99 | + lab = ROOT.TLatex() |
| 100 | + lab.SetTextSize(0.04) |
| 101 | + |
| 102 | + # vertical guides in **user** x‑coordinates (Iteration$ axis: 0–128) |
| 103 | + vlines = [0, 54, 84, 104, 127] |
| 104 | + vnames = ["isector", "itgl", "iqpt", "occupancy"] |
| 105 | + vcolors = [ROOT.kRed+1, ROOT.kBlue+1, ROOT.kGreen+2, ROOT.kMagenta+1] |
| 106 | + setups=["ref","apass2_closure-test-zAcc.GausSmooth_test3_streamer","apass2_closure-test-zAcc.GausSmooth_test4_streamer","apass2_closure-test-zAcc.GausSmooth_test2_streamer"] |
| 107 | + # variables to compare --------------------------------------------------- |
| 108 | + vars_ = [ |
| 109 | + "mTSITSTPC.mTPCChi2A", "mTSITSTPC.mTPCChi2C", |
| 110 | + "mTSTPC.mDCAr_A_NTracks", "mTSTPC.mDCAr_C_NTracks", |
| 111 | + "mTSTPC.mTPCNClA", "mTSTPC.mTPCNClC", |
| 112 | + "mITSTPCAll.mITSTPC_A_MatchEff", "mITSTPCAll.mITSTPC_C_MatchEff", |
| 113 | + "mdEdxQMax.mLogdEdx_A_RMS","mdEdxQMax.mLogdEdx_C_RMS", |
| 114 | + "mdEdxQMax.mLogdEdx_A_IROC_RMS","mdEdxQMax.mLogdEdx_C_IROC_RMS" |
| 115 | + ] |
| 116 | + cut = "mTSITSTPC.mDCAr_A_NTracks > 200" |
| 117 | + |
| 118 | + # open PDF --------------------------------------------------------------- |
| 119 | + canvas.Print(f"{pdf_path}[") # begin multipage |
| 120 | + |
| 121 | + for setup_index, (_, tree) in enumerate(trees[1:], start=1): |
| 122 | + if not tree: |
| 123 | + continue |
| 124 | + for var in vars_: |
| 125 | + expr = f"log({var}/F.{var}):Iteration$" |
| 126 | + # 2‑D density histogram |
| 127 | + tree.Draw(f"{expr}>>his(128,0,128,50,-0.05,0.05)", cut, "colz") |
| 128 | + # profile overlay |
| 129 | + tree.Draw(f"{expr}>>hp(128,0,128)", cut, "profsame") |
| 130 | + pad = ROOT.gPad |
| 131 | + ymin, ymax = -0.05, 0.05 |
| 132 | + # keep references so ROOT does not garbage‑collect the guides |
| 133 | + guides: list[ROOT.TLine] = [] |
| 134 | + for x, txt, col in zip(vlines, vnames, vcolors): |
| 135 | + # skip lines outside current x‑range (safety when reusing canvas) |
| 136 | + if x < 0 or x > 128:continue |
| 137 | + # 1) vertical line in **user** coordinates |
| 138 | + ln = ROOT.TLine(x, ymin, x, ymax) |
| 139 | + ln.SetLineColor(col) |
| 140 | + ln.SetLineStyle(2) |
| 141 | + ln.SetLineWidth(5) |
| 142 | + ln.Draw() |
| 143 | + guides.append(ln) |
| 144 | + # 2) text in NDC (pad‑relative) for stable position |
| 145 | + x_ndc = pad.XtoPad(x) # already NDC 0‑1 |
| 146 | + lab.SetTextColor(col) |
| 147 | + lab.DrawLatex(x + 0.02, 0.03, txt) |
| 148 | + |
| 149 | + # label of the setup on top‑left |
| 150 | + lab.SetTextColor(ROOT.kMagenta+2) |
| 151 | + lab.DrawLatex(0.15, 0.05, f"Setup {setups[setup_index]}") |
| 152 | + canvas.Modified(); canvas.Update() |
| 153 | + |
| 154 | + # ---------------------------------------------------------------- |
| 155 | + tag = var.split('.')[-1] |
| 156 | + canvas.SaveAs(str(outdir / f"ratio_{setup_index}_{tag}.png")) |
| 157 | + canvas.Print(str(pdf_path)) # add page |
| 158 | + |
| 159 | + # prevent ROOT from deleting the guides before next Draw() |
| 160 | + for ln in guides: |
| 161 | + pad.GetListOfPrimitives().Remove(ln) |
| 162 | + |
| 163 | + canvas.Print(f"{pdf_path}]") # close multipage |
| 164 | + return canvas |
0 commit comments