diff --git a/Processes/Pythia/CMakeLists.txt b/Processes/Pythia/CMakeLists.txt
index 3af574c892dd423cdef3b7f018265c9f6f3912af..de1630fd928355c70bf23683408c83757891220f 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 05895edaca71bc5a54ad2dccebd19fc7cdd883dc..9fb2d757a33f1922a8567b1c4b8e8df4dbc2fb7d 100644
--- a/Processes/Pythia/testPythia.cc
+++ b/Processes/Pythia/testPythia.cc
@@ -10,6 +10,7 @@
  */
 
 #include <corsika/process/pythia/Decay.h>
+#include <corsika/process/pythia/Interaction.h>
 #include <Pythia8/Pythia.h>
 
 #include <corsika/random/RNGManager.h>
@@ -97,7 +98,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;
@@ -111,7 +112,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));
 
@@ -154,4 +155,27 @@ TEST_CASE("pytia decay"){
     
   }
 
+  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);
+  }
+
 }