Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-05-23 08:12:15

0001 #include "analysisHelper.h"
0002 #include "RooUnfoldResponse.h"
0003 
0004 // ─────────────────────────────────────────────────────────────────────────────
0005 //  fillResponse.C
0006 //
0007 //  Fills the dijet pT response matrix and all wEEC histograms needed for
0008 //  both the 2D dijet pT unfolding and the new 3D wEEC unfolding.
0009 //
0010 //  Truth towers are constructed from TruthParticles using the UNSHIFTED tower
0011 //  grid (same eta/phi indices as reco) so that matched pairs share indices.
0012 //  Individual particles must have E > 0.5 GeV to contribute to a truth tower.
0013 //  Tower thresholds (0.25–80 GeV) are applied after summing.
0014 //
0015 //  Mode::kFull  — all events fill both response and measured histograms
0016 //  Mode::kHalf  — random ~50% split: training half fills response, measured
0017 //                 half fills hJetPt_meas and wEEC measured histograms
0018 //
0019 //  ── 2D dijet pT unfolding outputs ──────────────────────────────────────────
0020 //  response_jet_pT          — RooUnfoldResponse for dijet pT unfolding
0021 //  hJetPt_truth_matched     — matched truth per-event
0022 //  hJetPt_truth             — all truth (matched + missed) per-event
0023 //  hJetPt_reco_matched      — matched reco per-event
0024 //  hJetPt_reco              — all reco (matched + fake) per-event
0025 //  hJetPt_meas              — measured reco per-event
0026 //  hJetPt_truth_meas        — truth from measured half (kHalf diagnostic)
0027 //
0028 //  ── 3D wEEC unfolding outputs, per ΔΦ bin k ────────────────────────────────
0029 //  response_wEEC3D_{k}      — RooUnfoldResponse in 3D flat space
0030 //                             axes: (lead pT bin, subl pT bin, pair weight bin)
0031 //                             filled per tower pair in matched dijet events;
0032 //                             dijet-level fakes/misses also fill this response
0033 //  hWEEC3D_truth_{k}        — truth-tower pairs for all truth-valid events
0034 //                             (closure target: what unfolding should recover)
0035 //  hWEEC3D_meas_{k}         — reco-tower pairs for measured events
0036 //  hWEEC3D_data_{k}         — reco-tower pairs from real data (fillData.C)
0037 //
0038 //  ── retained 2D wEEC histograms (for old unfolding path) ───────────────────
0039 //  hWEEC_fake_{fr}          — flat 2D, fake dijet reco pairs
0040 //  hWEEC_miss_{ft}          — flat 2D, missed dijet truth pairs
0041 //  hWEEC_truth_{ft}         — flat 2D, truth pairs per truth dijet pT bin
0042 //  hWEEC_meas_{fr}          — flat 2D, measured reco pairs per reco dijet pT bin
0043 //  response_wEEC_{ft}       — old per-ft flat-2D response (kept for reference)
0044 // ─────────────────────────────────────────────────────────────────────────────
0045 void fillResponse(int jetSample = 20, int seg = 0, const char* outDir = ".",
0046                   Mode mode = Mode::kFull, float towCut = 0.25)
0047 {
0048     const bool halfClosure = (mode == Mode::kHalf);
0049     const bool vtxOnly     = (mode == Mode::kVtx);
0050 
0051     std::string outName = std::format(
0052         "{}/Jet{}/response_Jet{}_seg{:06d}_to_{:06d}-{}.root",
0053         outDir, jetSample, jetSample, seg, seg + 24, ModeLabel(mode));
0054         
0055     std::cout << "making file " << outName << std::endl;
0056     std::cout.flush();
0057 
0058     TFile* fOut = new TFile(outName.c_str(), "RECREATE");
0059 
0060     std::cout << "made file " << outName << std::endl;
0061     std::cout.flush();
0062 
0063     // ── vtx histogram (all modes) ─────────────────────────────────────────
0064     // hVtxMC is filled in every mode so the vtx distribution can always be
0065     // inspected.  In kVtx mode this is the only histogram filled — all
0066     // tower-pair and response-matrix work is skipped entirely.
0067     TH1D* hVtxMC = new TH1D("hVtxMC",
0068         "MC vtx_z (xsec weighted);v_{z} (cm);weighted events",
0069         nVtxBins, vtxZMin, vtxZMax);
0070     hVtxMC->Sumw2();
0071 
0072     if (vtxOnly)
0073     {
0074         // ── kVtx fast path ────────────────────────────────────────────────
0075         // Open the MC file and loop over events, filling hVtxMC with the
0076         // cross-section weight.  No tower maps, no jet matching, no pair loops.
0077         TFile* simFile = new TFile(SimFilePath(jetSample, seg).c_str(), "READ");
0078         if (!simFile || simFile->IsZombie()) {
0079             std::cerr << "Cannot open sim file\n";
0080             fOut->Close(); return;
0081         }
0082 
0083         TTree* T = (TTree*)simFile->Get("T");
0084         if (!T) { simFile->Close(); fOut->Close(); return; }
0085 
0086         EventInfo*                  eventInfo  = nullptr;
0087         std::vector<JetInfo>* truthJets = nullptr;
0088         T->SetBranchAddress("EventInfo", &eventInfo);
0089         T->SetBranchAddress("TruthJetInfo_r04", &truthJets);
0090 
0091         long nEvents = T->GetEntries();
0092         for (long ev = 0; ev < nEvents; ++ev) {
0093             std::cout << "kVtx: working on event " << ev << std::endl;
0094             T->GetEntry(ev);
0095 
0096             double vtx_z = eventInfo->get_z_vtx();
0097             if (std::abs(vtx_z) > 10.0) continue;
0098 
0099             // pThat slice cut — same guard as the full loop
0100             double maxTruthPT = -1;
0101             for (auto& j : *truthJets)
0102                 if (j.pt() > maxTruthPT) maxTruthPT = j.pt();
0103             if (maxTruthPT < get_jet_pTLow(jetSample) ||
0104                 maxTruthPT > get_jet_pTHigh(jetSample)) continue;
0105 
0106             double evtWeight = eventInfo->get_cross_section();
0107             hVtxMC->Fill(vtx_z, evtWeight);
0108         }
0109 
0110         simFile->Close(); delete simFile;
0111 
0112         fOut->cd();
0113         hVtxMC->Write();
0114         fOut->Close();
0115         std::cout << "Written (vtxOnly): " << outName << "\n";
0116         return;
0117     }
0118 
0119     // ── dijet pT histograms ───────────────────────────────────────────────
0120     TH1D* hJetPt_truth_matched = new TH1D("hJetPt_truth_matched", "", nTrueFlat(), 0, nTrueFlat());
0121     TH1D* hJetPt_truth         = new TH1D("hJetPt_truth",         "", nTrueFlat(), 0, nTrueFlat());
0122     TH1D* hJetPt_reco_matched  = new TH1D("hJetPt_reco_matched",  "", nRecoFlat(), 0, nRecoFlat());
0123     TH1D* hJetPt_reco          = new TH1D("hJetPt_reco",          "", nRecoFlat(), 0, nRecoFlat());
0124     TH1D* hJetPt_meas          = new TH1D("hJetPt_meas",          "", nRecoFlat(), 0, nRecoFlat());
0125     TH1D* hJetPt_truth_meas    = new TH1D("hJetPt_truth_meas",    "", nTrueFlat(), 0, nTrueFlat());
0126     hJetPt_truth_matched->Sumw2(); hJetPt_truth       ->Sumw2();
0127     hJetPt_reco_matched ->Sumw2(); hJetPt_reco        ->Sumw2();
0128     hJetPt_meas         ->Sumw2(); hJetPt_truth_meas  ->Sumw2();
0129 
0130     std::cout << "made jet hists" << std::endl;
0131     std::cout.flush();
0132 
0133     RooUnfoldResponse* respJetPt = nullptr;
0134     {
0135         //TH1D hJPReco ("hJetPt_reco_tmpl",  "", nRecoFlat, 0, nRecoFlat);
0136         //TH1D hJPTruth("hJetPt_truth_tmpl", "", nTrueFlat, 0, nTrueFlat);
0137         respJetPt = new RooUnfoldResponse(nRecoFlat(), 0, nRecoFlat(), nTrueFlat(), 0, nTrueFlat());
0138     }
0139 
0140     std::cout << "made jet response matrix" << std::endl;
0141     std::cout.flush();
0142 
0143     // ── 3D wEEC response matrices: one per ΔΦ bin k ───────────────────────
0144     // Each maps flat 3D (lead pT bin, subl pT bin, pair weight bin) on the
0145     // reco side to the same space on the truth side.
0146     //   iFlat3D = iL * (nSubl*nPairWeight) + iS * nPairWeight + iPw
0147     // Dijet-level fakes/misses are included directly in these response
0148     // matrices (all pairs from fake/miss dijet events call Fake/Miss).
0149     // Tower-level fakes/misses within matched events are also included.
0150     std::vector<RooUnfoldResponse*> respWEEC3D(nDphi, nullptr);
0151     for (int k = 0; k < nDphi; ++k)
0152         {
0153             //TH1D hReco ("hWEEC_reco_tmpl",  "", nRecoFlat3D, 0, nRecoFlat3D);
0154             //TH1D hTruth("hWEEC_truth_tmpl", "", nTrueFlat3D, 0, nTrueFlat3D);
0155         respWEEC3D[k] = new RooUnfoldResponse(nRecoFlat3D(), 0, nRecoFlat3D(), nTrueFlat3D(), 0, nTrueFlat3D());
0156         }
0157 
0158     std::cout << "made 3D response matrices" << std::endl;
0159     std::cout.flush();
0160 
0161     // ── 3D wEEC truth histograms (per ΔΦ bin k, training half) ───────────
0162     // Filled for ALL truth-valid events (matched + missed) per truth-tower pair.
0163     // Closure target: after unfolding hWEEC3D_meas, the projected result
0164     // should match the projection of this histogram.
0165     std::vector<TH1D*> hWEEC3D_truth(nDphi, nullptr);
0166     std::vector<TH1D*> hWEEC3D_misses(nDphi, nullptr);
0167     for (int k = 0; k < nDphi; ++k) {
0168         hWEEC3D_truth[k] = new TH1D(
0169             std::format("hWEEC3D_truth_{}", k).c_str(),
0170             std::format("Truth wEEC 3D k{};flat(iL,iS,iPw);pairs", k).c_str(),
0171             nTrueFlat3D(), 0, nTrueFlat3D());
0172         hWEEC3D_truth[k]->Sumw2();
0173 
0174         hWEEC3D_misses[k] = new TH1D(
0175             std::format("hWEEC3D_misses_{}", k).c_str(),
0176             std::format("Misses wEEC 3D k{};flat(iL,iS,iPw);pairs", k).c_str(),
0177             nTrueFlat3D(), 0, nTrueFlat3D());
0178         hWEEC3D_misses[k]->Sumw2();
0179     }
0180 
0181     std::cout << "made 3D truth hists" << std::endl;
0182     std::cout.flush();
0183 
0184     // ── Pair weight mean numerator/denominator (per ΔΦ bin k) ────────────
0185     // hWEEC3D_pwNum_{k}: sum of (evtWeight * pairWeight) per truth flat3D bin
0186     // hWEEC3D_pwDen_{k}: sum of evtWeight per truth flat3D bin
0187     // Dividing num/den at projection time gives the luminosity-weighted mean
0188     // pair weight within each bin, which is more accurate than the bin-center
0189     // approximation when the pair weight distribution is not flat within a bin.
0190     // Both histograms are filled at every site that fills hWEEC3D_truth.
0191     std::vector<TH1D*> hWEEC3D_pwNum(nDphi, nullptr);
0192     std::vector<TH1D*> hWEEC3D_pwDen(nDphi, nullptr);
0193     for (int k = 0; k < nDphi; ++k) {
0194         hWEEC3D_pwNum[k] = new TH1D(
0195             std::format("hWEEC3D_pwNum_{}", k).c_str(),
0196             std::format("PW num k{};flat(iL,iS,iPw);sum(w*evtW)", k).c_str(),
0197             nTrueFlat3D(), 0, nTrueFlat3D());
0198         hWEEC3D_pwDen[k] = new TH1D(
0199             std::format("hWEEC3D_pwDen_{}", k).c_str(),
0200             std::format("PW den k{};flat(iL,iS,iPw);sum(evtW)", k).c_str(),
0201             nTrueFlat3D(), 0, nTrueFlat3D());
0202         hWEEC3D_pwNum[k]->Sumw2();
0203         hWEEC3D_pwDen[k]->Sumw2();
0204     }
0205 
0206     // ── 3D wEEC measured histograms (per ΔΦ bin k, measured half) ─────────
0207     std::vector<TH1D*> hWEEC3D_meas(nDphi, nullptr);
0208     std::vector<TH1D*> hWEEC3D_fakes(nDphi, nullptr);
0209     std::vector<TH1D*> hWEEC3D_meas_truth(nDphi, nullptr);
0210     for (int k = 0; k < nDphi; ++k) {
0211         hWEEC3D_meas[k] = new TH1D(
0212             std::format("hWEEC3D_meas_{}", k).c_str(),
0213             std::format("Meas wEEC 3D k{};flat(iL,iS,iPw);pairs", k).c_str(),
0214             nRecoFlat3D(), 0, nRecoFlat3D());
0215         hWEEC3D_meas[k]->Sumw2();
0216 
0217         hWEEC3D_fakes[k] = new TH1D(
0218             std::format("hWEEC3D_fakes_{}", k).c_str(),
0219             std::format("Fakes wEEC 3D k{};flat(iL,iS,iPw);pairs", k).c_str(),
0220             nRecoFlat3D(), 0, nRecoFlat3D());
0221         hWEEC3D_fakes[k]->Sumw2();
0222 
0223         hWEEC3D_meas_truth[k] = new TH1D(
0224             std::format("hWEEC3D_meas_truth_{}", k).c_str(),
0225             std::format("Truth Meas Half wEEC 3D k{};flat(iL,iS,iPw);pairs", k).c_str(),
0226             nTrueFlat3D(), 0, nTrueFlat3D());
0227         hWEEC3D_meas_truth[k]->Sumw2();
0228     }
0229 
0230     std::cout << "made 3D meas hists" << std::endl;
0231     std::cout.flush();
0232 
0233     // ── 3D wEEC counts histograms (per ΔΦ bin k, response half) ──────────
0234     // 2D histogram matching the response matrix exactly: reco flat3D on X,
0235     // truth flat3D on Y.  Filled with unity weight (not the cross-section
0236     // weight) for every tower pair that enters a response matrix Fill call.
0237     // Fake and Miss calls are not counted here — they have no (reco, truth)
0238     // cell to trim.  Used by wEEC_doUnfolding.C for cell-by-cell trimming.
0239     std::vector<TH2D*> hWEEC3D_counts(nDphi, nullptr);
0240     for (int k = 0; k < nDphi; ++k) {
0241         hWEEC3D_counts[k] = new TH2D(
0242             std::format("hWEEC3D_counts_{}", k).c_str(),
0243             std::format("Counts wEEC 3D k{};reco flat3D;truth flat3D", k).c_str(),
0244             nRecoFlat3D(), 0, nRecoFlat3D(),
0245             nTrueFlat3D(), 0, nTrueFlat3D());
0246         // No Sumw2: raw event counts, not weighted sums.
0247     }
0248 
0249     std::cout << "made 3D counts hists" << std::endl;
0250     std::cout.flush();
0251 
0252     // ── Direct wEEC cross-check histograms ───────────────────────────────
0253     // Filled as direct ΔΦ histograms for cross-checks only — not unfolding
0254     // inputs.  Both filled for every trueInRange event (matched + missed),
0255     // mirroring hWEEC3D_truth coverage.
0256     //
0257     // hWEEC_particle:   particle-level wEEC — pairs over final-state particles
0258     //                   (calo==4) before tower binning.  No tower thresholds.
0259     //                   The physical observable the measurement corrects to.
0260     //
0261     // hWEEC_truthTower: truth-tower wEEC — pairs over truthMap with the same
0262     //                   0.25–80 GeV thresholds as the unfolding truth.
0263     //                   Quantifies tower discretization effects vs particles.
0264     TH1D* hWEEC_particle   = new TH1D("hWEEC_particle",
0265         "Particle-level wEEC;#Delta#phi;wEEC", nDphi, dPhiBins.data());
0266     TH1D* hWEEC_truthTower = new TH1D("hWEEC_truthTower",
0267         "Truth-tower wEEC;#Delta#phi;wEEC", nDphi, dPhiBins.data());
0268     hWEEC_particle  ->Sumw2();
0269     hWEEC_truthTower->Sumw2();
0270 
0271     TFile* simFile = new TFile(SimFilePath(jetSample, seg).c_str(), "READ");
0272     if (!simFile || simFile->IsZombie()) {
0273         std::cerr << "Cannot open " << SimFilePath(jetSample, seg) << "\n";
0274         if (simFile) delete simFile; return;
0275     }
0276     TTree* T = (TTree*)simFile->Get("T");
0277     if (!T) { simFile->Close(); delete simFile; return; }
0278 
0279     TRandom3 rng(jetSample * 100000 + seg);
0280 
0281     // ── branches ──────────────────────────────────────────────────────────
0282     EventInfo*            eventInfo = nullptr;
0283     std::vector<JetInfo>* recoJets  = nullptr;
0284     std::vector<JetInfo>* truthJets = nullptr;
0285     std::vector<Tower>*   towers    = nullptr;
0286     std::vector<Tower>*   particles = nullptr;
0287 
0288     T->SetBranchAddress("EventInfo",        &eventInfo);
0289     T->SetBranchAddress("JetInfo_r04",      &recoJets);
0290     T->SetBranchAddress("TruthJetInfo_r04", &truthJets);
0291     T->SetBranchAddress("TowerInfo",        &towers);
0292     T->SetBranchAddress("TruthParticles",   &particles);
0293 
0294     // ── event loop ────────────────────────────────────────────────────────
0295     long nEvents = T->GetEntries();
0296     long nGood = 0, nGoodReco = 0, nGoodTruth = 0, nMatched = 0;
0297     for (long ev = 0; ev < nEvents; ++ev)
0298     {
0299         T->GetEntry(ev);
0300 
0301         const bool isEven   = (rng.Rndm() < 0.5);
0302         const bool fillResp = !halfClosure || isEven;
0303         const bool fillMeas = !halfClosure || !isEven;
0304 
0305         double vtx_z = eventInfo->get_z_vtx();
0306      
0307         std::cout << std::format("Working on event {:5d}: vtx_z={:.2f}\n", ev, vtx_z);
0308         std::cout.flush();
0309         
0310         if (std::abs(vtx_z) > 10.0) continue;
0311 
0312         double evtWeight = eventInfo->get_cross_section();
0313 
0314         // Apply vtx_z reweighting only when building the response for real
0315         // data (kData mode).  In closure modes (kFull, kHalf) the MC vtx
0316         // distribution is self-consistent and reweighting would introduce
0317         // a spurious bias.
0318         if (mode == Mode::kData)
0319             evtWeight *= GetVtxWeight(vtx_z);
0320 
0321         double maxTruthPT = -1;
0322         for (auto& j : *truthJets)
0323             if (j.pt() > maxTruthPT) maxTruthPT = j.pt();
0324         if (maxTruthPT < get_jet_pTLow(jetSample) ||
0325             maxTruthPT > get_jet_pTHigh(jetSample)) continue;
0326 
0327         bool rValid = eventInfo->is_dijet_event(2)
0328                    && eventInfo->get_lead_pT(2)    >= recoLeadMin
0329                    && eventInfo->get_sublead_pT(2) >= recoSublMin;
0330         bool tValid = eventInfo->is_dijetTruth_event(2)
0331                    && eventInfo->get_leadTruth_pT(2)    >= trueLeadMin
0332                    && eventInfo->get_subleadTruth_pT(2) >= trueSublMin;
0333 
0334         if(!rValid && !tValid) continue;
0335 
0336         // Fill vtx histogram with the raw (pre-vtx-reweight) cross-section
0337         // so the stored MC distribution always reflects the true MC shape,
0338         // usable as the denominator in makeVtxWeights.C regardless of mode.
0339         hVtxMC->Fill(vtx_z, eventInfo->get_cross_section());
0340 
0341         double rLeadPT = rValid ? eventInfo->get_lead_pT(2)         : -1;
0342         double rSublPT = rValid ? eventInfo->get_sublead_pT(2)      : -1;
0343         double tLeadPT = tValid ? eventInfo->get_leadTruth_pT(2)    : -1;
0344         double tSublPT = tValid ? eventInfo->get_subleadTruth_pT(2) : -1;
0345 
0346         int iLreco = rValid ? FindBin(rLeadPT, recoLeadPtBins) : -1;
0347         int iSreco = rValid ? FindBin(rSublPT, recoSublPtBins) : -1;
0348         int iLtrue = tValid ? FindBin(tLeadPT, trueLeadPtBins) : -1;
0349         int iStrue = tValid ? FindBin(tSublPT, trueSublPtBins) : -1;
0350 
0351         bool recoInRange = rValid && iLreco >= 0 && iSreco >= 0;
0352         bool trueInRange = tValid && iLtrue >= 0 && iStrue >= 0;
0353 
0354         bool geoMatch = false;
0355         if (rValid && tValid)
0356             geoMatch = GetGeoMatch(*recoJets, *truthJets,
0357                                    rLeadPT, rSublPT, tLeadPT, tSublPT);
0358 
0359         // ── build reco tower map (vertex-shifted eta) ─────────────────────
0360         double recoMap[24][64] = {};
0361         if (recoInRange) {
0362             shiftEtaEdges(vtx_z);
0363             for (auto& tow : *towers) {
0364                 if (tow.get_calo() < 1 || tow.get_calo() > 3) continue;
0365                 fastjet::PseudoJet tj(tow.px(), tow.py(), tow.pz(), tow.e());
0366                 int etaBin = getEtaBin(tj.pseudorapidity(), vtx_z);
0367                 int phiBin = getPhiBin(tj.phi());
0368                 if (etaBin >= 0 && phiBin >= 0)
0369                     recoMap[etaBin][phiBin] += tow.e();
0370             }
0371         }
0372 
0373         // ── build truth tower map (unshifted eta — shares indices with reco) ──
0374         // Particles without calo 4 are not used since they are not truth particles
0375         // Particles with E > 0.5 GeV are assigned to the unshifted grid.
0376         // Tower thresholds applied after summing, same as reco.
0377         double truthMap[24][64] = {};
0378         if (trueInRange) {
0379             for (auto& part : *particles) {
0380                 if(part.get_calo() != 4) continue;
0381                 //if (part.e() <= 0.5) continue;
0382                 fastjet::PseudoJet pj(part.px(), part.py(), part.pz(), part.e());
0383                 int etaBin = getEtaBin(pj.pseudorapidity(), 0.0);  // unshifted
0384                 int phiBin = getPhiBin(pj.phi());
0385                 if (etaBin >= 0 && phiBin >= 0)
0386                     truthMap[etaBin][phiBin] += part.e();
0387             }
0388         }
0389 
0390         // ── fill dijet pT histograms ──────────────────────────────────────
0391         if (fillResp) {
0392             if (recoInRange && trueInRange && geoMatch) {
0393                 double rFlat = RecoFlatBinCenter(RecoFlatIndex(iLreco, iSreco));
0394                 double tFlat = TrueFlatBinCenter(TrueFlatIndex(iLtrue, iStrue));
0395                 respJetPt->Fill(rFlat, tFlat, evtWeight);
0396                 hJetPt_reco_matched ->Fill(rFlat, evtWeight);
0397                 hJetPt_reco         ->Fill(rFlat, evtWeight);
0398                 hJetPt_truth_matched->Fill(tFlat, evtWeight);
0399                 hJetPt_truth        ->Fill(tFlat, evtWeight);
0400             } else {
0401                 if (recoInRange) {
0402                     double rFlat = RecoFlatBinCenter(RecoFlatIndex(iLreco, iSreco));
0403                     respJetPt->Fake(rFlat, evtWeight);
0404                     hJetPt_reco->Fill(rFlat, evtWeight);
0405                 }
0406                 if (trueInRange) {
0407                     double tFlat = TrueFlatBinCenter(TrueFlatIndex(iLtrue, iStrue));
0408                     respJetPt->Miss(tFlat, evtWeight);
0409                     hJetPt_truth->Fill(tFlat, evtWeight);
0410                 }
0411             }
0412         }
0413         if (fillMeas && recoInRange) {
0414             hJetPt_meas->Fill(RecoFlatBinCenter(RecoFlatIndex(iLreco, iSreco)), evtWeight);
0415             if (halfClosure && trueInRange)
0416                 hJetPt_truth_meas->Fill(TrueFlatBinCenter(TrueFlatIndex(iLtrue, iStrue)), evtWeight);
0417         }
0418 
0419         // ══════════════════════════════════════════════════════════════════════
0420         //  3D wEEC tower-pair loop
0421         //
0422         //  The event is classified into exactly one of: matched, fake, missed.
0423         //  Each class has one tower-pair loop that computes all quantities once
0424         //  and fills every relevant histogram in a single pass.
0425         // ══════════════════════════════════════════════════════════════════════
0426 
0427         const bool isMatched = recoInRange && trueInRange && geoMatch;
0428         const bool isFake    = recoInRange && !isMatched;
0429         const bool isMiss    = trueInRange && !isMatched;
0430 
0431         ++nGood;
0432         if (recoInRange) ++nGoodReco;
0433         if (trueInRange) ++nGoodTruth;
0434         if (isMatched)   ++nMatched;
0435 
0436         std::cout << std::format(
0437             "ev {:5d}: reco={} truth={} match={} | "
0438             "good={} goodReco={} goodTruth={} matched={}\n",
0439             ev, (int)recoInRange, (int)trueInRange, (int)isMatched,
0440             nGood, nGoodReco, nGoodTruth, nMatched);
0441         std::cout.flush();
0442 
0443         // ── direct wEEC cross-checks (all trueInRange events) ────────────
0444         // Both histograms are filled here once per event, independently of
0445         // the matched/fake/miss classification, so their coverage matches
0446         // hWEEC3D_truth exactly (matched + missed).
0447         if (fillResp && trueInRange)
0448         {
0449             const double tPTmean2 = 0.25*(tLeadPT+tSublPT)*(tLeadPT+tSublPT);
0450 
0451             // ── particle-level wEEC ───────────────────────────────────────
0452             // Iterate over all calo==4 particles directly — no tower thresholds.
0453             // Build a lightweight list of (pT, phi) for this event first to
0454             // avoid recomputing pseudorapidity inside the double loop.
0455             struct PInfo { double pT, phi; };
0456             std::vector<PInfo> plist;
0457             plist.reserve(particles->size());
0458             for (auto& part : *particles) {
0459                 if (part.get_calo() != 4) continue;
0460                 fastjet::PseudoJet pj(part.px(), part.py(), part.pz(), part.e());
0461                 double eta = pj.pseudorapidity();
0462                 // Restrict to calorimeter acceptance — same η range as towers
0463                 if (std::abs(eta) > 1.1) continue;
0464                 double phi = pj.phi();
0465                 double pT = part.e() / std::cosh(eta);
0466                 if (pT <= 0) continue;
0467                 plist.push_back({pT, phi});
0468             }
0469             for (int i = 0; i < (int)plist.size(); ++i)
0470             for (int j = i+1; j < (int)plist.size(); ++j) {
0471                 double dphi   = DeltaPhi(plist[i].phi, plist[j].phi);
0472                 double pairW  = plist[i].pT * plist[j].pT / tPTmean2;
0473                 if (pairW < pairWeightMin || pairW > pairWeightMax) continue;
0474                 hWEEC_particle->Fill(dphi, evtWeight * pairW);
0475             }
0476 
0477             // ── truth-tower wEEC ──────────────────────────────────────────
0478             // Same tower thresholds and eta/phi lookup as the unfolding truth.
0479             for (int i = 0; i < 24*64; ++i) {
0480                 const int ei = i/64, pi = i%64;
0481                 double phi_i = getPhiCenter(pi);
0482                 const double tpT_i = truthMap[ei][pi] / cosh(getEtaCenter(ei, 0.0));
0483                 if (tpT_i <= towCut || tpT_i >= 80) continue;
0484 
0485                 for (int j = i+1; j < 24*64; ++j) {
0486                     const int ej = j/64, pj = j%64;
0487                     double phi_j = getPhiCenter(pj);
0488                     const double tpT_j = truthMap[ej][pj] / cosh(getEtaCenter(ej, 0.0));
0489                     if (tpT_j <= towCut || tpT_j >= 80) continue;
0490 
0491                     double dphi  = DeltaPhi(phi_i, phi_j);
0492                     double pairW = tpT_i * tpT_j / tPTmean2;
0493                     if (pairW < pairWeightMin || pairW > pairWeightMax) continue;
0494                     hWEEC_truthTower->Fill(dphi, evtWeight * pairW);
0495                 }
0496             }
0497         }
0498 
0499         // ── matched dijet ─────────────────────────────────────────────────
0500         // Handles all four tower-pair cases in one loop:
0501         //   hasReco && hasTruth  → response Fill + truth/meas hists
0502         //   hasReco only         → Fake + meas hist
0503         //   hasTruth only        → Miss + truth hist
0504         //   neither              → skipped
0505         // fillMeas fills (hWEEC3D_meas) are included here so no separate
0506         // reco-pair loop is needed for matched events.
0507         if (isMatched)
0508         {
0509             const double rPTmean2 = 0.25*(rLeadPT+rSublPT)*(rLeadPT+rSublPT);
0510             const double tPTmean2 = 0.25*(tLeadPT+tSublPT)*(tLeadPT+tSublPT);
0511 
0512             for (int i = 0; i < 24*64; ++i) {
0513                 const int ei = i/64, pi = i%64;
0514                 const double rE_i = recoMap[ei][pi];
0515                 const double tE_i = truthMap[ei][pi];
0516 
0517                 double phi_i = getPhiCenter(pi);
0518                 const double rpT_i = rE_i/cosh(getEtaCenter(ei, vtx_z));
0519                 const double tpT_i = tE_i/cosh(getEtaCenter(ei, 0.0));
0520                 
0521                 const bool rOk_i = (rpT_i > towCut && rpT_i < 80);
0522                 const bool tOk_i = (tpT_i > towCut && tpT_i < 80);
0523                 if (!rOk_i && !tOk_i) continue;
0524 
0525                 for (int j = i+1; j < 24*64; ++j) {
0526                     const int ej = j/64, pj = j%64;
0527                     const double rE_j = recoMap[ej][pj];
0528                     const double tE_j = truthMap[ej][pj];
0529 
0530                     double phi_j = getPhiCenter(pj);
0531                     const double rpT_j = rE_j/cosh(getEtaCenter(ej, vtx_z));
0532                     const double tpT_j = tE_j/cosh(getEtaCenter(ej, 0.0));
0533 
0534                     const bool rOk_j = (rpT_j > towCut && rpT_j < 80);
0535                     const bool tOk_j = (tpT_j > towCut && tpT_j < 80);
0536                     if (!rOk_j && !tOk_j) continue;
0537 
0538                     const double dphi  = DeltaPhi(phi_i, phi_j);
0539                     const int    iDphi = FindBin(dphi, dPhiBins);
0540                     if (iDphi < 0) continue;
0541 
0542                     const bool hasReco  = rOk_i && rOk_j;
0543                     const bool hasTruth = tOk_i && tOk_j;
0544 
0545                     if (hasReco && hasTruth) {
0546                         const double rPairW = rpT_i*rpT_j/rPTmean2;
0547                         const double tPairW = tpT_i*tpT_j/tPTmean2;
0548                         if (rPairW >= pairWeightMin && rPairW <= pairWeightMax &&
0549                             tPairW >= pairWeightMin && tPairW <= pairWeightMax) {
0550                             const int rIPw = FindBin(rPairW, pairWeightBins);
0551                             const int tIPw = FindBin(tPairW, pairWeightBins);
0552                             if (rIPw >= 0 && tIPw >= 0) {
0553                                 const double rF3Dcen = RecoFlat3DBinCenter(RecoFlat3DIndex(iLreco,iSreco,rIPw));
0554                                 const double tF3Dcen = TrueFlat3DBinCenter(TrueFlat3DIndex(iLtrue,iStrue,tIPw));
0555                                 if (fillResp) {
0556                                     respWEEC3D[iDphi]->Fill(rF3Dcen, tF3Dcen, evtWeight);
0557                                     hWEEC3D_counts[iDphi]->Fill(rF3Dcen, tF3Dcen);
0558                                     hWEEC3D_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0559                                     hWEEC3D_pwNum[iDphi]->Fill(tF3Dcen, evtWeight * tPairW);
0560                                     hWEEC3D_pwDen[iDphi]->Fill(tF3Dcen, evtWeight);
0561                                 }
0562                                 if (fillMeas) {
0563                                     hWEEC3D_meas[iDphi]->Fill(rF3Dcen, evtWeight);
0564                                     hWEEC3D_meas_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0565                                 }
0566                             }
0567                         }
0568                     } else if (hasReco) {
0569                         const double rPairW = rpT_i*rpT_j/rPTmean2;
0570                         if (rPairW >= pairWeightMin && rPairW <= pairWeightMax) {
0571                             const int rIPw = FindBin(rPairW, pairWeightBins);
0572                             if (rIPw >= 0) {
0573                                 const double rF3Dcen = RecoFlat3DBinCenter(RecoFlat3DIndex(iLreco,iSreco,rIPw));
0574                                 if (fillResp) {
0575                                     respWEEC3D[iDphi]->Fake(rF3Dcen, evtWeight);
0576                                     hWEEC3D_fakes[iDphi]->Fill(rF3Dcen, evtWeight);                                    
0577                                 }
0578                                 if (fillMeas) hWEEC3D_meas[iDphi]->Fill(rF3Dcen, evtWeight);
0579                             }
0580                         }
0581                     } else { // hasTruth only
0582                         const double tPairW = tpT_i*tpT_j/tPTmean2;
0583                         if (tPairW >= pairWeightMin && tPairW <= pairWeightMax) {
0584                             const int tIPw = FindBin(tPairW, pairWeightBins);
0585                             if (tIPw >= 0) {
0586                                 const double tF3Dcen = TrueFlat3DBinCenter(TrueFlat3DIndex(iLtrue,iStrue,tIPw));
0587                                 if (fillResp) {
0588                                     respWEEC3D[iDphi]->Miss(tF3Dcen, evtWeight);
0589                                     hWEEC3D_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0590                                     hWEEC3D_pwNum[iDphi]->Fill(tF3Dcen, evtWeight * tPairW);
0591                                     hWEEC3D_pwDen[iDphi]->Fill(tF3Dcen, evtWeight);
0592                                     hWEEC3D_misses[iDphi]->Fill(tF3Dcen, evtWeight);
0593                                 }
0594                                 if (fillMeas) {
0595                                     hWEEC3D_meas_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0596                                 }
0597                             }
0598                         }
0599                     }
0600                 }
0601             }
0602         }
0603 
0604         // ── fake dijet ────────────────────────────────────────────────────
0605         // Reco dijet with no matching truth dijet.  One reco-pair loop.
0606         //else if (isFake)
0607         if (isFake)
0608         {
0609             const double rPTmean2 = 0.25*(rLeadPT+rSublPT)*(rLeadPT+rSublPT);
0610 
0611             for (int i = 0; i < 24*64; ++i) {
0612                 const int ei = i/64, pi = i%64;
0613                 double phi_i = getPhiCenter(pi);
0614                 const double rpT_i = recoMap[ei][pi]/cosh(getEtaCenter(ei, vtx_z));
0615                 if (rpT_i <= towCut || rpT_i >= 80) continue;
0616 
0617                 for (int j = i+1; j < 24*64; ++j) {
0618                     const int ej = j/64, pj = j%64;
0619                     double phi_j = getPhiCenter(pj);
0620                     const double rpT_j = recoMap[ej][pj]/cosh(getEtaCenter(ej, vtx_z));
0621                     if (rpT_j <= towCut || rpT_j >= 80) continue;
0622 
0623                     const double dphi   = DeltaPhi(phi_i, phi_j);
0624                     const double rPairW = rpT_i*rpT_j/rPTmean2;
0625                     if (rPairW < pairWeightMin || rPairW > pairWeightMax) continue;
0626 
0627                     const int iDphi = FindBin(dphi, dPhiBins);
0628                     const int rIPw  = FindBin(rPairW, pairWeightBins);
0629                     if (iDphi < 0 || rIPw < 0) continue;
0630 
0631                     const double rF3Dcen = RecoFlat3DBinCenter(RecoFlat3DIndex(iLreco,iSreco,rIPw));
0632                     if (fillResp) {
0633                         respWEEC3D[iDphi]->Fake(rF3Dcen, evtWeight);
0634                         hWEEC3D_fakes[iDphi]->Fill(rF3Dcen, evtWeight);
0635                     }
0636                     if (fillMeas) hWEEC3D_meas[iDphi]->Fill(rF3Dcen, evtWeight);
0637                 }
0638             }
0639         }
0640 
0641         // ── missed dijet ──────────────────────────────────────────────────
0642         // Truth dijet with no matching reco dijet.  One truth-pair loop.
0643         //else if (isMiss)
0644         if (isMiss)
0645         {
0646             const double tPTmean2 = 0.25*(tLeadPT+tSublPT)*(tLeadPT+tSublPT);
0647 
0648             for (int i = 0; i < 24*64; ++i) {
0649                 const int ei = i/64, pi = i%64;
0650                 double phi_i = getPhiCenter(pi);
0651                 const double tpT_i = truthMap[ei][pi]/cosh(getEtaCenter(ei, 0.0));
0652                 if (tpT_i <= towCut || tpT_i >= 80) continue;
0653 
0654                 for (int j = i+1; j < 24*64; ++j) {
0655                     const int ej = j/64, pj = j%64;
0656                     double phi_j = getPhiCenter(pj);
0657                     const double tpT_j = truthMap[ej][pj]/cosh(getEtaCenter(ej, 0.0));
0658                     if (tpT_j <= towCut || tpT_j >= 80) continue;
0659 
0660                     const double dphi   = DeltaPhi(phi_i, phi_j);
0661                     const double tPairW = tpT_i*tpT_j/tPTmean2;
0662                     if (tPairW < pairWeightMin || tPairW > pairWeightMax) continue;
0663 
0664                     const int iDphi = FindBin(dphi, dPhiBins);
0665                     const int tIPw  = FindBin(tPairW, pairWeightBins);
0666                     if (iDphi < 0 || tIPw < 0) continue;
0667 
0668                     const double tF3Dcen = TrueFlat3DBinCenter(TrueFlat3DIndex(iLtrue,iStrue,tIPw));
0669                     if (fillResp) {
0670                         respWEEC3D[iDphi]->Miss(tF3Dcen, evtWeight);
0671                         hWEEC3D_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0672                         hWEEC3D_pwNum[iDphi]->Fill(tF3Dcen, evtWeight * tPairW);
0673                         hWEEC3D_pwDen[iDphi]->Fill(tF3Dcen, evtWeight);
0674                         hWEEC3D_misses[iDphi]->Fill(tF3Dcen, evtWeight);
0675                     }
0676                     if (fillMeas) {
0677                         hWEEC3D_meas_truth[iDphi]->Fill(tF3Dcen, evtWeight);
0678                     }
0679                 }
0680             }
0681         }
0682 
0683         /*
0684         // ── measured-half reco only (kHalf: not matched/fake/miss on resp side) ──
0685         // Events where !fillResp but fillMeas and recoInRange — pure reco fill.
0686         else if (!fillResp && fillMeas && recoInRange)
0687         {
0688             const double rPTmean2 = 0.25*(rLeadPT+rSublPT)*(rLeadPT+rSublPT);
0689 
0690             for (int i = 0; i < 24*64; ++i) {
0691                 const int ei = i/64, pi = i%64;
0692                 if (recoMap[ei][pi] <= 0.25 || recoMap[ei][pi] >= 80) continue;
0693                 double phi_i = getPhiCenter(pi);
0694                 while (phi_i >  TMath::Pi()) phi_i -= 2*TMath::Pi();
0695                 while (phi_i < -TMath::Pi()) phi_i += 2*TMath::Pi();
0696                 const double rpT_i = recoMap[ei][pi]/cosh(getEtaCenter(ei, vtx_z));
0697 
0698                 for (int j = i+1; j < 24*64; ++j) {
0699                     const int ej = j/64, pj = j%64;
0700                     if (recoMap[ej][pj] <= 0.25 || recoMap[ej][pj] >= 80) continue;
0701                     double phi_j = getPhiCenter(pj);
0702                     while (phi_j >  TMath::Pi()) phi_j -= 2*TMath::Pi();
0703                     while (phi_j < -TMath::Pi()) phi_j += 2*TMath::Pi();
0704                     const double rpT_j = recoMap[ej][pj]/cosh(getEtaCenter(ej, vtx_z));
0705 
0706                     const double dphi   = DeltaPhi(phi_i, phi_j);
0707                     const double rPairW = rpT_i*rpT_j/rPTmean2;
0708                     if (rPairW < pairWeightMin || rPairW > pairWeightMax) continue;
0709 
0710                     const int iDphi = FindBin(dphi, dPhiBins);
0711                     const int rIPw  = FindBin(rPairW, pairWeightBins);
0712                     if (iDphi < 0 || rIPw < 0) continue;
0713 
0714                     hWEEC3D_meas[iDphi]->Fill(
0715                         RecoFlat3DBinCenter(RecoFlat3DIndex(iLreco,iSreco,rIPw)), evtWeight);
0716                 }
0717             }
0718         }
0719 
0720         // ── measured-half truth (kHalf only) ─────────────────────────────
0721         // Fill truth-tower pairs for odd-half events that are trueInRange
0722         // but were not matched/fake/miss (i.e. !fillResp).  Events that
0723         // were matched/fake/miss already filled hWEEC3D_meas_truth above.
0724         if (!fillResp && fillMeas && trueInRange)
0725         {
0726             const double tPTmean2 = 0.25*(tLeadPT+tSublPT)*(tLeadPT+tSublPT);
0727 
0728             for (int i = 0; i < 24*64; ++i) {
0729                 const int ei = i/64, pi = i%64;
0730                 if (truthMap[ei][pi] <= 0.25 || truthMap[ei][pi] >= 80) continue;
0731                 double phi_i = getPhiCenter(pi);
0732                 while (phi_i >  TMath::Pi()) phi_i -= 2*TMath::Pi();
0733                 while (phi_i < -TMath::Pi()) phi_i += 2*TMath::Pi();
0734                 const double tpT_i = truthMap[ei][pi]/cosh(getEtaCenter(ei, 0.0));
0735 
0736                 for (int j = i+1; j < 24*64; ++j) {
0737                     const int ej = j/64, pj = j%64;
0738                     if (truthMap[ej][pj] <= 0.25 || truthMap[ej][pj] >= 80) continue;
0739                     double phi_j = getPhiCenter(pj);
0740                     while (phi_j >  TMath::Pi()) phi_j -= 2*TMath::Pi();
0741                     while (phi_j < -TMath::Pi()) phi_j += 2*TMath::Pi();
0742                     const double tpT_j = truthMap[ej][pj]/cosh(getEtaCenter(ej, 0.0));
0743 
0744                     const double dphi   = DeltaPhi(phi_i, phi_j);
0745                     const double tPairW = tpT_i*tpT_j/tPTmean2;
0746                     if (tPairW < pairWeightMin || tPairW > pairWeightMax) continue;
0747 
0748                     const int iDphi = FindBin(dphi, dPhiBins);
0749                     const int tIPw  = FindBin(tPairW, pairWeightBins);
0750                     if (iDphi < 0 || tIPw < 0) continue;
0751 
0752                     hWEEC3D_meas_truth[iDphi]->Fill(
0753                         TrueFlat3DBinCenter(TrueFlat3DIndex(iLtrue,iStrue,tIPw)), evtWeight);
0754                 }
0755             }
0756         }
0757         */
0758 
0759     } // end event loop
0760 
0761     simFile->Close(); delete simFile;
0762 
0763     // ── write output ──────────────────────────────────────────────────────
0764     std::cout << "writing file " << outName << std::endl;
0765     std::cout.flush();
0766 
0767     fOut->cd();
0768 
0769     respJetPt->Write("response_jet_pT");
0770     hJetPt_truth_matched->Write();
0771     hJetPt_truth        ->Write();
0772     hJetPt_reco_matched ->Write();
0773     hJetPt_reco         ->Write();
0774     hJetPt_meas         ->Write();
0775     if (halfClosure) hJetPt_truth_meas->Write();
0776 
0777     std::cout << "done writing jet plots" << std::endl;
0778 
0779     for (int k = 0; k < nDphi; ++k)
0780         respWEEC3D[k]->Write(std::format("response_wEEC3D_{}", k).c_str());
0781 
0782     std::cout << "done writing response matrices" << std::endl;
0783 
0784     for (int k = 0; k < nDphi; ++k)
0785         hWEEC3D_truth[k]->Write();
0786 
0787     std::cout << "done writing truth plots" << std::endl;
0788 
0789     for (int k = 0; k < nDphi; ++k) {
0790         hWEEC3D_pwNum[k]->Write();
0791         hWEEC3D_pwDen[k]->Write();
0792     }
0793 
0794     std::cout << "done writing pair weight mean hists" << std::endl;
0795 
0796     for (int k = 0; k < nDphi; ++k)
0797         hWEEC3D_meas[k]->Write();
0798 
0799     std::cout << "done writing meas plots" << std::endl;
0800 
0801     if (halfClosure) {
0802         for (int k = 0; k < nDphi; ++k)
0803             hWEEC3D_meas_truth[k]->Write();
0804         std::cout << "done writing meas truth plots" << std::endl;
0805     }
0806 
0807     for (int k = 0; k < nDphi; ++k)
0808         hWEEC3D_counts[k]->Write();
0809 
0810     for (int k = 0; k < nDphi; ++k)
0811         hWEEC3D_misses[k]->Write();
0812 
0813     for (int k = 0; k < nDphi; ++k)
0814         hWEEC3D_fakes[k]->Write();
0815 
0816     std::cout << "done writing counts hists" << std::endl;
0817 
0818     hVtxMC->Write();
0819 
0820     std::cout << "done writing vtx hist" << std::endl;
0821 
0822     hWEEC_particle  ->Write();
0823     hWEEC_truthTower->Write();
0824 
0825     std::cout << "done writing cross-check wEEC hists" << std::endl;
0826 
0827     fOut->Close();
0828     std::cout << "Written: " << outName << "\n";
0829 }