Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
// This is file contains the code to generate a dark sector shower in a strongly coupled, quasi-conformal hidden valley, often
// referred to as "soft unclustered energy patterns (SUEP)" or "softbomb" events. The shower is generated in its rest frame and
// for a realistic simulation this class needs to be interfaced with an event generator such as madgraph, pythia or herwig.
//
// The algorithm relies on arXiv:1305.5226. See arXiv:1612.00850 for a description of the model.
// Please cite both papers when using this code.
//
//
// Applicability:
// The ratio of the parameters m and T (m/T) should be an O(1) number. For m/T>>1 and m/T<<1 the theoretical description of the shower is not valid.
// The mass of the scalar which initiates the shower should be much larger than the mass of the mesons, in other words M>>m,T, by at least an order of magnitude.
//
// Written by Simon Knapen on 12/22/2019, following 1205.5226
// Adapted by Carlos Erice for CMSSW
#include "GeneratorInterface/Pythia8Interface/interface/SuepShower.h"

// constructor
SuepShower::SuepShower(double mass, double temperature, Pythia8::Rndm* rndmPtr) {
  edm::LogInfo("Pythia8Interface") << "Creating a SuepShower module";
  // Parameter setup
  darkmeson_mass_ = mass;
  fRndmPtr_ = rndmPtr;
  // No need to save temperature, as everything depends on it through m/T
  mass_over_T_ = darkmeson_mass_ / temperature;

  // 128 bit numerical precision, i.e. take machine precision
  tolerance_ = boost::math::tools::eps_tolerance<double>(128);

  // Median momentum
  p_m_ = sqrt(2 / (mass_over_T_ * mass_over_T_) * (1 + sqrt(1 + mass_over_T_ * mass_over_T_)));
  double pmax = sqrt(2 + 2 * sqrt(1 + mass_over_T_ * mass_over_T_)) / mass_over_T_;
  // Find the two cases where f(p)/f(pmax) = 1/e, given MB distribution, one appears at each side of pmax always
  p_plus_ = (boost::math::tools::bisect(
                 boost::bind(&SuepShower::logTestFunction, this, boost::placeholders::_1), pmax, 50.0, tolerance_))
                .first;  // first root
  p_minus_ = (boost::math::tools::bisect(
                  boost::bind(&SuepShower::logTestFunction, this, boost::placeholders::_1), 0.0, pmax, tolerance_))
                 .first;  // second root
  // Define the auxiliar quantities for the random generation of momenta
  lambda_plus_ = -fMaxwellBoltzmann(p_plus_) / fMaxwellBoltzmannPrime(p_plus_);
  lambda_minus_ = fMaxwellBoltzmann(p_minus_) / fMaxwellBoltzmannPrime(p_minus_);
  q_plus_ = lambda_plus_ / (p_plus_ - p_minus_);
  q_minus_ = lambda_minus_ / (p_plus_ - p_minus_);
  q_m_ = 1 - (q_plus_ + q_minus_);
}

SuepShower::~SuepShower() {}

// Generate a shower in the rest frame of the mediator
std::vector<Pythia8::Vec4> SuepShower::generateShower(double energy) {
  mediator_energy_ = energy;
  std::vector<Pythia8::Vec4> shower;
  double shower_energy = 0.0;

  // Fill up shower record
  while (shower_energy < mediator_energy_ || shower.size() < 2) {
    shower.push_back(generateFourVector());
    shower_energy += (shower.back()).e();
  }

  // Reballance momenta to ensure conservation
  // Correction starts at 0
  Pythia8::Vec4 correction = Pythia8::Vec4(0., 0., 0., 0.);
  for (const auto& daughter : shower) {
    correction = correction + daughter;
  }
  correction = correction / shower.size();
  // We only want to correct momenta first
  correction.e(0);
  for (auto& daughter : shower) {
    daughter = daughter - correction;
  }

  // With momentum conserved, balance energy. scale is the multiplicative factor needed such that sum_daughters((scale*p)^2+m^2) = E_parent, i.e. energy is conserved
  double scale;
  double minscale = 0.0;
  double maxscale = 2.0;
  while (SuepShower::reballanceFunction(minscale, shower) * SuepShower::reballanceFunction(maxscale, shower) > 0) {
    minscale = maxscale;
    maxscale *= 2;
  }

  scale =
      (boost::math::tools::bisect(boost::bind(&SuepShower::reballanceFunction, this, boost::placeholders::_1, shower),
                                  minscale,
                                  maxscale,
                                  tolerance_))
          .first;

  for (auto& daughter : shower) {
    daughter.px(daughter.px() * scale);
    daughter.py(daughter.py() * scale);
    daughter.pz(daughter.pz() * scale);
    // Force everything to be on-shell
    daughter.e(sqrt(daughter.pAbs2() + darkmeson_mass_ * darkmeson_mass_));
  }
  return shower;
}

