Rivet analyses

All-hadronic boosted ttbar at 13 TeV

Experiment: ATLAS (LHC)

Inspire ID: 2077575

Status: VALIDATED

Authors: - Ovidiu Miu

References: - arXiv: 2205.02817 - Expt page: ATLAS-TOPQ-2018-11

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - p + p -> ttbar (all-hadronic, boosted)

Measurements of single-, double-, and triple-differential cross-sections are presented for boosted top-quark pair-production in 13 TeV proton–proton collisions recorded by the ATLAS detector at the LHC. The top quarks are observed through their hadronic decay and reconstructed as large-radius jets with the leading jet having transverse momentum (pT) greater than 500 GeV. The observed data are unfolded to remove detector effects. The particle-level cross-section, multiplied by the t → WWb branching fraction and measured in a fiducial phase space defined by requiring the leading and second-leading jets to have pT > 500 GeV and pT > 350 GeV, respectively, is 331 ± 3(stat.) ± 39(syst.) fb. This is approximately 20% lower than the prediction of 398−49+48 fb by POWHEG+PYTHIA8 with next-to-leading-order (NLO) accuracy but consistent within the theoretical uncertainties. Results are also presented at the parton level, where the effects of top-quark decay, parton showering, and hadronization are removed such that they can be compared with fixed-order next-to-next-to-leading-order (NNLO) calculations. The parton-level cross-section, measured in a fiducial phase space similar to that at particle level, is 1.94 ± 0.02(stat.) ± 0.25(syst.)~pb. This agrees with the NNLO prediction of 1.96−0.17+0.02 pb. Reasonable agreement with the differential cross-sections is found for most NLO models, while the NNLO calculations are generally in better agreement with the data. The differential cross-sections are interpreted using a Standard Model effective field-theory formalism and limits are set on Wilson coefficients of several four-fermion operators.

Source code:ATLAS_2022_I2077575.cc

// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Math/LorentzTrans.hh"
#include "Rivet/Projections/FastJets.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/IdentifiedFinalState.hh"
#include "Rivet/Projections/LeptonFinder.hh"
#include "Rivet/Projections/PartonicTops.hh"
#include "Rivet/Projections/PromptFinalState.hh"
#include "Rivet/Projections/VetoedFinalState.hh"

namespace Rivet {


  /// @brief All-hadronic ttbar at 13 TeV
  class ATLAS_2022_I2077575 : public Analysis {
  public:

    /// Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2022_I2077575);

