diff --git a/Documentation/Examples/CMakeLists.txt b/Documentation/Examples/CMakeLists.txt
index 06b4467e4d89fc2576b5f6b031738e441d890a60..943bbdd2b3cf67d2e26e0427f896ab3f58fcba2a 100644
--- a/Documentation/Examples/CMakeLists.txt
+++ b/Documentation/Examples/CMakeLists.txt
@@ -45,6 +45,27 @@ target_link_libraries (cascade_example SuperStupidStack CORSIKAunits
 install (TARGETS cascade_example DESTINATION share/examples)
 CORSIKA_ADD_TEST (cascade_example)
 
+add_executable (cascade_proton_example cascade_proton_example.cc)
+target_compile_options(cascade_proton_example PRIVATE -g) # do not skip asserts
+target_link_libraries (cascade_proton_example SuperStupidStack CORSIKAunits
+  CORSIKAlogging
+  CORSIKArandom
+  ProcessSibyll
+  ProcessPythia
+  CORSIKAcascade
+  ProcessStackInspector
+  ProcessTrackWriter
+  ProcessTrackingLine
+  ProcessHadronicElasticModel
+  CORSIKAprocesses
+  CORSIKAparticles
+  CORSIKAgeometry
+  CORSIKAenvironment
+  CORSIKAprocesssequence
+  )
+install (TARGETS cascade_proton_example DESTINATION share/examples)
+CORSIKA_ADD_TEST (cascade_proton_example)
+
 add_executable (staticsequence_example staticsequence_example.cc)
 target_compile_options(staticsequence_example PRIVATE -g) # do not skip asserts
 target_link_libraries (staticsequence_example
diff --git a/Documentation/Examples/cascade_example.cc b/Documentation/Examples/cascade_example.cc
index 764eeeba0cd920bf2fa137d6b2f0afb99e7072b8..c154db988f183799d3c18f3dc8033bd8f600c2d3 100644
--- a/Documentation/Examples/cascade_example.cc
+++ b/Documentation/Examples/cascade_example.cc
@@ -242,9 +242,8 @@ int main() {
   theMedium->SetModelProperties<MyHomogeneousModel>(
       1_kg / (1_m * 1_m * 1_m),
       environment::NuclearComposition(
-          std::vector<particles::Code>{particles::Code::Nitrogen,
-                                       particles::Code::Oxygen},
-          std::vector<float>{(float)1. - fox, fox}));
+				      std::vector<particles::Code>{particles::Code::Nitrogen, particles::Code::Oxygen},
+          std::vector<float>{(float)1.-fox, fox}));
 
   universe.AddChild(std::move(theMedium));
 
@@ -259,6 +258,7 @@ int main() {
       particles::Code::KMinus, particles::Code::K0Long,  particles::Code::K0Short};
 
   random::RNGManager::GetInstance().RegisterRandomStream("s_rndm");
+  random::RNGManager::GetInstance().RegisterRandomStream("pythia");
   process::sibyll::Interaction sibyll(env);
   process::sibyll::NuclearInteraction sibyllNuc(env, sibyll);
   process::sibyll::Decay decay(trackedHadrons);
diff --git a/Documentation/Examples/cascade_proton_example.cc b/Documentation/Examples/cascade_proton_example.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b8851ffe622e1583a2ce55e60e7dd4a112065959
--- /dev/null
+++ b/Documentation/Examples/cascade_proton_example.cc
@@ -0,0 +1,325 @@
+
+/*
+ * (c) Copyright 2018 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/cascade/Cascade.h>
+#include <corsika/process/ProcessSequence.h>
+#include <corsika/process/hadronic_elastic_model/HadronicElasticModel.h>
+#include <corsika/process/stack_inspector/StackInspector.h>
+#include <corsika/process/tracking_line/TrackingLine.h>
+
+#include <corsika/setup/SetupStack.h>
+#include <corsika/setup/SetupTrajectory.h>
+
+#include <corsika/environment/Environment.h>
+#include <corsika/environment/HomogeneousMedium.h>
+#include <corsika/environment/NuclearComposition.h>
+
+#include <corsika/geometry/Sphere.h>
+
+#include <corsika/process/sibyll/Decay.h>
+#include <corsika/process/sibyll/Interaction.h>
+#include <corsika/process/sibyll/NuclearInteraction.h>
+
+#include <corsika/process/pythia/Decay.h>
+#include <corsika/process/pythia/Interaction.h>
+
+#include <corsika/process/track_writer/TrackWriter.h>
+
+#include <corsika/units/PhysicalUnits.h>
+
+#include <corsika/random/RNGManager.h>
+
+#include <corsika/utl/CorsikaFenv.h>
+
+#include <boost/type_index.hpp>
+using boost::typeindex::type_id_with_cvr;
+
+#include <iostream>
+#include <limits>
+#include <typeinfo>
+
+using namespace corsika;
+using namespace corsika::process;
+using namespace corsika::units;
+using namespace corsika::particles;
+using namespace corsika::random;
+using namespace corsika::setup;
+using namespace corsika::geometry;
+using namespace corsika::environment;
+
+using namespace std;
+using namespace corsika::units::si;
+
+class ProcessCut : public process::ContinuousProcess<ProcessCut> {
+
+  HEPEnergyType fECut;
+
+  HEPEnergyType fEnergy = 0_GeV;
+  HEPEnergyType fEmEnergy = 0_GeV;
+  int fEmCount = 0;
+  HEPEnergyType fInvEnergy = 0_GeV;
+  int fInvCount = 0;
+
+public:
+  ProcessCut(const HEPEnergyType cut)
+      : fECut(cut) {}
+
+  template <typename Particle>
+  bool isBelowEnergyCut(Particle& p) const {
+    // nuclei
+    if (p.GetPID() == particles::Code::Nucleus) {
+      auto const ElabNuc = p.GetEnergy() / p.GetNuclearA();
+      auto const EcmNN = sqrt(2. * ElabNuc * 0.93827_GeV);
+      if (ElabNuc < fECut || EcmNN < 10_GeV)
+        return true;
+      else
+        return false;
+    } else {
+      // TODO: center-of-mass energy hard coded
+      const HEPEnergyType Ecm = sqrt(2. * p.GetEnergy() * 0.93827_GeV);
+      if (p.GetEnergy() < fECut || Ecm < 10_GeV)
+        return true;
+      else
+        return false;
+    }
+  }
+
+  bool isEmParticle(Code pCode) const {
+    bool is_em = false;
+    // FOR NOW: switch
+    switch (pCode) {
+      case Code::Electron:
+        is_em = true;
+        break;
+      case Code::Positron:
+        is_em = true;
+        break;
+      case Code::Gamma:
+        is_em = true;
+        break;
+      default:
+        break;
+    }
+    return is_em;
+  }
+
+  void defineEmParticles() const {
+    // create bool array identifying em particles
+  }
+
+  bool isInvisible(Code pCode) const {
+    bool is_inv = false;
+    // FOR NOW: switch
+    switch (pCode) {
+      case Code::NuE:
+        is_inv = true;
+        break;
+      case Code::NuEBar:
+        is_inv = true;
+        break;
+      case Code::NuMu:
+        is_inv = true;
+        break;
+      case Code::NuMuBar:
+        is_inv = true;
+        break;
+      case Code::MuPlus:
+        is_inv = true;
+        break;
+      case Code::MuMinus:
+        is_inv = true;
+        break;
+
+      case Code::Neutron:
+        is_inv = true;
+        break;
+
+      case Code::AntiNeutron:
+        is_inv = true;
+        break;
+
+      default:
+        break;
+    }
+    return is_inv;
+  }
+
+  template <typename Particle>
+  LengthType MaxStepLength(Particle& p, setup::Trajectory&) const {
+    cout << "ProcessCut: MinStep: pid: " << p.GetPID() << endl;
+    cout << "ProcessCut: MinStep: energy (GeV): " << p.GetEnergy() / 1_GeV << endl;
+    const Code pid = p.GetPID();
+    if (isEmParticle(pid) || isInvisible(pid) || isBelowEnergyCut(p)) {
+      cout << "ProcessCut: MinStep: next cut: " << 0. << endl;
+      return 0_m;
+    } else {
+      LengthType next_step = 1_m * std::numeric_limits<double>::infinity();
+      cout << "ProcessCut: MinStep: next cut: " << next_step << endl;
+      return next_step;
+    }
+  }
+
+  template <typename Particle, typename Stack>
+  EProcessReturn DoContinuous(Particle& p, setup::Trajectory&, Stack&) {
+    const Code pid = p.GetPID();
+    HEPEnergyType energy = p.GetEnergy();
+    cout << "ProcessCut: DoContinuous: " << pid << " E= " << energy
+         << ", EcutTot=" << (fEmEnergy + fInvEnergy + fEnergy) / 1_GeV << " GeV" << endl;
+    EProcessReturn ret = EProcessReturn::eOk;
+    if (isEmParticle(pid)) {
+      cout << "removing em. particle..." << endl;
+      fEmEnergy += energy;
+      fEmCount += 1;
+      // p.Delete();
+      ret = EProcessReturn::eParticleAbsorbed;
+    } else if (isInvisible(pid)) {
+      cout << "removing inv. particle..." << endl;
+      fInvEnergy += energy;
+      fInvCount += 1;
+      // p.Delete();
+      ret = EProcessReturn::eParticleAbsorbed;
+    } else if (isBelowEnergyCut(p)) {
+      cout << "removing low en. particle..." << endl;
+      fEnergy += energy;
+      // p.Delete();
+      ret = EProcessReturn::eParticleAbsorbed;
+    }
+    return ret;
+  }
+
+  void Init() {
+    fEmEnergy = 0. * 1_GeV;
+    fEmCount = 0;
+    fInvEnergy = 0. * 1_GeV;
+    fInvCount = 0;
+    fEnergy = 0. * 1_GeV;
+    // defineEmParticles();
+  }
+
+  void ShowResults() {
+    cout << " ******************************" << endl
+         << " ParticleCut: " << endl
+         << " energy in em.  component (GeV):  " << fEmEnergy / 1_GeV << endl
+         << " no. of em.  particles injected:  " << fEmCount << endl
+         << " energy in inv. component (GeV):  " << fInvEnergy / 1_GeV << endl
+         << " no. of inv. particles injected:  " << fInvCount << endl
+         << " energy below particle cut (GeV): " << fEnergy / 1_GeV << endl
+         << " ******************************" << endl;
+  }
+
+  HEPEnergyType GetInvEnergy() const { return fInvEnergy; }
+  HEPEnergyType GetCutEnergy() const { return fEnergy; }
+  HEPEnergyType GetEmEnergy() const { return fEmEnergy; }
+};
+
+//
+// The example main program for a particle cascade
+//
+int main() {
+  feenableexcept(FE_INVALID);
+  // initialize random number sequence(s)
+  random::RNGManager::GetInstance().RegisterRandomStream("cascade");
+
+  // setup environment, geometry
+  environment::Environment env;
+  auto& universe = *(env.GetUniverse());
+
+  auto theMedium = environment::Environment::CreateNode<Sphere>(
+      Point{env.GetCoordinateSystem(), 0_m, 0_m, 0_m},
+      1_km * std::numeric_limits<double>::infinity());
+
+  using MyHomogeneousModel = environment::HomogeneousMedium<environment::IMediumModel>;
+  theMedium->SetModelProperties<MyHomogeneousModel>(
+      1_kg / (1_m * 1_m * 1_m),
+      environment::NuclearComposition(
+          std::vector<particles::Code>{particles::Code::Hydrogen},
+          std::vector<float>{(float)1.}));
+
+  universe.AddChild(std::move(theMedium));
+
+  const CoordinateSystem& rootCS = env.GetCoordinateSystem();
+
+  // setup processes, decays and interactions
+  tracking_line::TrackingLine<setup::Stack, setup::Trajectory> tracking(env);
+  stack_inspector::StackInspector<setup::Stack> p0(true);
+
+  const std::vector<particles::Code> trackedHadrons = {
+        particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::KPlus,
+        particles::Code::KMinus, particles::Code::K0Long,  particles::Code::K0Short};
+
+  
+  random::RNGManager::GetInstance().RegisterRandomStream("s_rndm");
+  random::RNGManager::GetInstance().RegisterRandomStream("pythia");
+  //  process::sibyll::Interaction sibyll(env);
+  process::pythia::Interaction pythia(env);
+  //  process::sibyll::NuclearInteraction sibyllNuc(env, sibyll);
+  //  process::sibyll::Decay decay(trackedHadrons);
+  process::pythia::Decay decay(trackedHadrons);
+  ProcessCut cut(20_GeV);
+
+  // random::RNGManager::GetInstance().RegisterRandomStream("HadronicElasticModel");
+  // process::HadronicElasticModel::HadronicElasticInteraction
+  // hadronicElastic(env);
+
+  process::TrackWriter::TrackWriter trackWriter("tracks.dat");
+
+  // assemble all processes into an ordered process list
+  // auto sequence = p0 << sibyll << decay << hadronicElastic << cut << trackWriter;
+  //  auto sequence = p0 << sibyll << sibyllNuc << decay << cut << trackWriter;
+  
+  auto sequence = p0 << pythia << decay << cut << trackWriter;
+
+  // cout << "decltype(sequence)=" << type_id_with_cvr<decltype(sequence)>().pretty_name()
+  // << "\n";
+
+  // setup particle stack, and add primary particle
+  setup::Stack stack;
+  stack.Clear();
+  const Code beamCode = Code::Proton;
+  const HEPMassType mass = particles::Proton::GetMass();
+  const HEPEnergyType E0 = 100_GeV; 
+  double theta = 0.;
+  double phi = 0.;
+
+  {
+    auto elab2plab = [](HEPEnergyType Elab, HEPMassType m) {
+      return sqrt(Elab * Elab - m * m);
+    };
+    HEPMomentumType P0 = elab2plab(E0, mass);
+    auto momentumComponents = [](double theta, double phi, HEPMomentumType ptot) {
+      return std::make_tuple(ptot * sin(theta) * cos(phi), ptot * sin(theta) * sin(phi),
+                             -ptot * cos(theta));
+    };
+    auto const [px, py, pz] =
+        momentumComponents(theta / 180. * M_PI, phi / 180. * M_PI, P0);
+    auto plab = corsika::stack::MomentumVector(rootCS, {px, py, pz});
+    cout << "input particle: " << beamCode << endl;
+    cout << "input angles: theta=" << theta << " phi=" << phi << endl;
+    cout << "input momentum: " << plab.GetComponents() / 1_GeV << endl;
+    Point pos(rootCS, 0_m, 0_m, 0_m);
+    stack.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
+                                 corsika::stack::MomentumVector, geometry::Point,
+                                 units::si::TimeType>{
+        beamCode, E0, plab, pos, 0_ns});
+  }
+
+  // define air shower object, run simulation
+  cascade::Cascade EAS(env, tracking, sequence, stack);
+  EAS.Init();
+  EAS.Run();
+
+  cout << "Result: E0=" << E0 / 1_GeV << endl;
+  cut.ShowResults();
+  const HEPEnergyType Efinal =
+      cut.GetCutEnergy() + cut.GetInvEnergy() + cut.GetEmEnergy();
+  cout << "total energy (GeV): " << Efinal / 1_GeV << endl
+       << "relative difference (%): " << (Efinal / E0 - 1.) * 100 << endl;
+}
diff --git a/Processes/Pythia/CMakeLists.txt b/Processes/Pythia/CMakeLists.txt
index b23c6d005f272854ac976104de24262996e98f47..adc953090c31505acf4fd4c6c85dd85435ca136c 100644
--- a/Processes/Pythia/CMakeLists.txt
+++ b/Processes/Pythia/CMakeLists.txt
@@ -5,12 +5,14 @@ set (
   MODEL_SOURCES
   Decay.cc
   Random.cc
+  Interaction.cc
   )
 
 set (
   MODEL_HEADERS
   Decay.h
   Random.h
+  Interaction.h
   )
 
 set (
diff --git a/Processes/Pythia/Interaction.cc b/Processes/Pythia/Interaction.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8c7d6d7527c6b0545dba8bb3872501ea3509ed71
--- /dev/null
+++ b/Processes/Pythia/Interaction.cc
@@ -0,0 +1,424 @@
+
+/*
+ * (c) Copyright 2018 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/process/pythia/Interaction.h>
+
+#include <corsika/environment/Environment.h>
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/geometry/FourVector.h>
+#include <corsika/setup/SetupStack.h>
+#include <corsika/setup/SetupTrajectory.h>
+#include <corsika/utl/COMBoost.h>
+
+#include <tuple>
+
+using std::cout;
+using std::endl;
+using std::tuple;
+
+using namespace corsika;
+using namespace corsika::setup;
+using Particle = Stack::StackIterator; // ParticleType;
+using Track = Trajectory;
+
+namespace corsika::process::pythia {
+
+  typedef corsika::geometry::Vector<corsika::units::si::hepmomentum_d> MomentumVector;
+  
+  Interaction::Interaction(environment::Environment const& env)
+    : fEnvironment(env) {}
+
+  Interaction::~Interaction() {
+    cout << "Pythia::Interaction n=" << fCount << endl;
+  }
+
+  void Interaction::Init() {
+
+    using random::RNGManager;
+
+    // initialize Pythia
+    if (!fInitialized) {
+
+      fPythia.readString("Print:quiet = on");
+      // TODO: proper process initialization for MinBias needed
+      fPythia.readString("HardQCD:all = on");      
+      fPythia.readString("ProcessLevel:resonanceDecays = off");
+
+      fPythia.init();
+      
+      // any decays in pythia? if yes need to define which particles
+      if(fInternalDecays){
+	// define which particles are passed to corsika, i.e. which particles make it into history
+	// even very shortlived particles like charm or pi0 are of interest here
+	const std::vector<particles::Code> HadronsWeWantTrackedByCorsika = {
+        particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::Pi0,
+        particles::Code::KMinus, particles::Code::KPlus,
+	particles::Code::K0Long,  particles::Code::K0Short,
+	particles::Code::SigmaPlus, particles::Code::SigmaMinus,
+	particles::Code::Lambda0,
+	particles::Code::Xi0, particles::Code::XiMinus,
+	particles::Code::OmegaMinus,
+	particles::Code::DPlus, particles::Code::DMinus, particles::Code::D0, particles::Code::D0Bar};
+	
+	Interaction::SetParticleListStable(HadronsWeWantTrackedByCorsika);
+      }
+
+      // basic initialization of cross section routines
+      fSigma.init( &fPythia.info, fPythia.settings, &fPythia.particleData, &fPythia.rndm );
+
+      fInitialized = true;
+    }
+  }
+
+  void Interaction::SetParticleListStable(const std::vector<particles::Code> particleList) {
+    for (auto p : particleList)
+      Interaction::SetStable( p );
+  }
+
+  void Interaction::SetUnstable(const particles::Code pCode) {
+    cout << "Pythia::Interaction: setting " << pCode << " unstable.." << endl;
+    fPythia.particleData.mayDecay( static_cast<int>( particles::GetPDG(pCode) ) , true);
+  }
+
+  void Interaction::SetStable(const particles::Code pCode) {
+    cout << "Pythia::Interaction: setting " << pCode << " stable.." << endl;
+    fPythia.particleData.mayDecay( static_cast<int>( particles::GetPDG(pCode) ) , false);
+  }
+
+
+  void Interaction::ConfigureLabFrameCollision( const particles::Code BeamId, const particles::Code TargetId,
+				   const units::si::HEPEnergyType BeamEnergy )
+  {
+    using namespace units::si;
+    // Pythia configuration of the current event
+    // very clumsy. I am sure this can be done better..
+	
+    // set beam
+    // beam id for pythia
+    auto const pdgBeam = static_cast<int>(particles::GetPDG(BeamId));
+    std::stringstream stBeam;
+    stBeam << "Beams:idA = " << pdgBeam;
+    fPythia.readString(stBeam.str());
+    // set target
+    auto pdgTarget = static_cast<int>(particles::GetPDG(TargetId));
+    // replace hydrogen with proton, otherwise pythia goes into heavy ion mode!
+    if(TargetId == particles::Code::Hydrogen)
+      pdgTarget = static_cast<int>( particles::GetPDG(particles::Code::Proton));	
+    std::stringstream stTarget;
+    stTarget << "Beams:idB = " << pdgTarget;
+    fPythia.readString(stTarget.str());
+    // set frame to lab. frame
+    fPythia.readString("Beams:frameType = 2");
+    // set beam energy
+    const double Elab = BeamEnergy / 1_GeV;
+    std::stringstream stEnergy;
+    stEnergy << "Beams:eA = " << Elab;
+    fPythia.readString(stEnergy.str());
+    // target at rest
+    fPythia.readString("Beams:eB = 0.");
+    // initialize this config
+    fPythia.init();
+  }
+
+  
+  bool Interaction::CanInteract(const corsika::particles::Code pCode)
+  {
+    return pCode == corsika::particles::Code::Proton || pCode == corsika::particles::Code::Neutron
+      || pCode == corsika::particles::Code::AntiProton || pCode == corsika::particles::Code::AntiNeutron
+      || pCode == corsika::particles::Code::PiMinus || pCode == corsika::particles::Code::PiPlus;
+  }  
+  
+  tuple<units::si::CrossSectionType, units::si::CrossSectionType>
+  Interaction::GetCrossSection(const particles::Code BeamId,
+                               const particles::Code TargetId,
+                               const units::si::HEPEnergyType CoMenergy) {
+    using namespace units::si;
+
+    // interaction possible in pythia?
+    if( TargetId == particles::Code::Proton || TargetId == particles::Code::Hydrogen ){
+      if( CanInteract(BeamId) && ValidCoMEnergy(CoMenergy) ){
+	// input particle PDG
+	auto const pdgCodeBeam = static_cast<int>( particles::GetPDG( BeamId ));
+	auto const pdgCodeTarget = static_cast<int>( particles::GetPDG( TargetId ));
+	const double  ecm = CoMenergy / 1_GeV;
+
+	// calculate cross section
+	fSigma.calc( pdgCodeBeam, pdgCodeTarget, ecm);
+	if(fSigma.hasSigmaTot()){
+	  const double sigEla = fSigma.sigmaEl();
+	  const double sigProd = fSigma.sigmaTot() - sigEla;
+	
+	  return std::make_tuple(sigProd * 1_mbarn, sigEla * 1_mbarn);
+
+	} else
+	  throw std::runtime_error("pythia cross section init failed");
+
+      } else {
+	return std::make_tuple(std::numeric_limits<double>::infinity() * 1_mbarn,
+			       std::numeric_limits<double>::infinity() * 1_mbarn);
+      }
+    } else {
+      throw std::runtime_error("invalid target for pythia");
+    }
+  }
+
+  template <>
+  units::si::GrammageType Interaction::GetInteractionLength(Particle& p, Track&) {
+
+    using namespace units;
+    using namespace units::si;
+    using namespace geometry;
+
+    // coordinate system, get global frame of reference
+    CoordinateSystem& rootCS =
+        RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
+
+    const particles::Code corsikaBeamId = p.GetPID();
+
+    // beam particles for pythia : 1, 2, 3 for p, pi, k
+    // read from cross section code table
+    const bool kInteraction = CanInteract(corsikaBeamId);
+
+    // FOR NOW: assume target is at rest
+    process::pythia::MomentumVector pTarget(rootCS, {0_GeV, 0_GeV, 0_GeV});
+
+    // total momentum and energy
+    HEPEnergyType Elab = p.GetEnergy() + constants::nucleonMass;
+    process::pythia::MomentumVector pTotLab(rootCS, {0_GeV, 0_GeV, 0_GeV});
+    pTotLab += p.GetMomentum();
+    pTotLab += pTarget;
+    auto const pTotLabNorm = pTotLab.norm();
+    // calculate cm. energy
+    const HEPEnergyType ECoM = sqrt(
+        (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy
+
+    cout << "Interaction: LambdaInt: \n"
+         << " input energy: " << p.GetEnergy() / 1_GeV << endl
+         << " beam can interact:" << kInteraction << endl
+         << " beam pid:" << p.GetPID() << endl;
+
+    // TODO: move limits into variables
+    if (kInteraction && Elab >= 8.5_GeV && ValidCoMEnergy(ECoM) ) {
+
+      // get target from environment
+      /*
+        the target should be defined by the Environment,
+        ideally as full particle object so that the four momenta
+        and the boosts can be defined..
+      */
+      const auto currentNode =
+          fEnvironment.GetUniverse()->GetContainingNode(p.GetPosition());
+      const auto mediumComposition =
+          currentNode->GetModelProperties().GetNuclearComposition();
+      // determine average interaction length
+      // weighted sum
+      int i = -1;
+      si::CrossSectionType weightedProdCrossSection = 0_mbarn;
+      // get weights of components from environment/medium
+      const auto w = mediumComposition.GetFractions();
+      // loop over components in medium
+      for (auto const targetId : mediumComposition.GetComponents()) {
+        i++;
+        cout << "Interaction: get interaction length for target: " << targetId << endl;
+
+        auto const [productionCrossSection, elaCrossSection] =
+            GetCrossSection(corsikaBeamId, targetId, ECoM);
+        [[maybe_unused]] auto elaCrossSectionCopy =
+            elaCrossSection; // ONLY TO AVOID COMPILER WARNING
+
+        cout << "Interaction: IntLength: pythia return (mb): "
+	     << productionCrossSection / 1_mbarn << endl
+	     << "Interaction: IntLength: weight : " << w[i] << endl;
+	
+        weightedProdCrossSection += w[i] * productionCrossSection;
+      }
+      cout << "Interaction: IntLength: weighted CrossSection (mb): "
+           << weightedProdCrossSection / 1_mbarn << endl
+	   << "Interaction: IntLength: average mass number: "
+           <<  mediumComposition.GetAverageMassNumber() << endl;
+
+      // calculate interaction length in medium
+      GrammageType const int_length =
+      	mediumComposition.GetAverageMassNumber() * units::constants::u / weightedProdCrossSection;
+      cout << "Interaction: "
+           << "interaction length (g/cm2): " << int_length / (0.001_kg) * 1_cm * 1_cm
+           << endl;
+
+      return int_length;
+    }
+
+    return std::numeric_limits<double>::infinity() * 1_g / (1_cm * 1_cm);
+  }
+  
+  /**
+     In this function PYTHIA is called to produce one event. The
+     event is copied (and boosted) into the shower lab frame.
+   */
+
+  template <>
+  process::EProcessReturn Interaction::DoInteraction(Particle& p, Stack&) {
+
+    using namespace units;
+    using namespace utl;
+    using namespace units::si;
+    using namespace geometry;
+
+
+    const auto corsikaBeamId = p.GetPID();
+    cout << "Pythia::Interaction: "
+         << "DoInteraction: " << corsikaBeamId << " interaction? "
+         << process::pythia::Interaction::CanInteract(corsikaBeamId) << endl;
+
+    if (particles::IsNucleus(corsikaBeamId)) {
+      // nuclei handled by different process, this should not happen
+      throw std::runtime_error("Nuclear projectile are not handled by PYTHIA!");
+    }
+
+    if (process::pythia::Interaction::CanInteract(corsikaBeamId)) {
+      
+      const CoordinateSystem& rootCS =
+          RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
+
+      // position and time of interaction, not used in Sibyll
+      Point pOrig = p.GetPosition();
+      TimeType tOrig = p.GetTime();
+
+      // define target
+      // FOR NOW: target is always at rest
+      const auto eTargetLab = 0_GeV + constants::nucleonMass;
+      const auto pTargetLab = MomentumVector(rootCS, 0_GeV, 0_GeV, 0_GeV);
+      const FourVector PtargLab(eTargetLab, pTargetLab);
+
+      // define projectile
+      HEPEnergyType const eProjectileLab = p.GetEnergy();
+      auto const pProjectileLab = p.GetMomentum();
+
+      cout << "Interaction: ebeam lab: " << eProjectileLab / 1_GeV << endl
+           << "Interaction: pbeam lab: " << pProjectileLab.GetComponents() / 1_GeV
+           << endl;
+      cout << "Interaction: etarget lab: " << eTargetLab / 1_GeV << endl
+           << "Interaction: ptarget lab: " << pTargetLab.GetComponents() / 1_GeV << endl;
+
+      const FourVector PprojLab(eProjectileLab, pProjectileLab);
+
+      // define target kinematics in lab frame
+      // define boost to and from CoM frame
+      // CoM frame definition in Pythia projectile: +z
+      COMBoost const boost(PprojLab, constants::nucleonMass);
+
+      // just for show:
+      // boost projecticle
+      auto const PprojCoM = boost.toCoM(PprojLab);
+
+      // boost target
+      auto const PtargCoM = boost.toCoM(PtargLab);
+
+      cout << "Interaction: ebeam CoM: " << PprojCoM.GetTimeLikeComponent() / 1_GeV
+           << endl
+           << "Interaction: pbeam CoM: "
+           << PprojCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+      cout << "Interaction: etarget CoM: " << PtargCoM.GetTimeLikeComponent() / 1_GeV
+           << endl
+           << "Interaction: ptarget CoM: "
+           << PtargCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+
+      cout << "Interaction: position of interaction: " << pOrig.GetCoordinates() << endl;
+      cout << "Interaction: time: " << tOrig << endl;
+
+      HEPEnergyType Etot = eProjectileLab + eTargetLab;
+      MomentumVector Ptot = p.GetMomentum();
+      // invariant mass, i.e. cm. energy
+      HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.squaredNorm());
+
+      // sample target mass number
+      const auto currentNode = fEnvironment.GetUniverse()->GetContainingNode(pOrig);
+      const auto& mediumComposition =
+          currentNode->GetModelProperties().GetNuclearComposition();
+      // get cross sections for target materials
+      /*
+        Here we read the cross section from the interaction model again,
+        should be passed from GetInteractionLength if possible
+       */
+      //#warning reading interaction cross section again, should not be necessary
+      auto const& compVec = mediumComposition.GetComponents();
+      std::vector<si::CrossSectionType> cross_section_of_components(compVec.size());
+
+      for (size_t i = 0; i < compVec.size(); ++i) {
+        auto const targetId = compVec[i];
+        const auto [sigProd, sigEla] =
+            GetCrossSection(corsikaBeamId, targetId, Ecm);
+        cross_section_of_components[i] = sigProd;
+        [[maybe_unused]] auto sigElaCopy =
+            sigEla; // to avoid not used warning in array binding
+      }
+
+      const auto corsikaTargetId =
+          mediumComposition.SampleTarget(cross_section_of_components, fRNG);
+      cout << "Interaction: target selected: " << corsikaTargetId << endl;
+      
+      if (corsikaTargetId != particles::Code::Hydrogen && corsikaTargetId != particles::Code::Neutron
+	  && corsikaTargetId != particles::Code::Proton )
+	throw std::runtime_error("DoInteraction: wrong target for PYTHIA");
+
+      cout << "Interaction: "
+           << " DoInteraction: E(GeV):" << eProjectileLab / 1_GeV
+           << " Ecm(GeV): " << Ecm / 1_GeV << endl;
+      
+      if (eProjectileLab < 8.5_GeV || !ValidCoMEnergy(Ecm)) {
+        cout << "Interaction: "
+             << " DoInteraction: should have dropped particle.. "
+             << "THIS IS AN ERROR" << endl;
+        throw std::runtime_error("energy too low for PYTHIA");
+	
+      } else {	
+        fCount++;
+
+	ConfigureLabFrameCollision( corsikaBeamId, corsikaTargetId, eProjectileLab );
+	
+	// create event in pytia
+	if(!fPythia.next())
+	  throw std::runtime_error("Pythia::DoInteraction: failed!");
+
+	// link to pythia stack
+	Pythia8::Event& event = fPythia.event;
+        // print final state
+	event.list();
+
+        MomentumVector Plab_final(rootCS, {0.0_GeV, 0.0_GeV, 0.0_GeV});
+        HEPEnergyType Elab_final = 0_GeV;
+	for (int i = 0; i < event.size(); ++i){
+	  Pythia8::Particle &pp = event[i];
+          // skip particles that have decayed in pythia
+	  if (!pp.isFinal()) continue;
+
+	  auto const pyId = particles::ConvertFromPDG(static_cast<particles::PDGCode>(pp.id()));
+
+          const MomentumVector pyPlab(rootCS, {pp.px()*1_GeV, pp.py()*1_GeV, pp.pz()*1_GeV});
+          HEPEnergyType const pyEn = pp.e() * 1_GeV;
+
+          // add to corsika stack
+          auto pnew = p.AddSecondary(
+              tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
+                    geometry::Point, units::si::TimeType>{pyId, pyEn, pyPlab, pOrig, tOrig});
+
+          Plab_final += pnew.GetMomentum();
+          Elab_final += pnew.GetEnergy();
+        }
+        cout << "conservation (all GeV): " << "Elab_final=" << Elab_final / 1_GeV
+             << ", Plab_final=" << (Plab_final / 1_GeV).GetComponents() << endl;
+      }
+      // delete current particle
+      p.Delete();
+    }
+    return process::EProcessReturn::eOk;
+  }
+
+} // namespace corsika::process::pythia
diff --git a/Processes/Pythia/Interaction.h b/Processes/Pythia/Interaction.h
new file mode 100644
index 0000000000000000000000000000000000000000..f384da655322cac969588308c85094358c11cd80
--- /dev/null
+++ b/Processes/Pythia/Interaction.h
@@ -0,0 +1,79 @@
+/*
+ * (c) Copyright 2018 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.
+ */
+
+#ifndef _corsika_process_pythia_interaction_h_
+#define _corsika_process_pythia_interaction_h_
+
+#include <Pythia8/Pythia.h>
+
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/process/InteractionProcess.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/units/PhysicalUnits.h>
+#include <tuple>
+
+namespace corsika::environment {
+  class Environment;
+}
+
+namespace corsika::process::pythia {
+
+  class Interaction : public corsika::process::InteractionProcess<Interaction> {
+
+    int fCount = 0;
+    bool fInitialized = false;
+
+  public:
+    Interaction(corsika::environment::Environment const& env);
+    ~Interaction();
+
+    void Init();
+
+    void SetParticleListStable(const std::vector<particles::Code>);
+    void SetUnstable(const corsika::particles::Code );
+    void SetStable(const corsika::particles::Code );
+
+    bool WasInitialized() { return fInitialized; }
+    bool ValidCoMEnergy(corsika::units::si::HEPEnergyType ecm) {
+      using namespace corsika::units::si;
+      return (10_GeV < ecm) && (ecm < 1_PeV);
+    }
+
+    bool CanInteract(const corsika::particles::Code);
+    void ConfigureLabFrameCollision(const corsika::particles::Code, const corsika::particles::Code,
+                    const corsika::units::si::HEPEnergyType);
+    std::tuple<corsika::units::si::CrossSectionType, corsika::units::si::CrossSectionType>
+    GetCrossSection(const corsika::particles::Code BeamId,
+                    const corsika::particles::Code TargetId,
+                    const corsika::units::si::HEPEnergyType CoMenergy);
+
+    template <typename Particle, typename Track>
+    corsika::units::si::GrammageType GetInteractionLength(Particle&, Track&);
+
+    /**
+       In this function PYTHIA is called to produce one event. The
+       event is copied (and boosted) into the shower lab frame.
+     */
+
+    template <typename Particle, typename Stack>
+    corsika::process::EProcessReturn DoInteraction(Particle&, Stack&);
+
+  private:
+    corsika::environment::Environment const& fEnvironment;
+    corsika::random::RNG& fRNG =
+      corsika::random::RNGManager::GetInstance().GetRandomStream("pythia");
+    Pythia8::Pythia fPythia;
+    Pythia8::SigmaTotal fSigma;
+    const bool fInternalDecays = true;
+  };
+
+} // namespace corsika::process::pythia
+
+#endif
diff --git a/Processes/Pythia/testPythia.cc b/Processes/Pythia/testPythia.cc
index 9e2d5dd39cd7a02297c22f298802cc79d5f040a9..418ff6973b6c51cc0c8a9cc6fa96c06e9c8988a3 100644
--- a/Processes/Pythia/testPythia.cc
+++ b/Processes/Pythia/testPythia.cc
@@ -9,6 +9,8 @@
  * the license.
  */
 
