diff --git a/Configuration/Generator/python/DisplacedParticleGun_cfi.py b/Configuration/Generator/python/DisplacedParticleGun_cfi.py new file mode 100644 index 0000000000000..d7ad6c7dcbb57 --- /dev/null +++ b/Configuration/Generator/python/DisplacedParticleGun_cfi.py @@ -0,0 +1,51 @@ +import FWCore.ParameterSet.Config as cms + +generator = cms.EDProducer( + "DisplacedParticleGunProducer", + PGunParameters = cms.PSet( + # particle direction + MinPt = cms.double(5.), + MaxPt = cms.double(100.), + MinPhi = cms.double(-3.141592653589793), + MaxPhi = cms.double(+3.141592653589793), + MinTheta = cms.double(-3.141592653589793 / 4), + MaxTheta = cms.double(3.141592653589793 / 4), + + # displaced vertex in the transverse plane (cm) + # located in front of HGCAL's CE-E surface (also avoiding the moderator) + RMin = cms.double(60.), + RMax = cms.double(90.), + MinVtxPhi = cms.double(0.0), + MaxVtxPhi = cms.double(2.0 * 3.141592653589793), + ZVtx = cms.double(321.), + + NParticles = cms.int32(1), + PartID = cms.int32(22), # photon + + # how to sample the vertex radius + UniformDensityInR = cms.bool(False), + + MaxTries = cms.uint32(10000), + + # if True: derive theta to hit a region of the HGCAL CE-E back surface + # the phi of the particle's direction is matched to the phi of the vertex + # which avoid particles crossing the beamline and restricts particles to a tighter region of the detector + # if False: use MinTheta/MaxTheta + PointingToHGCAL = cms.bool(True), + + # only used if PointingToHGCAL == True, + # corresponds to the central third of HGCAL's CE-E back surface + # note that these values might not be optimal if the default vertex coordinates' configuration is modified + RMinBackSurfaceHGCAL = cms.double(75.80), + RMaxBackSurfaceHGCAL = cms.double(120.23), + + # if True: the particle's direction is projected back to the z=0 plane, + # ensuring compatibility with the specified range + RestrictRInZPlaneAtZero = cms.bool(True), + RMinAtZero = cms.double(0.), + RMaxAtZero = cms.double(100.), + + ), + Verbosity = cms.untracked.int32(1), + firstRun = cms.untracked.uint32(1), +) diff --git a/Configuration/PyReleaseValidation/python/relval_steps.py b/Configuration/PyReleaseValidation/python/relval_steps.py index 7267b3f078335..434ec6ecf7952 100644 --- a/Configuration/PyReleaseValidation/python/relval_steps.py +++ b/Configuration/PyReleaseValidation/python/relval_steps.py @@ -4786,10 +4786,13 @@ def gen2024HiMix(fragment,howMuch): } if beamspot is not None: upgradeStepDict['GenSim'][k]['--beamspot']=beamspot - + upgradeStepDict['GenSimCloseBy'][k] = deepcopy(upgradeStepDict['GenSim'][k]) upgradeStepDict['GenSimCloseBy'][k]['--beamspot'] = 'CloseBy' - + + upgradeStepDict['GenSimDisplaced'][k] = deepcopy(upgradeStepDict['GenSim'][k]) + upgradeStepDict['GenSimDisplaced'][k]['--beamspot'] = 'CloseBy' # still using DBrealisticHLLHC + upgradeStepDict['GenSimHLBeamSpot'][k] = {'-s' : 'GEN,SIM', '-n' : 10, '--conditions' : gt+'_13TeV', @@ -4802,7 +4805,7 @@ def gen2024HiMix(fragment,howMuch): upgradeStepDict['GenSimHLBeamSpot14'][k] = deepcopy(upgradeStepDict['GenSimHLBeamSpot'][k]) upgradeStepDict['GenSimHLBeamSpot14'][k]['--conditions'] = gt - upgradeStepDict['GenSimHLBeamSpotCloseBy'][k] = upgradeStepDict['GenSimCloseBy'][k] + upgradeStepDict['GenSimHLBeamSpotCloseBy'][k] = deepcopy(upgradeStepDict['GenSimCloseBy'][k]) upgradeStepDict['Sim'][k] = {'-s' : 'SIM', '-n' : 10, @@ -5040,7 +5043,7 @@ def gen2024HiMix(fragment,howMuch): # in case special WF has PU-specific changes: apply *after* basic PU step is created specialWF.setupPU(upgradeStepDict, k, upgradeProperties[year][k]) - + for step in upgradeStepDict.keys(): # we need to do this for each fragment if ('Sim' in step and ('Fast' not in step and step != 'Sim')) or ('Premix' in step) or ('Sim' not in step and 'Gen' in step): diff --git a/Configuration/PyReleaseValidation/python/relval_upgrade.py b/Configuration/PyReleaseValidation/python/relval_upgrade.py index 57907606f90b4..48b0001f7a1cd 100644 --- a/Configuration/PyReleaseValidation/python/relval_upgrade.py +++ b/Configuration/PyReleaseValidation/python/relval_upgrade.py @@ -45,13 +45,15 @@ def notForGenOnly(key,specialType): step = 'GenSimHLBeamSpotCloseBy' elif 'CloseBy' in frag or 'CE_E' in frag or 'CE_H' in frag: step = 'GenSimCloseBy' + elif 'DisplacedParticleGun' in frag: + step = 'GenSimDisplaced' stepMaker = makeStepNameSim elif 'Gen' in step: if 'HLBeamSpot' in step: if '14TeV' in frag: step = 'GenHLBeamSpot14' stepMaker = makeStepNameSim - + if 'HARVEST' in step: hasHarvest = True for specialType,specialWF in upgradeWFs.items(): if notForGenOnly(key,specialType): ## we don't need all the flavors for the GEN diff --git a/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py b/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py index fb1e68b543434..dc3f9ed132afe 100644 --- a/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py +++ b/Configuration/PyReleaseValidation/python/upgradeWorkflowComponents.py @@ -224,6 +224,7 @@ def condition(self, fragment, stepList, key, hasHarvest): 'Sim', 'GenSim', 'GenSimCloseBy', + 'GenSimDisplaced', 'GenSimHLBeamSpot', 'GenSimHLBeamSpot14', 'GenSimHLBeamSpotCloseBy', @@ -921,7 +922,7 @@ def setup_(self, step, stepName, stepDict, k, properties): if 'HARVESTGlobal' in step: stepDict[stepName][k] = merge([self.step4, stepDict[step][k]]) def condition(self, fragment, stepList, key, hasHarvest): - return ('CloseByPGun_Barrel') in fragment and ('Run4' in key) + return 'CloseByPGun_Barrel' in fragment and 'Run4' in key upgradeWFs['ticl_barrel'] = UpgradeWorkflow_ticl_barrel( steps = [ @@ -952,7 +953,7 @@ def setup_(self, step, stepName, stepDict, k, properties): if 'HARVESTGlobal' in step: stepDict[stepName][k] = merge([self.step4, stepDict[step][k]]) def condition(self, fragment, stepList, key, hasHarvest): - return ('CloseByPGun_Barrel') in fragment and ('Run4' in key) and ('PU' in key) + return 'CloseByPGun_Barrel' in fragment and 'Run4' in key and 'PU' in key upgradeWFs['ticl_barrel_CPfromPU'] = UpgradeWorkflow_ticl_barrel_CPfromPU( steps = [ @@ -2165,7 +2166,7 @@ def condition(self, fragment, stepList, key, hasHarvest): class UpgradeWorkflow_NGTScouting(UpgradeWorkflow): def setup_(self, step, stepName, stepDict, k, properties): # skip RECO, ALCA and HARVEST - if ('ALCA' in step) or ('Reco' in step) or ('HLT' in step): + if any(x in step for x in ('ALCA', 'Reco', 'HLT')): stepDict[stepName][k] = None elif 'DigiTrigger' in step: # Add the aging customization @@ -2179,8 +2180,10 @@ def setup_(self, step, stepName, stepDict, k, properties): stepDict[stepName][k] = merge([self.step3, stepDict[step][k]]) else: stepDict[stepName][k] = merge([stepDict[step][k]]) + def condition(self, fragment, stepList, key, hasHarvest): - return (fragment=="TTbar_14TeV" or fragment=="SingleMuPt15Eta0p_0p4") and 'Run4' in key + return (fragment=='TTbar_14TeV' or fragment=='SingleMuPt15Eta0p_0p4' or 'Displaced' in fragment) and 'Run4' in key + upgradeWFs['NGTScouting'] = UpgradeWorkflow_NGTScouting( steps = [ 'Reco', @@ -3978,4 +3981,5 @@ def __init__(self, howMuch, dataset): ('Hydjet_Quenched_MinBias_5519GeV_cfi', UpgradeFragment(U2000by1,'HydjetQMinBias_5519GeV')), ('SingleMuPt15Eta0_0p4_cfi', UpgradeFragment(Kby(9,100),'SingleMuPt15Eta0p_0p4')), ('CloseByPGun_Barrel_Front_cfi', UpgradeFragment(Kby(9,100),'CloseByPGun_Barrel_Front')), + ('DisplacedParticleGun_cfi', UpgradeFragment(Kby(9,100),'DisplacedPGun')), ]) diff --git a/IOMC/ParticleGuns/src/DisplacedParticleGunProducer.cc b/IOMC/ParticleGuns/src/DisplacedParticleGunProducer.cc new file mode 100644 index 0000000000000..fcd6351ec2a98 --- /dev/null +++ b/IOMC/ParticleGuns/src/DisplacedParticleGunProducer.cc @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "HepMC/GenEvent.h" + +#include "FWCore/AbstractServices/interface/RandomNumberGenerator.h" +#include "FWCore/Framework/interface/ESHandle.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/EventSetup.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "FWCore/Utilities/interface/isFinite.h" +#include "SimDataFormats/GeneratorProducts/interface/GenEventInfoProduct.h" +#include "SimDataFormats/GeneratorProducts/interface/HepMCProduct.h" +#include "SimGeneral/HepPDTRecord/interface/ParticleDataTable.h" + +namespace edm { + + namespace { + + // Hard-coded HGCAL CE-E back surface of layer 25/26 + // or, equivalently, of front face of CE-E backplate absorber 1 + // values in centimeters + constexpr double kCeeBackZ = 362.18; + constexpr double kCeeBackRMin = 31.36; + constexpr double kCeeBackRMax = 164.67; + + // Sample r uniformly in area between [rmin, rmax] (uniform point density) + double shootUniformDensity(CLHEP::HepRandomEngine* eng, double rmin, double rmax) { + const double r2 = CLHEP::RandFlat::shoot(eng, rmin * rmin, rmax * rmax); + return std::sqrt(r2); + } + + // Sample r uniformly between [rmin, rmax] + double shootUniformR(CLHEP::HepRandomEngine* eng, double rmin, double rmax) { + return CLHEP::RandFlat::shoot(eng, rmin, rmax); + } + + // Ensure the particle hits HGCAL's CE-E back surface within [rMin; rMax] + bool hitsZPlaneWithinR(double x0, + double y0, + double z0, // vertex coordinates + double px, + double py, + double pz, // particle momentum + double zPlane, + double rMin, + double rMax, + int verbose) { + const double t = (zPlane - z0) / pz; + if (t <= 0.0) { + return false; + } + + // project (x, y) into the plane assuming straight trajectories + const double xHit = x0 + t * px; + const double yHit = y0 + t * py; + const double rHit = std::hypot(xHit, yHit); + + if (verbose > 0) { + std::cout << "hitsZPlaneWithin " << " | return=" << static_cast(rHit >= rMin && rHit <= rMax) + << " | rHit=" << rHit << ", rMin=" << rMin << ", rMax=" << rMax << ", t=" << t + << ", zPlane=" << zPlane << ", z0=" << z0 << ", pz=" << pz << std::endl; + } + + return (rHit >= rMin && rHit <= rMax); + } + + // ensures theta is never too close to zero, which makes the computation of pz unstable + double pickSensibleTheta(CLHEP::HepRandomEngine* eng, double amin, double amax) { + double theta = 0.; + while (std::abs(theta) < 1e-6) { + theta = CLHEP::RandFlat::shoot(eng, amin, amax); + } + return theta; + } + + std::tuple computeMomentum(double pt, double theta, double phi) { + double px = pt * std::cos(phi); + double py = pt * std::sin(phi); + double pz = 0.; + if (std::abs(theta) < 1e-6) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Theta is too close to zero: " << theta << ". Unstable pz."; + } + + pz = pt / std::abs(std::tan(theta)); + + // shoot the particle along the same plane but in the negative theta direction + // reminder: theta is measured from the x axis in counter-clockwise fashion + if (theta < 0.) { + px = -px; + py = -py; + } + + return {px, py, pz}; + } + + } // namespace + + class DisplacedParticleGunProducer : public edm::global::EDProducer<> { + public: + explicit DisplacedParticleGunProducer(const ParameterSet&); + ~DisplacedParticleGunProducer() override = default; + + static void fillDescriptions(ConfigurationDescriptions& descriptions); + + private: + void produce(edm::StreamID, edm::Event& e, const edm::EventSetup& es) const override; + + double fPtMin = 0.; + double fPtMax = 0.; + double fPhiMin = 0.; + double fPhiMax = 0.; + double fRMin = 0.; + double fRMax = 0.; + double fPhiVtxMin = 0.; + double fPhiVtxMax = 0.; + double fZVtx = 0.; + int fNParticles = 1; + int fPartID; + bool fUniformDensityInR = false; + unsigned int fMaxTries = 1000; + + // If true: derive theta range from hard-coded HGCAL CE-E back surface envelope + // (with R in [RMinBackSurfaceHGCAL, RMaxBackSurfaceHGCAL]) and vertex rho + // If false: sample theta uniformly in [MinTheta, MaxTheta] + bool fPointingToHGCAL = true; + bool fRestrictRInZPlaneAtZero = true; + double fRMinBackSurfaceHGCAL = kCeeBackRMin; + double fRMaxBackSurfaceHGCAL = kCeeBackRMax; + double fThetaMin = 0.; + double fThetaMax = 0.; + std::optional fRMinAtZero = std::nullopt; + std::optional fRMaxAtZero = std::nullopt; + + const ESGetToken fPDGTableToken; + int fVerbosity = 0; + }; + + DisplacedParticleGunProducer::DisplacedParticleGunProducer(const ParameterSet& pset) + : fPDGTableToken(esConsumes<>()) { + Service rng; + if (!rng.isAvailable()) { + throw cms::Exception("Configuration") + << "The RandomNumberProducer module requires the RandomNumberGeneratorService\n" + "which appears to be absent. Please add that service to your configuration\n" + "or remove the modules that require it."; + } + + const auto pgun = pset.getParameter("PGunParameters"); + + fPtMin = pgun.getParameter("MinPt"); + fPtMax = pgun.getParameter("MaxPt"); + fPhiMin = pgun.getParameter("MinPhi"); + fPhiMax = pgun.getParameter("MaxPhi"); + fThetaMin = pgun.getParameter("MinTheta"); + fThetaMax = pgun.getParameter("MaxTheta"); + fPhiVtxMin = pgun.getParameter("MinVtxPhi"); + fPhiVtxMax = pgun.getParameter("MaxVtxPhi"); + fRMin = pgun.getParameter("RMin"); + fRMax = pgun.getParameter("RMax"); + fZVtx = pgun.getParameter("ZVtx"); + fNParticles = pgun.getParameter("NParticles"); + fPartID = pgun.getParameter("PartID"); + fUniformDensityInR = pgun.getParameter("UniformDensityInR"); + fMaxTries = pgun.getParameter("MaxTries"); + fVerbosity = pset.getUntrackedParameter("Verbosity"); + fPointingToHGCAL = pgun.getParameter("PointingToHGCAL"); + fRestrictRInZPlaneAtZero = pgun.getParameter("RestrictRInZPlaneAtZero"); + + if (fRestrictRInZPlaneAtZero && !fPointingToHGCAL) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Currently RestrictRInZPlaneAtZero only works if PointingToHGCAL is active."; + } + + if (fPointingToHGCAL) { + fRMinBackSurfaceHGCAL = pgun.getParameter("RMinBackSurfaceHGCAL"); + fRMaxBackSurfaceHGCAL = pgun.getParameter("RMaxBackSurfaceHGCAL"); + } + + if (fRestrictRInZPlaneAtZero) { + fRMinAtZero = pgun.getParameter("RMinAtZero"); + fRMaxAtZero = pgun.getParameter("RMaxAtZero"); + if (fRMaxAtZero <= fRMinAtZero) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please fix RMaxAtZero/RMinAtZero"; + } + if (fRMinAtZero < 0.) { + throw cms::Exception("DisplacedParticleGunProducer") << "RMinAtZero must be positive."; + } + } + + if (fPtMax <= fPtMin) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please fix MinPt/MaxPt"; + } + if (fPhiMax <= fPhiMin) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please fix MinPhi/MaxPhi"; + } + if (fThetaMax <= fThetaMin) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please ensure MinTheta <= MaxTheta."; + } + if (fPhiVtxMax <= fPhiVtxMin) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please fix MinVtxPhi/MaxVtxPhi"; + } + if (fRMax <= fRMin) { + throw cms::Exception("DisplacedParticleGunProducer") << "Please fix RMin/RMax"; + } + if (fRestrictRInZPlaneAtZero && fPointingToHGCAL && fRMax > fRMaxAtZero && fRMax > fRMaxBackSurfaceHGCAL) { + throw cms::Exception("DisplacedParticleGunProducer") + << "There are values of R at z=" << fZVtx << "cm for which an intersection for R in [" << *fRMinAtZero << "; " + << *fRMaxAtZero << "]cm at z=0cm is impossible. Please update your configuration."; + } + if (fRMin < 0) { + throw cms::Exception("DisplacedParticleGunProducer") << "RMin must be positive."; + } + if (fMaxTries == 0) { + throw cms::Exception("DisplacedParticleGunProducer") << "MaxTries must be > 0"; + } + if (fRMaxBackSurfaceHGCAL <= fRMinBackSurfaceHGCAL) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Please ensure RMaxBackSurfaceHGCAL > RMinBackSurfaceHGCAL."; + } + if (fRMaxBackSurfaceHGCAL > kCeeBackRMax || fRMinBackSurfaceHGCAL < kCeeBackRMin) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Please ensure RMaxBackSurfaceHGCAL <= kCeeBackRMax and RMinBackSurfaceHGCAL >= kCeeBackRMin."; + } + + produces("unsmeared"); + produces(); + } + + void DisplacedParticleGunProducer::fillDescriptions(ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("AddAntiParticle", false); + + edm::ParameterSetDescription pgun; + + // particle direction + pgun.add("MinPt", 5.); + pgun.add("MaxPt", 100.); + pgun.add("MinPhi", -std::numbers::pi); + pgun.add("MaxPhi", +std::numbers::pi); + + // vertex displacement (cm) + pgun.add("RMin", 0.); + pgun.add("RMax", 10.); + pgun.add("MinVtxPhi", 0.); + pgun.add("MaxVtxPhi", 2 * std::numbers::pi); + pgun.add("ZVtx", 0.); + + pgun.add("NParticles", 1); + pgun.add("PartID", 22); + + pgun.add("UniformDensityInR", false); + + pgun.add("MaxTries", 1000u); + + // A particle shot at the extremities of the HGCAL surface will not traverse a substantial fraction of the detector. + // We use RMinBackSurfaceHGCAL and RMaxBackSurfaceHGCAL to expose only a given region of the HGCAL surface + // The default arguments correspond to the inner third of HGCAL's surface (R in ~[58.79, 91.58]cm). + // At (R=200,z=0)cm, an uncharged particle pointing to R=58.79cm at the HGCAL surface (the most extreme case) exits the calorimeter at the + // back face of the CE-E, crossing all its layers. + // For R>200cm there is no guarantee all CE-E layers will be crossed, so a tighter R range might be needed. + // For z>0cm the angles will become more extreme, so a tighter R range might be needed. + // The above reasoning breaks for charged particles, since the bending under the magnetic filed can enormously extend the particle's reach. + pgun.add("PointingToHGCAL", true); + pgun.add("RMinBackSurfaceHGCAL", 58.79); + pgun.add("RMaxBackSurfaceHGCAL", 91.58); + pgun.add("MinTheta", -std::numbers::pi / 2 + 1e-6); + pgun.add("MaxTheta", std::numbers::pi / 2 - 1e-6); + + pgun.add("RestrictRInZPlaneAtZero", true); + pgun.addOptionalNode(edm::ParameterDescription("RMinAtZero", 0., true), true); + pgun.addOptionalNode(edm::ParameterDescription("RMaxAtZero", 150., true), true); + + desc.add("PGunParameters", pgun); + + desc.addUntracked("Verbosity", 0); + desc.addUntracked("firstRun", 1); + + descriptions.add("DisplacedParticleGunProducer", desc); + } + + void DisplacedParticleGunProducer::produce(edm::StreamID, edm::Event& e, const edm::EventSetup& es) const { + edm::Service rng; + CLHEP::HepRandomEngine* engine = &rng->getEngine(e.streamID()); + + if (fVerbosity > 0) { + LogDebug("DisplacedParticleGunProducer") + << " DisplacedParticleGunProducer : Begin New Event Generation" << std::endl; + } + + HepMC::GenEvent* fEvt = new HepMC::GenEvent(); + + if (fPointingToHGCAL) { + if (kCeeBackZ <= fZVtx) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Invalid hard-coded HGCAL surface envelope: " + << "kCeeBackZ = " << kCeeBackZ << "cm, fZVtx = " << fZVtx << " (check ZVtx)."; + } + } + + int barcode = 1; + + for (int ip = 0; ip < fNParticles; ++ip) { + // --- Sample displaced vertex in transverse annulus (z fixed) --- + const double RVtx = + fUniformDensityInR ? shootUniformDensity(engine, fRMin, fRMax) : shootUniformR(engine, fRMin, fRMax); + const double phiVtx = CLHEP::RandFlat::shoot(engine, fPhiVtxMin, fPhiVtxMax); + const double xVtx = RVtx * std::cos(phiVtx); + const double yVtx = RVtx * std::sin(phiVtx); + + auto const& pdgTable = es.getData(fPDGTableToken); + const HepPDT::ParticleData* pData = pdgTable.particle(HepPDT::ParticleID(std::abs(fPartID))); + if (!pData) { + throw cms::Exception("DisplacedParticleGunProducer") << "Particle ID " << fPartID << " not found in PDG table"; + } + const double mass = pData->mass().value(); + + if (pData->charge() != 0 && fPointingToHGCAL) { + throw cms::Exception("DisplacedParticleGunProducer") << "The logic that points particles to HGCAL's CE-E back " + "face assumes that particles move in straight lines."; + } + + double theta = 0., px = 0., py = 0., pz = 0.; + double phi = phiVtx; /* the particle's direction has the same phi as its vertex, ie., + it moves on a 2D plane parallel to the z axis */ + const double pt = CLHEP::RandFlat::shoot(engine, fPtMin, fPtMax); + if (fPointingToHGCAL) { + bool accepted = false; + for (unsigned int itry = 0; itry < fMaxTries; ++itry) { + theta = pickSensibleTheta(engine, fThetaMin, fThetaMax); + std::tie(px, py, pz) = computeMomentum(pt, theta, phi); + if (edm::isNotFinite(pz) || pz <= 0.0) { + continue; // must go towards +z plane + } + if (fVerbosity > 0) { + std::cout << "phiVtx=" << phiVtx << ", RVtx=" << RVtx << ", pT=" << pt << ", theta=" << theta + << ", phi=" << phi << std::endl; + } + + bool checkBackSurface = hitsZPlaneWithinR( + xVtx, yVtx, fZVtx, px, py, pz, kCeeBackZ, fRMinBackSurfaceHGCAL, fRMaxBackSurfaceHGCAL, fVerbosity); + bool checkZero = + fRestrictRInZPlaneAtZero && + hitsZPlaneWithinR( + xVtx, yVtx, fZVtx, -px, -py, -pz, 0., fRMinAtZero.value(), fRMaxAtZero.value(), fVerbosity); + if (checkBackSurface && checkZero) { + accepted = true; + break; + } + } + if (!accepted) { + throw cms::Exception("DisplacedParticleGunProducer") + << "Failed to generate a particle intersecting HGCAL CE-E back surface after MaxTries=" << fMaxTries + << ". Vertex located at: (R=" << RVtx << "cm, phiVtx=" << phiVtx << ", z=" << fZVtx + << "cm). HGCAL band located at R in [" << fRMinBackSurfaceHGCAL << "; " << fRMaxBackSurfaceHGCAL + << "]cm at z=" << kCeeBackZ << "cm."; + } + } else { // if (!fPointingToHGCAL) + theta = pickSensibleTheta(engine, fThetaMin, fThetaMax); + phi = CLHEP::RandFlat::shoot(engine, fPhiMin, fPhiMax); + std::tie(px, py, pz) = computeMomentum(pt, theta, phi); + } + + const double p2 = px * px + py * py + pz * pz; + const double energy = std::sqrt(p2 + mass * mass); + + HepMC::FourVector p(px, py, pz, energy); + + HepMC::GenVertex* vtx = + new HepMC::GenVertex(HepMC::FourVector(xVtx * CLHEP::cm, yVtx * CLHEP::cm, fZVtx * CLHEP::cm, 0.0)); + + HepMC::GenParticle* part = new HepMC::GenParticle(p, fPartID, 1); + part->suggest_barcode(barcode++); + + vtx->add_particle_out(part); + fEvt->add_vertex(vtx); + + if (fVerbosity > 0) { + vtx->print(); + part->print(); + } + } + + fEvt->set_event_number(e.id().event()); + fEvt->set_signal_process_id(20); + + if (fVerbosity > 0) { + fEvt->print(); + } + + auto bProduct = std::make_unique(); + bProduct->addHepMCData(fEvt); + e.put(std::move(bProduct), "unsmeared"); + + auto genEventInfo = std::make_unique(fEvt); + e.put(std::move(genEventInfo)); + + if (fVerbosity > 0) { + std::cout << " DisplacedParticleGunProducer : Event Generation Done. " << std::endl; + } + } + +} // namespace edm + +#include "FWCore/Framework/interface/MakerMacros.h" +using edm::DisplacedParticleGunProducer; +DEFINE_FWK_MODULE(DisplacedParticleGunProducer);