IAP GITLAB

Skip to content
Snippets Groups Projects
UrQMD.cc 10.73 KiB
/*
 * (c) Copyright 2019 CORSIKA Project, corsika-project@lists.kit.edu
 *
 * See file AUTHORS for a list of contributors.
 *
 * This software is distributed under the terms of the GNU General Public
 * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
 * the license.
 */

#include <corsika/geometry/QuantityVector.h>
#include <corsika/geometry/Vector.h>
#include <corsika/particles/ParticleProperties.h>
#include <corsika/process/urqmd/UrQMD.h>
#include <corsika/units/PhysicalUnits.h>

#include <algorithm>
#include <functional>
#include <iostream>
#include <random>

using namespace corsika::process::UrQMD;
using namespace corsika::units::si;

UrQMD::UrQMD() { iniurqmd_(); }

using SetupStack = corsika::setup::Stack;
using SetupParticle = corsika::setup::Stack::StackIterator;
using SetupProjectile = corsika::setup::StackView::StackIterator;

template <typename TParticle> // need template here, as this is called both with
                              // SetupParticle as well as SetupProjectile
CrossSectionType UrQMD::GetCrossSection(TParticle const& vProjectile,
                                        corsika::particles::Code vTargetCode) const {
  using namespace units::si;

  // TODO: energy cuts, return 0 for non-hadrons

  auto const projectileCode = vProjectile.GetPID();
  auto const projectileEnergyLab = vProjectile.GetEnergy();

  // the following is a translation of ptsigtot() into C++
  if (projectileCode != particles::Code::Nucleus &&
      !IsNucleus(vTargetCode)) { // both particles are "special"
    auto const mProj = particles::GetMass(projectileCode);
    auto const mTar = particles::GetMass(vTargetCode);
    double sqrtS =
        sqrt(units::si::detail::static_pow<2>(mProj) +
             units::si::detail::static_pow<2>(mTar) + 2 * projectileEnergyLab * mTar) *
        (1 / 1_GeV);

    // we must set some UrQMD globals first...
    auto const [ityp, iso3] = ConvertToUrQMD(projectileCode);
    inputs_.spityp[0] = ityp;
    inputs_.spiso3[0] = iso3;

    auto const [itypTar, iso3Tar] = ConvertToUrQMD(vTargetCode);
    inputs_.spityp[1] = itypTar;
    inputs_.spiso3[1] = iso3Tar;

    int one = 1;
    int two = 2;
    return sigtot_(one, two, sqrtS) * 1_mb;
  } else {
    int const Ap =
        (projectileCode == particles::Code::Nucleus) ? vProjectile.GetNuclearA() : 1;
    int const At = IsNucleus(vTargetCode) ? particles::GetNucleusA(vTargetCode) : 1;

    double const maxImpact = nucrad_(Ap) + nucrad_(At) + 2 * options_.CTParam[30 - 1];
    return 10_mb * M_PI * units::si::detail::static_pow<2>(maxImpact);
    // is a constant cross-section really reasonable?
  }
}

bool UrQMD::CanInteract(particles::Code vCode) const {
  // According to the manual, UrQMD can use all mesons, baryons and nucleons
  // which are modeled also as input particles. I think it is safer to accept
  // only the usual long-lived species as input.
  // TODO: Charmed mesons should be added to the list, too

  static particles::Code const validProjectileCodes[] = {
      particles::Code::Nucleus, particles::Code::Proton,      particles::Code::AntiProton,
      particles::Code::Neutron, particles::Code::AntiNeutron, particles::Code::PiPlus,
      particles::Code::PiMinus, particles::Code::KPlus,       particles::Code::KMinus,
      particles::Code::K0,      particles::Code::K0Bar};

  return std::find(std::cbegin(validProjectileCodes), std::cend(validProjectileCodes),
                   vCode) != std::cend(validProjectileCodes);
}

GrammageType UrQMD::GetInteractionLength(SetupParticle& vParticle) const {
  if (!CanInteract(vParticle.GetPID())) {
    // we could do the canInteract check in GetCrossSection, too but if
    // we do it here we have the advantage of avoiding the loop
    return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm);
  }

  auto const& mediumComposition =
      vParticle.GetNode()->GetModelProperties().GetNuclearComposition();
  using namespace std::placeholders;

  CrossSectionType const weightedProdCrossSection = mediumComposition.WeightedSum(
      std::bind(&UrQMD::GetCrossSection<decltype(vParticle)>, this, vParticle, _1));

  return mediumComposition.GetAverageMassNumber() * units::constants::u /
         weightedProdCrossSection;
}