//////// Private Methods ////////
// Maxwell-boltzman distribution, slightly massaged
const double SuepShower::fMaxwellBoltzmann(double p) {
  return p * p * exp(-mass_over_T_ * p * p / (1 + sqrt(1 + p * p)));
}

// Derivative of maxwell-boltzmann
const double SuepShower::fMaxwellBoltzmannPrime(double p) {
  return exp(-mass_over_T_ * p * p / (1 + sqrt(1 + p * p))) * p * (2 - mass_over_T_ * p * p / sqrt(1 + p * p));
}

// Test function to be solved for p_plus_ and p_minus_
const double SuepShower::logTestFunction(double p) { return log(fMaxwellBoltzmann(p) / fMaxwellBoltzmann(p_m_)) + 1.0; }

// Generate one random 4 std::vector from the thermal distribution
const Pythia8::Vec4 SuepShower::generateFourVector() {
  double en, phi, theta, momentum;  //kinematic variables of the 4 std::vector

  // First do momentum, following naming of arxiv:1305.5226
  double U, V, X, Y, E;
  int i = 0;
  while (i < 100) {
    // Very rarely (<1e-5 events) takes more than one loop to converge, set to 100 for safety
    U = fRndmPtr_->flat();
    V = fRndmPtr_->flat();

    if (U < q_m_) {
      Y = U / q_m_;
      X = (1 - Y) * (p_minus_ + lambda_minus_) + Y * (p_plus_ - lambda_plus_);
      if (V < fMaxwellBoltzmann(X) / fMaxwellBoltzmann(p_m_) && X > 0) {
        break;
      }
    } else {
      if (U < q_m_ + q_plus_) {
        E = -log((U - q_m_) / q_plus_);
        X = p_plus_ - lambda_plus_ * (1 - E);
        if (V < exp(E) * fMaxwellBoltzmann(X) / fMaxwellBoltzmann(p_m_) && X > 0) {
          break;
        }
      } else {
        E = -log((U - (q_m_ + q_plus_)) / q_minus_);
        X = p_minus_ + lambda_minus_ * (1 - E);
        if (V < exp(E) * fMaxwellBoltzmann(X) / fMaxwellBoltzmann(p_m_) && X > 0) {
          break;
        }
      }
    }
  }
  // X is the dimensionless momentum, p/m
  momentum = X * darkmeson_mass_;

  // now do the angles, isotropic
  phi = 2.0 * M_PI * (fRndmPtr_->flat());
  theta = acos(2.0 * (fRndmPtr_->flat()) - 1.0);

  // compose the 4 std::vector
  en = sqrt(momentum * momentum + darkmeson_mass_ * darkmeson_mass_);
  Pythia8::Vec4 daughterFourMomentum =
      Pythia8::Vec4(momentum * cos(phi) * sin(theta), momentum * sin(phi) * sin(theta), momentum * sin(theta), en);
  return daughterFourMomentum;
}

// Auxiliary function, to be solved in order to impose energy conservation
// To ballance energy, we solve for "scale" by demanding that this function vanishes, i.e. that shower energy and mediator energy are equal
// By rescaling the momentum rather than the energy, avoid having to do these annoying rotations from the previous version
const double SuepShower::reballanceFunction(double scale, const std::vector<Pythia8::Vec4>& shower) {
  double showerEnergy = 0.0;
  for (const auto& daughter : shower) {
    showerEnergy += sqrt(scale * scale * daughter.pAbs2() + darkmeson_mass_ * darkmeson_mass_);
  }
  return showerEnergy - mediator_energy_;
}