]> git.uio.no Git - u/mrichter/AliRoot.git/blobdiff - PYTHIA8/pythia8170/src/HadronScatter.cxx
Update to pythi8.170
[u/mrichter/AliRoot.git] / PYTHIA8 / pythia8170 / src / HadronScatter.cxx
diff --git a/PYTHIA8/pythia8170/src/HadronScatter.cxx b/PYTHIA8/pythia8170/src/HadronScatter.cxx
new file mode 100644 (file)
index 0000000..651e30b
--- /dev/null
@@ -0,0 +1,1144 @@
+// HadronScatter.cc is a part of the PYTHIA event generator.
+// Copyright (C) 2012 Torbjorn Sjostrand.
+// PYTHIA is licenced under the GNU GPL version 2, see COPYING for details.
+// Please respect the MCnet Guidelines, see GUIDELINES for details.
+
+#include "HadronScatter.h"
+
+namespace Pythia8 {
+
+//==========================================================================
+
+// The SigmaPartialWave class
+//  Reads in tables of partial wave data to provide dSigma/dCos(theta)
+//  The generic classes of process are:
+//    process = 0 (pi-pi), 1 (pi-K), 2 (pi-N)
+//  Subprocesses are defined (along with isospin coefficients) in:
+//    setupSubprocesses();
+//  Individual subprocesses are selected using:
+//    setSubprocess(subprocess); or setSubprocess(PDG1, PDG2);
+//  Internally, there are two std::map's, to convert between:
+//    subprocess <==> PDG1, PDG2
+//
+//  Data are read in from files:
+//   Lines starting with a '#' are comments
+//   Lines starting with 'set' provide options:
+//    set eType   [Wcm | Tlab] - energy bins in Wcm or Tlab
+//    set eUnit   [MeV | GeV]  - energy unit
+//    set input   [eta,delta | Sn,delta  | Tr,Ti | mod,phi ]
+//                             - format of columns in partial waves
+//    set dUnit   [deg | rad]  - unit of phase shifts
+//    set norm    [0 | 1]      - normalisation
+//   Column headers give L,2I[,2J] (2J for e.g. piN)
+//   Input types: Sn,delta -> Sn = 1 - eta^2
+//                mod,phi  -> amplitude T_L = |T_L| exp(i phi_L)
+//   Normalisation: 0 -> dSigma/dOmega = 1 / k^2 |T_L|^2
+//                  1 -> dSigma/dOmega = 16 / s  |T_L|^2
+//
+//  Internally data is stored as (J = 0 for spinless):
+//   pwData[L * LSHIFT + 2I * ISHIFT + J][energy_bin_centre] = T
+//  where the energy is Wcm in GeV.
+//
+//  This is stored using std::map's, to take into account that not all
+//  L,I,J states are always present (e.g. negligable contributions or
+//  conservation rules) and that bin sizes are not fixed.
+//
+//  Re[T] and Im[T] are interpolated between bins and extrapolated down to
+//  threshold from the first two bins. Above energy_bin_centre of the final
+//  bin, no extrapolation is done and the final bin value is always used.
+//
+//  A simple scheme to provide correct distributions for cos(theta) at a
+//  given CM energy is included. Efficiency is not too bad, but can likely
+//  be greatly improved.
+// 
+//  For each subprocess, a grid in bins of Wcm and cos(theta) is setup with:
+//    setupGrid();
+//  The size of the grid is set by the constants:
+//    const double SigmaPartialWave::WCMBIN;
+//    const double SigmaPartialWave::CTBIN;
+//  For each bin of (Wcm, ct), the maximum sigma elastic is found by
+//  splitting this bin into subbins multiple times, controlled by:
+//    const int SigmaPartialWave::SUBBIN;
+//    const int SigmaPartialWave::ITER
+//  With the final maxium sigma elastic given by this value multipled by
+//  a safety factor:
+//    const double SigmaPartialWave::GRIDSAFETY
+//
+//  To pick values of cos(theta) for a given CM energy, a:
+//    pickCosTheta(Wcm);
+//  function is provided. The above grid is used as an overestimate, to
+//  pick properly distributed values of cos(theta).
+
+//--------------------------------------------------------------------------
+
+// Constants
+
+// pwData[L * LSHIFT + 2I * ISHIFT + J]
+const int     SigmaPartialWave::LSHIFT     = 1000000;
+const int     SigmaPartialWave::ISHIFT     = 1000;
+
+// Convert GeV^-2 to mb
+const double  SigmaPartialWave::CONVERT2MB = 0.389380;
+
+// Size of bin in Wcm and cos(theta)
+const double  SigmaPartialWave::WCMBIN     = 0.005;
+const double  SigmaPartialWave::CTBIN      = 0.2;
+// Number of subbins and iterations
+const int     SigmaPartialWave::SUBBIN     = 2;
+const int     SigmaPartialWave::ITER       = 2;
+// Safety value to add on to grid maxima
+const double  SigmaPartialWave::MASSSAFETY = 0.001;
+const double  SigmaPartialWave::GRIDSAFETY = 0.05;
+
+
+//--------------------------------------------------------------------------
+
+// Perform initialization and store pointers.
+
+bool SigmaPartialWave::init(int processIn, string xmlPath, string filename,
+                            Info *infoPtrIn, ParticleData *particleDataPtrIn,
+                            Rndm *rndmPtrIn) {
+  // Store incoming pointers
+  infoPtr         = infoPtrIn;
+  particleDataPtr = particleDataPtrIn;
+  rndmPtr         = rndmPtrIn;
+
+  // Check incoming process is okay
+  if (processIn < 0 || processIn > 2) {
+    infoPtr->errorMsg("Error in SigmaPartialWave::init: "
+      "unknown process");
+    return false;
+  }
+  process = processIn;
+
+  // Setup subprocesses and isospin coefficients
+  setupSubprocesses();
+  setSubprocess(0);
+
+  // Read in partial-wave data
+  if (!readFile(xmlPath, filename)) return false;
+
+  // Setup vector for Legendre polynomials
+  PlVec.resize(Lmax);
+  if (Lmax > 0) PlVec[0] = 1.;
+  // And derivatives if needed
+  if (process == 2) {
+    PlpVec.resize(Lmax);
+    if (Lmax > 0) PlpVec[0] = 0.;
+    if (Lmax > 1) PlpVec[1] = 1.;
+  }
+
+  // Setup grid for integration
+  setupGrid();
+
+  return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Read input data file
+
+bool SigmaPartialWave::readFile(string xmlPath, string filename) {
+  // Create full path and open file
+  string fullPath = xmlPath + filename;
+  ifstream ifs(fullPath.c_str());
+  if (!ifs.good()) {
+    infoPtr->errorMsg("Error in SigmaPartialWave::init: "
+      "could not read data file");
+    return false;
+  }
+
+  // Default unit settings
+  int eType = 0;  // 0 = Wcm, 1 = Tlab
+  int eUnit = 0;  // 0 = GeV, 1 = MeV
+  int input = 0;  // 0 = eta, delta; 1 = Sn, delta (Sn = 1 - eta^2);
+                  // 2 = Treal, Tim, 3 = mod, phi
+  int dUnit = 0;  // 0 = deg, 1 = rad
+      norm  = 0;  // 0 = standard, 1 = sqrt(s) / sqrt(s - 4Mpi^2)
+
+  // Once we have a header line, each column corresponds to
+  // values of L, I and J.
+  Lmax = Imax = 0;
+  binMax = 0.;
+  vector < int > Lvec, Ivec, Jvec;
+
+  // Parse the file
+  string line;
+  while (ifs.good()) {
+    // Get line, convert to lowercase and strip leading whitespace
+    getline(ifs, line);
+    for (unsigned int i = 0; i < line.length(); i++)
+      line[i] = tolower(line[i]);
+    string::size_type startPos = line.find_first_not_of("  ");
+    if (startPos != string::npos) line = line.substr(startPos);
+    // Skip blank lines and lines that start with '#'
+    if (line.length() == 0 || line[0] == '#') continue;
+
+    // Tokenise line on whitespace (spaces or tabs)
+    string lineT = line;
+    vector < string > token;
+    while (true) {
+      startPos = lineT.find_first_of("  ");
+      token.push_back(lineT.substr(0, startPos));
+      if (startPos == string::npos) break;
+      startPos = lineT.find_first_not_of("      ", startPos + 1);
+      if (startPos == string::npos) break;
+      lineT = lineT.substr(startPos);
+    }
+
+    // Settings
+    if (token[0] == "set") {
+      bool badSetting = false;
+
+      // eType
+      if        (token[1] == "etype") {
+        if      (token[2] == "wcm")       eType = 0;
+        else if (token[2] == "tlab")      eType = 1;
+        else    badSetting = true;
+
+      // eUnit
+      } else if (token[1] == "eunit") {
+        if      (token[2] == "gev")       eUnit = 0;
+        else if (token[2] == "mev")       eUnit = 1;
+        else    badSetting = true;
+
+      // input
+      } else if (token[1] == "input") {
+        if      (token[2] == "eta,delta") input = 0;
+        else if (token[2] == "sn,delta")  input = 1;
+        else if (token[2] == "tr,ti")     input = 2;
+        else if (token[2] == "mod,phi")   input = 3;
+        else    badSetting = true;
+
+      // dUnit
+      } else if (token[1] == "dunit") {
+        if      (token[2] == "deg")       dUnit = 0;
+        else if (token[2] == "rad")       dUnit = 1;
+        else    badSetting = true;
+
+      // norm
+      } else if (token[1] == "norm") {
+        if      (token[2] == "0")         norm = 0;
+        else if (token[2] == "1")         norm = 1;
+        else    badSetting = true;
+      }
+
+      // Bad setting
+      if (badSetting) {
+        infoPtr->errorMsg("Error in SigmaPartialWave::init: "
+          "bad setting line");
+        return false;
+      }
+      continue;
+    }
+
+    // Header line
+    if (line.substr(0, 1).find_first_of("0123456789.") != 0) {
+      // Clear current stored L,2I,2J values
+      Lvec.clear(); Ivec.clear(); Jvec.clear();
+
+      // Parse header
+      bool badHeader = false;
+      for (unsigned int i = 1; i < token.size(); i++) {
+        // Extract L
+        startPos = token[i].find_first_of(",");
+        if (startPos == string::npos) { badHeader = true; break; }
+        string Lstr = token[i].substr(0, startPos);
+        token[i] = token[i].substr(startPos + 1);
+        // Extract 2I
+        string Istr;
+        startPos = token[i].find_first_of(",    ");
+        if (startPos == string::npos) {
+          Istr = token[i];
+          token[i] = "";
+        } else {
+          Istr = token[i].substr(0, startPos);
+          token[i] = token[i].substr(startPos + 1);
+        }
+        // Extract 2J
+        string Jstr("0");
+        if (token[i].length() != 0) Jstr = token[i];
+
+        // Convert to integers and store
+        int L, I, J;
+        stringstream Lss(Lstr); Lss >> L;
+        stringstream Iss(Istr); Iss >> I;
+        stringstream Jss(Jstr); Jss >> J;
+        if (Lss.fail() || Iss.fail() || Jss.fail())
+          { badHeader = true; break; }
+        Lvec.push_back(L);
+        Ivec.push_back(I);
+        Jvec.push_back(J);
+        Lmax = max(Lmax, L);
+        Imax = max(Imax, I);
+      }
+      if (badHeader) {
+        infoPtr->errorMsg("Error in SigmaPartialWave::init: "
+          "malformed header line");
+        return false;
+      }
+
+    // Data line
+    } else {
+      bool badData = false;
+
+      // Check there are the correct number of columns
+      if (token.size() != 2 * Lvec.size() + 1) badData = true;
+
+      // Extract energy
+      double eNow = 0.;
+      if (!badData) {
+        stringstream eSS(token[0]);
+        eSS >> eNow;
+        if (eSS.fail()) badData = true;
+        // Convert to GeV if needed
+        if (eUnit == 1) eNow *= 1e-3;
+        // Convert to Wcm if needed
+        if (eType == 1) eNow = sqrt(2. * mB * eNow + pow2(mA + mB));
+        binMax = max(binMax, eNow);
+      }
+
+      // Extract eta/phase shifts
+      if (!badData) {
+        for (unsigned int i = 1; i < token.size(); i += 2) {
+          // L,2I,2J
+          int LIJidx = (i - 1) / 2;
+          int L = Lvec[LIJidx];
+          int I = Ivec[LIJidx];
+          int J = Jvec[LIJidx];
+
+          double i1, i2;
+          stringstream i1SS(token[i]);     i1SS >> i1;
+          stringstream i2SS(token[i + 1]); i2SS >> i2;
+          if (i1SS.fail() || i2SS.fail()) { badData = true; break; }
+
+          // Sn to eta
+          if (input == 1) i1 = sqrt(1. - i1);
+          // Degrees to radians
+          if ((input == 0 || input == 1 || input == 3) &&
+              dUnit == 0) i2 *= M_PI / 180.; 
+          
+          // Convert to Treal and Timg
+          complex T(0., 0.);
+          if (input == 0 || input == 1) {
+            T = (i1 * exp(2. * complex(0., 1.) * i2) - 1.) /
+                2. / complex(0., 1.);
+          } else if (input == 2) {
+            T = complex(i1, i2);
+          } else if (input == 3) {
+            T = i1 * exp(complex(0., 1.) * i2);
+          }
+
+          // Store
+          pwData[L * LSHIFT + I * ISHIFT + J][eNow] = T;
+        }
+      }
+      if (badData) {
+        infoPtr->errorMsg("Error in SigmaPartialWave::init: "
+          "malformed data line");
+        return false;
+      }
+    }
+  }
+
+  // Make sure it was EOF that caused us to end
+  if (!ifs.eof()) { ifs.close(); return false; }
+
+  // Maximum values of L and I
+  Lmax++; Imax++;
+
+  return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Setup isospin coefficients and subprocess mapping
+
+void SigmaPartialWave::setupSubprocesses() {
+
+  // Setup isospin coefficients
+  switch (process) {
+  // pi-pi
+  case 0:
+    // Map subprocess to incoming
+    subprocessMax = 6;
+    sp2in[0] = pair < int, int > ( 211,  211);
+    sp2in[1] = pair < int, int > ( 211, -211);
+    sp2in[2] = pair < int, int > ( 211,  111);
+    sp2in[3] = pair < int, int > ( 111,  111);
+    sp2in[4] = pair < int, int > (-211,  111);
+    sp2in[5] = pair < int, int > (-211, -211);
+    // Incoming to subprocess
+    for (int i = 0; i < subprocessMax; i++) in2sp[sp2in[i]] = i;
+    // Isospin coefficients
+    isoCoeff[0][0] = 0.;    isoCoeff[0][2] = 0.;    isoCoeff[0][4] = 1.;
+    isoCoeff[1][0] = 1./3.; isoCoeff[1][2] = 1./2.; isoCoeff[1][4] = 1./6.;
+    isoCoeff[2][0] = 0.;    isoCoeff[2][2] = 1./2.; isoCoeff[2][4] = 1./2.;
+    isoCoeff[3][0] = 1./3.; isoCoeff[3][2] = 0.;    isoCoeff[3][4] = 2./3.;
+    isoCoeff[4][0] = 0.;    isoCoeff[4][2] = 1./2.; isoCoeff[4][4] = 1./2.;
+    isoCoeff[5][0] = 0.;    isoCoeff[5][2] = 0.;    isoCoeff[5][4] = 1.;
+
+    break;
+
+  // pi-K and pi-N
+  case 1: case 2:
+    int id1, id2;
+    if (process == 1) { id1 = 321;  id2 = 311;  }
+    else              { id1 = 2212; id2 = 2112; }
+    
+    // Map subprocess to incoming
+    subprocessMax = 12;
+    sp2in[0] = pair < int, int > ( 211, id1);
+    sp2in[1] = pair < int, int > ( 211, id2);
+    sp2in[2] = pair < int, int > ( 111, id1);
+    sp2in[3] = pair < int, int > ( 111, id2);
+    sp2in[4] = pair < int, int > (-211, id1);
+    sp2in[5] = pair < int, int > (-211, id2);
+    // Isospin coefficients
+    isoCoeff[0][1]  = 0.;      isoCoeff[0][3]  = 1.;
+    isoCoeff[1][1]  = 2. / 3.; isoCoeff[1][3]  = 1. / 3.;
+    isoCoeff[2][1]  = 1. / 3.; isoCoeff[2][3]  = 2. / 3.;
+    isoCoeff[3][1]  = 1. / 3.; isoCoeff[3][3]  = 2. / 3.;
+    isoCoeff[4][1]  = 2. / 3.; isoCoeff[4][3]  = 1. / 3.;
+    isoCoeff[5][1]  = 0.;      isoCoeff[5][3]  = 1.;
+    // Antiparticles
+    for (int i = 0; i < 6; i++) {
+      id1 = ((sp2in[i].first == 111) ? +1 : -1) * sp2in[i].first;
+      sp2in[i + 6] = pair < int, int > (id1, -sp2in[i].second);
+      isoCoeff[i + 6] = isoCoeff[i];
+    }
+    // Map incoming to subprocess
+    for (int i = 0; i < subprocessMax; i++) in2sp[sp2in[i]] = i;
+
+    break;
+  }
+
+  return;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Setup grids for integration
+
+void SigmaPartialWave::setupGrid() {
+  // Reset sigma maximum
+  sigElMax = 0.;
+
+  // Go through each subprocess
+  gridMax.resize(subprocessMax);
+  gridNorm.resize(subprocessMax);
+  for (int sp = 0; sp < subprocessMax; sp++) {
+    // Setup subprocess
+    setSubprocess(sp);
+
+    // Bins in Wcm
+    int nBin1 = int( (binMax - mA - mB) / WCMBIN );
+    gridMax[subprocess].resize(nBin1);
+    gridNorm[subprocess].resize(nBin1);
+    for (int n1 = 0; n1 < nBin1; n1++) {
+      // Bin lower and upper
+      double bl1 = mA + mB + double(n1) * WCMBIN;
+      double bu1 = bl1 + WCMBIN;
+
+      // Bins in cos(theta)
+      int    nBin2 = int( 2. / CTBIN );
+      gridMax[subprocess][n1].resize(nBin2);
+      for (int n2 = 0; n2 < nBin2; n2++) {
+        // Bin lower and upper
+        double bl2 = -1. + double(n2) * CTBIN;
+        double bu2 = bl2 + CTBIN;
+
+        // Find maximum
+        double maxSig = 0.;
+        double bl3 = bl1, bu3 = bu1, bl4 = bl2, bu4 = bu2;
+        for (int iter = 0; iter < ITER; iter++) {
+          int    i3Save = -1, i4Save = -1;
+          double step3 = (bu3 - bl3) / double(SUBBIN);
+          double step4 = (bu4 - bl4) / double(SUBBIN);
+          for (int i3 = 0; i3 <= SUBBIN; i3++) {
+            double Wcm = bl3 + double(i3) * step3;
+            for (int i4 = 0; i4 <= SUBBIN; i4++) {
+              double ct = bl4 + double(i4) * step4;
+              double ds = dSigma(Wcm, ct);
+              if (ds > maxSig) {
+                i3Save = i3;
+                i4Save = i4;
+                maxSig = ds;
+              }
+            }
+          }
+          // Set new min/max
+          if (i3Save == -1 && i4Save == -1) break;
+          if (i3Save > -1) {
+            bl3 = bl3 + ((i3Save == 0)      ? 0. : i3Save - 1.) * step3;
+            bu3 = bl3 + ((i3Save == SUBBIN) ? 1. : 2.)          * step3;
+          }
+          if (i4Save > -1) {
+            bl4 = bl4 + ((i4Save == 0)      ? 0. : i4Save - 1.) * step4;
+            bu4 = bl4 + ((i4Save == SUBBIN) ? 1. : 2.)          * step4;
+          }
+        } // for (iter)
+
+        // Save maximum value
+        gridMax[subprocess][n1][n2]  = maxSig * (1. + GRIDSAFETY);
+        gridNorm[subprocess][n1]    += maxSig * (1. + GRIDSAFETY) * CTBIN;
+        sigElMax = max(sigElMax, maxSig);
+
+      } // for (n2)
+    } // for (n1)
+  } // for (sp)
+
+  return;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Pick a cos(theta) value
+
+double SigmaPartialWave::pickCosTheta(double Wcm) {
+  // Find grid bin in Wcm
+  int WcmBin = int((Wcm - mA - mB) / WCMBIN);
+  if (WcmBin < 0) WcmBin = 0;
+  if (WcmBin >= int(gridMax[subprocess].size()))
+    WcmBin = int(gridMax[subprocess].size() - 1);
+  
+  // Pick a value of cos(theta)
+  double ct, wgt;
+
+  do {
+    // Sample from overestimate and inverse
+    double y   = rndmPtr->flat() * gridNorm[subprocess][WcmBin];
+    double sum = 0.;
+    int    ctBin;
+    for (ctBin = 0; ctBin < int(2. / CTBIN); ctBin++) {
+      if (sum + CTBIN * gridMax[subprocess][WcmBin][ctBin] > y) break;
+      sum += CTBIN * gridMax[subprocess][WcmBin][ctBin];
+    }
+
+    // Linear interpolation
+    double x1 = -1. + CTBIN * double(ctBin);
+    double y1 = sum;
+    double x2 = x1 + CTBIN;
+    double y2 = sum + CTBIN * gridMax[subprocess][WcmBin][ctBin];
+           ct = (x2 - x1) / (y2 - y1) * (y - y1) + x1;
+    wgt = dSigma(Wcm, ct) / gridMax[subprocess][WcmBin][ctBin];
+    if (wgt >= 1.) {
+      infoPtr->errorMsg("Warning in SigmaPartialWave::pickCosTheta: "
+        "weight above unity");
+      break;
+    }
+  } while (wgt <= rndmPtr->flat());
+
+  return ct;
+}
+
+//--------------------------------------------------------------------------
+
+// Set subprocess
+
+bool SigmaPartialWave::setSubprocess(int spIn) {
+  if (sp2in.find(spIn) == sp2in.end()) return false;
+  subprocess = spIn;
+  pair < int, int > in = sp2in[spIn];
+  idA = in.first;
+  mA  = particleDataPtr->m0(idA);
+  idB = in.second;
+  mB  = particleDataPtr->m0(idB);
+  return true;
+}
+
+bool SigmaPartialWave::setSubprocess(int idAin, int idBin) {
+  pair < int, int > in = pair < int, int > (idAin, idBin);
+  if (in2sp.find(in) == in2sp.end()) {
+    // Try the other way around as well
+    swap(in.first, in.second);
+    if (in2sp.find(in) == in2sp.end()) return false;
+  }
+  subprocess = in2sp[in];
+  idA = idAin;
+  mA  = particleDataPtr->m0(idA);
+  idB = idBin;
+  mB  = particleDataPtr->m0(idB);
+  return true;
+}
+
+//--------------------------------------------------------------------------
+
+// Calculate: mode = 0 (sigma elastic), 1 (sigma total), 2 (dSigma/dcTheta)
+
+double SigmaPartialWave::sigma(int mode, double Wcm, double cTheta) {
+  // Below threshold, return 0
+  if (Wcm < (mA + mB + MASSSAFETY)) return 0.;
+
+  // Return values
+  complex amp[2] = { complex(0., 0.) };
+  double  sig    = 0.;
+
+  // Kinematic variables
+  double s  = pow2(Wcm);
+  double k2 = (s - pow2(mB + mA)) * (s - pow2(mB - mA)) / 4. / s;
+
+  // Precompute all required Pl and Pl' values
+  double sTheta = 0.;
+  if (mode == 2) {
+    if (process == 2) sTheta = sqrt(1. - pow2(cTheta));
+    legendreP(cTheta, ((process == 2) ? true : false));
+  }
+
+  // Loop over L
+  for (int L = 0; L < Lmax; L++) {
+
+    // Loop over J (only J = 0 for spinless)
+    complex ampJ[2] = { complex(0., 0.) };
+    int Jstart = (process != 2) ? 0 : 2 * L - 1;
+    int Jend   = (process != 2) ? 1 : 2 * L + 2;
+    int Jstep  = (process != 2) ? 1 : 2;
+    int Jcount = 0;
+    for (int J = Jstart; J < Jend; J += Jstep, Jcount++) {
+
+      // Loop over isospin coefficients
+      for (int I = 0; I < Imax; I++) {
+        if (isoCoeff[subprocess][I] == 0.) continue;
+
+        // Check wave exists
+        int LIJ = L * LSHIFT + I * ISHIFT + J;
+        if (pwData.find(LIJ) == pwData.end()) continue;
+
+        // Extrapolation / interpolation (not for last bin)
+        map < double, complex >::iterator it = pwData[LIJ].upper_bound(Wcm);
+        if (it == pwData[LIJ].begin()) ++it;
+        double ar, ai;
+        if (it == pwData[LIJ].end()) {
+          ar = (--it)->second.real();
+          ai = it->second.imag();
+        } else {
+          double  eA   = it->first;
+          complex ampA = (it--)->second;
+          double  eB   = it->first;
+          complex ampB = it->second;
+
+          ar = (ampA.real() - ampB.real()) / (eA - eB) *
+               (Wcm - eB) + ampB.real();
+          ai = (ampA.imag() - ampB.imag()) / (eA - eB) *
+               (Wcm - eB) + ampB.imag();
+        }
+
+        // Isospin sum
+        ampJ[Jcount] += isoCoeff[subprocess][I] * complex(ar, ai);
+      }
+    }
+
+    // Partial wave sum. Sigma elastic
+    if (mode == 0) {
+      if        (process == 0 || process == 1) {
+        sig += (2. * L + 1.) * (ampJ[0] * conj(ampJ[0])).real();
+      } else if (process == 2) {
+        sig += ( (L + 0.) * (ampJ[0] * conj(ampJ[0])).real() +
+                 (L + 1.) * (ampJ[1] * conj(ampJ[1])).real() );
+      }
+
+    // Sigma total
+    } else if (mode == 1) {
+      if        (process == 0 || process == 1) {
+        sig += (2. * L + 1.) * ampJ[0].imag();
+      } else if (process == 2) {
+        sig += ( (L + 0.) * ampJ[0].imag() + (L + 1.) * ampJ[1].imag() );
+      }
+
+    // dSigma
+    } else if (mode == 2) {
+      if        (process == 0 || process == 1) {
+        amp[0] += (2. * L + 1.) * ampJ[0] * PlVec[L];
+      } else if (process == 2) {
+        amp[0] += ((L + 0.) * ampJ[0] + double(L + 1.) * ampJ[1]) * PlVec[L];
+        amp[1] += complex(0., 1.) * (ampJ[1] - ampJ[0]) * sTheta * PlpVec[L];
+      }
+    }
+
+  } // for (L)
+
+  // Normalisation and return
+  if (mode == 0 || mode == 1) {
+    if      (norm == 0)  sig *= 4.  * M_PI / k2 * CONVERT2MB;
+    else if (norm == 1)  sig *= 64. * M_PI / s  * CONVERT2MB;
+
+  } else if (mode == 2) {
+    sig = (amp[0] * conj(amp[0])).real() + (amp[1] * conj(amp[1])).real();
+    if      (norm == 0) sig *= 2.  * M_PI / k2 * CONVERT2MB;
+    else if (norm == 1) sig *= 32. * M_PI / s  * CONVERT2MB;
+  }
+  // Half for identical
+  return ((idA == idB) ? 0.5 : 1.) * sig;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Bonnet's recursion formula for Legendre polynomials and derivatives
+
+void SigmaPartialWave::legendreP(double ct, bool deriv) {
+  if (Lmax > 1) PlVec[1] = ct;
+  for (int L = 2; L < Lmax; L++) {
+    PlVec[L] = ( (2. * L - 1.) * ct * PlVec[L - 1] -
+                 (L - 1.) * PlVec[L - 2] ) / double(L);
+    if (deriv)
+      PlpVec[L] = ( (2. * L - 1.) * (PlVec[L - 1] + ct * PlpVec[L - 1]) -
+                    (L - 1.) * PlpVec[L - 2] ) / double(L);
+  }
+  return;
+}
+
+
+//==========================================================================
+
+// HadronScatter class
+
+//--------------------------------------------------------------------------
+
+// Perform initialization and store pointers.
+
+bool HadronScatter::init(Info* infoPtrIn, Settings& settings,
+                         Rndm *rndmPtrIn, ParticleData *particleDataPtr) {
+  // Save incoming pointers
+  infoPtr = infoPtrIn;
+  rndmPtr = rndmPtrIn;
+
+  // Main settings
+  doHadronScatter = settings.flag("HadronScatter:scatter");
+  afterDecay      = settings.flag("HadronScatter:afterDecay");
+  allowDecayProd  = settings.flag("HadronScatter:allowDecayProd");
+  scatterRepeat   = settings.flag("HadronScatter:scatterRepeat");
+  // Hadron selection
+  hadronSelect    = settings.mode("HadronScatter:hadronSelect");
+  Npar            = settings.parm("HadronScatter:N");
+  kPar            = settings.parm("HadronScatter:k");
+  pPar            = settings.parm("HadronScatter:p");
+  // Scattering probability
+  scatterProb     = settings.mode("HadronScatter:scatterProb");
+  jPar            = settings.parm("HadronScatter:j");
+  rMax            = settings.parm("HadronScatter:rMax");
+  rMax2           = rMax * rMax;
+  doTile          = settings.flag("HadronScatter:tile");
+
+  // String fragmentation and MPI settings
+  pTsigma         = 2.0 * settings.parm("StringPT:sigma");
+  pTsigma2        = pTsigma * pTsigma;
+  double pT0ref   = settings.parm("MultipartonInteractions:pT0ref");
+  double eCMref   = settings.parm("MultipartonInteractions:eCMref");
+  double eCMpow   = settings.parm("MultipartonInteractions:eCMpow");
+  double eCMnow   = infoPtr->eCM();
+  pT0MPI          = pT0ref * pow(eCMnow / eCMref, eCMpow);
+
+  // Tiling
+  double mp2 = particleDataPtr->m0(111) * particleDataPtr->m0(111);
+  double eA  = infoPtr->eA();
+  double eB  = infoPtr->eB();
+  double pzA =  sqrt(eA * eA - mp2);
+  double pzB = -sqrt(eB * eB - mp2);
+  yMax = 0.5 * log((eA + pzA) / (eA - pzA));
+  yMin = 0.5 * log((eB + pzB) / (eB - pzB));
+  // Size in y and phi
+  if (doTile) {
+    ytMax  = int((yMax - yMin) / rMax);
+    ytSize = (yMax - yMin) / double(ytMax);
+    ptMax  = int(2. * M_PI / rMax);
+    ptSize = 2. * M_PI / double(ptMax);
+  } else {
+    ytMax  = 1;
+    ytSize = yMax - yMin;
+    ptMax  = 1;
+    ptSize = 2. * M_PI;
+  }
+  // Initialise tiles
+  tile.resize(ytMax);
+  for (int yt = 0; yt < ytMax; yt++) tile[yt].resize(ptMax);
+
+  // Find path to data files, i.e. xmldoc directory location.
+  // Environment variable takes precedence, else use constructor input. 
+  // XXX - as in Pythia.cc, but not passed around in e.g. Info/Settings,
+  //       so redo here
+  string xmlPath = "";
+  const char* PYTHIA8DATA = "PYTHIA8DATA"; 
+  char* envPath = getenv(PYTHIA8DATA);
+  if (envPath != 0 && *envPath != '\0') {
+    int i = 0;
+    while (*(envPath+i) != '\0') xmlPath += *(envPath+(i++)); 
+  } else xmlPath = "../xmldoc";
+  if (xmlPath[ xmlPath.length() - 1 ] != '/') xmlPath += "/";
+
+  // Hadron scattering partial wave cross sections
+  if ( !sigmaPW[0].init(0, xmlPath, "pipi-Froggatt.dat",
+                        infoPtr, particleDataPtr, rndmPtr) ) return false;
+  if ( !sigmaPW[1].init(1, xmlPath, "piK-Estabrooks.dat",
+                        infoPtr, particleDataPtr, rndmPtr) ) return false;
+  if ( !sigmaPW[2].init(2, xmlPath, "piN-SAID-WI08.dat",
+                        infoPtr, particleDataPtr, rndmPtr) ) return false;
+  sigElMax = 0.;
+  sigElMax = max(sigElMax, sigmaPW[0].getSigmaElMax());
+  sigElMax = max(sigElMax, sigmaPW[1].getSigmaElMax());
+  sigElMax = max(sigElMax, sigmaPW[2].getSigmaElMax());
+
+  // DEBUG
+  debugOutput();
+
+  return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Debug output
+
+void HadronScatter::debugOutput() {
+  // Print settings
+  cout << "Hadron scattering:" << endl
+       << " scatter        = " << ((doHadronScatter) ? "on" : "off") << endl
+       << " afterDecay     = " << ((afterDecay)      ? "on" : "off") << endl
+       << " allowDecayProd = " << ((allowDecayProd)  ? "on" : "off") << endl
+       << " scatterRepeat  = " << ((scatterRepeat)   ? "on" : "off") << endl
+       << " tile           = " << ((doTile) ? "on" : "off") << endl
+       << "  yMin          = " << yMin << endl
+       << "  yMax          = " << yMax << endl
+       << "  ytMax         = " << ytMax << endl
+       << "  ytSize        = " << ytSize << endl
+       << "  ptMax         = " << ptMax << endl
+       << "  ptSize        = " << ptSize << endl
+       << endl
+       << " hadronSelect   = " << hadronSelect << endl
+       << "  N             = " << Npar << endl
+       << "  k             = " << kPar << endl
+       << "  p             = " << pPar << endl
+       << endl
+       << " scatterProb    = " << scatterProb << endl
+       << "  j             = " << jPar << endl
+       << "  rMax          = " << rMax << endl
+       << endl
+       << " pTsigma        = " << pTsigma2 << endl
+       << " pT0MPI         = " << pT0MPI << endl
+       << endl
+       << " sigElMax       = " << sigElMax << endl << endl;
+
+  return;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Perform hadron scattering
+
+void HadronScatter::scatter(Event& event) {
+  // Reset tiles
+  for (int yt = 0; yt < ytMax; yt++)
+    for (int pt = 0; pt < ptMax; pt++)
+      tile[yt][pt].clear();
+
+  // Generate list of hadrons which can take part in the scattering
+  for (int i = 0; i < event.size(); i++)
+    if (event[i].isFinal() && event[i].isHadron() && canScatter(event, i))
+      tile[yTile(event, i)][pTile(event, i)].insert(HSIndex(i, i));
+
+  // Generate all pairwise interaction probabilities
+  vector < HadronScatterPair > scatterList;
+  // For each tile and for each hadron in the tile do pairing
+  for (int pt1 = 0; pt1 < ptMax; pt1++)
+    for (int yt1 = 0; yt1 < ytMax; yt1++)
+      for (set < HSIndex >::iterator si1 = tile[yt1][pt1].begin();
+           si1 != tile[yt1][pt1].end(); si1++)
+        tileIntProb(scatterList, event, *si1, yt1, pt1, false);
+  // Sort by ordering measure (largest to smallest)
+  sort(scatterList.rbegin(), scatterList.rend());
+
+  // Reset list of things that have scattered
+  if (scatterRepeat) scattered.clear();
+
+  // Do scatterings
+  while (scatterList.size() > 0) {
+    // Check still valid and scatter
+    HadronScatterPair &hsp = scatterList[0];
+    if (!event[hsp.i1.second].isFinal() || !event[hsp.i2.second].isFinal()) {
+      scatterList.erase(scatterList.begin());
+      continue;
+    }
+    // Remove old entries in tiles and scatter
+    tile[hsp.yt1][hsp.pt1].erase(hsp.i1);
+    tile[hsp.yt2][hsp.pt2].erase(hsp.i2);
+    hadronScatter(event, hsp);
+    // Store new indices for later
+    HSIndex iNew1 = hsp.i1, iNew2 = hsp.i2;
+
+    // Check if hadrons can scatter again
+    bool resort = false;
+    if (scatterRepeat) {
+      // Check for new scatters of iNew1 and iNew2
+      HSIndex iNew = iNew1;
+      for (int i = 0; i < 2; i++) {
+        if (canScatter(event, iNew.second)) {
+          // If both can scatter again, make sure they can't scatter
+          // with each other
+          if (i == 1) scattered.insert(HSIndex(min(iNew1.first, iNew2.first),
+                                             max(iNew1.first, iNew2.first)));
+          int yt = yTile(event, iNew.second);
+          int pt = pTile(event, iNew.second);
+          resort = tileIntProb(scatterList, event, iNew, yt, pt, true);
+          tile[yt][pt].insert(iNew);
+        }
+        iNew = iNew2;
+      } // for (i)
+
+    } // if (scatterRepeat)
+
+    // Remove the old entry and onto next
+    scatterList.erase(scatterList.begin());
+    // Resort list if anything added
+    if (resort) sort(scatterList.rbegin(), scatterList.rend());
+
+  } // while (scatterList.size() > 0)
+
+  // Done.
+  return;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Criteria for if a hadron is allowed to scatter or not
+
+bool HadronScatter::canScatter(Event& event, int i) {
+  // Probability to accept
+  double p = 0.;
+
+  // Pions, K+, K-, p+, pbar- only
+  if (scatterProb == 1 || scatterProb == 2)
+    if (event[i].idAbs() != 111 && event[i].idAbs() != 211 &&
+        event[i].idAbs() != 321 && event[i].idAbs() != 2212) 
+      return false;
+
+  // Probability
+  switch (hadronSelect) {
+  case 0:
+    double t1 = exp( - event[i].pT2() / 2. / pTsigma2);
+    double t2 = pow(pT0MPI, pPar) /
+                pow(pT0MPI * pT0MPI + event[i].pT2(), pPar / 2.);
+    p = Npar * t1 / ( (1 - kPar) * t1 + kPar * t2 );
+    break;
+  }
+
+  // Return true/false
+  if (p > rndmPtr->flat()) return true;
+  else                     return false;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Probability for scattering
+bool HadronScatter::doesScatter(Event& event, const HSIndex &i1, 
+  const HSIndex &i2) {
+  Particle &p1 = event[i1.second];
+  Particle &p2 = event[i2.second];
+
+  // Can't come from the same decay
+  if (!allowDecayProd && event[i1.first].mother1() ==
+      event[i2.first].mother1() && event[event[i1.first].mother1()].isHadron())
+    return false;
+
+  // Check that the two hadrons have not already scattered
+  if (scatterRepeat &&
+      scattered.find(HSIndex(min(i1.first, i2.first), max(i1.first, i2.first)))
+      != scattered.end()) return false;
+
+  // K-K, p-p and K-p not allowed
+  int id1 = min(p1.idAbs(), p2.idAbs());
+  int id2 = max(p1.idAbs(), p2.idAbs());
+  if (scatterProb == 1 || scatterProb == 2) {
+    if ((id1 == 321 || id1 == 2212) && id1 == id2) return false;
+    if (id1 == 321 && id2 == 2212)                 return false;
+  }
+
+  // Distance in y - phi space
+  double dy  = p1.y() - p2.y(); 
+  double dp  = abs(p1.phi() - p2.phi());
+  if (dp > M_PI) dp = 2 * M_PI - dp;
+  double dr2 = dy * dy + dp * dp;
+  double p   = max(0., 1. - dr2 / rMax2);
+
+  // Additional factor depending on scatterProb
+  if (scatterProb == 0 || scatterProb == 1) {
+    p *= jPar;
+
+  // Additional pair dependent probability
+  } else if (scatterProb == 2) {
+    // Wcm and which partial wave amplitude to use
+    double Wcm = (p1.p() + p2.p()).mCalc();
+    int    pw = 0;
+    if      ((id1 == 111 || id1 == 211) && (id2 == 111 || id2 == 211)) pw = 0;
+    else if ((id1 == 111 || id1 == 211) && id2 == 321)                 pw = 1;
+    else if ((id1 == 111 || id1 == 211) && id2 == 2212)                pw = 2;
+    else {
+      infoPtr->errorMsg("Error in HadronScatter::doesScatter:"
+        "unknown subprocess");
+    }
+    if (!sigmaPW[pw].setSubprocess(p1.id(), p2.id())) {
+      infoPtr->errorMsg("Error in HadronScatter::doesScatter:"
+        "setSubprocess failed");
+    } else {
+      p *= 1 - exp(-jPar * sigmaPW[pw].sigmaEl(Wcm));
+    }
+  }
+
+  // Return true/false
+  return (p > rndmPtr->flat());
+}
+
+
+//--------------------------------------------------------------------------
+
+// Calculate ordering measure
+
+double HadronScatter::measure(Event& event, int idx1, int idx2) {
+  Particle &p1 = event[idx1];
+  Particle &p2 = event[idx2];
+  return abs(p1.pT() / p1.mT() - p2.pT() / p2.mT());
+}
+
+
+//--------------------------------------------------------------------------
+
+// Scatter a pair
+
+bool HadronScatter::hadronScatter(Event& event, HadronScatterPair &hsp) {
+  bool doSwap = (0.5 < rndmPtr->flat()) ? true : false;
+  if (doSwap) swap(hsp.i1, hsp.i2);
+
+  Particle &p1 = event[hsp.i1.second];
+  Particle &p2 = event[hsp.i2.second];
+
+  // Pick theta and phi (always flat)
+  double ct = 0., phi = 2 * M_PI * rndmPtr->flat();
+
+  // Flat for all flavours
+  if (scatterProb == 0 || scatterProb == 1) {
+    ct  = 2. * rndmPtr->flat() - 1.;
+
+  // pi-pi, pi-K and pi-p only using partial wave amplitudes
+  } else if (scatterProb == 2) {
+    // Wcm and which partial wave amplitude to use
+    int    id1 = min(p1.idAbs(), p2.idAbs());
+    int    id2 = max(p1.idAbs(), p2.idAbs());
+    double Wcm = (p1.p() + p2.p()).mCalc();
+    int    pw  = 0;
+    if      ((id1 == 111 || id1 == 211) && (id2 == 111 || id2 == 211)) pw = 0;
+    else if ((id1 == 111 || id1 == 211) && id2 == 321)                 pw = 1;
+    else if ((id1 == 111 || id1 == 211) && id2 == 2212)                pw = 2;
+    else {
+      infoPtr->errorMsg("Error in HadronScatter::hadronScatter:"
+        "unknown subprocess");
+    }
+    sigmaPW[pw].setSubprocess(p1.id(), p2.id());
+    ct = sigmaPW[pw].pickCosTheta(Wcm);
+  }
+
+  // Rotation
+  RotBstMatrix sMat;
+  sMat.toCMframe(p1.p(), p2.p());
+  sMat.rot(acos(ct), phi);
+  sMat.fromCMframe(p1.p(), p2.p());
+  Vec4 v1 = p1.p(), v2 = p2.p();
+  v1.rotbst(sMat);
+  v2.rotbst(sMat);
+
+  // Update event record
+  int iNew1 = event.copy(hsp.i1.second, 98);
+  event[iNew1].p(v1);
+  event[iNew1].e(event[iNew1].eCalc());
+  event[hsp.i1.second].statusNeg();
+  int iNew2 = event.copy(hsp.i2.second, 98);
+  event[iNew2].p(v2);
+  event[iNew2].e(event[iNew2].eCalc());
+  event[hsp.i2.second].statusNeg();
+
+  // New indices in event record
+  hsp.i1.second = iNew1;
+  hsp.i2.second = iNew2;
+
+  if (doSwap) swap(hsp.i1, hsp.i2);
+  return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+// Calculate pair interaction probabilities in nearest-neighbour tiles
+// (yt1, pt1) represents centre cell (8):
+//    7 | 0 | 1
+//   -----------
+//    6 | 8 | 2
+//   -----------
+//    5 | 4 | 3
+
+bool HadronScatter::tileIntProb(vector < HadronScatterPair > &hsp,
+    Event &event, const HSIndex &i1, int yt1, int pt1, bool doAll) {
+  // Track if a new pair is added
+  bool pairAdded = false;
+
+  // Special handling for pairing in own tile
+  if (!doAll) {
+    set < HSIndex >::iterator si2 = tile[yt1][pt1].find(i1);
+    while (++si2 != tile[yt1][pt1].end())
+      // Calculate if scatters
+      if (doesScatter(event, i1, *si2)) {
+        double m = measure(event, i1.second, si2->second);
+        hsp.push_back(HadronScatterPair(i1, yt1, pt1, *si2, yt1, pt1, m));
+      }
+  }
+
+  // And the rest
+  int tileMax = (doAll) ? 9 : 4;
+  for (int tileNow = 0; tileNow < tileMax; tileNow++) {
+    // New (yt, pt) coordinate
+    int yt2 = yt1, pt2 = pt1;
+    switch (tileNow) {
+    case 0:        ++pt2; break;
+    case 1: ++yt2; ++pt2; break;
+    case 2: ++yt2;        break;
+    case 3: ++yt2; --pt2; break;
+    case 4:        --pt2; break;
+    case 5: --yt2; --pt2; break;
+    case 6: --yt2;        break;
+    case 7: --yt2; ++pt2; break;
+    }
+
+    // Limit in rapidity tiles
+    if (yt2 < 0 || yt2 >= ytMax) continue;
+    // Wraparound for phi tiles
+    if (pt2 < 0) {
+      pt2 = ptMax - 1;
+      if (pt2 == pt1 || pt2 == pt1 + 1) continue;
+
+    } else if (pt2 >= ptMax) {
+      pt2 = 0;
+      if (pt2 == pt1 || pt2 == pt1 - 1) continue;
+    }
+
+    // Interaction probability
+    for (set < HSIndex >::iterator si2 = tile[yt2][pt2].begin();
+         si2 != tile[yt2][pt2].end(); si2++) {
+      // Calculate if scatters
+      if (doesScatter(event, i1, *si2)) {
+        double m = measure(event, i1.second, si2->second);
+        hsp.push_back(HadronScatterPair(i1, yt1, pt1, *si2, yt2, pt2, m));
+        pairAdded = true;
+      }
+    }
+  }
+
+  return pairAdded;
+}
+
+//==========================================================================
+
+} // namespace Pythia8