From c61683ef13909c651d3981d795e00a5a9bca53a6 Mon Sep 17 00:00:00 2001
From: Carl Gwilliam <gwilliam@hep.ph.liv.ac.uk>
Date: Wed, 30 Mar 2022 11:17:02 +0100
Subject: [PATCH] Add scripts to generator events from Foresee and a sampler to
 read these in to the DIF + config for this.  Add ability to read HepMC input
 events.  Add Validation alg

---
 .../python/FaserParticleGunConfig.py          |  47 ++
 Generators/ForeseeGenerator/CMakeLists.txt    |  12 +
 .../events_14TeV_m0.1GeV_c1e-05to_11_-11.npy  | Bin 0 -> 59240 bytes
 .../ForeseeGenerator/python/ForeseeSampler.py | 433 ++++++++++++++++++
 .../ForeseeGenerator/python/Validate.py       | 198 ++++++++
 .../share/generate_forsee_events.py           | 256 +++++++++++
 Generators/HEPMCReader/CMakeLists.txt         |   9 +
 .../HEPMCReader/python/HepMCReaderConfig.py   |  24 +
 .../test/G4FaserAlgConfigNew_Test.py          |  18 +-
 9 files changed, 994 insertions(+), 3 deletions(-)
 create mode 100644 Generators/ForeseeGenerator/CMakeLists.txt
 create mode 100644 Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy
 create mode 100644 Generators/ForeseeGenerator/python/ForeseeSampler.py
 create mode 100644 Generators/ForeseeGenerator/python/Validate.py
 create mode 100644 Generators/ForeseeGenerator/share/generate_forsee_events.py
 create mode 100644 Generators/HEPMCReader/CMakeLists.txt
 create mode 100644 Generators/HEPMCReader/python/HepMCReaderConfig.py

diff --git a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
index 28bb6147..89b64e1c 100644
--- a/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
+++ b/Generators/FaserParticleGun/python/FaserParticleGunConfig.py
@@ -140,6 +140,51 @@ def FaserParticleGunDecayInFlightCfg(ConfigFlags, **kwargs) :
 
     return cfg
 
+
+def FaserParticleGunForeseeCfg(ConfigFlags, **kwargs) :
+    # Supported keyword arguments:
+    # model_path    (detault: $PWD)
+    # model_name   (default: DarkPhoton)
+    # mother_mass   (default: 0.01 GeV)
+    # com_energy    (default: 14 TeV)
+    # daughter1_pid (default:  11)
+    # daughter2_pid (default: -11)
+    # mother_pid    (default: none)
+    # mother_pos    (default: CylinderSampler([0, 100**2],[0, 2*pi],[-1500, 0],0))
+    #
+    # Note that ALL of these can be samplers themselves - either the simple, "literal" variety or a sampler object configured by the caller
+    #
+
+    cfg = FaserParticleGunCommonCfg(ConfigFlags, **kwargs)
+
+    pg = cfg.getPrimary()
+
+    from DIFGenerator.DIFSampler import CylinderSampler
+    
+    from DIFGenerator import DIFSampler
+    pg.sampler = DIFSampler(
+        daughter1_pid = kwargs.get("daughter1_pid", 11),
+        daughter2_pid = kwargs.get("daughter2_pid", -11),
+        mother_pid = kwargs.get("mother_pid", None),
+        mother_pos = kwargs.get("mother_pos", CylinderSampler([0, 100**2],[0, 2*pi],[-1500, 0],0))
+        )
+
+    from ForeseeGenerator.ForeseeSampler import ForeseeNumpySampler
+    mother_mom = ForeseeNumpySampler(
+        model_path = kwargs.get("model_path", "."),
+        model_name = kwargs.get("model_name", "DarkPhoton"),
+        com_energy = kwargs.get("com_energy", "14"),
+        mother_mass = kwargs.get("mother_mass", 0.01),
+        daughter1_pid = kwargs.get("daughter1_pid", 11),
+        daughter2_pid = kwargs.get("daughter2_pid", -11),
+        )
+    
+    pg.sampler.mother_sampler = PG.ParticleSampler(pid=kwargs.get("mother_pid", None),mom=mother_mom,n=1,pos=pg.sampler.mother_sampler.pos)
+    pg.sampler.mother_sampler.mass_override = False
+
+
+    return cfg
+
 def FaserParticleGunCfg(ConfigFlags) :
     generator = ConfigFlags.Sim.Gun.setdefault("Generator", "SingleParticle")
     kwargs = ConfigFlags.Sim.Gun
@@ -149,5 +194,7 @@ def FaserParticleGunCfg(ConfigFlags) :
         return FaserParticleGunCosmicsCfg(ConfigFlags, **kwargs)
     elif generator == "DecayInFlight" :
         return FaserParticleGunDecayInFlightCfg(ConfigFlags, **kwargs)
+    elif generator == "Foresee" :
+        return FaserParticleGunForeseeCfg(ConfigFlags, **kwargs)    
     else :
         return FaserParticleGunSingleParticleCfg(ConfigFlags, **kwargs )