corsika::process::EProcessReturn UrQMD::DoInteraction(SetupProjectile& vProjectile) {
  using namespace units::si;

  auto const projectileCode = vProjectile.GetPID();
  auto const projectileEnergyLab = vProjectile.GetEnergy();
  auto const& projectileMomentumLab = vProjectile.GetMomentum();
  auto const& projectilePosition = vProjectile.GetPosition();
  auto const projectileTime = vProjectile.GetTime();

  // sample target particle
  auto const& mediumComposition =
      vProjectile.GetNode()->GetModelProperties().GetNuclearComposition();
  auto const componentCrossSections = std::invoke([&]() {
    auto const& components = mediumComposition.GetComponents();
    std::vector<CrossSectionType> crossSections;
    crossSections.reserve(components.size());

    for (auto const c : components) {
      crossSections.push_back(GetCrossSection(vProjectile, c));
    }

    return crossSections;
  });

  auto const targetCode = mediumComposition.SampleTarget(componentCrossSections, fRNG);
  auto const targetA = particles::GetNucleusA(targetCode);
  auto const targetZ = particles::GetNucleusZ(targetCode);

  inputs_.nevents = 1;
  sys_.eos = 0; // could be configurable in principle
  inputs_.outsteps = 1;
  sys_.nsteps = 1;

  // initialization regarding projectile
  if (particles::Code::Nucleus == projectileCode) {
    // is this everything?
    inputs_.prspflg = 0;

    sys_.Ap = vProjectile.GetNuclearA();
    sys_.Zp = vProjectile.GetNuclearZ();
    rsys_.ebeam = (projectileEnergyLab - vProjectile.GetMass()) * (1 / 1_GeV) /
                  vProjectile.GetNuclearA();

    rsys_.bdist = nucrad_(targetA) + nucrad_(sys_.Ap) + 2 * options_.CTParam[30 - 1];

    int const id = 1;
    cascinit_(sys_.Zp, sys_.Ap, id);
  } else {
    inputs_.prspflg = 1;
    sys_.Ap = 1; // even for non-baryons this has to be set, see vanilla UrQMD.f
    rsys_.bdist = nucrad_(targetA) + nucrad_(1) + 2 * options_.CTParam[30 - 1];
    rsys_.ebeam = (projectileEnergyLab - vProjectile.GetMass()) * (1 / 1_GeV);

    auto const [ityp, iso3] = ConvertToUrQMD(projectileCode);
    // todo: conversion of K_long/short into strong eigenstates;
    inputs_.spityp[0] = ityp;
    inputs_.spiso3[0] = iso3;
  }

  // initilazation regarding target
  if (particles::IsNucleus(targetCode)) {
    sys_.Zt = targetZ;
    sys_.At = targetA;
    inputs_.trspflg = 0; // nucleus as target
    int const id = 2;
    cascinit_(sys_.Zt, sys_.At, id);
  } else {
    inputs_.trspflg = 1; // special particle as target
    auto const [ityp, iso3] = ConvertToUrQMD(targetCode);
    inputs_.spityp[1] = ityp;
    inputs_.spiso3[1] = iso3;
  }

  int iflb = 0; // flag for retrying interaction in case of empty event, 0 means retry
  urqmd_(iflb);

  // now retrieve secondaries from UrQMD
  auto const& originalCS = projectileMomentumLab.GetCoordinateSystem();
  geometry::CoordinateSystem const zAxisFrame =
      originalCS.RotateToZ(projectileMomentumLab);

  for (int i = 0; i < sys_.npart; ++i) {
    auto const code = ConvertFromUrQMD(isys_.ityp[i], isys_.iso3[i]);
    // "coor_.p0[i] * 1_GeV" is likely off-shell as UrQMD doesn't preserve masses well
    auto momentum = geometry::Vector(
        zAxisFrame,
        geometry::QuantityVector<dimensionless_d>{coor_.px[i], coor_.py[i], coor_.pz[i]} *
            1_GeV);

    auto const energy = sqrt(momentum.squaredNorm() + square(particles::GetMass(code)));

    momentum.rebase(originalCS); // transform back into standard lab frame
    std::cout << i << " " << code << " " << momentum.GetComponents() << std::endl;

    vProjectile.AddSecondary(
        std::tuple<particles::Code, HEPEnergyType, stack::MomentumVector, geometry::Point,
                   TimeType>{code, energy, momentum, projectilePosition, projectileTime});
  }

  std::cout << "UrQMD generated " << sys_.npart << " secondaries!" << std::endl;

  return process::EProcessReturn::eOk;
}