    /// Book histograms and initialise projections before the run
    void init() {

      // Get options particle-level only.
      _mode = 0;
      if (getOption("TMODE") == "PARTICLE") _mode = 0;
      if (getOption("TMODE") == "BOTH") _mode = 1;

      // External bins for 2D and 3D cross-sections
      std::vector<double> t1_pt_2D_bins_1 = {0.5, 0.55, 0.6, 0.75, 2.0};
      std::vector<double> t1_pt_2D_bins_2 = {0.5, 0.55, 0.625, 0.75, 2.0};
      std::vector<double> t_and_tt_y_2D_bins = {0.0, 0.2, 0.5, 1.0, 2.0};
      std::vector<double> tt_pt_2D_bins = {0.0, 0.1, 0.2, 0.35, 1.0};
      std::vector<double> tt_m_3D_bins = {0.9, 1.2, 1.5, 4.0};

      //histogram booking
      book(_h["inclusive_particle"], 2, 1, 1);
      if (_mode) {
        book(_h["inclusive_parton"], 147, 1, 1);
      }
      book_hist("t_pt", 3);
      book_hist("t_y", 4);
      book_hist("t1_pt", 5);
      book_hist("t1_y", 6);
      book_hist("t2_pt", 7);
      book_hist("t2_y", 8);
      book_hist("tt_m", 9);
      book_hist("tt_pt", 10);
      book_hist("tt_y", 11);
      book_hist("tt_chi", 12);
      book_hist("tt_yboost", 13);
      book_hist("tt_pout", 14);
      book_hist("tt_dPhi", 15);
      book_hist("tt_Ht", 16);
      book_hist("tt_cosThStar", 17);
      book_hist_2D("t1_pt_t2_pt_2D", t1_pt_2D_bins_1, 18);
      book_hist_2D("t1_y_t2_y_2D", t_and_tt_y_2D_bins, 22);
      book_hist_2D("t1_y_t1_pt_2D", t_and_tt_y_2D_bins, 26);
      book_hist_2D("t2_y_t2_pt_2D", t_and_tt_y_2D_bins, 30);
      book_hist_2D("t1_pt_tt_pt_2D", t1_pt_2D_bins_2, 34);
      book_hist_2D("t1_pt_tt_m_2D", t1_pt_2D_bins_2, 38);
      book_hist_2D("tt_y_t1_pt_2D", t_and_tt_y_2D_bins, 42);
      book_hist_2D("tt_y_t1_y_2D", t_and_tt_y_2D_bins, 46);
      book_hist_2D("t1_y_tt_m_2D", t_and_tt_y_2D_bins, 50);
      book_hist_2D("tt_y_tt_m_2D", t_and_tt_y_2D_bins, 54);
      book_hist_2D("tt_pt_tt_m_2D", tt_pt_2D_bins, 58);
      book_hist_2D("tt_y_tt_pt_2D", t_and_tt_y_2D_bins, 62);
      book_hist_2D("tt_y_1_tt_m_t1_pt_3D", tt_m_3D_bins, 66);
      book_hist_2D("tt_y_2_tt_m_t1_pt_3D", tt_m_3D_bins, 69);
      book_hist_2D("tt_y_3_tt_m_t1_pt_3D", tt_m_3D_bins, 72);

      // Projections
      const Cut dressed_lep = Cuts::abseta < 2.5 && Cuts::pT >= 25 * GeV;
      const Cut all_dressed_lep = Cuts::abseta < 2.5;
      const Cut eta_full = Cuts::abseta < 4.5;

      // All final state particles
      const FinalState fs(eta_full);

      // Get photons to dress leptons
      const FinalState photons(Cuts::abspid == PID::PHOTON);

      // Projection to find the electrons
      PromptFinalState electrons(Cuts::abspid == PID::ELECTRON, TauDecaysAs::PROMPT);
      LeptonFinder dressedelectrons(electrons, photons, 0.1, dressed_lep);
      declare(dressedelectrons, "elecs");
      LeptonFinder alldressedelectrons(electrons, photons, 0.1, all_dressed_lep);

      // Projection to find the muons
      PromptFinalState muons(Cuts::abspid == PID::MUON, TauDecaysAs::PROMPT);
      LeptonFinder dressedmuons(muons, photons, 0.1, dressed_lep);
      declare(dressedmuons, "muons");
      LeptonFinder alldressedmuons(muons, photons, 0.1, all_dressed_lep);

      // Small-R jet clustering
      VetoedFinalState vfs(fs);
      vfs.addVetoOnThisFinalState(alldressedelectrons);
      vfs.addVetoOnThisFinalState(alldressedmuons);
      FastJets sjets(vfs, JetAlg::ANTIKT, 0.4, JetMuons::ALL, JetInvisibles::DECAY);
      declare(sjets, "sjets");

      // Large-R jet clustering.
      FastJets ljets(fs, JetAlg::ANTIKT, 1.0, JetMuons::NONE, JetInvisibles::NONE);
      ljets.addTrf(new fastjet::Filter(fastjet::JetDefinition(fastjet::kt_algorithm, 0.2),
                                       fastjet::SelectorPtFractionMin(0.05)));
      declare(ljets, "ljets");

      if (_mode) {
        PartonicTops partonTops;
        declare(partonTops, "partonicTops");
      }
    }