diff --git a/Generators/ForeseeGenerator/CMakeLists.txt b/Generators/ForeseeGenerator/CMakeLists.txt
new file mode 100644
index 00000000..e2e5137b
--- /dev/null
+++ b/Generators/ForeseeGenerator/CMakeLists.txt
@@ -0,0 +1,12 @@
+################################################################################
+# Package: ForeseeGenerator
+################################################################################
+
+# Declare the package name:
+atlas_subdir( ForeseeGenerator )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
+atlas_install_joboptions( share/*.py )
+
diff --git a/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy b/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy
new file mode 100644
index 0000000000000000000000000000000000000000..36320c3f1c9cced47c9307b48529b444a2384be0
GIT binary patch
literal 59240
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
Q>JoWy{u{Yo>ufUo4@^-v{{R30

literal 0
HcmV?d00001

diff --git a/Generators/ForeseeGenerator/python/ForeseeSampler.py b/Generators/ForeseeGenerator/python/ForeseeSampler.py
new file mode 100644
index 00000000..60df119b
--- /dev/null
+++ b/Generators/ForeseeGenerator/python/ForeseeSampler.py
@@ -0,0 +1,433 @@
+import numpy as np
+
+import ParticleGun as PG
+
+class ForeseeNumpySampler(PG.MomSampler):
+    """ Sample from the output of Foresee generation in numpy format with columns E, theta, weight"""
+    def __init__(self, model_path = ".", model_name = "DarkPhoton",  com_energy = "14", mother_mass = 0.01,  coupling = None, daughter1_pid = 11, daughter2_pid = -11):
+
+        self.path = model_path
+        self.modelname = model_name
+        self.energy = com_energy
+        self._mass = mother_mass
+        self.coupling = coupling
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+
+        self.rng = np.random.default_rng()
+        self.xs = 0
+
+        self.read()
+
+    def read(self):
+        "Read data from numpy file in format E, theta, weight"
+
+        if self.path.endswith(".npy"):
+            filename = self.path
+        elif self.coupling is None:
+            filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self._mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+        else:
+            filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self._mass}GeV_c{self.coupling}to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+
+        print(f"Reading data from file: {filename}")
+        self.data = np.load(filename)
+
+        # Create probablity for each mode as weight / sum(weights)
+        self.prob = self.data[2]/np.sum(self.data[2])
+        
+        return
+
+    def mass(self):
+        "Mass converted from GeV to MeV"
+        return self._mass * 1000
+
+    def shoot(self):
+        "Choose a random item from the data, based on the probability"
+
+        # TODO: what about reuse of events?
+        energy, theta, weight = self.rng.choice(self.data, axis = 1, p = self.prob)
+
+        self.xs += weight
+
+        # Create mother momentum, randomly sampling phi
+        mother_mom = PG.EThetaMPhiSampler(energy, theta, self.mass(), [0, 2*np.pi])
+
+        return mother_mom.shoot()
+
+class ForeseeSampler(PG.MomSampler):
+    """Create events from foresee directly on the fly
+
+    Requires:
+        * foresee to be downloaded and in python path
+
+        cd <PATH>
+        git clone https://github.com/KlingFelix/FORESEE.git
+        export PYTHONPATH=$PYTHONPATH:<PATH>/FORESEE/src/        
+        
+        * scikit-hep installed
+
+        pip install scikit-hep --user
+        
+        * forsee files dir symlinked to the run dir
+
+        ln -s <PATH>/FORESEE/files .
+
+    """
+    
+    
+    def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, mother_pid = None):
+        self.modelname = modelname
+        self.model = Model(self.modelname)
+        self.energy = energy
+        self._mass = mass
+        self.couplings = [couplings] if isinstance(couplings, str) else couplings
+        self.mother_pid = mother_pid
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+
+        self.rng = np.random.default_rng()
+        self.xs = 0
+
+        if not os.path.exists("files"):
+            os.symlink(os.path.expandvars("$Calypso_DIR/../calypso/Generators/foresee/files"), "files")
+
+        self.pid_map = { 
+            (11, 11) : "e_e",           
+            (13, 13) : "mu_mu",
+            (22, 22) : "gamma_gamma",
+            }
+
+        self.mode = self.pid_map.get((self.daughter1_pid, self.daughter2_pid), None)
+        if self.mode is None:
+            sys.exit(f"Undefined decay to {self.daughter1_pid} + {self.daughter2_pid} for {self.modelname}")
+
+        self.foresee = Foresee()
+        self.foresee.set_detector(selection="np.sqrt(x.x**2 + x.y**2)< 0.1", channels=[self.mode], distance=480, length=1.5 , luminosity=150)        
+
+
+        if self.modelname == "DarkPhoton":
+            self.data = self.darkphoton()
+        elif self.modelname == "ALP-W":
+            self.data = self.alps()
+        else:
+            sys.exit(f"Unknown model {self.modelname}")
+
+        return
+
+
+    def mass(self):
+        return self._mass * 1000
+
+    def darkphoton(self):
+
+        # Production modes
+        self.model.add_production_2bodydecay(
+            pid0 =  "111",
+            pid1 = "22",
+            br = "2.*0.99 * coupling**2 * pow(1.-pow(mass/self.masses('111'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+    
+        self.model.add_production_2bodydecay(
+            pid0 = "221",
+            pid1 = "22",
+            br = "2.*0.39 * coupling**2 * pow(1.-pow(mass/self.masses('221'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+
+        # Handwavey 
+        self.model.add_production_mixing(
+            pid = "113",
+            mixing = "coupling * 0.3/5. * 0.77545**2/abs(mass**2-0.77545**2+0.77545*0.147*1j)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            )
+
+        # Question on validity as FASER gets larger
+        self.model.add_production_direct(
+            label = "Brem",
+            energy = self.energy,
+            condition = "p.pt<1",
+            coupling_ref=1,
+            )
+        
+        self.model.add_production_direct(
+            label = "DY",
+            energy = self.energy,
+            coupling_ref=1,
+            massrange=[1.5, 10.]
+            )
+
+        return self.decays()
+
+
+    def alps(self):
+
+        self.model.add_production_2bodydecay(
+            pid0 = "5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20, # Vary over out of phi and theta 
+            )
+    
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "-5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20,
+            )
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "130",
+            pid1 = "111",
+            br = "4.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        self.model.add_production_2bodydecay(
+            pid0 = "321",
+            pid1 = "211",
+            br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        return self.decays()
+
+
+    def decays(self):
+        # Decays
+        self.model.set_ctau_1d(
+            filename=f"files/models/{self.modelname}/ctau.txt", 
+            coupling_ref=1
+            )
+
+        # TODO take into account BR
+        self.model.set_br_1d(
+            modes = [self.mode],
+            filenames=[f"files/models/{self.modelname}/br/{self.mode}.txt"] 
+            )
+
+        # LLP spectrum
+        self.foresee.set_model(model=self.model)
+        
+        plt = self.foresee.get_llp_spectrum(self._mass, coupling=1, do_plot=True)  # This is just a reference coupling 
+        plt.savefig(f"{self.modelname}.png")
+
+        def flatten(l):
+            return [i for sublist in l for i in sublist]
+
+        coups, ctaus, nsigs, energies, weights, thetas = self.foresee.get_events(mass=self._mass, energy=self.energy, couplings=self.couplings)
+
+        return [flatten(thetas), flatten(energies), flatten(weights)] 
+
+    def shoot(self):
+        # Create probablity for each mode as weight / sum(weights)
+        prob = self.data[2]/np.sum(self.data[2])
+
+        # Choose a random item from the data, base on the probability
+        # TODO: what about reuse of events?
+        theta_mother, e_mother, w = self.rng.choice(self.data, axis = 1, p = prob)
+
+        self.xs += w
+
+        # Create other momentum
+        mother_mom = PG.EThetaMPhiSampler(e_mother*1000, theta_mother, self.mass(), [0,2*np.pi])
+
+        return mother_mom.shoot()
+
+if __name__ == "__main__":
+
+    # Testing ...
+
+    import os
+    from math import sqrt, log10
+    import matplotlib.pyplot as plt
+    import matplotlib
+    from DIFGenerator import DIFSampler
+    
+    
+    path = os.path.expandvars("$Calypso_DIR/../calypso/Generators/ForeseeGenerator/data/events_14TeV_m0.1GeV_c1e-05to_11_-11.npy")
+
+
+    modelname = "DarkPhoton"
+    mass = 0.1
+
+    theta = []
+    mom = []
+
+    d0theta = []
+    d0mom = []
+
+    d1theta = []
+    d1mom = []
+
+    # Accounting for rounding
+    epsilon = 5
+
+    # Mother position sampler over Z
+    mother_pos = PG.PosSampler(x=0,y=0,z=[-1500,0],t=0) 
+    
+    # Create mother momentum sampler reading data from foresee
+    mother_mom_sampler = ForeseeNumpySampler(model_path = path, model_name = modelname, com_energy = "14", mother_mass = 0.1, coupling = 1e-5,  daughter1_pid = 11, daughter2_pid = -11)
+    
+    # Create decay-in-flight
+    d = DIFSampler(11, -11, None)
+    d.mother_sampler = PG.ParticleSampler(pid=None,mom=mother_mom_sampler,n=1,pos=mother_pos)
+    d.mother_sampler.mass_override = False
+    
+    # Loop over a range of events
+    for i in range(1000):        
+
+        # Shoot the decay in flight
+        daughters = d.shoot()
+
+        # Get mother and sum of daugthers and check these make sense.
+        mother_mom = d.mother_mom
+        s = daughters[0].mom+daughters[1].mom
+
+        try:            
+            assert mother_mom.E() - epsilon <= s.E() <= mother_mom.E() + epsilon
+            assert mother_mom.P() - epsilon <= s.P()<= mother_mom.P() + epsilon 
+            assert mother_mom.Px() - epsilon <= s.Px() <= mother_mom.Px() + epsilon
+            assert mother_mom.Py() - epsilon <= s.Py() <= mother_mom.Py() + epsilon
+            assert mother_mom.Pz() - epsilon <= s.Pz() <= mother_mom.Pz() + epsilon
+            assert daughters[0].pos.X() == daughters[1].pos.X() == d.mother_pos.X()
+            assert daughters[0].pos.Y() == daughters[1].pos.Y() == d.mother_pos.Y()
+            assert daughters[0].pos.Z() == daughters[1].pos.Z() == d.mother_pos.Z()
+        except AssertionError:
+            print("Error on run " + str(i))
+
+            print("mother particle:")
+            print(" E = "     + str(mother_mom.E()))
+            print(" M = "     + str(mother_mom.M()))
+            print(" P = "     + str(mother_mom.P()))
+            print(" Px = "    + str(mother_mom.Px()))
+            print(" Py = "    + str(mother_mom.Py()))
+            print(" Pz = "    + str(mother_mom.Pz()))
+            print(" theta = " + str(mother_mom.Theta()))
+            print(" phi = "   + str(mother_mom.Phi()))
+            print(" x = "     + str(d.mother_pos.X()))
+            print(" y = "     + str(d.mother_pos.Y()))
+            print(" z = "     + str(d.mother_pos.Z()))
+
+            print("daughter 0 particle:")
+            print(" E = "     + str(daughters[0].mom.E()))
+            print(" M = "     + str(daughters[0].mom.M()))
+            print(" P = "     + str(daughters[0].mom.P()))
+            print(" Px = "    + str(daughters[0].mom.Px()))
+            print(" Py = "    + str(daughters[0].mom.Py()))
+            print(" Pz = "    + str(daughters[0].mom.Pz()))
+            print(" theta = " + str(daughters[0].mom.Theta()))
+            print(" phi = "   + str(daughters[0].mom.Phi()))
+            print(" x = "     + str(daughters[0].pos.X()))
+            print(" y = "     + str(daughters[0].pos.Y()))
+            print(" z = "     + str(daughters[0].pos.Z()))
+
+            print("daughter 1 particle:")
+            print(" E = "     + str(daughters[1].mom.E()))
+            print(" M = "     + str(daughters[1].mom.M()))
+            print(" P = "     + str(daughters[1].mom.P()))
+            print(" Px = "    + str(daughters[1].mom.Px()))
+            print(" Py = "    + str(daughters[1].mom.Py()))
+            print(" Pz = "    + str(daughters[1].mom.Pz()))
+            print(" theta = " + str(daughters[1].mom.Theta()))
+            print(" phi = "   + str(daughters[1].mom.Phi()))
+            print(" x = "     + str(daughters[1].pos.X()))
+            print(" y = "     + str(daughters[1].pos.Y()))
+            print(" z = "     + str(daughters[1].pos.Z()))
+
+            raise
+
+        # Store mother info to plot
+        theta.append(log10(mother_mom.Theta()))
+        mom.append(log10(mother_mom.P()/1000.))
+
+        # Store mother info to plot
+        d0theta.append(log10(daughters[0].mom.Theta()))
+        d0mom.append(log10(daughters[0].mom.P()/1000.))
+        d1theta.append(log10(daughters[1].mom.Theta()))
+        d1mom.append(log10(daughters[1].mom.P()/1000.))
+        
+
+ 
+    # Plot mother from sampling events
+    prange=[[-6, 0, 120],[ 0, 5, 50]]
+    tmin, tmax, tnum = prange[0]
+    pmin, pmax, pnum = prange[1]
+    t_edges = np.logspace(tmin, tmax, num=tnum+1)
+    p_edges = np.logspace(pmin, pmax, num=pnum+1)   
+
+    ticks = np.array([[np.linspace(10**(j),10**(j+1),9)] for j in range(-7,6)]).flatten()
+    ticks = [np.log10(x) for x in ticks]
+    ticklabels = np.array([[r"$10^{"+str(j)+"}$","","","","","","","",""] for j in range(-7,6)]).flatten()
+    matplotlib.rcParams.update({'font.size': 15})
+
+    
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=theta,y=mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_m{mass}.png")
+
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=d0theta,y=d0mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_d0_m{mass}.png")
+
+    fig = plt.figure(figsize=(8,5.5))
+    ax = plt.subplot(1,1,1)
+    h=ax.hist2d(x=d1theta,y=d1mom,
+                bins=[tnum,pnum],range=[[tmin,tmax],[pmin,pmax]],
+                norm=matplotlib.colors.LogNorm(), cmap="hsv",
+                )
+    fig.colorbar(h[3], ax=ax)
+    ax.set_xlabel(r"angle wrt. beam axis $\theta$ [rad]")
+    ax.set_ylabel(r"momentum $p$ [GeV]")
+    ax.set_xticks(ticks)
+    ax.set_xticklabels(ticklabels)
+    ax.set_yticks(ticks)
+    ax.set_yticklabels(ticklabels)
+    ax.set_xlim(tmin, tmax)
+    ax.set_ylim(pmin, pmax)
+    plt.savefig(f"{modelname}_PG_d1_m{mass}.png")
+    
+
+    print (f"x-sect = {mother_mom_sampler.xs} pb")
+    
+
+
+        
diff --git a/Generators/ForeseeGenerator/python/Validate.py b/Generators/ForeseeGenerator/python/Validate.py
new file mode 100644
index 00000000..20f3893e
--- /dev/null
+++ b/Generators/ForeseeGenerator/python/Validate.py
@@ -0,0 +1,198 @@
+from AthenaPython.PyAthena import StatusCode, McEventCollection, HepMC, CLHEP
+from GeneratorModules.EvgenAnalysisAlg import EvgenAnalysisAlg
+
+import ROOT as R
+import numpy as np
+import os
+
+def fix():
+    "Python Fixes for HepMC"
+    def add(self, other):
+        self.set(self.x() + other.x(), self.y() + other.y(),
+                 self.z() + other.z(), self.t() + other.t())
+        return self
+
+    HepMC.FourVector.__iadd__ = add
+    del add
+
+    return
+
+class HistSvc(object):
+    "Class to deal with histograms"
+
+    def __init__(self):
+        self.hists = {}
+
+    def add(self, name, nbinsX = None, loX = None, hiX = None, nbinsY = None, loY = None, hiY = None, title = None, arrayX = None, arrayY = None):
+        hname = os.path.basename(name)
+
+        if title is None:  title = hname
+
+        if nbinsY is not None:
+            self.hists[name] = R.TH2F(hname, title, nbinsX, loX, hiX, nbinsY, loY, hiY)
+        elif arrayX is not None and arrayY is not None:
+            self.hists[name] = R.TH2F(hname, title, len(arrayX) - 1, arrayX, len(arrayY) - 1, arrayY)
+        elif arrayX is not None and arrayY is None and nbinsY is not None:
+            self.hists[name] = R.TH2F(hname, title, len(arrayX) - 1, arrayX, nbinsY, loY, hiY)
+        elif arrayX is None and arrayY is not None:
+            self.hists[name] = R.TH2F(hname, title, nbinsX, loX, hiX, len(arrayY) - 1, arrayY)            
+        elif arrayX is not None:
+            self.hists[name] = R.TH1F(hname, title, len(arrayX) - 1, arrayX)
+        else:
+            self.hists[name] = R.TH1F(hname, title, nbinsX, loX, hiX)                
+
+    def __getitem__(self, name):
+        return self.hists[name]
+
+    def write(self, name):
+
+        f = R.TFile.Open(name, "RECREATE")
+    
+        for n, h in self.hists.items():
+            path = os.path.dirname(n)
+            if path and not f.GetDirectory(path):
+                f.mkdir(path)
+            
+            f.cd(path)
+            h.Write()
+
+        f.Close()
+
+        return
+
+class EvgenValidation(EvgenAnalysisAlg):
+    "Gen-level validation"
+
+    def __init__(self, name = "EvgenValidation"):
+        super(EvgenValidation, self).__init__(name=name)
+        self.hists = HistSvc()
+        self.ndaughter = 2
+        self.outname = "validation.root"
+
+    def binning(self):
+        "binning for theta vs phi plot"
+        tmin, tmax, tnum = [-6, 0, 12]
+        pmin, pmax, pnum = [ 0, 5, 5]
+        t_edges = np.logspace(tmin, tmax, num=tnum+1)
+        p_edges = np.logspace(pmin, pmax, num=pnum+1)
+        return t_edges, p_edges
+
+    def initialize(self):
+
+        # All daughters
+        self.hists.add("Masses", 100, 0, 0.01)
+        self.hists.add("PIDs", 60, -30, 30)
+
+        # Daughter i
+        tbins, pbins = self.binning()
+        for i in range(self.ndaughter):
+            self.hists.add(f"P_d{i}", 100, 0, 10000)
+            self.hists.add(f"Pt_d{i}", 100, 0, 1)
+            self.hists.add(f"Theta_d{i}", 10, 0, 0.001)
+            self.hists.add(f"Phi_d{i}", 16, -3.2, 3.2)
+            self.hists.add(f"ThetaVsP_d{i}", arrayX = tbins, arrayY = pbins)
+
+        # Mother
+        self.hists.add("P_M", 100, 0, 10000) 
+        self.hists.add("Pt_M", 100, 0, 1)       
+        self.hists.add("Theta_M", 10, 0, 0.001)
+        self.hists.add("Phi_M", 16, -3.2, 3.2)
+        self.hists.add("Mass_M", 100, 0, 1)
+        self.hists.add("ThetaVsP_M", arrayX = tbins, arrayY = pbins)
+        
+        return StatusCode.Success
+
+
+    def fillKin(self, label, p, mass = False, twoD = True):
+
+        self.hists[f"P_{label}"].Fill(p.rho()/1000, 1)        
+        self.hists[f"Pt_{label}"].Fill(p.perp()/1000, 1)
+        self.hists[f"Theta_{label}"].Fill(p.theta(), 1)
+        self.hists[f"Phi_{label}"].Fill(p.phi(), 1)
+
+        if mass:
+            self.hists[f"Mass_{label}"].Fill(p.m()/1000, 1)
+
+        if twoD:
+            self.hists[f"ThetaVsP_{label}"].Fill(p.theta(), p.rho()/1000, 1)
+
+        return
+
+    def fillDaughter(self, p):
+        self.hists["Masses"].Fill(p.momentum().m()/1000, 1)
+        self.hists["PIDs"].Fill(p.pdg_id())
+        return
+
+    def execute(self):
+        evt = self.events()[0]
+
+        # Loop over all particles in events (assuming mother not stored)
+        momenta = []
+        mother = HepMC.FourVector(0,0,0,0)
+        for i, p in enumerate(evt.particles):
+            #p.print()
+            self.fillDaughter(p)
+            momenta.append(p.momentum())    
+            mother += p.momentum()
+
+        # Fill daughter plots
+        for i in range(self.ndaughter):
+            self.fillKin(f"d{i}", momenta[i])
+
+        # Fill mother plots
+        self.fillKin("M", mother, mass = True)
+            
+        return StatusCode.Success
+
+    def finalize(self):
+        self.hists.write(self.outname)
+        return StatusCode.Success
+        
+if __name__ == "__main__":
+
+    import argparse, sys
+    parser = argparse.ArgumentParser(description="Run gen-level validation")
+    parser.add_argument("file", help = "full path to imput file")
+    parser.add_argument("--ndaugthers", "-d", default = 2, type = float, help = "Number of daugthers to plot")
+    parser.add_argument("--output", "-o",  default = "validation.root", help = "Name of output file")
+    parser.add_argument("--mcEventKey", "-k",  default = "BeamTruthEvent", help = "Name of MC collection")
+    parser.add_argument("--nevents", "-n", default = -1, type = int, help = "Number of events to process")    
+    args = parser.parse_args()    
+
+    from AthenaCommon.Logging import log
+    from AthenaCommon.Constants import DEBUG
+    log.setLevel(DEBUG)
+    
+    from AthenaCommon.Configurable import Configurable
+    Configurable.configurableRun3Behavior = 1
+
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-01"             # Always needed; must match FaserVersion
+    ConfigFlags.GeoModel.FaserVersion     = "FASER-01"           # Default FASER geometry
+    ConfigFlags.Detector.EnableFaserSCT = True
+    ConfigFlags.Input.Files = [args.file]
+    ConfigFlags.lock()
+
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    cfg = MainServicesCfg(ConfigFlags)
+    
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    cfg.merge(PoolReadCfg(ConfigFlags))
+
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from AthenaConfiguration.ComponentFactory import CompFactory
+
+    import McParticleEvent.Pythonizations
+    fix()
+    
+    acc = ComponentAccumulator()
+    valid = EvgenValidation()
+    valid.McEventKey = args.mcEventKey
+    valid.ndaughters = args.ndaugthers
+    valid.outname = args.output
+    acc.addEventAlgo(valid)    
+    cfg.merge(acc)
+
+    sc = cfg.run(maxEvents = args.nevents)
+    sys.exit(not sc.isSuccess())
diff --git a/Generators/ForeseeGenerator/share/generate_forsee_events.py b/Generators/ForeseeGenerator/share/generate_forsee_events.py
new file mode 100644
index 00000000..11a10017
--- /dev/null
+++ b/Generators/ForeseeGenerator/share/generate_forsee_events.py
@@ -0,0 +1,256 @@
+import os
+
+import numpy as np
+
+from foresee import Foresee, Model, Utility
+
+class ForeseeGenerator(object):
+    """
+    Generate LLP particles within FASER acceptance from FORESEE
+    """
+    
+    def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, mother_pid = None):
+
+        self.modelname = modelname
+        self.energy = energy
+        self.mass = mass
+        self.couplings = [couplings] if isinstance(couplings, (str, int, float)) else couplings
+        self.mother_pid = mother_pid
+        self.daughter1_pid = daughter1_pid
+        self.daughter2_pid = daughter2_pid
+
+        #if not os.path.exists("files"):
+        #    os.symlink(os.path.expandvars("$Calypso_DIR/../calypso/Generators/foresee/files"), "files")
+
+        # Set decay mode ...
+        
+        self.pid_map = { 
+            (11, 11) : "e_e",           
+            (13, 13) : "mu_mu",
+            (22, 22) : "gamma_gamma",
+            }
+
+        self.mode = self.pid_map.get((abs(self.daughter1_pid), abs(self.daughter2_pid)), None)
+        if self.mode is None:
+            sys.exit(f"Undefined decay to {self.daughter1_pid} + {self.daughter2_pid} for {self.modelname}")
+
+        # Set detector ...
+        self.foresee = Foresee()
+        self.foresee.set_detector(selection="np.sqrt(x.x**2 + x.y**2)< 0.1", channels=[self.mode], distance=480, length=1.5 , luminosity=150)        
+
+        # Set model ...
+        self.model = Model(self.modelname)
+        
+        if self.modelname == "DarkPhoton":
+            self.data = self.darkphoton()
+        elif self.modelname == "ALP-W":
+            self.data = self.alps()
+        else:
+            sys.exit(f"Unknown model {self.modelname}")
+
+        return
+
+    def darkphoton(self):
+
+        # Production modes
+        self.model.add_production_2bodydecay(
+            pid0 =  "111",
+            pid1 = "22",
+            br = "2.*0.99 * coupling**2 * pow(1.-pow(mass/self.masses('111'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+    
+        self.model.add_production_2bodydecay(
+            pid0 = "221",
+            pid1 = "22",
+            br = "2.*0.39 * coupling**2 * pow(1.-pow(mass/self.masses('221'),2),3)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10)
+
+        # This is a bit handwavey according to Felix
+        self.model.add_production_mixing(
+            pid = "113",
+            mixing = "coupling * 0.3/5. * 0.77545**2/abs(mass**2-0.77545**2+0.77545*0.147*1j)",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            )
+
+        # Question on validity as FASER gets larger but OK for currrent size according to Felix
+        self.model.add_production_direct(
+            label = "Brem",
+            energy = self.energy,
+            condition = "p.pt<1",
+            coupling_ref=1,
+            )
+        
+        self.model.add_production_direct(
+            label = "DY",
+            energy = self.energy,
+            coupling_ref=1,
+            massrange=[1.5, 10.]
+            )
+
+        return self.decays()
+
+
+    def alps(self):
+
+        self.model.add_production_2bodydecay(
+            pid0 = "5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20, # Vary over phi and theta 
+            )
+            
+        self.model.add_production_2bodydecay(
+            pid0 = "-5",
+            pid1 = "321",
+            br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))",
+            generator = "Pythia8",
+            energy = self.energy,
+            nsample = 20,
+            )
+        
+        self.model.add_production_2bodydecay(
+            pid0 = "130",
+            pid1 = "111",
+            br = "4.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        self.model.add_production_2bodydecay(
+            pid0 = "321",
+            pid1 = "211",
+            br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))",
+            generator = "EPOSLHC",
+            energy = self.energy,
+            nsample = 10,
+            )
+
+        return self.decays()
+
+
+    def decays(self):
+        # Set up liftime and BRs
+        
+        self.model.set_ctau_1d(
+            filename=f"files/models/{self.modelname}/ctau.txt", 
+            coupling_ref=1
+            )
+
+        self.model.set_br_1d(
+            modes = [self.mode],
+            filenames=[f"files/models/{self.modelname}/br/{self.mode}.txt"] 
+            )
+
+        # Get LLP spectrum
+        self.foresee.set_model(model=self.model)        
+        plt = self.foresee.get_llp_spectrum(self.mass, coupling=1, do_plot=True)  # This is just a reference coupling 
+        #plt.savefig(f"{self.modelname}.png")
+
+        def flatten(l):
+            return [i for sublist in l for i in sublist]
+
+        # Get list of events within detector
+        coups, ctaus, nsigs, energies, weights, thetas = self.foresee.get_events(mass=self.mass, energy=self.energy, couplings=self.couplings)
+
+        # Return energy (converting to MeV), theta and weights
+        return [[e*1000 for e in flatten(energies)], flatten(thetas), flatten(weights)] 
+
+    def write(self):
+        # Write LLP results to a file
+        
+        energies, thetas, weights = self.data
+
+        dirname = f"files/models/{self.modelname}/events"
+        if not os.path.exists(dirname):
+            os.mkdir(dirname)
+
+        if len(self.couplings) == 1:
+            filename = f"{dirname}/events_{self.energy}TeV_m{self.mass}GeV_c{self.couplings[0]}to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+        else:
+            filename = f"{dirname}/events_{self.energy}TeV_m{self.mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy"
+
+        print(f"save data to file: {filename}")
+        np.save(filename,[energies,thetas, weights])
+
+        return
+
+def setup_foresee(path):
+
+    if path is None:
+        return
+
+    # Add foresee to python path
+    path = os.path.expandvars(os.path.expanduser(path))
+    os.sys.path.append(f"{path}/FORESEE/src")
+
+    # Symlink foresee files dir to current dir
+    if not os.path.exists("files"):
+        os.symlink(os.path.expandvars(f"{path}/FORESEE/files"), "files")
+
+    # Install scikit-hep if needed.
+
+    try:
+        from skhep.math.vectors import LorentzVector, Vector3D
+    except ModuleNotFoundError:
+        os.system("pip install scikit-hep --user")
+        try:
+            from skhep.math.vectors import LorentzVector, Vector3D
+        except ModuleNotFoundError:
+            raise ModuleNotFoundError("Unable to find skhep.  Please install the scikit-hep package")
+        
+    return
+
+
+def parse_couplings(data):    
+
+    try:
+        couplings = [float(d) for d in data]
+    except ValueError:
+        try:
+            couplings = np.logspace(*eval(data[0]))
+        except:
+            sys.exit("Unable to parse couplings")
+
+    return couplings
+
+if __name__ == "__main__":
+
+    import argparse, sys
+    
+    parser = argparse.ArgumentParser(description="Run FORSEE generation")
+    parser.add_argument("model", help = "Name of foresee model")
+    parser.add_argument("--mass", "-m", required = True, type = float, help = "Mass of mother [GeV]")
+    parser.add_argument("--couplings", "-c", required = True, nargs = "+", help = "Couplings of mother (either single/mulitple values or tuple to pass to np.logspace)")
+    parser.add_argument("--pid1", required = True, type = int, help = "PID of daughter 1")
+    parser.add_argument("--pid2", default = None, help = "PID of daughter 2 (if not set then will be -PID1)")
+    parser.add_argument("--Ecom", default = "14", help = "Center of mass energy [TeV]")
+    parser.add_argument("--path", default = ".", help = "Path to foresee installation")
+    args = parser.parse_args()
+
+    setup_foresee(args.path)
+    
+
+    # Create PIDs
+    if args.pid2 is None:
+        args.pid2 = -args.pid1
+    
+    couplings = parse_couplings(args.couplings)
+
+    print(f"Generating {args.model} events at Ecom = {args.Ecom}") 
+    print(f"   mother mass = {args.mass} GeV")
+    print(f"   decay = {args.pid1} {args.pid2}")
+    print(f"   couplings = {couplings}")
+
+    f = ForeseeGenerator(args.model, args.Ecom, args.mass, couplings, args.pid1, args.pid2)
+    f.write()
+    
+        
+
diff --git a/Generators/HEPMCReader/CMakeLists.txt b/Generators/HEPMCReader/CMakeLists.txt
new file mode 100644
index 00000000..3076e7ab
--- /dev/null
+++ b/Generators/HEPMCReader/CMakeLists.txt
@@ -0,0 +1,9 @@
+################################################################################
+# Package: HEPMCGenie
+################################################################################
+
+# Declare the package name:
+atlas_subdir( HEPMCReader )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
diff --git a/Generators/HEPMCReader/python/HepMCReaderConfig.py b/Generators/HEPMCReader/python/HepMCReaderConfig.py
new file mode 100644
index 00000000..72081a86
--- /dev/null
+++ b/Generators/HEPMCReader/python/HepMCReaderConfig.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+
+# import sys
+from AthenaConfiguration.MainServicesConfig import AthSequencer
+from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+from AthenaConfiguration.ComponentFactory import CompFactory
+
+from TruthIO.TruthIOConf import HepMCReadFromFile
+
+
+def HepMCReaderCfg(ConfigFlags, **kwargs) :
+    cfg = ComponentAccumulator(AthSequencer("AthBeginSeq", Sequential = True))
+
+    
+    from TruthIO.TruthIOConf import HepMCReadFromFile
+    hepmc = CompFactory.HepMCReadFromFile(name = kwargs.setdefault("name", "FASERHepMCReader"))
+    hepmc.InputFile = ConfigFlags.Input.Files[0] 
+    hepmc.McEventKey = kwargs.setdefault("McEventKey", "BeamTruthEvent")
+    
+    cfg.addEventAlgo(hepmc, sequenceName = "AthBeginSeq", primary = True) # to run *before* G4
+
+    return cfg
diff --git a/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py b/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
index 709797ae..d6e254c8 100644
--- a/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
+++ b/Simulation/G4Faser/G4FaserAlg/test/G4FaserAlgConfigNew_Test.py
@@ -82,11 +82,23 @@ if __name__ == '__main__':
 #
     if ConfigFlags.Input.Files and ConfigFlags.Input.Files != ["_CALYPSO_GENERIC_INPUTFILE_NAME_"] :
         print("Input.Files = ",ConfigFlags.Input.Files)
+
+#
+# If so, and only one file that ends in .events read as HepMC
+#
+        if len(ConfigFlags.Input.Files) == 1 and ConfigFlags.Input.Files[0].endswith(".events"):
+            from HEPMCReader.HepMCReaderConfig import HepMCReaderCfg
+            cfg.merge(HepMCReaderCfg(ConfigFlags))
+
+            from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+            cfg.merge(McEventSelectorCfg(ConfigFlags))
+
 #
-# If so, set up to read it
+# Else, set up to read it as a pool.root file
 #
-        from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
-        cfg.merge(PoolReadCfg(ConfigFlags))
+        else:
+            from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+            cfg.merge(PoolReadCfg(ConfigFlags))
 #
 # If not, configure the particle gun as requested, or using defaults
 #
-- 
GitLab