Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-10-15 08:17:25

0001 #include "corral_class.h"
0002 #include "fluct_common.h"   // SIPHIMASK windows (README_PID.md sec 10.3)
0003 #include "corral_version.h" // CORRAL_GIT, CORRAL_BUILD_TIME (made at build time, CMakeLists.txt)
0004 #include <TNamed.h>
0005 #include <TParameter.h>
0006 
0007 //------------------------------------------------------------
0008 // Build (or adopt) the input TChain. This must not touch any other member of
0009 // 'this' -- it runs in the constructor's member-initializer-list, before
0010 // fReader (and everything bound to fReader) exists yet.
0011 TChain* corral::BuildInputChain(TTree *tree){
0012     //
0013     if (tree != 0){
0014         // caller supplied a tree/chain directly (e.g. for local testing against
0015         // a single already-open file/tree rather than the default grid glob)
0016         TChain *given = dynamic_cast<TChain*>(tree);
0017         if (given) return given;
0018         TChain *wrap = new TChain(tree->GetName());
0019         wrap->Add(tree->GetCurrentFile()->GetName());
0020         return wrap;
0021     }
0022     //
0023     //---- no job list (-l): the whole production in one job (useful for testing). The file glob is
0024     //---- $CORRAL_TREES if set, else the 795xx runs of the WSU copy of ana573 below (ana532 until 2026-09-27;
0025     //---- 795xx only from 2026-10-08, when runs 81566 and 81584-81586 start landing in the same directory).
0026     //----   e.g. export CORRAL_TREES='/path/to/production/outputCollect_*.root'  (quote the glob)
0027     const char* env = getenv("CORRAL_TREES");
0028     TString GLOB    = (env && *env) ? TString(env) : TString("/rs/rs_grp_rhi/sPHENIX/ana573/795*/outputCollect_*.root");
0029     TChain *chain   = new TChain("outTree","Collect tree chain");
0030     int nf          = chain->Add(GLOB.Data());
0031     cout<<"corral -- input trees "<<GLOB<<((env && *env)?"  ($CORRAL_TREES)":"  (default; set CORRAL_TREES to change)")<<": "<<nf<<" files"<<endl;
0032     if (nf==0) cout<<"corral -- WARNING: no input files match "<<GLOB<<" (unmounted disk? wrong CORRAL_TREES?)"<<endl;
0033     return chain;
0034 }
0035 
0036 //------------------------------------------------------------
0037 corral::corral(TTree *tree, int ntd, TString runstr, TString outname)
0038   : ntodo(ntd)
0039   , RunString(runstr)
0040   , OutputName(outname)
0041   , doCrossing(true)
0042   , doOldPID(false)
0043   , doSiPhiMask(true)
0044   , doCMMask(true)      // README_SplitTracks573 sec 38: default ON (2026-09-30), w in [-0.07,0.10); "nocmmask" off, "cmmaskAABB" override
0045   , doSegVeto(false)        // README.md: "segveto" = skip the segments in bad_segments.txt
0046   , doSepMaps(false)        // README_CQcomparison sec 2.3: diagnostic separation maps (~+5 min per chunk); "sepmaps" on
0047   , valCMMaskLo(-0.07)
0048   , valCMMaskHi(0.10)
0049   , nSiPhiMask(NSIPHIMASK_MAIN)
0050   , doQCut(false)
0051   , FinePhiBinning(false)   // only set true below if RunString requests it
0052   , valQCut(-1.0)
0053   , valSLCut(0.18)      // pre-pass production default (sec 17.6, superseding sec 14.1's 0.60):
0054                         // round-2/refinement LS SL x SKF survey's SKF>=0.10 row crosses its
0055                         // aggregate canaries (P/H ring, C(Q) 10-20MeV, R2 step) around SL~0.13-0.14,
0056                         // but the zoom maps show residual "satellite" structure flanking the (0,0)
0057                         // ridge that only clears by SL~0.17; 0.18 is the nearest grid point past
0058                         // that, at a small extra cost (~13% relative) in kept sibling pairs vs 0.14.
0059   , valSiKeyCut(0.10)   // pre-pass production default (sec 17.6, superseding sec 14.1's 0.20): of
0060                         // the round-2/refinement grid's four SKF gates (10/40/60/70/90), only
0061                         // SKF>=0.10 reaches the genuinely-partial-MVTX, INTT-disagreeing candidate
0062                         // population (SiSplitScore~.167/.333) that sec 17.4/17.5 showed is real
0063                         // duplicate signal, not background (position concentration 46-53% vs 10.1%
0064                         // for the mvtxFrac==0 reference) -- gates >=0.40 silently leave those
0065                         // genuine duplicates unflagged. sec 17.5: SiSplitScore (MVTX-primary/
0066                         // INTT-secondary, sec 13.9) itself remains the discriminator -- checked
0067                         // directly against a pure MVTX-only fraction ("MKF") and kept, INTT's
0068                         // tiebreak vote in the partial band measurably tracks real duplicates
0069                         // rather than adding noise.
0070   , valRadialGapCut(0.45)   // sec 17.14 second-path threshold, see corral_class.h member comment --
0071                             // already-measured sideband dividing line (sec 17.11), not yet a scanned
0072                             // optimum.
0073   , DISABLE_LScuts(false)   // sec 17.9: default OFF, i.e. the pregate + SL/SKF/RadialGap split-track
0074                             // pre-pass is ON by default. "noLS" in RunString sets this true and disables it.
0075   , DISABLE_RG(false)   // sec 17.17: default OFF, i.e. the RadialGap path (path 2) is ON by default
0076                         // alongside path 1. "noRG" in RunString sets this true and disables path 2 only.
0077   , doSplitRemoval(true)    // ON by default as of sec 13.8.18/14.1, values finalized sec 17.6, switch
0078                             // philosophy inverted sec 17.9 -- this pre-pass, at the valSLCut/
0079                             // valSiKeyCut defaults above (the finalized LS working point, sec 17.6),
0080                             // is the production split-track mitigation, driven by !DISABLE_LScuts
0081                             // (recomputed below once RunString is parsed). With NO RunString at all
0082                             // ("-s" omitted, or "-s \"\""), these defaults ARE the present best-effort
0083                             // split-track removal -- no token is required to get it (sec 14.4).
0084   , doLooperVeto(true)  // README_SplitTracks573 sec 35 (2026-09-29): ON; "nolooper" turns it off
0085   , valLooperRSum(0.08) // sec 35: default 0.08 (2026-09-29, tests at 0.04/0.06/0.08); "looperNN" (0.02-0.10)
0086   , valZvtxNB(16)       // 16 zvtx slices = 2 cm over +-16 cm (sec 38: finer slices do not help)
0087   , doULSTest(true)     // ON by default as of 2026-09-22 (user's call, after sec 15.7/15.8): the ULS
0088                         // pair-level veto (drop flagged pi+pi- SIBLING pair; sec 16.7: never mixed) at the
0089                         // sec 15.7 working point below. "noulstest" in RunString turns it off;
0090                         // "ulstestNN" overrides the threshold. Independently re-derived and reconfirmed
0091                         // after the sec 16 restart, via a different method (fixed-k windowed survival
0092                         // function, sec 18.5/18.7): same conclusion both times.
0093   , doDauCheck(true)    // README_CQcomparison sec 2.2 (2026-10-03, option 2): ON; "nodau" turns it off
0094   , valULSTestCut(0.05)     // sec 15.7 full-stats 14-point scan: best non-degenerate point (>=0.05,
0095                             // 0.10, 0.15 bit-identical; >=0.00 degenerate). Was 0.60 (sec 15.6 reuse).
0096                             // sec 18.7 (2026-09-23): reconfirmed via fine 0.01/0.02/0.03 grid -- gap below
0097                             // 0.05 is empty (SiSplitScore is quantized: 0 by default, jumps straight to a
0098                             // discrete nonzero value), and the 0.05-0.99 falloff is gradual with no sharp
0099                             // optimum, so this value is not tuned to a peak -- it just clears the floor.
0100   , doXTFClean(true)        // sec 18.22/18.25: ON by default -- "noXTF" turns it (and the two below) off
0101   , doMixNoAdjTF(true)  // sec 18.23/18.25: ON by default -- "noXTF" or "mixAdjTF" turns it off
0102   , doXTFStrict(true)       // sec 18.22/18.25: ON by default -- "noXTF" off; plain "XTFclean" selects non-strict
0103   , nXTF_pairs(0), nXTF_byINTT(0), nXTF_byQuality(0), nXTF_neither(0), nXTF_losers(0)
0104   , doTFDup(true)           // 2026-09-26: ON by default -- "noTFdup" turns it off (no-op without a bco branch)
0105   , nTFDup_rows(0), TFDup_maxAbsDvz(0)
0106   , doIsoVtx(false), valIsoNX(0), valIsoDZ(0.)      // README.md "isovtxNNZZ": isolated vertices only, default OFF
0107   , ONLY_FIRST_XINGPOS(false)   // sec 18.21 follow-up 2, OFF by default -- "XingPos" turns it on
0108   , xingSel(3)              // 2026-10-08: ZB by default -- "AllX" = all crossings, "Xing0" / "NearX0" the other samples
0109   , ONLY_FIRST_TF(false)        // sec 18.21, OFF by default -- "onlyfirsttf" in RunString turns it on
0110   , valDcaxyCut(1.5)        // |dcaxy| cut (cm), "dxyNN" = NN/100 cm
0111   , valDcazCut(1.5)     // |dcaz| cut (cm), "dczNN" = NN/10 cm
0112   , NTPCCUT(18)             // LL=8 (inclusive), 18 is open but better
0113   , PTMINCUT(0.1)
0114   , valFullMVTX(2), valFullMVTXdinv(0.0), valPregateDEta(0.040) // README_SplitTracks573 sec 14/15/18:
0115   , valTSepDy(0.06), valTSepDphi(2.0), valISepDphi(2.), valVzMax(16.), valNchLo(0), valNchHi(9999), valFinZMax(8.)                          // constants except dps / tsepm (tokens)
0116   , valDPsW(2.0), valDPsR(0.06)
0117   , nentriesfile(0)
0118   , fChain(BuildInputChain(tree))
0119   , fReader(fChain)
0120   , run(fReader,"run")
0121   , segment(fReader,"segment")
0122   , evt(fReader,"evt")
0123   , trigVec(fReader,"trigVec")
0124   , nvtx(fReader,"nvtx")
0125   , vtxntr(fReader,"vtxntr")
0126   , vtxx(fReader,"vtxx")
0127   , vtxy(fReader,"vtxy")
0128   , vtxz(fReader,"vtxz")
0129   , crossing(fReader,"crossing")
0130   , etotem(fReader,"etotem")
0131   , etotih(fReader,"etotih")
0132   , etotoh(fReader,"etotoh")
0133   , etotioh(fReader,"etotioh")
0134   , etotepd(fReader,"etotepd")
0135   , ntr(fReader,"ntr")
0136   , pid(fReader,"pid")
0137   , indv0(fReader,"indv0")
0138   , primary(fReader,"primary")
0139   , quality(fReader,"quality")
0140   , chisq(fReader,"chisq")
0141   , chg(fReader,"chg")
0142   , ptot(fReader,"ptot")
0143   , eta(fReader,"eta")
0144   , phi(fReader,"phi")
0145   , pt(fReader,"pt")
0146   , seedeta(fReader,"seedeta")
0147   , seedphi(fReader,"seedphi")
0148   , seedpt(fReader,"seedpt")
0149   , ntpc(fReader,"ntpc")
0150   , nmvtx(fReader,"nmvtx")
0151   , nintt(fReader,"nintt")
0152   , dcaxy(fReader,"dcaxy")
0153   , dcaz(fReader,"dcaz")
0154   , dedx70n(fReader,"dedx70n")
0155   , dedx70s(fReader,"dedx70s")
0156   , dedxKFP(fReader,"dedxKFP")
0157   , layermask(fReader,"layermask")
0158   , siclukey(fReader,"siclukey")
0159   , tpcsector(fReader,"tpcsector")
0160   , tpcarclen(fReader,"tpcarclen")
0161   , tpcz(fReader,"tpcz")
0162   , nv0(fReader,"nv0")
0163   , v0pid(fReader,"v0pid")
0164   , v0x(fReader,"v0x")
0165   , v0y(fReader,"v0y")
0166   , v0z(fReader,"v0z")
0167   , v0px(fReader,"v0px")
0168   , v0py(fReader,"v0py")
0169   , v0pz(fReader,"v0pz")
0170   , v0pt(fReader,"v0pt")
0171   , v0ptot(fReader,"v0ptot")
0172   , v0eta(fReader,"v0eta")
0173   , v0phi(fReader,"v0phi")
0174   , v0ene(fReader,"v0ene")
0175   , v0mass(fReader,"v0mass")
0176   , v0ctau(fReader,"v0ctau")
0177   , v0decaylen(fReader,"v0decaylen")
0178   , v0chi2ndf(fReader,"v0chi2ndf")
0179   , v0dira(fReader,"v0dira")
0180   , v0pvdca(fReader,"v0pvdca")
0181   , v0decayx(fReader,"v0decayx")
0182   , v0decayy(fReader,"v0decayy")
0183   , v0decayz(fReader,"v0decayz")
0184   , v0indtr1(fReader,"v0indtr1")
0185   , v0indtr2(fReader,"v0indtr2")
0186 {
0187     //
0188     gPrintViaErrorHandler = kTRUE;
0189     gErrorIgnoreLevel = kWarning;
0190     //
0191     //---- 2026-10-08: "Raw" = every cleaner and correction OFF and all crossings, i.e. shorthand for RAW_TOKENS below. It is expanded
0192     //---- into RunString here, before any token is parsed, so the logs show the switches in force.
0193     {
0194         static const char* RAW_TOKENS = "noLS_noulstest_noXTF_noTFdup_nolooper_notsep_noisep_nocross_nocmmask";
0195         if (RunString.Contains("Raw",TString::kIgnoreCase)){
0196             RunString += TString("_")+RAW_TOKENS;
0197             //---- all crossings, unless a crossing sample is asked for too (e.g. "Raw_Xing0")
0198             if (!RunString.Contains("Xing0",TString::kIgnoreCase) && !RunString.Contains("NearX0",TString::kIgnoreCase)
0199                 && !RunString.Contains("ZB",TString::kIgnoreCase)) RunString += "_AllX";
0200             cout<<"reader::reader -- Raw = "<<RAW_TOKENS<<" (+ AllX unless Xing0/NearX0/ZB)  -> RunString="<<RunString<<endl;
0201         }
0202     }
0203     //---- README_Crossing.md (2026-09-25): crossing correction (pt-ordering + post-CF bin correction)
0204     //---- is the DEFAULT. "nocross" turns off both. The old "cross" token is removed.
0205     if (RunString.Contains("nocross",TString::kIgnoreCase)){
0206         doCrossing = false;
0207         cout<<"RunString="<<RunString<<"\t doCrossing="<<(int)doCrossing<<endl;
0208     }
0209     //---- README_PID.md (2026-09-28): PID from the KFP dE/dx gates (pi, then p, then K) is the DEFAULT.
0210     //---- "oldpid" goes back to the legacy pion-only cut (dedx70s<400, any momentum).
0211     if (RunString.Contains("oldpid",TString::kIgnoreCase)){
0212         doOldPID = true;
0213         cout<<"RunString="<<RunString<<"\t doOldPID="<<(int)doOldPID<<endl;
0214     }
0215     //---- README_PID.md sec 10.3 (2026-09-29): the ana573 vertex-phi mask is the DEFAULT; "nosiphimask" turns it off.
0216     if (RunString.Contains("nosiphimask",TString::kIgnoreCase)){
0217         doSiPhiMask = false;
0218         cout<<"RunString="<<RunString<<"\t doSiPhiMask="<<(int)doSiPhiMask<<endl;
0219     }
0220     if (RunString.Contains("siphimaskall",TString::kIgnoreCase)){
0221         nSiPhiMask = NSIPHIMASK;
0222         cout<<"RunString="<<RunString<<"\t nSiPhiMask="<<nSiPhiMask<<" (all vertex-phi windows)"<<endl;
0223     }
0224     //---- README_SplitTracks573 sec 18: value tokens are accepted only within sane limits; anything else
0225     //---- (including a malformed number) stops the job, it is never silently clamped.
0226     auto saneOrDie  = [&](const char* tok, double v, double lo, double hi){
0227         if (v<lo || v>hi){
0228             cout<<"reader::reader -- run-string token '"<<tok<<"' gives "<<v<<", outside the sane range ["<<lo<<","<<hi<<"]. Exiting."<<endl;
0229             exit(1);
0230         }
0231     };
0232     if (RunString.Contains("ntpc",TString::kIgnoreCase)){
0233         int ij          = RunString.Index("ntpc",0,TString::kIgnoreCase);
0234         TString sntpc   = RunString(ij+4,2);
0235         NTPCCUT         = sntpc.IsDigit() ? atoi(sntpc.Data()) : -1;
0236         saneOrDie("ntpcNN",NTPCCUT,10,40);
0237         cout<<"RunString="<<RunString<<"\t      sntpc="<<sntpc<<"\t NTPCCUT="<<NTPCCUT<<endl;
0238     }
0239     //---- README.md (2026-10-12): "dczNN" = track |dcaz| < NN/10 cm (default 1.5 cm). A flat pedestal of tracks from other
0240     //---- collisions lies under the dcaz peak, twice as high at the 8158x rate (ana573/CutFlow_ana573.C).
0241     if (RunString.Contains("dcz",TString::kIgnoreCase)){
0242         int ij  = RunString.Index("dcz",0,TString::kIgnoreCase);
0243         TString sd  = RunString(ij+3,2);
0244         if (!sd.IsDigit()){ cout<<"RunString="<<RunString<<": dcz needs 2 digits (dczNN = |dcaz| < NN/10 cm)"<<endl; exit(1); }
0245         valDcazCut  = atoi(sd.Data())/10.;
0246         saneOrDie("dczNN",valDcazCut,0.2,1.5);
0247     }
0248     cout<<"reader::reader -- track |dcaz| < "<<valDcazCut<<" cm"<<(valDcazCut!=1.5 ? " (dcz)" : " (default)")<<endl;
0249     //---- README.md (2026-10-12): "dxyNN" = track |dcaxy| < NN/100 cm (default 1.5 cm). Tracks attached from other collisions
0250     //---- point back 5-7x worse in xy (rms 0.2-0.25 cm vs 0.037 cm, ana573/CutFlow_ana573.C page 2).
0251     if (RunString.Contains("dxy",TString::kIgnoreCase)){
0252         int ij  = RunString.Index("dxy",0,TString::kIgnoreCase);
0253         TString sd  = RunString(ij+3,2);
0254         if (!sd.IsDigit()){ cout<<"RunString="<<RunString<<": dxy needs 2 digits (dxyNN = |dcaxy| < NN/100 cm)"<<endl; exit(1); }
0255         valDcaxyCut = atoi(sd.Data())/100.;
0256         saneOrDie("dxyNN",valDcaxyCut,0.05,0.99);
0257     }
0258     cout<<"reader::reader -- track |dcaxy| < "<<valDcaxyCut<<" cm"<<(valDcaxyCut!=1.5 ? " (dxy)" : " (default)")<<endl;
0259     //
0260     //
0261     //---- README_SplitTracks573 sec 18 (token trim): the LS pregate half-width "dpsNN" (1.0-4.0 deg) and the
0262     //---- two-track cut "tsepmYYPP" (abs(dy) 0.02-0.10, abs(dphi*) 1.0-4.0 deg) / "notsep". Everything else of
0263     //---- the ana573 LS working point is a constant (corral_class.h). Checked clean against every other
0264     //---- token (none contains "dps" or "tsep"); "notsep" is tested before "tsepm".
0265     if (RunString.Contains("dps",TString::kIgnoreCase)){
0266         int ij  = RunString.Index("dps",0,TString::kIgnoreCase);
0267         TString sw = RunString(ij+3,2);
0268         valDPsW = sw.IsDigit() ? atoi(sw.Data())/10. : -1;
0269         saneOrDie("dpsNN",valDPsW,1.0,4.0);
0270     }
0271     if (RunString.Contains("notsep",TString::kIgnoreCase)){
0272         valTSepDy   = 0.;
0273     } else if (RunString.Contains("tsepm",TString::kIgnoreCase)){
0274         int ij  = RunString.Index("tsepm",0,TString::kIgnoreCase);
0275         TString sy = RunString(ij+5,2), sp = RunString(ij+7,2);
0276         valTSepDy   = sy.IsDigit() ? atoi(sy.Data())/100. : -1;
0277         valTSepDphi = sp.IsDigit() ? atoi(sp.Data())/10.  : -1;
0278         saneOrDie("tsepmYYPP (dy)",valTSepDy,0.02,0.10);
0279         saneOrDie("tsepmYYPP (dphi*)",valTSepDphi,1.0,4.0);
0280     }
0281     //---- near-vertex two-track cut (sec 29): ON by default at 2.0 deg (user, 2026-09-28). "noisep" turns it off (tested
0282     //---- first: it contains "isep"); "isepPPP" sets the half-width, 0.3-4.0 deg. No other token contains "isep".
0283     if (RunString.Contains("noisep",TString::kIgnoreCase)){
0284         valISepDphi = 0.;
0285     } else if (RunString.Contains("isep",TString::kIgnoreCase)){
0286         int ij  = RunString.Index("isep",0,TString::kIgnoreCase);
0287         TString sw = RunString(ij+4,3);
0288         valISepDphi = sw.IsDigit() ? atoi(sw.Data())/10. : -1;
0289         saneOrDie("isepPPP",valISepDphi,0.3,4.0);
0290     }
0291     //---- "vzNN": event abs(zvtx) max in cm, 6-16 (sec 30). Checked clean against every other token (none contains "vz").
0292     if (RunString.Contains("vz",TString::kIgnoreCase)){
0293         int ij  = RunString.Index("vz",0,TString::kIgnoreCase);
0294         TString sw = RunString(ij+2,2);
0295         valVzMax    = sw.IsDigit() ? atoi(sw.Data()) : -1;
0296         saneOrDie("vzNN",valVzMax,6.,16.);
0297     }
0298     cout<<"reader::reader -- event abs(zvtx) < "<<valVzMax<<" cm"<<endl;
0299     //---- "nchLLHH": event multiplicity class LL <= N_ch <= HH (README_CQcomparison.md sec 2.1). Checked clean against every
0300     //---- other token (none contains "nch").
0301     if (RunString.Contains("nch",TString::kIgnoreCase)){
0302         int ij  = RunString.Index("nch",0,TString::kIgnoreCase);
0303         TString sl = RunString(ij+3,2), sh = RunString(ij+5,2);
0304         valNchLo    = sl.IsDigit() ? atoi(sl.Data()) : -1;
0305         valNchHi    = sh.IsDigit() ? atoi(sh.Data()) : -1;
0306         saneOrDie("nchLLHH (LL)",valNchLo,0,40);
0307         saneOrDie("nchLLHH (HH)",valNchHi,valNchLo,99);
0308         cout<<"reader::reader -- multiplicity class: only events with "<<valNchLo<<" <= N_ch <= "<<valNchHi<<" reach CalcRm"<<endl;
0309     }
0310     //---- "fzNN": Finalize's physics abs(zvtx) range, 2-16 cm (sec 30). Checked clean (no other token contains "fz").
0311     if (RunString.Contains("fz",TString::kIgnoreCase)){
0312         int ij  = RunString.Index("fz",0,TString::kIgnoreCase);
0313         TString sw = RunString(ij+2,2);
0314         valFinZMax  = sw.IsDigit() ? atoi(sw.Data()) : -1;
0315         saneOrDie("fzNN",valFinZMax,2.,16.);
0316     }
0317     cout<<"reader::reader -- near-vertex two-track pair cut (isep) "<<(valISepDphi>0?Form("ON: reject abs(dphi*(R=3 cm))<%.1f deg at abs(dy)<1.0, LS and ULS, sibling and mixed",valISepDphi):"OFF")<<endl;
0318     cout<<"reader::reader -- LS pregate: centred on the split peak, abs(dphi) within "<<valDPsW<<" deg of dphi0(R="<<valDPsR<<" m), deta semi-axis "<<valPregateDEta<<endl;
0319     //---- "noMVTX": OFF switch for LS path 3 (the MVTX-match path), README_SplitTracks573 sec 22. Checked clean
0320     //---- against every other token (none contains "mvtx").
0321     if (RunString.Contains("noMVTX",TString::kIgnoreCase)) valFullMVTX = 0;
0322     cout<<"reader::reader -- LS path 3 (MVTX match): "<<(valFullMVTX>0?Form(">= %d matched MVTX layers",valFullMVTX):"DISABLED (noMVTX)")<<endl;
0323     cout<<"reader::reader -- two-track LS pair cut "<<(valTSepDy>0?Form("ON: reject abs(dy)<%.2f && min over R=0.30-0.78 m of abs(dphi*)<%.1f deg, sibling and mixed",valTSepDy,valTSepDphi):"OFF (notsep)")<<endl;
0324     //
0325     if (RunString.Contains("qcut",TString::kIgnoreCase)){   // Default qcut is 150 MeV/c
0326         doQCut      = true;
0327         valQCut     = 0.150;
0328     }
0329     if (doQCut){ cout<<"reader::reader -- Q cut enabled... valQCut = "<<valQCut<<endl; }
0330     //
0331     //---- SiSplitScore (MVTX-led SKF) gate VALUE for the split-track REMOVAL pre-pass
0332     //---- (corral_loop.cxx, sec 13.8.4/17.6) -- "skfNN" overrides the VALUE only (e.g. for a future
0333     //---- re-scan), without touching whether the pre-pass itself is on. If omitted, uses valSiKeyCut's
0334     //---- default (0.10, sec 17.6).
0335     if (RunString.Contains("skf",TString::kIgnoreCase)){
0336         int ij      = RunString.Index("skf",0,TString::kIgnoreCase);
0337         TString sskf    = RunString(ij+3,2);
0338         valSiKeyCut = sskf.IsDigit() ? atoi(sskf.Data())/100. : -1;
0339         saneOrDie("skfNN",valSiKeyCut,0.05,0.95);
0340         cout<<"reader::reader -- pre-pass SiSplitScore gate overridden via 'skf' token... valSiKeyCut = "<<valSiKeyCut<<endl;
0341     }
0342     //---- SL threshold VALUE override for the same pre-pass -- "removecutNN" (e.g. for a future
0343     //---- re-scan), without touching whether the pre-pass itself is on. If omitted, uses valSLCut's
0344     //---- default (0.18, sec 17.6).
0345     if (RunString.Contains("removecut",TString::kIgnoreCase)){
0346         int ij          = RunString.Index("removecut",0,TString::kIgnoreCase);
0347         TString sremove = RunString(ij+9,2);
0348         valSLCut        = sremove.IsDigit() ? atoi(sremove.Data())/100. : -1;
0349         saneOrDie("removecutNN",valSLCut,0.05,0.60);
0350         cout<<"reader::reader -- pre-pass SL threshold overridden via 'removecut' token... valSLCut = "<<valSLCut<<endl;
0351     }
0352     //---- RadialGap threshold VALUE override for the pre-pass's SECOND flagging path (sec 17.14) --
0353     //---- "radialgapNN" (e.g. for a future re-scan), without touching whether the pre-pass itself is
0354     //---- on. If omitted, uses valRadialGapCut's default (0.45, sec 17.14).
0355     if (RunString.Contains("radialgap",TString::kIgnoreCase)){
0356         int ij          = RunString.Index("radialgap",0,TString::kIgnoreCase);
0357         TString srg     = RunString(ij+9,2);
0358         valRadialGapCut = srg.IsDigit() ? atoi(srg.Data())/100. : -1;
0359         saneOrDie("radialgapNN",valRadialGapCut,0.20,0.90);
0360         cout<<"reader::reader -- pre-pass RadialGap threshold overridden via 'radialgap' token... valRadialGapCut = "<<valRadialGapCut<<endl;
0361     }
0362     //---- split-track REMOVAL pre-pass (corral_loop.cxx, sec 13.8.4/17.6/17.9, RadialGap path added
0363     //---- 17.14) -- ON by default (the finalized LS working point, sec 17.6). New philosophy as of sec
0364     //---- 17.9: once a cut is finalized it defaults ON, and the RunString provides only an OFF switch
0365     //---- -- no separate "enable" token is needed any more (superseding "removeNN"/bare "REMOVE"/
0366     //---- "noremove" below sec 13.8.4-era). "noLS" sets DISABLE_LScuts, turning the whole pre-pass
0367     //---- (both flagging paths) off; "noRG" (sec 17.17) turns off ONLY the RadialGap path, path 1
0368     //---- (SL/SKF) stays live -- a finer-grained switch for isolating path 2's own effect.
0369     if (RunString.Contains("noLS",TString::kIgnoreCase)){
0370         DISABLE_LScuts  = true;
0371     }
0372     if (RunString.Contains("noRG",TString::kIgnoreCase)){
0373         DISABLE_RG  = true;
0374     }
0375     doSplitRemoval  = !DISABLE_LScuts;
0376     cout<<"reader::reader -- split-track REMOVAL pre-pass "<<(doSplitRemoval?"enabled":"DISABLED")
0377         <<"... valSLCut = "<<valSLCut<<", valSiKeyCut = "<<valSiKeyCut<<", valRadialGapCut = "<<valRadialGapCut
0378         <<", RadialGap path "<<(DISABLE_RG?"DISABLED (noRG)":"enabled")<<endl;
0379     //
0380     //---- README.md (2026-10-12): "segveto" = skip the (run, DST segment)s listed in bad_segments.txt (acceptance outliers)
0381     if (RunString.Contains("segveto",TString::kIgnoreCase)) doSegVeto = true;
0382     cout<<"reader::reader -- segment veto "<<(doSegVeto?"ON (segveto)":"OFF")<<endl;
0383     //---- README_CQcomparison sec 2.3: separation-map diagnostic (histograms hSep*, own mixing), default OFF; "sepmaps" = on
0384     if (RunString.Contains("sepmaps",TString::kIgnoreCase)) doSepMaps = true;
0385     cout<<"reader::reader -- separation maps (README_CQcomparison sec 2.3) "<<(doSepMaps?"ON (sepmaps)":"OFF")<<endl;
0386     //---- README_SplitTracks573 sec 38: central-membrane mask, default ON, w in [-0.07,0.10); "nocmmask" = off; "cmmaskAABB" = w in [-0.AA, 0.BB) (sane: each 0.00-0.20)
0387     if (RunString.Contains("nocmmask",TString::kIgnoreCase)){
0388         doCMMask    = false;
0389         cout<<"RunString="<<RunString<<"\t doCMMask="<<(int)doCMMask<<endl;
0390     } else if (RunString.Contains("cmmask",TString::kIgnoreCase)){
0391         int ij      = RunString.Index("cmmask",0,TString::kIgnoreCase); // the occurrence followed by 4 digits
0392         while (ij>=0 && !TString(RunString(ij+6,4)).IsDigit()) ij = RunString.Index("cmmask",ij+1,TString::kIgnoreCase);
0393         if (ij<0) ij = RunString.Index("cmmask",0,TString::kIgnoreCase);
0394         TString sl  = RunString(ij+6,2), sh = RunString(ij+8,2);
0395         if (!sl.IsDigit() || !sh.IsDigit()){ cout<<"RunString="<<RunString<<": cmmask needs 4 digits (cmmaskAABB)"<<endl; exit(1); }
0396         doCMMask    = true;
0397         valCMMaskLo = -atoi(sl.Data())/100.; valCMMaskHi = atoi(sh.Data())/100.;
0398         saneOrDie("cmmaskAABB (lo)",-valCMMaskLo,0.,0.20); saneOrDie("cmmaskAABB (hi)",valCMMaskHi,0.,0.20);
0399         cout<<"RunString="<<RunString<<"\t central-membrane mask ON, w in ["<<valCMMaskLo<<","<<valCMMaskHi<<")"<<endl;
0400     }
0401     //---- README_SplitTracks573 sec 35: the looper veto is ON by default; "nolooper" turns it off.
0402     if (RunString.Contains("nolooper",TString::kIgnoreCase)){
0403         doLooperVeto    = false;
0404         cout<<"RunString="<<RunString<<"\t doLooperVeto="<<(int)doLooperVeto<<endl;
0405     } else if (RunString.Contains("looper",TString::kIgnoreCase)){      // "looperNN": the relative-sum cut, sane range only
0406         int ij      = RunString.Index("looper",0,TString::kIgnoreCase);
0407         TString sl  = RunString(ij+6,2);
0408         if (sl.IsDigit()){ valLooperRSum = atoi(sl.Data())/100.; saneOrDie("looperNN",valLooperRSum,0.02,0.10); }
0409         cout<<"RunString="<<RunString<<"\t looper veto relative-sum cut = "<<valLooperRSum<<endl;
0410     }
0411     //---- ULS veto (README sec 15/18.10/18.24) -- opposite-charge SiSplitScore TRACK-level removal
0412     //---- in corral_loop.cxx (no angular gate since sec 18.24), ON by default at 0.05 since 2026-09-22; completely independent of
0413     //---- doSplitRemoval above. "noulstest" turns it off (checked FIRST -- it contains
0414     //---- "ulstest" as a substring); "ulstestNN" turns it on at threshold 0.NN.
0415     if (RunString.Contains("noulstest",TString::kIgnoreCase)){
0416         doULSTest       = false;
0417     } else if (RunString.Contains("ulstest",TString::kIgnoreCase)){
0418         int ij          = RunString.Index("ulstest",0,TString::kIgnoreCase);
0419         TString sulstest    = RunString(ij+7,2);
0420         doULSTest       = true;
0421         valULSTestCut   = sulstest.IsDigit() ? atoi(sulstest.Data())/100. : -1;
0422         saneOrDie("ulstestNN",valULSTestCut,0.02,0.95);
0423     }
0424     cout<<"reader::reader -- ULS pair veto (sec 15) "<<(doULSTest?"enabled":"DISABLED")
0425         <<"... valULSTestCut = "<<valULSTestCut<<endl;
0426     //---- README_CQcomparison sec 2.2 (2026-10-03, option 2): V0-daughter partner check, ON; "nodau" turns it off.
0427     //---- Checked clean: no other token contains "nodau".
0428     if (RunString.Contains("nodau",TString::kIgnoreCase))   doDauCheck  = false;
0429     cout<<"reader::reader -- V0-daughter partners (README_CQcomparison sec 2.2): "<<(doDauCheck?"on (Lambda/Lbar daughters: LS paths dR<0.05 except pion-tagged partners of a p/pbar, ULS never an (anti)proton partner of a pion daughter; not K0S)":"OFF")<<endl;
0430     //
0431     //---- sec 18.21: opt-in TF-deduplication diagnostic, OFF by default. "onlyfirsttf" verified
0432     //---- clean against every token this codebase parses (ulstest/SL/REMOVE/SIKEY/cross/qcut/ntpc/
0433     //---- slcut/sikeycut/removecut/radialgap/noRG/noLS).
0434     if (RunString.Contains("onlyfirsttf",TString::kIgnoreCase)){
0435         ONLY_FIRST_TF   = true;
0436     }
0437     cout<<"reader::reader -- ONLY_FIRST_TF (sec 18.21) "<<(ONLY_FIRST_TF?"enabled -- ~85% stats cost":"DISABLED")<<endl;
0438     //---- sec 18.22/18.23, DEFAULT since sec 18.25 (2026-09-24): cross-crossing duplicate-track cleaner
0439     //---- (strict variant) + neighboring-TF mixed-event-pair exclusion. Tokens (all checked clean
0440     //---- against every token above, case-insensitive substring matching):
0441     //----   "noXTF"          -- turn OFF all three (restores the pre-sec-18.22 behavior exactly)
0442     //----   "XTFclean"       -- cleaner ON, NON-strict ("neither" case keeps the better-quality copy)
0443     //----   "XTFcleanStrict" -- cleaner ON, strict (the default)
0444     //----   "mixNoAdjTF"     -- adjacent-TF exclusion ON (the default; re-enables it after "noXTF")
0445     //----   "mixAdjTF"       -- adjacent-TF exclusion OFF (NOT "noAdjTF": that is a substring of "mixNoAdjTF")
0446     if (RunString.Contains("noXTF",TString::kIgnoreCase)){
0447         doXTFClean      = false;
0448         doXTFStrict     = false;
0449         doMixNoAdjTF    = false;
0450     }
0451     if (RunString.Contains("XTFclean",TString::kIgnoreCase)){       // sec 18: non-strict ("XTFcleanStrict" dropped: it is the default)
0452         doXTFClean      = true;
0453         doXTFStrict     = false;
0454     }
0455     if (RunString.Contains("mixAdjTF",TString::kIgnoreCase)){
0456         doMixNoAdjTF    = false;
0457     }
0458     cout<<"reader::reader -- doMixNoAdjTF (sec 18.23) "<<(doMixNoAdjTF?(doXTFClean?"enabled -- skip |devt|==1":"enabled -- XTFclean OFF, so skip |devt|<=1 (same-TF too)"):"DISABLED")<<endl;
0459     cout<<"reader::reader -- doXTFClean (sec 18.22) "<<(doXTFClean?"enabled":"DISABLED")
0460         <<(doXTFStrict?" -- STRICT (neither-case: drop both copies)":"")<<endl;
0461     //---- overlapping-TF collision copies (needs the Collect "bco" branch). "noTFdup" checked clean
0462     //---- against every other token (none contains "tfdup").
0463     if (RunString.Contains("noTFdup",TString::kIgnoreCase)){
0464         doTFDup = false;
0465     }
0466     cout<<"reader::reader -- doTFDup (overlapping-TF copies, key run+bco+crossing) "<<(doTFDup?"enabled":"DISABLED")<<endl;
0467     //---- README.md (2026-10-12): "isovtxNNZZ" = isolated vertices only (no other vertex within +-NN crossings and |dz| < ZZ cm)
0468     if (RunString.Contains("isovtx",TString::kIgnoreCase)){
0469         int ij      = RunString.Index("isovtx",0,TString::kIgnoreCase);
0470         TString sn  = RunString(ij+6,2), sz = RunString(ij+8,2);
0471         if (!sn.IsDigit() || !sz.IsDigit()){ cout<<"RunString="<<RunString<<": isovtx needs 4 digits (isovtxNNZZ)"<<endl; exit(1); }
0472         doIsoVtx    = true; valIsoNX = atoi(sn.Data()); valIsoDZ = atoi(sz.Data());
0473         saneOrDie("isovtxNNZZ (NN crossings)",valIsoNX,1,50); saneOrDie("isovtxNNZZ (ZZ cm)",valIsoDZ,1,30);
0474     }
0475     cout<<"reader::reader -- isolated vertices only "<<(doIsoVtx?Form("ON: no other vertex within +-%d crossings and |dz| < %.0f cm (isovtx)",valIsoNX,valIsoDZ):"OFF")<<endl;
0476     //---- sec 18.21 follow-up 2: keep only the first crossing>0 row per TF (pure streaming, <=1/TF).
0477     //---- "XingPos" checked clean against every token above (incl. Xing0).
0478     if (RunString.Contains("XingPos",TString::kIgnoreCase)){
0479         ONLY_FIRST_XINGPOS  = true;
0480     }
0481     cout<<"reader::reader -- ONLY_FIRST_XINGPOS (sec 18.21) "<<(ONLY_FIRST_XINGPOS?"enabled -- first crossing>0 row per TF only":"DISABLED")<<endl;
0482     //---- 2026-10-08: crossing selection, three distinct samples (see corral_class.h xingSel). "Xing0", "NearX0"
0483     //---- and "ZB" checked clean against each other and every other token parsed here. ZB is the DEFAULT (no token);
0484     //---- "AllX" = all crossings (beware "siphimaskallX..." without a separator).
0485     {
0486         int nsel    = 0;
0487         if (RunString.Contains("AllX",TString::kIgnoreCase)){   xingSel = 0; ++nsel; }
0488         if (RunString.Contains("Xing0",TString::kIgnoreCase)){  xingSel = 1; ++nsel; }
0489         if (RunString.Contains("NearX0",TString::kIgnoreCase)){ xingSel = 2; ++nsel; }
0490         if (RunString.Contains("ZB",TString::kIgnoreCase)){     xingSel = 3; ++nsel; }
0491         if (nsel>1){ cout<<"reader::reader -- more than one of Xing0 / NearX0 / ZB / AllX given, they exclude each other"<<endl; exit(1); }
0492     }
0493     cout<<"reader::reader -- crossing selection: "<<(xingSel==1?TString("Xing0 -- crossing==0 only (triggered collision)"):
0494         xingSel==2?TString::Format("NearX0 -- %d<=crossing<=%d, crossing!=0 (trigger neighbours)",XNEAR_LO,XNEAR_HI):
0495         xingSel==3?TString::Format("ZB -- %d<=crossing<%d or %d<crossing<=%d (streaming, away from the trigger and the readout edges)",ZB_LO,XNEAR_LO,XNEAR_HI,ZB_HI):TString("all crossings"))<<endl;
0496     //
0497     if (RunString.Contains("548",TString::kIgnoreCase)){ FinePhiBinning = true; }
0498     if (FinePhiBinning){ cout<<"reader::reader -- FinePhiBinning enabled..."<<endl; }
0499     //
0500     //nentriesfile  = 362192817;        // 1st pass, 240 jobs failed
0501     //nentriesfile  = 376229665;        // 2nd pass, all 8522 jobs successful
0502     //nentriesfile  = 512609163;        // 3rd pass, 8898 jobs successful -- old dataset, not this one
0503     //nentriesfile  = 30171889;         // run_ecuts_cf pre-recut (4k TF/segment), superseded 2026-09-21
0504     //---- total entries of the default glob, hardcoded to skip the slow GetEntries() rescan. Used ONLY
0505     //---- for the default glob; -l lists and $CORRAL_TREES are counted below. If files are added to the
0506     //---- default dataset, update the value (lists/count_entries.C) or set it to 0 to force the rescan.
0507     const Long64_t nentriesfile_ana532  = 50498542;     // ana532 (was run_ecuts_cf) re-cut (5k TF/segment, sec 12/13),
0508                                                         // all 1554 files, live GetEntries() 2026-09-21
0509     const Long64_t nentriesfile_ana573_795xx    = 642480426;    // ana573 795xx complete: all 4581 files (79514 798, 79515 1891, 79516 1892
0510                                                         // chunks; the 79514 rerun landed 2026-09-28), count_entries.C
0511                                                         // 2026-09-28 (lists/ana573_795xx/segment_entries.txt, 0 unreadable)
0512     (void)nentriesfile_ana532;
0513     const char* envTrees    = getenv("CORRAL_TREES");
0514     if (tree==0 && !(envTrees && *envTrees))    // default glob (ana573 795xx) only
0515     nentriesfile    = nentriesfile_ana573_795xx;
0516     //
0517     if (nentriesfile==0){
0518         cout<<"Getting tree Nevt..."<<endl;
0519         nentriesfile    = fChain->GetEntries();
0520     }
0521     cout<<"Nentriesfile = "<<nentriesfile<<endl;
0522     //
0523 }
0524 
0525 //------------------------------------------------------------
0526 corral::~corral()
0527 {
0528     // Members destruct in reverse declaration order: all TTreeReaderValue/Array
0529     // members go first, then fReader, then fChain last -- exactly the order that
0530     // keeps each object valid while whatever depends on it is torn down. Nothing
0531     // here needs manual teardown.
0532 }
0533 
0534 //------------------------------------------------------------
0535 //---- README.md "info/" (2026-10-12): how this output was made, as text objects in the directory info/ of the root file.
0536 //---- Finalize checks that every chunk agrees, copies info/ into corral_Finalize.root and prints it on the title page.
0537 //----   info/runstring  -s run string (after the "Raw" expansion)      info/settings  SettingsText(), one line per switch
0538 //----   info/list       -l job list (or the input glob)                info/code      git commit of the source + build time
0539 //----   info/segveto    bad_segments file and number of segments       info/count_*   counters (TParameter<double>, add up)
0540 //----   info/cut_*      cut values (TParameter<double>) for the QA pages
0541 TString corral::SettingsText(){
0542     TString s;
0543     auto L  = [&](const TString &t){ s += t; s += "\n"; };
0544     auto on = [](bool b){ return b ? "ON" : "OFF"; };
0545     L(Form("events: |vtx x|,|vtx y| < 0.5 cm, |z_{vtx}| < %.0f cm in %d slices (the physics range is chosen in Finalize, fz)",valVzMax,valZvtxNB));
0546     L(TString("crossing selection: ")+(xingSel==0 ? TString("AllX (all crossings)") : xingSel==1 ? TString("Xing0 (crossing 0, the triggered collision)") :
0547         xingSel==2 ? TString::Format("NearX0 (%d..%d without 0)",XNEAR_LO,XNEAR_HI) : TString::Format("ZB (%d..%d outside NearX0 %d..%d)",ZB_LO,ZB_HI,XNEAR_LO,XNEAR_HI)));
0548     L(Form("segment veto (segveto) %s;  isolated vertices (isovtx) %s",on(doSegVeto),doIsoVtx ? Form("ON: +-%d crossings, |dz| < %.0f cm",valIsoNX,valIsoDZ) : "OFF"));
0549     if (valNchHi<9999) L(Form("multiplicity class (nch): %d <= N_ch <= %d",valNchLo,valNchHi));
0550     L(Form("overlapping-TF copies skipped (TFdup) %s;  cross-crossing duplicate cleaner (XTF) %s%s;  adjacent-TF mixing exclusion %s",
0551         on(doTFDup),on(doXTFClean),doXTFClean ? (doXTFStrict ? " strict" : " non-strict") : "",on(doMixNoAdjTF)));
0552     L(Form("tracks: %.2f < p_{T} < 20.1 GeV/c, ntpc >= %d, |dca_{xy}| < %.2f cm, |dca_{z}| < %.2f cm",PTMINCUT,NTPCCUT,valDcaxyCut,valDcazCut));
0553     { TString w; if (doSiPhiMask) for (int k=0;k<nSiPhiMask;k++) w += Form(" [%.0f,%.0f)",SIPHIMASK_LO[k],SIPHIMASK_HI[k]);
0554       L(Form("SiPhiMask %s%s;  central-membrane mask %s",on(doSiPhiMask),doSiPhiMask ? Form(", vertex phi (deg):%s",w.Data()) : "",
0555         doCMMask ? Form("ON, w in [%.2f,%.2f)",valCMMaskLo,valCMMaskHi) : "OFF")); }
0556     L(doOldPID ? TString("PID: legacy (oldpid), pi = dedx70s < 400") :
0557         TString::Format("PID: KFP dE/dx gates, pi then p (outside K) then K (outside p); p_{max} pi %.1f, K %.1f, p %.1f GeV/c",Species_pmax_pid[0],Species_pmax_pid[1],Species_pmax_pid[2]));
0558     L(Form("pair p_{T} min: pi %.2f, K %.2f, p %.2f GeV/c;  |y| max: pi %.2f, K %.2f, p %.2f, V0 %.2f;  h+- |eta| < %.2f",
0559         Species_ptmin[0],Species_ptmin[1],Species_ptmin[2],Species_yu[0],Species_yu[1],Species_yu[2],Species_yu[5],Hadron_etau));
0560     L(Form("V0s in pairs: within %.0f sigma of the mass peak (K0S %.4f+-%.4f, La %.4f+-%.4f, Lbar %.4f+-%.4f GeV); daughters in no pair type",
0561         V0PEAK_NSIG,V0PEAK_MU[0],V0PEAK_SIG[0],V0PEAK_MU[1],V0PEAK_SIG[1],V0PEAK_MU[2],V0PEAK_SIG[2]));
0562     L(Form("LS split-track removal %s (SL < %.2f & SiSplitScore >= %.2f; RadialGap >= %.2f %s; MVTX path %s);  pregate dps %.1f deg",
0563         on(doSplitRemoval),valSLCut,valSiKeyCut,valRadialGapCut,DISABLE_RG ? "OFF" : "ON",valFullMVTX>0 ? "ON" : "OFF",valDPsW));
0564     L(Form("ULS veto %s (SiSplitScore >= %.2f);  V0-daughter partners (dau) %s;  looper veto %s (|p| < %.2f, p-sum < %.2f)",
0565         on(doULSTest),valULSTestCut,on(doDauCheck),on(doLooperVeto),LOOPER_PMAX,valLooperRSum));
0566     L(Form("two-track cuts: tsep %s, isep %s",valTSepDy>0 ? Form("|dy| < %.2f & |dphi*| < %.1f deg",valTSepDy,valTSepDphi) : "OFF",
0567         valISepDphi>0 ? Form("|dy| < 1.0 & |dphi*(3 cm)| < %.1f deg",valISepDphi) : "OFF"));
0568     L(Form("crossing correction %s;  Q cut %s;  (dy,dphi) binning %s",on(doCrossing),doQCut ? Form("Q > %.3f GeV",valQCut) : "OFF",FinePhiBinning ? "5 x 48" : "default"));
0569     return s;
0570 }
0571 void corral::WriteInfo(TDirectory *top, const std::vector<std::pair<TString,TString>> &texts, const std::vector<std::pair<TString,double>> &counts){
0572     TDirectory *keep    = gDirectory;
0573     TDirectory *d       = top->GetDirectory("info") ? top->GetDirectory("info") : top->mkdir("info","how this output was made (README.md, info/)");
0574     d->cd();
0575     TNamed("runstring",RunString.Data()).Write("",TObject::kOverwrite);
0576     TNamed("settings",SettingsText().Data()).Write("",TObject::kOverwrite);
0577     TNamed("list",(ListName!="" ? ListName : TString("default input glob")).Data()).Write("",TObject::kOverwrite);
0578     TNamed("code",Form("git %s (commit date %s), built %s",CORRAL_GIT,CORRAL_GIT_DATE,CORRAL_BUILD_TIME)).Write("",TObject::kOverwrite);
0579     for (auto &t : texts) TNamed(t.first.Data(),t.second.Data()).Write("",TObject::kOverwrite);
0580     for (auto &c : counts) TParameter<double>(("count_"+c.first).Data(),c.second).Write("",TObject::kOverwrite);
0581     //---- cut values for Finalize's QA pages (drawn on the summed histograms); Finalize takes them from the first chunk
0582     const std::vector<std::pair<const char*,double>> cuts   = {{"ptmin",PTMINCUT},{"ntpc",(double)NTPCCUT},{"dcaxy",valDcaxyCut},{"dcaz",valDcazCut},
0583         {"vzmax",valVzMax},{"vxymax",0.5},{"xingsel",(double)xingSel},{"siphimask",doSiPhiMask ? (double)nSiPhiMask : 0.}};
0584     for (auto &c : cuts) TParameter<double>(Form("cut_%s",c.first),c.second).Write("",TObject::kOverwrite);
0585     keep->cd();
0586 }