## main510

Back to index.

    // main510.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:
    //            Lisa Carloni
    //            Torbjorn Sjostrand

    // Keywords:
    //            Hidden Valley

    // Hidden Valley: omnibus version for various scenarios in 1 TeV e+e-.
    // See L. Carloni, J. Rathsman and T. Sjostrand, JHEP 04 (2011) 091
    // for details.

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

    // Avoid division by zero when calculating cosTheta.
    const double PABSMIN = 1e-10;

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

    int main() {

      // Number of events and CM energy.
      int nEvent = 10000;
      double eCM = 1000.;

      // Superselection of scenarios: 0 = free, 1 = abelian, 2 = non-abelian.
      int supersel = 0;

      // Selection of scenarios. Based on classification in JHEP 04 (2011) 091.
      // 1 = KMA;  e+ e- -> Z' -> qv qvbar -> gammav.
      // 2 = KMNA; e+ e- -> Z' -> qv qvbar -> piv, rhov.
      // 3 = SMA;  e+ e- -> gamma* -> Ev Evbar -> e+ e- qv qvbar -> gammav.
      // 4 = SMNA; e+ e- -> gamma* -> Ev Evbar -> e+ e- qv qvbar -> piv, rhov.
      // 5 = KMA;  e+ e- -> gamma* -> qv qvbar -> gammav.
      // 6 = KMNA; e+ e- -> gamma* -> qv qvbar -> piv, rhov.
      int scenario   = 4;

      // Key mass choices: Ev, qv, gammav = piv.
      // Note: if NA then mqv = mgampiv/2 is forced, i.e. mqv ignored.
      double mEv     = 250.;
      double mqv     = 50.;
      double mgampiv = 10.0;

      // Number of qv replicas in NA models. Production rhov/(piv + rhov).
      int nFlav      = 4;
      double probVec = 0.75;

      // Coupling strength alpha in showers. (Here frozen; can be made running.)
      int alphaHVorder = 0;
      double alphaHV = 0.2;

      // a and b (scaled) parameter in fragmentation function.
      double aLund     = 0.68;    // default 0.68; larger means more particles
      double bLund     = 0.98;    // default 0.98: larger means fewer particles
      double sigmaLund = 0.335;   // default 0.335; larger means broader pT

      // Allow or not ISR of photons off incoming e+e-
      bool allowISR  = true;

      // Max angle for beam pipe. Used to remove much ISR.
      double theBeam = 0.050;

      // Isolation cone for leptons: require no charged inside it,
      // and at most given fraction neutral energy.
      double theIsol  = 0.10;
      double fracIsol = 0.20;

      //------------------------------------------------------------------------

      // Superselection overwrites above values.

      // Abelian (A) scenario.
      if (supersel == 1) {
        scenario = 1;
        mqv      = 20.;
        mgampiv  = 10.;
        alphaHV  = 0.3;
      }

      // Non-Abelian (NA) scenario.
      if (supersel == 2) {
        scenario = 2;
        mqv      = 5.;
        mgampiv  = 10.;
        alphaHV  = 0.15;
        nFlav    = 4;
        // Example of (unrealistic?) changed fragmentation parameters.
        aLund     = 0.12;
        bLund     = 2.0;
        sigmaLund = 0.35;
        // Rescale b and sigma, based on ratio m(rho_HV) / m(rho_QCD).
        double rescale = mgampiv / 0.775;
        bLund         /= pow2(rescale);
        sigmaLund     *= rescale;
      }

      //------------------------------------------------------------------------

      // Generator. Shorthand for event.
      Pythia pythia;
      Event& event = pythia.event;

      // Set up specifics of each scenario.

      // KMA scenario.
      if (scenario == 1) {
        pythia.readString("HiddenValley:Ngauge = 1");
        pythia.readString("HiddenValley:spinqv = 0");

        // f fbar -> Zv -> qv qvbar.
        pythia.readString("HiddenValley:ffbar2Zv = on");
        pythia.readString("4900023:onMode = off");
        pythia.readString("4900023:onIfAny = 4900101");

      // KMNA scenario.
      } else if (scenario == 2) {
        pythia.readString("HiddenValley:Ngauge = 3");
        pythia.readString("HiddenValley:spinFv = 2");

        // f fbar -> Zv -> qv qvbar.
        pythia.readString("HiddenValley:ffbar2Zv = on");
        pythia.readString("4900023:onMode = off");
        pythia.readString("4900023:onIfAny = 4900101");

      // SMA scenario.
      } else if (scenario == 3) {
        pythia.readString("HiddenValley:Ngauge = 1");
        pythia.readString("HiddenValley:spinFv = 1");
        pythia.readString("HiddenValley:spinqv = 0");
        pythia.readString("HiddenValley:kappa  = 1.");

        // f fbar -> Fv Fvbar -> f qv fbar qvbar
        pythia.readString("HiddenValley:ffbar2EvEvbar = on");

      // SMNA scenario.
      } else if (scenario == 4) {
        pythia.readString("HiddenValley:Ngauge = 3");
        pythia.readString("HiddenValley:spinFv = 1");
        pythia.readString("HiddenValley:spinqv = 0");
        pythia.readString("HiddenValley:kappa  = 1.");

        // f fbar -> Fv Fvbar -> f qv fbar qvbar
        pythia.readString("HiddenValley:ffbar2EvEvbar = on");

      // KMA scenario, with Ev masking as qv.
      } else if (scenario == 5) {
        pythia.readString("HiddenValley:Ngauge = 1");
        pythia.readString("HiddenValley:spinFv = 1");

        // f fbar -> gamma/Z -> qv qvbar.
        pythia.readString("HiddenValley:doKinMix = on");
        pythia.readString("HiddenValley:ffbar2EvEvbar = on");

      // KMNA scenario, with Ev masking as qv.
      } else if (scenario == 6) {
        pythia.readString("HiddenValley:Ngauge = 3");
        pythia.readString("HiddenValley:spinFv = 1");

        // f fbar -> gamma/Z -> qv qvbar.
        pythia.readString("HiddenValley:doKinMix = on");
        pythia.readString("HiddenValley:ffbar2EvEvbar = on");
      }

      // For NA allow HV-fragmentation, with/without off-diagonal mesons.
      if (scenario%2 == 0) {
        pythia.readString("HiddenValley:fragment = on");
        pythia.settings.mode("HiddenValley:nFlav", nFlav);
        pythia.settings.parm("HiddenValley:probVector", probVec);
        if (supersel == 2) {
          pythia.readString("HiddenValley:setabsigma = 2");
          pythia.settings.parm("HiddenValley:aLund", aLund);
          pythia.settings.parm("HiddenValley:bLund", bLund);
          pythia.settings.parm("HiddenValley:sigmaLund", sigmaLund);
        }
      }

      //------------------------------------------------------------------------

      // Set up common (or not used) parameters. Initialize generator.

      // In non-Abelian scenarios force m_qv = m_piv/2 = m_rhov/2.
      if (scenario%2 == 0) mqv = 0.5 * mgampiv;

      // Set Ev, qv, gamma_v, piv, rhov masses.
      pythia.particleData.m0( 4900011, mEv );
      pythia.particleData.m0( 4900101, mqv );
      pythia.particleData.m0( 4900022, mgampiv );
      pythia.particleData.m0( 4900111, mgampiv );
      pythia.particleData.m0( 4900113, mgampiv );
      pythia.particleData.m0( 4900211, mgampiv );
      pythia.particleData.m0( 4900213, mgampiv );

      // In scenarios 5 and 6 Ev masks for qv.
      if (scenario == 5 || scenario == 6) {
        pythia.particleData.m0( 4900011, mqv );
        pythia.particleData.mWidth( 4900011, 0. );
        pythia.readString("4900011:mayDecay = false");
        pythia.readString("4900011:chargeType = 0");
        pythia.readString("4900011:isVisible = false");
      }

      // In scenario 3 and 4 set mEv min
      if (scenario == 3 || scenario == 4) {
        pythia.particleData.mMin( 4900011, 0.1*mEv );
      }

      // Set gamma_v/pi_v/rho_v decay on, except channels close to threshold.
      pythia.readString("4900022:mayDecay = true");
      pythia.readString("4900022:onMode = on");
      pythia.readString("4900111:onMode = on");
      pythia.readString("4900113:onMode = on");
      if (mgampiv < 11.0) {
        pythia.readString("4900022:offIfAny = 5");
        pythia.readString("4900111:offIfAny = 5");
        pythia.readString("4900113:offIfAny = 5");
      }
      if (mgampiv < 4.0) {
        pythia.readString("4900022:offIfAny = 4");
        pythia.readString("4900111:offIfAny = 4");
        pythia.readString("4900113:offIfAny = 4");
      }
      if (mgampiv < 3.6) {
        pythia.readString("4900022:offIfAny = 15");
        pythia.readString("4900111:offIfAny = 15");
        pythia.readString("4900113:offIfAny = 15");
      }

      // Valley shower parameters.
      pythia.readString("HiddenValley:FSR = on");
      pythia.settings.mode("HiddenValley:alphaOrder", alphaHVorder);
      pythia.settings.parm("HiddenValley:alphaFSR", alphaHV);
      pythia.settings.parm("HiddenValley:pTminFSR", max(0.5, 0.1 * mgampiv));

      // Switch off the photons from ISR (from the PDF).
      pythia.settings.flag("PDF:lepton", allowISR);

      // Initialization for e+e- at selected CM energy.
      pythia.readString("Beams:idA = -11");
      pythia.readString("Beams:idB =  11");
      pythia.settings.parm("Beams:eCM", eCM);

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

      //------------------------------------------------------------------------

      // Histograms. Initialize event analysis.

      // Basic plots.
      Hist nBackH("number of decays back into SM", 50, -0.5, 49.5);
      Hist nNonDH("number of non-diagonal mesons", 50, -0.5, 49.5);
      Hist eBackH("total energy radiated back", 100, 0., eCM);
      Hist eSpBackH("energy spectrum of decays back", 100, 0., 0.5 * eCM);
      Hist theBackH("theta angle between decays back", 100, 0., M_PI);
      Hist theBackFH("theta angle between decays back, fast", 100, 0., M_PI);
      Hist theBackSH("theta angle between decays back, slow", 100, 0., M_PI);
      Hist pTmissH("missing pT", 100, 0., 0.5 * eCM);
      Hist eISRH("total energy in ISR photons", 100, 0., eCM);
      Hist mISRH("reduced mass after ISR photons", 100, 0., eCM);
      Hist nChargedH("charged multiplicity in detector", 100, -0.5, 99.5);

      // Lepton spectra.
      Hist eLeptonH("lepton energy spectum", 100, 0., 0.5 * eCM);
      Hist isolLeptonH("isolated lepton fraction (E)", 100, 0., 0.5 * eCM);
      Hist neLeptonH("neutral energy fraction to lepton", 100, 0., 1.0);
      Hist eelimLeptonH("eliminated lepton energy spectum", 100, 0., 0.5 * eCM);
      Hist ekeptLeptonH("kept lepton energy spectum", 100, 0., 0.5 * eCM);
      Hist mLeptonPairH("kept lepton pair mass spectrum", 100, 0., 2.5 * mgampiv);

      // Hadron and lepton spectra.
      Hist mHadH("invariant mass of non-leptons", 100, 0.,  2.5 * mgampiv);
      Hist mAllLowH("invariant mass of all, low range", 100, 0.,  2.5 * mgampiv);
      Hist mAllHighH("invariant mass of all, full range", 100, 0., eCM);

      // Jets with Jade algorithm.
      Hist nJetH("number of jets", 100, -0.5, 99.5);
      Hist mJetH("mass distribution of jets", 100, 0., 5. * mgampiv);
      Hist cosTheBeamAllH("cosTheta to beam, all jets", 100, 0., 1.);
      Hist cosTheBeamMassH("cosTheta to beam, massive jets", 100, 0., 1.);
      Hist thetaPairAllH("theta of jet pair, all jets", 100, 0., M_PI);
      Hist thetaPairMassH("theta of jet pair, massive jets", 100, 0., M_PI);
      Hist cosThePairAllH("cosTheta of jet pair, all jets", 100, -1., 1.);
      Hist cosThePairMassH("cosTheta of jet pair, massive jets", 100, -1., 1.);

      // Linearized sphericity and thrust.
      Hist spheriAllH("sphericity almost all events", 100, 0., 1.);
      Hist spheriMassH("sphericity massive events", 100, 0., 1.);
      Hist thrustAllH("thrust almost all events", 100, 0.5, 1.);
      Hist thrustMassH("thrust massive events", 100, 0.5, 1.);

      // Shorthand for some HV codes.
      int gammav = 4900022;
      int piv    = 4900111;
      int rhov   = 4900113;
      int pivND  = 4900211;
      int rhovND = 4900213;

      // Derived quantities for analysis.
      double cosTheBeam = cos(theBeam);
      double cosTheIsol = cos(theIsol);

      // Set up linearized Sphericity and Thrust.
      Sphericity sph(1.);
      Thrust thr;

      // Set up Jade jet finding and use m_{gamma_v/pi_v/rho_v} as scale.
      // Note: easy to switch to Durham instead.
      ClusterJet jade("Jade");
      double mJade   = mgampiv;
      double mJetMin = 0.5 * mgampiv;

      //------------------------------------------------------------------------

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

        // Generate events.
        if (!pythia.next()) continue;

        // First analysis of event, searching for gammav/piv/rhov.
        int    nBack = 0;
        double eBack = 0.;
        vector<int> iBack;
        int    nNonD = 0;
        Vec4 pSumVis = 0.;
        Vec4 pISR    = 0.;
        for (int i = 0; i < event.size(); ++i) {
          int id = event[i].id();
          bool isgampirho  = (id == gammav || id == piv || id == rhov);
          bool noCopyBelow = (event[i].isFinal()
            || (event[i].daughter1() != event[i].daughter2()));

          // Histogram gammav/piv/rhov. Sum energy in ISR photons.
          if (isgampirho && noCopyBelow) {
            ++nBack;
            iBack.push_back(i);
            double eNow = event[i].e();
            eBack += eNow;
            eSpBackH.fill( eNow);
          }
          if (abs(id) == pivND || abs(id) == rhovND) ++nNonD;
          if (event[i].isFinal() && event[i].isVisible())
            pSumVis += event[i].p();
          if (id == 22 && (event[i].status() == 43 || event[i].status() == 44
            || event[i].status() == 62 || event[i].status() == 63))
            pISR += event[i].p();

        // End of particle loop. Histogram properties.
        }
        nBackH.fill( nBack);
        eBackH.fill( eBack);
        nNonDH.fill( nNonD);
        double eSep = 5. * mgampiv;
        for (int j1 = 0; j1 < nBack; ++j1)
        for (int j2 = j1 + 1; j2 < nBack; ++j2) {
          Vec4 p1 = event[iBack[j1]].p();
          Vec4 p2 = event[iBack[j2]].p();
          double the12 = theta( p1, p2);
          theBackH.fill( the12 );
          if (p1.e() > eSep && p2.e() > eSep) theBackFH.fill( the12 );
          if (p1.e() < eSep && p2.e() < eSep) theBackSH.fill( the12 );
        }
        pTmissH.fill( pSumVis.pT());
        eISRH.fill( pISR.e());
        mISRH.fill( (Vec4( 0., 0., 0., eCM) - pISR).mCalc() );

        // Remove particles near beampipe.
        double cosTheNow;
        for (int i = 0; i < event.size(); ++i) if (event[i].isFinal()) {
          cosTheNow = abs(event[i].pz()) / max(PABSMIN, event[i].pAbs());
          if (cosTheNow > cosTheBeam) event[i].statusNeg();
        }

        // Charged multiplicity in detector.
        int nCharged = 0;
        for (int i = 0; i < event.size(); ++i)
          if (event[i].isFinal() && event[i].isCharged()) ++nCharged;
        nChargedH.fill( nCharged);

        // Find all final leptons (e+-, mu+-) in an event.
        vector<int>  iLepton;
        vector<bool> isIsol;
        vector<bool> isKept;
        for (int i = 0; i < event.size(); ++i) if ( event[i].isFinal()
        && (event[i].idAbs() == 11 || event[i].idAbs() == 13) ) {
          iLepton.push_back(i);
          isKept.push_back(true);

          // Number of charged and neutral particles in cone around each.
          int nChIsol = 0;
          vector<int> iNeutral;
          double eLepton = event[i].e();
          double eIsol   = 0.;
          for (int j = 0; j < event.size(); ++j)
            if (j != i && event[j].isFinal() && event[i].pAbs2() != 0
              && event[j].pAbs2() != 0
              && costheta( event[i].p(), event[j].p() ) > cosTheIsol) {
            if (event[j].isCharged()) ++nChIsol;
            else {
              iNeutral.push_back(j);
              eIsol += event[j].e();
            }
          }

          // Lepton isolated or not; for isolated resum any photons.
          bool isIsolNow = (nChIsol == 0 && eIsol < fracIsol * eLepton);
          isIsol.push_back( isIsolNow);
          if (isIsolNow)
          for (int j = 0; j < int(iNeutral.size()); ++j) {
            event[i].p() += event[iNeutral[j]].p();
            event[iNeutral[j]].statusNeg();
          }

          // Histogram lepton properties.
          eLeptonH.fill( eLepton);
          if (isIsolNow) isolLeptonH.fill( eLepton);
          neLeptonH.fill( eIsol / eLepton);
        }

        // For SM scenarios search out highest-energy e+ and e-.
        if (scenario == 3 || scenario == 4) {
          int iEpos = 0;
          int iEneg = 0;
          int jEpos = 0;
          int jEneg = 0;
          double eEpos = 0.;
          double eEneg = 0.;
          for (int j = 0; j < int(iLepton.size()); ++j) {
            int i = iLepton[j];
            if (event[i].id() == -11 && event[i].e() > eEpos) {
              iEpos = i;
              jEpos = j;
              eEpos = event[i].e();
            }
            if (event[i].id() == 11 && event[i].e() > eEneg) {
              iEneg = i;
              jEneg = j;
              eEneg = event[i].e();
            }
          }

          // Eliminate them from the event record and mark in lepton list.
          if (iEpos > 0) {
            event[iEpos].statusNeg();
            isKept[jEpos] = false;
            eelimLeptonH.fill( eEpos);
          }
          if (iEneg > 0) {
            event[iEneg].statusNeg();
            isKept[jEneg] = false;
            eelimLeptonH.fill( eEneg);
          }
        }

        // Energy of remaining leptons and mass spectrum of pairs.
        for (int j1 = 0; j1 < int(iLepton.size()); ++j1) if (isKept[j1]) {
          int i1 = iLepton[j1];
          ekeptLeptonH.fill( event[i1].e() );
          for (int j2 = j1 + 1; j2 < int(iLepton.size()); ++j2) if (isKept[j2]) {
            int i2 = iLepton[j2];
            if (event[i1].id() + event[i2].id() == 0) {
              double mPair = (event[i1].p() + event[i2].p()).mCalc();
              mLeptonPairH.fill( mPair );
            }
          }
        }

        // Momentum sum of all visible and of non-leptons.
        Vec4 pSumHad, pSumAll;
        int nSumHad = 0;
        int nSumAll = 0;
        for (int i = 0; i < event.size(); ++i)
        if (event[i].isFinal() && event[i].isVisible()) {
          pSumAll += event[i].p();
          ++nSumAll;
          if (event[i].idAbs() != 11 && event[i].idAbs() != 13) {
            pSumHad += event[i].p();
            ++nSumHad;
          }
        }
        double mSumAll = pSumAll.mCalc();
        if (nSumHad > 0) mHadH.fill( pSumHad.mCalc() );
        if (nSumAll > 0) mAllLowH.fill( mSumAll );
        if (nSumAll > 0) mAllHighH.fill( mSumAll );

        // Cluster jets and study angles to beam axis and between jets.
        double mJ, mK, costheBeam, thetaPair, cosThePair;
        if ( mSumAll > mJetMin && jade.analyze(event, 0., mJade, 1, 0) ) {
          nJetH.fill( jade.size());
          for (int j = 0; j < jade.size(); ++j) {
            mJ = jade.p(j).mCalc();
            mJetH.fill( mJ);
            costheBeam = abs( jade.p(j).pz()) / max(PABSMIN, jade.p(j).pAbs());
            cosTheBeamAllH.fill( costheBeam);
            if (mJ > mJetMin) cosTheBeamMassH.fill( costheBeam);
            for (int k = 0; k < j; ++k) {
              mK = jade.p(k).mCalc();
              thetaPair = theta( jade.p(j), jade.p(k));
              cosThePair = cos(thetaPair);
              thetaPairAllH.fill( thetaPair);
              if (mJ > mJetMin && mK > mJetMin) thetaPairMassH.fill( thetaPair);
              cosThePairAllH.fill( cosThePair);
              if (mJ > mJetMin && mK > mJetMin) cosThePairMassH.fill( cosThePair);
            }
          }
        }

        // Linearized Sphericity and Thrust analysis.
        if ( mSumAll > mJetMin) {

          // Bost to rest frame of system and check it worked.
          event.bst( -pSumAll.px()/pSumAll.e(), -pSumAll.py()/pSumAll.e(),
                     -pSumAll.pz()/pSumAll.e() );
          Vec4 pSumCheck;
          for (int i = 0; i < event.size(); ++i)
          if (event[i].isFinal() && event[i].isVisible())
            pSumCheck += event[i].p();
          if (pSumCheck.pAbs() > 0.1) cout << " Error: failed boost to rest "
            << pSumCheck;

          // Linearized sphericity.
          if (sph.analyze( event)) {
            spheriAllH.fill( sph.sphericity());
            if (mSumAll > 2. * mgampiv) spheriMassH.fill( sph.sphericity());
          }

          // Thrust.
          if (thr.analyze( event)) {
            thrustAllH.fill( thr.thrust());
            if (mSumAll > 2. * mgampiv) thrustMassH.fill( thr.thrust());
          }
        }

      // End of event loop.
      }

      // Statistics. Histograms.
      pythia.stat();
      isolLeptonH /= eLeptonH;
      cout << nNonDH << nBackH << eBackH << eSpBackH << theBackH << theBackFH
           << theBackSH << pTmissH << eISRH << mISRH << nChargedH
           << eLeptonH << isolLeptonH << neLeptonH << eelimLeptonH
           << ekeptLeptonH << mLeptonPairH << mHadH << mAllLowH
           << mAllHighH << nJetH << mJetH << cosTheBeamAllH
           << cosTheBeamMassH << thetaPairAllH << thetaPairMassH
           << cosThePairAllH << cosThePairMassH  << spheriAllH
           << spheriMassH << thrustAllH << thrustMassH;

      // Done.
      return 0;
    }