main371

Back to index.

// main371.cc is a part of the PYTHIA event generator.
// Copyright (C) 2026 Torbjorn Sjostrand.
// PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
// Please respect the MCnet Guidelines, see GUIDELINES for details.

// Authors:
//            Torbjorn Sjostrand

// Keywords:
//            Top
//            Toponium

// Study ttbar production above and below threshold,
// starting out from the equations in
// V. Fadin,  V. Khoze and T. Sjostrand, Z. Phys. C48 (1990) 613,
// with some further assumptions in the below-threshold simulation.

#include "Pythia8/Pythia.h"
using namespace Pythia8;

//============================================================================

int main() {

  // Restrict to threshold region only or not.
  bool thresholdOnly = true;

  // Full event generation or only cross-section-oriented studies.
  bool fullEvents = false;

  // Number of events.
  int nEvent = 1000000;

  // LHC collision energy.
  double eCM = 13000.;

  // Example of possible setup values.
  // topModelSetup = 0 is Born, = 1 is simple Coulomb, = 2 is Green's,
  // = 3 is reduced top quark width, = 4 reduced width in cross section.
  int    topModelSetup   = 3;

  // Suitable values for given topModelSetup.
  int    topModel        = min( topModelSetup, 2);
  double mt              = 172.5;
  double gammat          = (topModelSetup != 3) ? 1.34 : 0.1;
  // Warning:  a small gammatGreen may lead to event weights above unity.
  double gammatGreen     = (topModelSetup != 4) ? 1.34 : 0.2;
  double thresholdRegion = 10.;
  int    alphasOrder     = 2;
  double alphasValue     = 0.118;
  double ggSingletFrac   = 2./7.;
  double qqSingletFrac   = 0.;
  bool   pseudoscalar    = true;
  double eBegDamp        = 10.;
  double eEndDamp        = 20.;

  // Generator.
  Pythia pythia;

  // Process selection. LHC at 13 TeV.
  pythia.readString("Top:gg2ttbar = on");
  pythia.readString("Top:qqbar2ttbar = on");
  pythia.settings.parm("Beams:eCM", eCM);

  // Restrict to threshold region or not.
  if (thresholdOnly) {
    pythia.readString("PhaseSpace:mHatMin = 300.");
    pythia.readString("PhaseSpace:mHatMax = 400.");
  }
  else pythia.readString("PhaseSpace:mHatMin = 200.");

  // Feed in desired values.
  pythia.settings.mode("TopThreshold:model", topModel);
  pythia.particleData.m0(6, mt);
  pythia.readString("6:doForceWidth = true");
  pythia.particleData.mWidth( 6, gammat);
  pythia.settings.parm("TopThreshold:tWidthGreen", gammatGreen);
  pythia.settings.parm("TopThreshold:thrRegion", thresholdRegion);
  pythia.settings.mode("TopThreshold:alphasOrder", alphasOrder);
  pythia.settings.parm("TopThreshold:alphasValue", alphasValue);
  pythia.settings.parm("TopThreshold:ggSingletFrac", ggSingletFrac);
  pythia.settings.parm("TopThreshold:qqSingletFrac", qqSingletFrac);
  pythia.settings.flag("TopThreshold:pseudoscalar", pseudoscalar);
  pythia.settings.parm("TopThreshold:psEBeginDamp", eBegDamp);
  pythia.settings.parm("TopThreshold:psEEndDamp", eEndDamp);

  // Switch off (most) code parts not relevant here. Reduce printout.
  if (!fullEvents) {
    pythia.readString("PartonLevel:ISR = off");
    pythia.readString("PartonLevel:FSR = off");
    pythia.readString("PartonLevel:MPI = off");
    pythia.readString("HadronLevel:all = off");
  }
  pythia.readString("Next:numberCount = 100000");

  // If Pythia fails to initialize, exit with error.
  if (!pythia.init()) return 1;

  // Histograms.
  Hist mHatOri("original threshold", 100, -20., 80.);
  Hist mHatOriLow("original threshold", 100, -10., 30.);
  Hist mHatThr("corrected threshold", 100, -20., 80.);
  Hist mHatLow("ttbar invariant mass, low", 100, 300., 400.);
  Hist mHatAll("ttbar invariant mass, all", 100, 200., 1200.);
  Hist mTopMax("max(m_t, m_tbar) mass distribution", 100, 100., 200.);
  Hist mTopMin("min(m_t, m_tbar) mass distribution", 100, 100., 200.);
  Hist betaPair("beta factor of pair", 100, 0., 1.);
  Hist mHatOriBT("original threshold, below", 100, -20., 80.);
  Hist mHatOriBTLow("original threshold, below", 100, -10., 30.);
  Hist mHatThrBT("corrected threshold, below", 100, -10., 30.);
  Hist mHatLowBT("ttbar invariant mass, low, below", 100, 300., 400.);
  Hist mHatAllBT("ttbar invariant mass, all, below", 100, 200., 1200.);
  Hist mTopMaxBT("max(m_t, m_tbar) mass distribution, below", 100, 100., 200.);
  Hist mTopMinBT("min(m_t, m_tbar) mass distribution, below", 100, 100., 200.);
  Hist betaPairBT("beta factor of pair, below", 100, 0., 1.);
  Hist mShiftBT("shift in top mass, after - before", 100, -45., 5.);

  // Counters. Real numbers since some events can come with weights > 1.
  double nEventEff = 0., nBelow2mt = 0., nBelow = 0., nBelowgg = 0.,
    nBelowqq = 0., nAbovegg = 0., nAboveqq = 0., eBelow = 0.;

  // Begin event loop.
  for (int iEvent = 0; iEvent < nEvent; ++iEvent) {

    // Generate events.
    if (!pythia.next()) continue;
    double wt   = pythia.info.weight();

    // Original threshold value and other properties.
    double eThr = pythia.info.toponiumE;
    double mt1T = pythia.info.toponiumm3;
    double mt2T = pythia.info.toponiumm4;
    double mHat = pythia.info.mHat();
    double mt1  = pythia.info.m3Hat();
    double mt2  = pythia.info.m4Hat();
    double rat1 = pow2( mt1 / mHat);
    double rat2 = pow2( mt2 / mHat);
    double beta = sqrtpos( pow2(1 - rat1 - rat2) - 4. * rat1 * rat2);

    // Histogram for all events.
    mHatOri.fill( eThr, wt);
    mHatOriLow.fill( eThr, wt);
    mHatThr.fill( mHat - mt1 - mt2, wt);
    mHatLow.fill( mHat, wt);
    mHatAll.fill( mHat, wt);
    mTopMax.fill( max(mt1,mt2), wt);
    mTopMin.fill( min(mt1,mt2), wt);
    betaPair.fill( beta, wt);

    // Histogram for below-threshold events (and some above).
    nEventEff += wt;
    if (mHat < 2. * mt) nBelow2mt += wt;
    if (eThr < 0.) {
      nBelow += wt;
      if (pythia.info.code() == 601) nBelowgg += wt;
      else nBelowqq += wt;
      eBelow += eThr * wt;
      mHatOriBT.fill( eThr, wt);
      mHatOriBTLow.fill( eThr, wt);
      mHatThrBT.fill( mHat - mt1 - mt2, wt);
      mHatLowBT.fill( mHat, wt);
      mHatAllBT.fill( mHat, wt);
      mTopMaxBT.fill( max(mt1,mt2), wt);
      mTopMinBT.fill( min(mt1,mt2), wt);
      betaPairBT.fill( beta, wt);
      mShiftBT.fill( mt1 - mt1T, wt);
      mShiftBT.fill( mt2 - mt2T, wt);
    } else {
      if (pythia.info.code() == 601) nAbovegg += wt;
      else nAboveqq += wt;
    }

  // End of event loop.
  }

  // Normalize histogram to cross section, in pb/GeV.
  double sigmaGen = 1e9 * pythia.info.sigmaGen();
  double sigmapb = sigmaGen  / nEventEff;
  mHatOri      *= sigmapb;
  mHatOriLow   *= 2.5 * sigmapb;
  mHatThr      *= sigmapb;
  mHatLow      *= sigmapb;
  mHatAll      *= 0.1 * sigmapb;
  mTopMax      *= sigmapb;
  mTopMin      *= sigmapb;
  betaPair     *= 100. * sigmapb;
  mHatOriBT    *= sigmapb;
  mHatOriBTLow *= 2.5 * sigmapb;
  mHatThrBT    *= sigmapb;
  mHatLowBT    *= sigmapb;
  mHatAllBT    *= 0.1 * sigmapb;
  mTopMaxBT    *= sigmapb;
  mTopMinBT    *= sigmapb;
  betaPairBT   *= 100. * sigmapb;
  mShiftBT     *= 2. * sigmapb;

  // Statistics and cross sections
  pythia.stat();
  double sigma2mt = sigmapb * nBelow2mt;
  double sigAbv   = sigmapb * (nEventEff - nBelow);
  double sigAbvgg = sigmapb * nAbovegg;
  double sigAbvqq = sigmapb * nAboveqq;
  double sigBel   = sigmapb * nBelow;
  double sigBelgg = sigmapb * nBelowgg;
  double sigBelqq = sigmapb * nBelowqq;
  cout << "\n total sigma in studied region = " << fixed << setprecision(3)
       << sigmaGen << " pb whereof below 2 mt = " << sigma2mt << " pb"
       << endl << " sigma above threshold = "  << sigAbv << " pb" << endl
       << "   whereof gg = " << sigAbvgg << " pb and qq = " << sigAbvqq
       << " pb" << endl << " sigma below threshold = " << sigBel
       << " pb" << endl << "   whereof gg = " << sigBelgg << " pb and qq = "
       << sigBelqq << " pb" << endl;
  eBelow /= max(1., nBelow);
  cout << " average energy for below-threshold part = " << eBelow
       << " GeV" << endl;

  // Histograms with pyplot.
  HistPlot hpl("plot371");
  bool doOptional = true;

  // Spectra relative to event-by-event threshold, including below.
  hpl.frame("fig371", "Energy above/below threshold, original tops",
    "$E$ (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}E$ (pb/GeV)");
  hpl.add( mHatOri - mHatOriBT, "h,black", "above-threshold part");
  hpl.add( mHatOriBT, "h,red", "below-threshold part");
  hpl.plot();

  // Ditto, but with new t/tbar masses for below-threshold part.
  hpl.frame("", "Energy above threshold, new top masses where necessary",
    "$E'$ (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}E'$ (pb/GeV)");
  hpl.add( mHatThr, "h,black", "all");
  hpl.add( mHatThrBT, "h,red", "below-threshold part");
  hpl.plot();

  // Pair mass spectra close to threshold.
  hpl.frame("", "Invariant mass of ttbar pair, near threshold",
    "$m(t+tbar) (GeV)$", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)");
  hpl.add( mHatLow, "h,black", "all");
  hpl.add( mHatLowBT, "h,red", "below-threshold part");
  hpl.plot();

  // Pair mass spectra over full range.
  hpl.frame("", "Invariant mass of ttbar pair, full range",
    "$m(t+tbar) (GeV)$", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)");
  hpl.add( mHatAll, "h,black", "all");
  hpl.add( mHatAllBT, "h,red", "below-threshold part");
  hpl.plot();

  // Top/antitop mass spectra.
  hpl.frame("", "Larger of top and antitop masses",
    "max($m(t), m(tbar)$) (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ "
    "(pb/GeV)");
  hpl.add( mTopMax, "h,black", "all");
  hpl.add( mTopMaxBT, "h,red", "below-threshold part");
  hpl.plot(100., 200., 0.01, 100., true, false);

  hpl.frame("", "Smaller of top and antitop masses",
    "min($m(t), m(tbar)$) (GeV)", "$\\mathrm{d}\\sigma/\\mathrm{d}m$ "
    "(pb/GeV)");
  hpl.add( mTopMin, "h,black", "all");
  hpl.add( mTopMinBT, "h,red", "below-threshold part");
  hpl.plot(100., 200., 0.01, 100., true, false);

  // Top/antitop separation.
  hpl.frame("", "Beta factor in t-tbar pair",
    "$\\beta$", "$\\mathrm{d}\\sigma/\\mathrm{d}\\beta$ (pb)");
  hpl.add( betaPair, "h,black", "all");
  hpl.add( betaPairBT, "h,red", "below-threshold part");
  hpl.plot();

  // Redefinition change of top masses in below-threshold pairs.
  hpl.frame("", "Shift of below-threshold top masses", "$\\Delta m$ (GeV)",
    "$\\mathrm{d}\\sigma/\\mathrm{d}(\\Delta m)$ (pb/GeV)");
  hpl.add( mShiftBT, "h,red", "below-threshold part");
  hpl.plot();

  // For inclusion in talk.
  if (doOptional) {
    hpl.frame("fig371mass", "", "$\\hat{m}$ (GeV)",
      "$\\mathrm{d}\\sigma/\\mathrm{d}\\hat{m}$ (pb/GeV)", 4.8, 4.8);
    hpl.add( mHatLowBT, "h,red", "below threshold");
    hpl.add( mHatLow, "h,black", "all");
    hpl.plot();
    hpl.frame("fig371BreitWigners", "", "$m$ (GeV)",
      "$\\mathrm{d}\\sigma/\\mathrm{d}m$ (pb/GeV)", 4.8, 4.8);
    hpl.add( mTopMax, "h,black", "max($m_{t1},m_{t2}$) all");
    hpl.add( mTopMin, "h,blue", "min($m_{t1},m_{t2}$) all");
    hpl.add( mTopMaxBT, "h,red", "max($m_{t1},m_{t2}$) below threshold");
    hpl.add( mTopMinBT, "h,magenta", "min($m_{t1},m_{t2}$) below threshold");
    hpl.plot(140., 200., 0.01, 1000., true, false);
  }

  // Done.
  return 0;
}