+#include <corsika/process/pythia/Decay.h>
+#include <corsika/process/pythia/Interaction.h>
 #include <Pythia8/Pythia.h>
 #include <corsika/process/pythia/Decay.h>
 
@@ -94,7 +96,7 @@ TEST_CASE("Pythia", "[processes]") {
 using namespace corsika;
 using namespace corsika::units::si;
 
-TEST_CASE("pytia decay") {
+TEST_CASE("pythia process"){  
 
   // setup environment, geometry
   environment::Environment env;
@@ -108,7 +110,7 @@ TEST_CASE("pytia decay") {
   theMedium->SetModelProperties<MyHomogeneousModel>(
       1_kg / (1_m * 1_m * 1_m),
       environment::NuclearComposition(
-          std::vector<particles::Code>{particles::Code::Oxygen}, std::vector<float>{1.}));
+          std::vector<particles::Code>{particles::Code::Hydrogen}, std::vector<float>{1.}));
 
   universe.AddChild(std::move(theMedium));
 
@@ -148,4 +150,28 @@ TEST_CASE("pytia decay") {
                                                                           stack);
     [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
   }
+
+  SECTION("pythia interaction") {
+    
+    setup::Stack stack;
+    const HEPEnergyType E0 = 100_GeV;
+    HEPMomentumType P0 =
+        sqrt(E0 * E0 - particles::PiPlus::GetMass() * particles::PiPlus::GetMass());
+    auto plab = corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
+    geometry::Point pos(cs, 0_m, 0_m, 0_m);
+    auto particle = stack.AddParticle(
+        std::tuple<particles::Code, units::si::HEPEnergyType,
+                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
+            particles::Code::PiPlus, E0, plab, pos, 0_ns});
+
+        
+    process::pythia::Interaction model(env);
+    
+    model.Init();
+    /*[[maybe_unused]] const process::EProcessReturn ret =*/model.DoInteraction(particle,
+                                                                          stack);
+    [[maybe_unused]] const GrammageType length =
+      model.GetInteractionLength(particle, track);
+  }
+
 }