    void analyze(const Event& event) {

      if (_mode) {

        // Parton-level top quarks
        const Particles partonicTops = apply<PartonicTops>(event, "partonicTops").particlesByPt();
        FourMomentum top, tbar;
        bool foundT = false, foundTBar = false;
        for (const Particle& ptop : partonicTops) {
          const int pid = ptop.pid();
          if (pid == PID::TQUARK) {
            top = ptop.momentum();
            foundT = true;
          }
          else if (pid == -PID::TQUARK) {
            tbar = ptop.momentum();
            foundTBar = true;
          }
        }

        FourMomentum t1_parton, t2_parton, ttbar_parton;
        if (foundT && foundTBar) {
          t1_parton = top.pT2() > tbar.pT2() ? top : tbar;
          t2_parton = top.pT2() > tbar.pT2() ? tbar : top;
          ttbar_parton = t1_parton + t2_parton;

          if (t1_parton.pT() > 500 * GeV && t2_parton.pT() > 350 * GeV) {

            const double chi_parton = calcChi(t1_parton, t2_parton);
            const double cosThetaStar_parton = abs(calcCosThetaStar(t1_parton, t2_parton));
            if (cosThetaStar_parton == -99) {
              MSG_DEBUG("ttbar going faster than light! Vetoing event. Try turning of partonic tops?");
              vetoEvent;
            }
            const double pout_parton = abs(calcPout(t1_parton, t2_parton));
            const double dPhi_parton = deltaPhi(t1_parton, t2_parton);

            const FourMomentum& randomTopParton = t1_parton; // : t2_parton;

            if (_mode) _h["inclusive_parton"]->fill(0);

            fill_hist_parton("t_pt", randomTopParton.pT() / TeV);
            fill_hist_parton("t_y", randomTopParton.absrap());

            fill_hist_parton("t1_pt", t1_parton.pT() / TeV);
            fill_hist_parton("t1_y", t1_parton.absrap());
            fill_hist_parton("t2_pt", t2_parton.pT() / TeV);
            fill_hist_parton("t2_y", t2_parton.absrap());

            fill_hist_parton("tt_m", ttbar_parton.mass() / TeV);
            fill_hist_parton("tt_pt", ttbar_parton.pT() / TeV);
            fill_hist_parton("tt_Ht", (t1_parton.pT() + t2_parton.pT()) / TeV);
            fill_hist_parton("tt_y", ttbar_parton.absrap());

            fill_hist_parton("tt_yboost", 0.5 * abs(t1_parton.rapidity() + t2_parton.rapidity()));
            fill_hist_parton("tt_chi", chi_parton);
            fill_hist_parton("tt_cosThStar", cosThetaStar_parton);
            fill_hist_parton("tt_pout", pout_parton / TeV);
            fill_hist_parton("tt_dPhi", dPhi_parton);

            fill_hist_2D_parton("t1_pt_t2_pt_2D", t1_parton.pT() / TeV, t2_parton.pT() / TeV);
            fill_hist_2D_parton("t1_y_t2_y_2D", t1_parton.absrap(), t2_parton.absrap());
            fill_hist_2D_parton("t1_y_t1_pt_2D", t1_parton.absrap(), t1_parton.pT() / TeV);
            fill_hist_2D_parton("t2_y_t2_pt_2D", t2_parton.absrap(), t2_parton.pT() / TeV);
            fill_hist_2D_parton("t1_pt_tt_pt_2D", t1_parton.pT() / TeV, ttbar_parton.pT() / TeV);
            fill_hist_2D_parton("t1_pt_tt_m_2D", t1_parton.pT() / TeV, ttbar_parton.mass() / TeV);
            fill_hist_2D_parton("tt_y_t1_pt_2D", ttbar_parton.absrap(), t1_parton.pT() / TeV);
            fill_hist_2D_parton("tt_y_t1_y_2D", ttbar_parton.absrap(), t1_parton.absrap());
            fill_hist_2D_parton("t1_y_tt_m_2D", t1_parton.absrap(), ttbar_parton.mass() / TeV);
            fill_hist_2D_parton("tt_y_tt_m_2D", ttbar_parton.absrap(), ttbar_parton.mass() / TeV);
            fill_hist_2D_parton("tt_pt_tt_m_2D", ttbar_parton.pT() / TeV, ttbar_parton.mass() / TeV);
            fill_hist_2D_parton("tt_y_tt_pt_2D", ttbar_parton.absrap(), ttbar_parton.pT() / TeV);
            if (ttbar_parton.absrap() < 0.3)
              fill_hist_2D_parton("tt_y_1_tt_m_t1_pt_3D", ttbar_parton.mass() / TeV, t1_parton.pT() / TeV);
            else if (ttbar_parton.absrap() < 0.9)
              fill_hist_2D_parton("tt_y_2_tt_m_t1_pt_3D", ttbar_parton.mass() / TeV, t1_parton.pT() / TeV);
            else if (ttbar_parton.absrap() < 2.0)
              fill_hist_2D_parton("tt_y_3_tt_m_t1_pt_3D", ttbar_parton.mass() / TeV, t1_parton.pT() / TeV);
          }
        }
      }

      // Get small-R jets
      const FastJets& sjets_fj = apply<FastJets>(event, "sjets");
      const Jets all_sjets = sjets_fj.jetsByPt(Cuts::pT > 25 * GeV && Cuts::abseta < 2.5);

      // Get dressed leptons
      DressedLeptons dressedElectrons = apply<LeptonFinder>(event, "elecs").dressedLeptons();
      DressedLeptons dressedMuons = apply<LeptonFinder>(event, "muons").dressedLeptons();

      // Perform lepton isolation
      idiscardIfAnyDeltaRLess(dressedElectrons, all_sjets, 0.4);
      idiscardIfAnyDeltaRLess(dressedMuons, all_sjets, 0.4);

      // Veto on leptons
      if (!dressedElectrons.empty()) vetoEvent;
      if (!dressedMuons.empty()) vetoEvent;

      // Get large-R jets
      const FastJets& ljets_fj = apply<FastJets>(event, "ljets");
      const Jets& trimmedJets = ljets_fj.jetsByPt();

      // Check large-R jets
      Jets ljets;
      vector<bool> b_tagged;
      for (const Jet& jet : trimmedJets) {

        if (jet.pT() < 200 * GeV) continue;
        if (jet.pT() > 3000 * GeV) continue;
        if (jet.mass() > jet.pT()) continue;
        if (jet.mass() < 50 * GeV) continue;
        if (jet.abseta() > 2.0) continue;

        ljets += jet;
        b_tagged += jet.bTagged(Cuts::pT > 5 * GeV);
      }

      if (ljets.size() < 2) vetoEvent;

      // Identify top and anti top, compute some event variables
      int top1Index(-1);
      int top2Index(-1);

      double deltaMass(FLT_MAX);
      for (int i = 0; i < (int)ljets.size(); i++) {
        if (ljets[i].pT() < 500 * GeV) continue;
        const double diff = std::abs(ljets[i].mass() - 172.5 * GeV);
        if (diff < deltaMass) {
          deltaMass = diff;
          top1Index = i;
        }
      }

      if (top1Index == -1) vetoEvent;

      deltaMass = FLT_MAX;
      for (int i = 0; i < (int)ljets.size(); ++i) {
        if (i == top1Index || ljets[i].pT() < 350 * GeV) continue;
        const double diff = std::abs(ljets[i].mass() - 172.5 * GeV);
        if (diff < deltaMass) {
          deltaMass = diff;
          top2Index = i;
        }
      }

      if (top2Index == -1) vetoEvent;

      if (ljets[top1Index].pT() < ljets[top2Index].pT()) std::swap(top1Index, top2Index);

      const FourMomentum ttbar = ljets[top1Index].momentum() + ljets[top2Index].momentum();
      const FourMomentum t1 = ljets[top1Index].momentum();
      const FourMomentum t2 = ljets[top2Index].momentum();

      const double chi = calcChi(t1, t2);
      const double cosThetaStar = abs(calcCosThetaStar(t1, t2));
      if (cosThetaStar == -99) {
        MSG_DEBUG("real ttbar going faster than light! This should not happen. Vetoing event.");
        vetoEvent;
      }
      const double pout = abs(calcPout(t1, t2));
      const double dPhi = deltaPhi(t1, t2);

      // b-tagging for particle done on large-R jets
      if (!(b_tagged[top1Index] && b_tagged[top2Index])) vetoEvent;

      // Continues with signal region cuts
      if (abs(t1.mass() - 172.5 * GeV) > 50 * GeV) vetoEvent;
      if (abs(t2.mass() - 172.5 * GeV) > 50 * GeV) vetoEvent;

      const FourMomentum& randomTopJet = t1; // : t2;

      _h["inclusive_particle"]->fill(0);

      fill_hist("t_pt", randomTopJet.pT() / TeV);
      fill_hist("t_y", randomTopJet.absrap());

      fill_hist("t1_pt", t1.pT() / TeV);
      fill_hist("t1_y", t1.absrap());
      fill_hist("t2_pt", t2.pT() / TeV);
      fill_hist("t2_y", t2.absrap());

      fill_hist("tt_m", ttbar.mass() / TeV);
      fill_hist("tt_pt", ttbar.pT() / TeV);
      fill_hist("tt_Ht", (t1.pT() + t2.pT()) / TeV);
      fill_hist("tt_y", ttbar.absrap());

      fill_hist("tt_yboost", 0.5 * abs(t1.rapidity() + t2.rapidity()));
      fill_hist("tt_chi", chi);
      fill_hist("tt_cosThStar", cosThetaStar);
      fill_hist("tt_pout", pout / TeV);
      fill_hist("tt_dPhi", dPhi);

      fill_hist_2D("t1_pt_t2_pt_2D", t1.pT() / TeV, t2.pT() / TeV);
      fill_hist_2D("t1_y_t2_y_2D", t1.absrap(), t2.absrap());
      fill_hist_2D("t1_y_t1_pt_2D", t1.absrap(), t1.pT() / TeV);
      fill_hist_2D("t2_y_t2_pt_2D", t2.absrap(), t2.pT() / TeV);
      fill_hist_2D("t1_pt_tt_pt_2D", t1.pT() / TeV, ttbar.pT() / TeV);
      fill_hist_2D("t1_pt_tt_m_2D", t1.pT() / TeV, ttbar.mass() / TeV);
      fill_hist_2D("tt_y_t1_pt_2D", ttbar.absrap(), t1.pT() / TeV);
      fill_hist_2D("tt_y_t1_y_2D", ttbar.absrap(), t1.absrap());
      fill_hist_2D("t1_y_tt_m_2D", t1.absrap(), ttbar.mass() / TeV);
      fill_hist_2D("tt_y_tt_m_2D", ttbar.absrap(), ttbar.mass() / TeV);
      fill_hist_2D("tt_pt_tt_m_2D", ttbar.pT() / TeV, ttbar.mass() / TeV);
      fill_hist_2D("tt_y_tt_pt_2D", ttbar.absrap(), ttbar.pT() / TeV);
      if (ttbar.absrap() < 0.3)
        fill_hist_2D("tt_y_1_tt_m_t1_pt_3D", ttbar.mass() / TeV, t1.pT() / TeV);
      else if (ttbar.absrap() < 0.9)
        fill_hist_2D("tt_y_2_tt_m_t1_pt_3D", ttbar.mass() / TeV, t1.pT() / TeV);
      else if (ttbar.absrap() < 2.0)
        fill_hist_2D("tt_y_3_tt_m_t1_pt_3D", ttbar.mass() / TeV, t1.pT() / TeV);
    }