/**
 * the random number generator function of UrQMD
 */
double corsika::process::UrQMD::ranf_(int&) {
  static corsika::random::RNG& rng =
      corsika::random::RNGManager::GetInstance().GetRandomStream("UrQMD");
  static std::uniform_real_distribution<double> dist;

  return dist(rng);
}

corsika::particles::Code corsika::process::UrQMD::ConvertFromUrQMD(int vItyp, int vIso3) {
  int const pdgInt =
      pdgid_(vItyp, vIso3); // use the conversion function provided by UrQMD
  if (pdgInt == 0) {        // pdgid_ returns 0 on error
    throw std::runtime_error("UrQMD pdgid() returned 0");
  }
  auto const pdg = static_cast<particles::PDGCode>(pdgInt);
  return particles::ConvertFromPDG(pdg);
}

std::pair<int, int> corsika::process::UrQMD::ConvertToUrQMD(
    corsika::particles::Code code) {
  static const std::map<int, std::pair<int, int>> mapPDGToUrQMD{
      // data mostly from github.com/afedynitch/ParticleDataTool
      {22, {100, 0}},      // gamma
      {111, {101, 0}},     // pi0
      {211, {101, 2}},     // pi+
      {-211, {101, -2}},   // pi-
      {321, {106, 1}},     // K+
      {-321, {-106, -1}},  // K-
      {311, {106, -1}},    // K0
      {-311, {-106, 1}},   // K0bar
      {2212, {1, 1}},      // p
      {2112, {1, -1}},     // n
      {-2212, {-1, -1}},   // pbar
      {-2112, {-1, 1}},    // nbar
      {221, {102, 0}},     // eta
      {213, {104, 2}},     // rho+
      {-213, {104, -2}},   // rho-
      {113, {104, 0}},     // rho0
      {323, {108, 2}},     // K*+
      {-323, {108, -2}},   // K*-
      {313, {108, 0}},     // K*0
      {-313, {-108, 0}},   // K*0-bar
      {223, {103, 0}},     // omega
      {333, {109, 0}},     // phi
      {3222, {40, 2}},     // Sigma+
      {3212, {40, 0}},     // Sigma0
      {3112, {40, -2}},    // Sigma-
      {3322, {49, 0}},     // Xi0
      {3312, {49, -1}},    // Xi-
      {3122, {27, 0}},     // Lambda0
      {2224, {17, 4}},     // Delta++
      {2214, {17, 2}},     // Delta+
      {2114, {17, 0}},     // Delta0
      {1114, {17, -2}},    // Delta-
      {3224, {41, 2}},     // Sigma*+
      {3214, {41, 0}},     // Sigma*0
      {3114, {41, -2}},    // Sigma*-
      {3324, {50, 0}},     // Xi*0
      {3314, {50, -1}},    // Xi*-
      {3334, {55, 0}},     // Omega-
      {411, {133, 2}},     // D+
      {-411, {133, -2}},   // D-
      {421, {133, 0}},     // D0
      {-421, {-133, 0}},   // D0-bar
      {441, {107, 0}},     // etaC
      {431, {138, 1}},     // Ds+
      {-431, {138, -1}},   // Ds-
      {433, {139, 1}},     // Ds*+
      {-433, {139, -1}},   // Ds*-
      {413, {134, 1}},     // D*+
      {-413, {134, -1}},   // D*-
      {10421, {134, 0}},   // D*0
      {-10421, {-134, 0}}, // D*0-bar
      {443, {135, 0}},     // jpsi
  };

  return mapPDGToUrQMD.at(static_cast<int>(GetPDG(code)));
}