    void finalize() {
      // Normalize histograms to cross-section in femtobarns (for consistency with HEPData)
      const double sf = crossSection() / picobarn * 1000 / sumOfWeights();
      for (auto& h_it : _h) {
        scale(h_it.second, sf);
        // Parton-level distributions corrected for all-hadronic branching fraction
        if (h_it.first.find("_parton") != string::npos) scale(h_it.second, 2.1882987);
        // Normalized distributions
        if (h_it.first.find("_norm") != string::npos) normalize(h_it.second, 1.0, false);
      }
      // Multi-dimensional cross-sections
      double norm_3D = 0, norm_3D_parton = 0;
      for (auto& h_it : _h_multi) {
        if (h_it.first.find("_parton") != string::npos) scale(h_it.second, 2.1882987);
        if (h_it.first.find("_norm") != string::npos) {
          scale(h_it.second, sf);
          if (h_it.first.find("_3D") != string::npos) {
            if (h_it.first.find("_parton") != string::npos)
              norm_3D_parton += h_it.second->integral(false);
            else
              norm_3D += h_it.second->integral(false);
            if (h_it.first.find("tt_y_1") != string::npos) {
              scale(h_it.second, 1. / 0.3);
            }
            if (h_it.first.find("tt_y_2") != string::npos) {
              scale(h_it.second, 1. / 0.6);
            }
            if (h_it.first.find("tt_y_3") != string::npos) {
              scale(h_it.second, 1. / 1.1);
            }
          }
          else {
            const double norm_2D = h_it.second->integral(false);
            scale(h_it.second, safediv(1.0, norm_2D));
            divByGroupWidth(h_it.second);
          }
        }
        else {
          if (h_it.first.find("_3D") != string::npos) {
            if (h_it.first.find("tt_y_1") != string::npos) {
              scale(h_it.second, 1. / 0.3);
            }
            if (h_it.first.find("tt_y_2") != string::npos) {
              scale(h_it.second, 1. / 0.6);
            }
            if (h_it.first.find("tt_y_3") != string::npos) {
              scale(h_it.second, 1. / 1.1);
            }
            scale(h_it.second, sf);
            divByGroupWidth(h_it.second);
          }
          else {
            scale(h_it.second, sf);
            divByGroupWidth(h_it.second);
          }
        }
      }
      scale(_h_multi["tt_y_1_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
      divByGroupWidth(_h_multi["tt_y_1_tt_m_t1_pt_3D_norm"]);
      scale(_h_multi["tt_y_2_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
      divByGroupWidth(_h_multi["tt_y_2_tt_m_t1_pt_3D_norm"]);
      scale(_h_multi["tt_y_3_tt_m_t1_pt_3D_norm"], safediv(1, norm_3D));
      divByGroupWidth(_h_multi["tt_y_3_tt_m_t1_pt_3D_norm"]);
      if (_mode) {
        scale(_h_multi["tt_y_1_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
        divByGroupWidth(_h_multi["tt_y_1_tt_m_t1_pt_3D_parton_norm"]);
        scale(_h_multi["tt_y_2_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
        divByGroupWidth(_h_multi["tt_y_2_tt_m_t1_pt_3D_parton_norm"]);
        scale(_h_multi["tt_y_3_tt_m_t1_pt_3D_parton_norm"], safediv(1, norm_3D_parton));
        divByGroupWidth(_h_multi["tt_y_3_tt_m_t1_pt_3D_parton_norm"]);
      }
    }


    double calcChi(const FourMomentum& t1, const FourMomentum& t2) {
      double ystar = 0.5 * (t1.rapidity() - t2.rapidity());
      double chi = exp(2 * abs(ystar));
      return chi;
    }

    double calcCosThetaStar(const FourMomentum& t1, const FourMomentum& t2) {
      FourMomentum ttbar = t1 + t2;
      LorentzTransform centreOfMassTrans;
      ttbar.setX(0);
      ttbar.setY(0);
      if (ttbar.betaVec().mod2() > 1) return -99;
      centreOfMassTrans.setBetaVec(-ttbar.betaVec());
      FourMomentum t1_star = centreOfMassTrans.transform(t1);
      double cosThetaStar;
      if (t1_star.p3().mod2() >= 0) {
        cosThetaStar = t1_star.pz() / t1_star.p3().mod();
      }
      else {
        return -99;
      }
      return cosThetaStar;
    }

    double calcPout(const FourMomentum& t1, const FourMomentum& t2) {
      Vector3 t1V = t1.p3();
      Vector3 t2V = t2.p3();
      Vector3 zUnit = Vector3(0., 0., 1.);
      Vector3 vPerp = zUnit.cross(t1V);

      double pout = vPerp.dot(t2V) / vPerp.mod();
      return pout;
    }


  private:

    size_t _mode;
    map<string, Histo1DPtr> _h;
    map<string, Histo1DGroupPtr> _h_multi;

    //some functions for booking, filling and scaling the histograms
    void fill_hist(const std::string name, double value) {
      _h[name]->fill(value);
      _h[name + "_norm"]->fill(value);
    }

    void fill_hist_parton(const std::string name, double value) {
      _h[name + "_parton"]->fill(value);
      _h[name + "_parton_norm"]->fill(value);
    }

    void fill_hist_2D(const std::string name, double value_external, double value_internal) {
      _h_multi[name]->fill(value_external, value_internal);
      _h_multi[name + "_norm"]->fill(value_external, value_internal);
    }

    void fill_hist_2D_parton(const std::string name, double value_external, double value_internal) {
      _h_multi[name + "_parton"]->fill(value_external, value_internal);
      _h_multi[name + "_parton_norm"]->fill(value_external, value_internal);
    }

    void book_hist(const std::string name, unsigned int index) {
      book(_h[name], index, 1, 1);
      book(_h[name + "_norm"], index + 72, 1, 1);
      if (_mode) {
        book(_h[name + "_parton"], index + 145, 1, 1);
        book(_h[name + "_parton_norm"], index + 217, 1, 1);
      }
    }

    void book_hist_2D(const std::string name, const vector<double>& external_bins, unsigned int index) {
      book(_h_multi[name], external_bins);
      book(_h_multi[name + "_norm"], external_bins);
      if (_mode) {
        book(_h_multi[name + "_parton"], external_bins);
        book(_h_multi[name + "_parton_norm"], external_bins);
      }
      for (size_t i = 0; i < _h_multi[name]->numBins(); ++i) {
        book(_h_multi[name]->bin(i + 1), index + i, 1, 1);
        book(_h_multi[name + "_norm"]->bin(i + 1), index + 72 + i, 1, 1);
        if (_mode) {
          book(_h_multi[name + "_parton"]->bin(i + 1), index + 145 + i, 1, 1);
          book(_h_multi[name + "_parton_norm"]->bin(i + 1), index + 217 + i, 1, 1);
        }
      }
    }
  };


  RIVET_DECLARE_PLUGIN(ATLAS_2022_I2077575);
}