diff --git a/Core/include/Acts/Utilities/PdgParticle.hpp b/Core/include/Acts/Utilities/PdgParticle.hpp index c8da3ec9f96fd8eb97554ba867ac78d86c97f6f7..3ad4722ca26f91a1e38f8316be049eec5587058a 100644 --- a/Core/include/Acts/Utilities/PdgParticle.hpp +++ b/Core/include/Acts/Utilities/PdgParticle.hpp @@ -8,10 +8,13 @@ #pragma once +#include <cstdint> + namespace Acts { /// Symbolic values for commonly used PDG particle numbers. -enum PdgParticle : int { +enum PdgParticle : int32_t { + eInvalid = 0, eElectron = 11, eAntiElectron = -eElectron, ePositron = -eElectron, diff --git a/Fatras/include/ActsFatras/EventData/Barcode.hpp b/Fatras/include/ActsFatras/EventData/Barcode.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f19e11be3e52901fd499d545537e5c0b0790590a --- /dev/null +++ b/Fatras/include/ActsFatras/EventData/Barcode.hpp @@ -0,0 +1,126 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2018-2020 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include <cstdint> + +#include "Acts/Utilities/MultiIndex.hpp" + +namespace ActsFatras { + +/// Particle identifier that encodes additional event information. +/// +/// The barcode has to fulfill two separate requirements: be able to act as +/// unique identifier for particles within an event and to encode details +/// on the event structure for fast lookup. Since we only care about tracking +/// here, we need to support two scenarios: +/// +/// * Identify which primary/secondary vertex particles belong to. No +/// information on intermediate/unstable/invisible particles needs to be +/// retained. This information is already available in the underlying +/// generator event and should not be duplicated. +/// * If (visible) particles convert, decay, or interact with the detector, +/// we need to be able to identify the initial (primary) particle. Typical +/// examples are pion nuclear interactions or electron/gamma conversions. +/// +/// The vertex information is encoded as two numbers that define the +/// primary and secondary vertex. The primary vertex must be non-zero. +/// Particles with a zero secondary vertex originate directly from the primary +/// vertex. +/// +/// Within one vertex (primary+secondary) each particle is identified by +/// a particle, generation, and sub-particle number. Particles originating +/// from the vertex must have zero generation and zero sub-particle number; +/// a consequence is that only non-zero generation can have non-zero +/// sub-particle numbers. A non-zero generation indicates that the particle +/// is a descendant of the original particle, e.g. from interactions or decay, +/// while the sub-particle number identifies the the descendat particle. +/// +/// With this encoding, non-primary particles and their primary parent can +/// be easily identified at the expense of not storing the exact decay history. +/// +/// A barcode with all elements set to zero (the default value) is an invalid +/// value that can be used e.g. to mark missing or unknown particles. +/// +/// ## Example +/// +/// A particle generated in a primary interaction might have the barcode +/// +/// 2|0|14|0|0 -> vertex=2 (primary), particle=14, generation=0, sub=0 +/// +/// A simulation module might generate an interaction and create two new +/// particles. These are descendants of the initial particle and the simulation +/// module can generate the new barcodes directly by increasing the +/// generation number and chosing sub-particle identifiers: +/// +/// 2|0|14|1|0 -> vertex=2 (primary), particle=14, generation=1, sub=0 +/// 2|0|14|1|1 -> vertex=2 (primary), particle=14, generation=1, sub=1 +/// +/// If these secondary particles generate further tertiary particles +/// the barcode would be e.g. +/// +/// 2|0|14|2|0 -> vertex=2 (primary), particle=14, generation=2, sub=0 +/// +/// ## Possible issues +/// +/// The hierachical nature of the barcode allows barcode creation without +/// a central service. Since the full history is not stored, generated barcodes +/// for higher-generation particles can overlap when generated by independent +/// interactions. Assuming an initial primary particle with barcode +/// +/// 3|4|5|0|0 -> particle=5 +/// +/// a first interaction might create a secondary particle by increasing the +/// generation number (without destroying the initial particle) +/// +/// 3|4|5|1|0 -> particle=5, generation+=1, first sub-particle +/// +/// The initial particle gets simulated further and at another step a second +/// interaction also creates a new particle. Since it knows nothing about +/// the previously created particle (no central service), it will generate +/// +/// 3|4|5|1|0 -> particle=5, generation+=1, first sub-particle +/// +/// which is identical to the previously create barcode. These cases can be +/// easily solved by renumbering the sub-particle identifier within each +/// generation to contain unique values. However, this can only be done when all +/// particles are known. +class Barcode : public Acts::MultiIndex<uint64_t, 12, 12, 16, 8, 16> { + public: + /// Return the primary vertex identifier. + constexpr Value vertexPrimary() const { return level(0); } + /// Return the secondary vertex identifier. + constexpr Value vertexSecondary() const { return level(1); } + /// Return the particle identifier. + constexpr Value particle() const { return level(2); } + /// Return the generation identifier. + constexpr Value generation() const { return level(3); } + /// Return the sub-particle identifier. + constexpr Value subParticle() const { return level(4); } + + /// Set the primary vertex identifier. + constexpr Barcode& setVertexPrimary(Value id) { return set(0, id), *this; } + /// Set the secondary vertex identifier. + constexpr Barcode& setVertexSecondary(Value id) { return set(1, id), *this; } + /// Set the parent particle identifier. + constexpr Barcode& setParticle(Value id) { return set(2, id), *this; } + /// Set the particle identifier. + constexpr Barcode& setGeneration(Value id) { return set(3, id), *this; } + /// Set the process identifier. + constexpr Barcode& setSubParticle(Value id) { return set(4, id), *this; } + + /// Construct a new barcode representing a descendant particle. + /// + /// @param sub sub-particle index of the new barcode. + Barcode makeDescendant(Value sub = 0u) const { + return Barcode(*this).setGeneration(generation() + 1).setSubParticle(sub); + } +}; + +} // namespace ActsFatras diff --git a/Fatras/include/ActsFatras/EventData/Particle.hpp b/Fatras/include/ActsFatras/EventData/Particle.hpp index d5a08f2ef737e358e02e80c1e0b9c463dee99c87..0d207fec9ee404d36a45176a2f54d3bb96fc5810 100644 --- a/Fatras/include/ActsFatras/EventData/Particle.hpp +++ b/Fatras/include/ActsFatras/EventData/Particle.hpp @@ -9,206 +9,202 @@ #pragma once #include <cmath> +#include <cstdint> +#include <limits> -#include "Acts/Geometry/GeometryID.hpp" -#include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Definitions.hpp" -#include "Acts/Utilities/Helpers.hpp" -#include "Acts/Utilities/Units.hpp" +#include "Acts/Utilities/PdgParticle.hpp" +#include "ActsFatras/EventData/Barcode.hpp" namespace ActsFatras { -/// Typedef the pdg code -typedef int pdg_type; - -/// Typedef the process code -typedef unsigned int process_code; - -/// Typedef barcode -typedef unsigned int barcode_type; +/// Process type identifier. +/// +/// Encodes the type of process that generated the particle. +enum class ProcessType : uint32_t { + eUndefined = 0, +}; /// Simulation particle information and kinematic state. class Particle { public: - /// Default - Particle() = default; + using Scalar = double; + using Vector3 = Acts::ActsVector<Scalar, 3>; + using Vector4 = Acts::ActsVector<Scalar, 4>; - /// @brief Construct a particle consistently + /// Construct a default particle with invalid identity. + Particle() = default; + /// Construct a particle at rest with explicit mass and charge. /// - /// @param pposition The particle position at construction - /// @param pmomentum The particle momentum at construction - /// @param pm The particle mass - /// @param pq The partilce charge - /// @param pbarcode The particle barcode - Particle(const Acts::Vector3D &position, const Acts::Vector3D &momentum, - double m, double q, pdg_type pdg = 0, barcode_type barcode = 0, - double startTime = 0.) - : m_position(position), - m_momentum(momentum), - m_m(m), - m_q(q), - m_p(momentum.norm()), - m_pT(Acts::VectorHelpers::perp(momentum)), - m_pdg(pdg), - m_barcode(barcode), - m_timeStamp(startTime) { - m_E = std::sqrt(m_p * m_p + m_m * m_m); - m_beta = (m_p / m_E); - m_gamma = (m_E / m_m); - } - - /// @brief Set the limits + /// @param id Encoded identifier within an event + /// @param pdg PDG particle number + /// @param mass Particle mass in native units + /// @param charge Particle charge in native units /// - /// @param x0Limit the limit in X0 to be passed - /// @param l0Limit the limit in L0 to be passed - /// @param timeLimit the readout time limit to be passed - void setLimits(double x0Limit, double l0Limit, - double timeLimit = std::numeric_limits<double>::max()) { - m_limitInX0 = x0Limit; - m_limitInL0 = l0Limit; - m_timeLimit = timeLimit; + /// @warning It is the users responsibility that charge and mass match + /// the PDG particle number. + Particle(Barcode id, Acts::PdgParticle pdg, Scalar mass, Scalar charge) + : m_id(id), m_pdg(pdg), m_charge(charge), m_mass(mass) {} + Particle(const Particle &) = default; + Particle(Particle &&) = default; + Particle &operator=(const Particle &) = default; + Particle &operator=(Particle &&) = default; + + /// Set the process type that generated this particle. + Particle &process(ProcessType proc) { return m_process = proc, *this; } + /// Set the space-time position four-vector. + Particle &setPosition4(const Vector4 &pos4) { + m_position4 = pos4; + return *this; } - - /// @brief Update the particle with applying energy loss - /// - /// @param deltaE is the energy loss to be applied - void scatter(Acts::Vector3D nmomentum) { - m_momentum = std::move(nmomentum); - m_pT = Acts::VectorHelpers::perp(m_momentum); + /// Set the space-time position four-vector from three-position and time. + Particle &setPosition4(const Vector3 &position, Scalar time) { + m_position4.head<3>() = position; + m_position4[3] = time; + return *this; } - - /// @brief Update the particle with applying energy loss + /// Set the space-time position four-vector from scalar components. + Particle &setPosition4(Scalar x, Scalar y, Scalar z, Scalar time) { + m_position4[0] = x; + m_position4[1] = y; + m_position4[2] = z; + m_position4[3] = time; + return *this; + } + /// Set the direction three-vector + Particle &setDirection(const Vector3 &direction) { + m_direction = direction; + m_direction.normalize(); + return *this; + } + /// Set the direction three-vector from scalar components. + Particle &setDirection(Scalar dx, Scalar dy, Scalar dz) { + m_direction[0] = dx; + m_direction[1] = dy; + m_direction[2] = dz; + m_direction.normalize(); + return *this; + } + /// Set the absolute momentum. + Particle &setMomentum(Scalar momentum) { + m_momentum = momentum; + return *this; + } + /// Change the energy by the given amount. /// - /// @param deltaE is the energy loss to be applied - void energyLoss(double deltaE) { - // particle falls to rest - if (m_E - deltaE < m_m) { - m_E = m_m; - m_p = 0.; - m_pT = 0.; - m_beta = 0.; - m_gamma = 1.; - m_momentum = Acts::Vector3D(0., 0., 0.); - m_alive = false; + /// Energy loss corresponds to a negative change. If the updated energy + /// would result in an unphysical value, the particle is put to rest, i.e. + /// its absolute momentum is set to zero. + Particle &correctEnergy(Scalar delta) { + const auto newEnergy = std::hypot(m_mass, m_momentum) + delta; + if (newEnergy <= m_mass) { + m_momentum = Scalar(0); + } else { + m_momentum = std::sqrt(newEnergy * newEnergy - m_mass * m_mass); } - // updatet the parameters - m_E -= deltaE; - m_p = std::sqrt(m_E * m_E - m_m * m_m); - m_momentum = m_p * m_momentum.normalized(); - m_pT = Acts::VectorHelpers::perp(m_momentum); - m_beta = (m_p / m_E); - m_gamma = (m_E / m_m); + return *this; } - /// @brief Update the particle with a new position and momentum, - /// this corresponds to a step update + /// Encoded particle identifier within an event. + Barcode id() const { return m_id; } + /// Which type of process generated this particle. + ProcessType process() const { return m_process; } + /// PDG particle number that identifies the type. + Acts::PdgParticle pdg() const { return m_pdg; } + /// Particle charge. + Scalar charge() const { return m_charge; } + /// Particle mass. + Scalar mass() const { return m_mass; } + + /// Space-time position four-vector. + const Vector4 &position4() const { return m_position4; } + /// Three-position, i.e. spatial coordinates without the time. + auto position() const { return m_position4.head<3>(); } + /// Time coordinate. + Scalar time() const { return m_position4[3]; } + /// Energy-momentum four-vector. + Vector4 momentum4() const { + Vector4 mom4; + // stored direction is always normalized + mom4[0] = m_momentum * m_direction[0]; + mom4[1] = m_momentum * m_direction[1]; + mom4[2] = m_momentum * m_direction[2]; + mom4[3] = energy(); + return mom4; + } + /// Three-direction, i.e. the normalized momentum three-vector. + const Vector3 &direction() const { return m_direction; } + /// Absolute momentum. + Scalar momentum() const { return m_momentum; } + /// Total energy, i.e. norm of the four-momentum. + Scalar energy() const { return std::hypot(m_mass, m_momentum); } + + /// Charge over absolute momentum. + Scalar chargeOverMomentum() const { return m_charge / m_momentum; } + /// Relativistic velocity. + Scalar beta() const { return m_momentum / energy(); } + /// Relativistic gamma factor. + Scalar gamma() const { return std::hypot(1, m_momentum / m_mass); } + + /// Check if the particle is alive, i.e. is not at rest. + operator bool() const { return Scalar(0) < m_momentum; } + /// Check if the particle is dead, i.e is at rest. + bool operator!() const { return m_momentum <= Scalar(0); } + + /// Register material that the particle has passed. /// - /// @param position New position after update - /// @param momentum New momentum after update - /// @param deltaPathX0 passed since last step - /// @param deltaPathL0 passed since last step - /// @param deltaTime The time elapsed + /// @param thicknessX0 material thickness measured in radiation lengths + /// @param thicknessL0 material thickness measured in interaction lengths + Particle &addPassedMaterial(Scalar thicknessX0, Scalar thicknessL0) { + m_pathX0 += thicknessX0; + m_pathL0 += thicknessL0; + return *this; + } + /// Set the material limits. /// - /// @return break condition - bool update(const Acts::Vector3D &position, const Acts::Vector3D &momentum, - double deltaPahtX0 = 0., double deltaPahtL0 = 0., - double deltaTime = 0.) { - m_position = position; - m_momentum = momentum; - m_p = momentum.norm(); - if (m_p) { - m_pT = Acts::VectorHelpers::perp(momentum); - m_E = std::sqrt(m_p * m_p + m_m * m_m); - m_timeStamp += deltaTime; - m_beta = (m_p / m_E); - m_gamma = (m_E / m_m); - - // set parameters and check limits - m_pathInX0 += deltaPahtX0; - m_pathInL0 += deltaPahtL0; - m_timeStamp += deltaTime; - if (m_pathInX0 >= m_limitInX0 || m_pathInL0 >= m_limitInL0 || - m_timeStamp > m_timeLimit) { - m_alive = false; - } - } - return !m_alive; + /// @param limitX0 maximum radiation lengths the particle can pass + /// @param limitL0 maximum interaction lengths the particle can pass + Particle &setMaterialLimits(Scalar limitX0, Scalar limitL0) { + m_limitX0 = limitX0; + m_limitL0 = limitL0; + return *this; } - - /// @brief Access methods: position - const Acts::Vector3D &position() const { return m_position; } - - /// @brief Access methods: momentum - const Acts::Vector3D &momentum() const { return m_momentum; } - - /// @brief Access methods: p - double p() const { return m_p; } - - /// @brief Access methods: pT - double pT() const { return m_pT; } - - /// @brief Access methods: E - double E() const { return m_E; } - - /// @brief Access methods: m - double m() const { return m_m; } - - /// @brief Access methods: beta - double beta() const { return m_beta; } - - /// @brief Access methods: gamma - double gamma() const { return m_gamma; } - - /// @brief Access methods: charge - double q() const { return m_q; } - - /// @brief Access methods: pdg code - pdg_type pdg() const { return m_pdg; } - - /// @brief Access methods: barcode - barcode_type barcode() const { return m_barcode; } - - /// @brief Access methods: path/X0 - double pathInX0() const { return m_pathInX0; } - - /// @brief Access methods: limit/X0 - double limitInX0() const { return m_limitInX0; } - - /// @brief Access methods: pdg code - double pathInL0() const { return m_limitInX0; } - - /// @brief Access methods: barcode - double limitInL0() const { return m_limitInL0; } - - /// @brief boolean operator indicating the particle to be alive - operator bool() { return m_alive; } + /// The passed material measured in radiation lengths. + Scalar pathInX0() const { return m_pathX0; } + /// The passed material measured in interaction lengths. + Scalar pathInL0() const { return m_pathL0; } + /// The maximum radation length the particle is allowed to pass. + Scalar pathLimitX0() const { return m_limitX0; } + /// The maximum interaction length the particle is allowed to pass. + Scalar pathLimitL0() const { return m_limitL0; } private: - Acts::Vector3D m_position = Acts::Vector3D(0., 0., 0.); //!< kinematic info - Acts::Vector3D m_momentum = Acts::Vector3D(0., 0., 0.); //!< kinematic info - - double m_m = 0.; //!< particle mass - double m_E = 0.; //!< total energy - double m_q = 0.; //!< the charge - double m_beta = 0.; //!< relativistic beta factor - double m_gamma = 1.; //!< relativistic gamma factor - double m_p = 0.; //!< momentum magnitude - double m_pT = 0.; //!< transverse momentum magnitude - pdg_type m_pdg = 0; //!< pdg code of the particle - barcode_type m_barcode = 0; //!< barcode of the particle - - double m_pathInX0 = 0.; //!< passed path in X0 - double m_limitInX0 = - std::numeric_limits<double>::max(); //!< path limit in X0 - - double m_pathInL0 = 0.; //!< passed path in L0 - double m_limitInL0 = - std::numeric_limits<double>::max(); //!< path limit in X0 - - double m_timeStamp = 0.; //!< passed time elapsed - double m_timeLimit = std::numeric_limits<double>::max(); // time limit - - bool m_alive = true; //!< the particle is alive + // identity, i.e. things that do not change over the particle lifetime. + /// Particle identifier within the event. + Barcode m_id; + /// Process type specifier. + ProcessType m_process = ProcessType::eUndefined; + /// PDG particle number. + Acts::PdgParticle m_pdg = Acts::PdgParticle::eInvalid; + // Particle charge and mass. + Scalar m_charge = Scalar(0); + Scalar m_mass = Scalar(0); + // kinematics, i.e. things that change over the particle lifetime. + Vector3 m_direction = Vector3::UnitZ(); + Scalar m_momentum = Scalar(0); + Vector4 m_position4 = Vector4::Zero(); + // simulation-specific X0/L0 information and limits + // these values are here to simplify the simulation of (nuclear) interactions. + // instead of checking at every surface whether an interaction should occur we + // can draw an overall limit once. the relevant interaction only needs to + // be executed once the limit is reached. + // this information is not really particle-specific and should probably be + // handled separately. for now, storing it directly here is the simplest + // solution. + Scalar m_pathX0 = Scalar(0); + Scalar m_pathL0 = Scalar(0); + Scalar m_limitX0 = std::numeric_limits<Scalar>::max(); + Scalar m_limitL0 = std::numeric_limits<Scalar>::max(); }; } // namespace ActsFatras diff --git a/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheBloch.hpp b/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheBloch.hpp index e047119d09e1711728fc41c76b712c961499c4b3..f588440ea6a80027a618867ea53a1fddffcbeb1f 100644 --- a/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheBloch.hpp +++ b/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheBloch.hpp @@ -8,69 +8,68 @@ #pragma once +#include <vector> + #include "Acts/Material/Interactions.hpp" -#include "ActsFatras/Kernel/LandauDistribution.hpp" +#include "Acts/Material/MaterialProperties.hpp" +#include "ActsFatras/EventData/Particle.hpp" +#include "ActsFatras/Utilities/LandauDistribution.hpp" namespace ActsFatras { -/// @brief The struct for the EnergyLoss physics list -/// -/// This generates the energy loss according to the Bethe-Bloch -/// description, applying a landau generated enery loss +/// Simulate energy loss using the Bethe-Bloch/Landau description. /// -/// It follows the interface of EnergyLoss samplers in Fatras -/// that could return radiated photons for further processing, -/// however, for the Bethe-Bloch application the return vector -/// is always 0. +/// Energy loss is computed using the most probable value and appropriate +/// fluctuations from a Landau distribution. No secondaries are generated +/// for the removed energy. struct BetheBloch { /// The flag to include BetheBloch process or not bool betheBloch = true; - /// Scaling for most probable value double scaleFactorMPV = 1.; - /// Scaling for Sigma double scaleFactorSigma = 1.; - /// @brief Call operator for the Bethe Bloch energy loss - /// - /// @tparam generator_t is a random number generator type - /// @tparam detector_t is the detector information type - /// @tparam particle_t is the particle information type + /// Simulate energy loss and update the particle parameters. /// - /// @param[in] generator is the random number generator - /// @param[in] detector the detector information - /// @param[in] particle the particle which is being scattered + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being updated + /// @return Empty secondaries containers. /// - /// @return empty vector for BetheBloch - no secondaries created - template <typename generator_t, typename detector_t, typename particle_t> - std::vector<particle_t> operator()(generator_t &generator, - const detector_t &detector, - particle_t &particle) const { + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + std::vector<Particle> operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { // Do nothing if the flag is set to false if (not betheBloch) { return {}; } - // Create a random landau distribution between in the intervall [0,1] - LandauDistribution landauDist(0., 1.); - double landau = landauDist(generator); - double qop = particle.q() / particle.p(); - - // @TODO Double investigate if we could do one call - double energyLoss = Acts::computeEnergyLossLandau( - detector, particle.pdg(), particle.m(), qop, particle.q()); - double energyLossSigma = Acts::computeEnergyLossLandauSigma( - detector, particle.pdg(), particle.m(), qop, particle.q()); + // compute energy loss distribution parameters + const auto pdg = particle.pdg(); + const auto m = particle.mass(); + const auto qOverP = particle.chargeOverMomentum(); + const auto q = particle.charge(); + // most probable value + const auto energyLoss = + Acts::computeEnergyLossLandau(slab, pdg, m, qOverP, q); + // Gaussian-equivalent sigma + const auto energyLossSigma = + Acts::computeEnergyLossLandauSigma(slab, pdg, m, qOverP, q); // Simulate the energy loss - double sampledEnergyLoss = scaleFactorMPV * std::fabs(energyLoss) + - scaleFactorSigma * energyLossSigma * landau; + // TODO landau location and scale parameters are not identical to the most + // probable value and the Gaussian-equivalent sigma + LandauDistribution lossDistribution(scaleFactorMPV * energyLoss, + scaleFactorSigma * energyLossSigma); + const auto loss = lossDistribution(generator); // Apply the energy loss - particle.energyLoss(sampledEnergyLoss); + particle.correctEnergy(-loss); - // return empty children + // Generates no new particles return {}; } }; diff --git a/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheHeitler.hpp b/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheHeitler.hpp index 90e8e21147f770b7488f11cf06c49744575677e8..5890977d338426b6193360a9701f42978c0279bc 100644 --- a/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheHeitler.hpp +++ b/Fatras/include/ActsFatras/Physics/EnergyLoss/BetheHeitler.hpp @@ -9,57 +9,54 @@ #pragma once #include <random> +#include <vector> -namespace ActsFatras { +#include "Acts/Material/MaterialProperties.hpp" +#include "ActsFatras/EventData/Particle.hpp" -const double log_2 = std::log(2.); +namespace ActsFatras { -/// The struct for the EnergyLoss physics list +/// Simulate electron energy loss using the Bethe-Heitler description. /// -/// Bethe-Heitler for electron brem description as described here: +/// Bethe-Heitler for electron bremsstrahlung description as described here: /// "A Gaussian-mixture approximation of the Bethe–Heitler model of electron /// energy loss by bremsstrahlung" R. Frühwirth -/// struct BetheHeitler { /// The flag to include BetheHeitler process or not bool betheHeitler = true; - /// A scaling factor to double scaleFactor = 1.; - /// @brief Call operator for the Bethe-Heitler energy loss - /// - /// @tparam generator_t is a random number generator type - /// @tparam detector_t is the detector information type - /// @tparam particle_t is the particle information type + /// Simulate energy loss and update the particle parameters. /// - /// @param[in] generator is the random number generator - /// @param[in] detector the detector information - /// @param[in] particle the particle which is being scattered + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being updated + /// @return Empty secondaries containers. /// - /// @return eventually produced photons - template <typename generator_t, typename detector_t, typename particle_t> - std::vector<particle_t> operator()(generator_t &generator, - const detector_t &detector, - particle_t &particle) const { + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + std::vector<Particle> operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { // Do nothing if the flag is set to false if (not betheHeitler) { return {}; } - double tInX0 = detector.thickness() / detector.material().X0(); - // Take a random gamma-distributed value - depending on t/X0 - std::gamma_distribution<double> gDist(tInX0 / log_2, 1.); + std::gamma_distribution<double> gDist(slab.thicknessInX0() / std::log(2.0), + 1.0); - double u = gDist(generator); - double z = std::exp(-1. * u); - double sampledEnergyLoss = std::abs(scaleFactor * particle.E() * (z - 1.)); + const auto u = gDist(generator); + const auto z = std::exp(-u); + const auto sampledEnergyLoss = + std::abs(scaleFactor * particle.energy() * (z - 1.)); // apply the energy loss - particle.energyLoss(sampledEnergyLoss); + particle.correctEnergy(-sampledEnergyLoss); - // @TODO return photons, needs particle_creator_t + // TODO return the lost energy as a photon return {}; } }; diff --git a/Fatras/include/ActsFatras/Physics/Scattering/GaussianMixture.hpp b/Fatras/include/ActsFatras/Physics/Scattering/GaussianMixture.hpp index adb985087f2735a62fee0a76812098f264fd6641..ceed5c179c5840f57104e2dbc76c8c4a303390da 100644 --- a/Fatras/include/ActsFatras/Physics/Scattering/GaussianMixture.hpp +++ b/Fatras/include/ActsFatras/Physics/Scattering/GaussianMixture.hpp @@ -11,15 +11,15 @@ #include <random> #include "Acts/Material/Interactions.hpp" +#include "Acts/Material/MaterialProperties.hpp" +#include "ActsFatras/EventData/Particle.hpp" namespace ActsFatras { -/// @brief The struct to be provided to the Scatterer action -/// This is the gaussian mixture +/// Generate scattering angles using a Gaussian mixture model. struct GaussianMixture { /// Steering parameter - bool log_include = true; - + bool optGaussianMixtureG4 = false; double gausMixSigma1_a0 = 8.471e-1; double gausMixSigma1_a1 = 3.347e-2; double gausMixSigma1_a2 = -1.843e-3; @@ -30,43 +30,37 @@ struct GaussianMixture { double gausMixEpsilon_b1 = 1.106e-1; double gausMixEpsilon_b2 = -5.729e-3; - bool optGaussianMixtureG4 = false; - - /// @brief Call operator to perform this scattering + /// Generate a single 3D scattering angle. /// - /// @tparam generator_t is a random number generator type - /// @tparam detector_t is the detector information type - /// @tparam particle_t is the particle information type + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being scattered + /// @return a 3d scattering angle /// - /// @param[in] generator is the random number generator - /// @param[in] detector the detector information - /// @param[in] particle the particle which is being scattered - /// - /// @return a scattering angle in 3D - template <typename generator_t, typename detector_t, typename particle_t> - double operator()(generator_t &generator, const detector_t &detector, - particle_t &particle) const { + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + double operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { /// Calculate the highland formula first - double qop = particle.q() / particle.p(); double sigma = Acts::computeMultipleScatteringTheta0( - detector, particle.pdg(), particle.m(), qop); - + slab, particle.pdg(), particle.mass(), particle.chargeOverMomentum(), + particle.charge()); double sigma2 = sigma * sigma; // Gauss distribution, will be sampled with generator std::normal_distribution<double> gaussDist(0., 1.); - // Uniform distribution, will be sampled with generator std::uniform_real_distribution<double> uniformDist(0., 1.); // Now correct for the tail fraction // d_0' double beta2 = particle.beta() * particle.beta(); - double dprime = detector.thickness() / (detector.material().X0() * beta2); + double dprime = slab.thickness() / (slab.material().X0() * beta2); double log_dprime = std::log(dprime); // d_0'' double log_dprimeprime = - std::log(std::pow(detector.material().Z(), 2.0 / 3.0) * dprime); + std::log(std::pow(slab.material().Z(), 2.0 / 3.0) * dprime); // get epsilon double epsilon = @@ -82,7 +76,7 @@ struct GaussianMixture { // G4 optimised / native double Gaussian model if (optGaussianMixtureG4) { - sigma2 = 225. * dprime / (particle.p() * particle.p()); + sigma2 = 225. * dprime / (particle.momentum() * particle.momentum()); } // throw the random number core/tail if (uniformDist(generator) < epsilon) { diff --git a/Fatras/include/ActsFatras/Physics/Scattering/GeneralMixture.hpp b/Fatras/include/ActsFatras/Physics/Scattering/GeneralMixture.hpp index 1b7381c332adc5e9353c02e68ef95d51774bffae..022b897a463554bcd536c1fe8463680a5ed859dd 100644 --- a/Fatras/include/ActsFatras/Physics/Scattering/GeneralMixture.hpp +++ b/Fatras/include/ActsFatras/Physics/Scattering/GeneralMixture.hpp @@ -11,47 +11,40 @@ #include <random> #include "Acts/Material/Interactions.hpp" +#include "Acts/Material/MaterialProperties.hpp" +#include "Acts/Utilities/PdgParticle.hpp" +#include "ActsFatras/EventData/Particle.hpp" namespace ActsFatras { -/// This scatter emulated core and tail scattering +/// Generate scattering angles using a general mixture model. +/// +/// Emulates core and tail scattering as described in +/// +/// General mixture model Fruehwirth, M. Liendl. +/// Comp. Phys. Comm. 141 (2001) 230-246 /// -/// General mixture model Fruehwirth, M. Liendl. -/// Comp. Phys. Comm. 141 (2001) 230-246 struct GeneralMixture { /// Steering parameter bool log_include = true; - - //- Scale the mixture level + /// Scale the mixture level double genMixtureScalor = 1.; - /// @brief Call operator to perform this scattering + /// Generate a single 3D scattering angle. /// - /// @tparam generator_t is a random number generator type - /// @tparam detector_t is the detector information type - /// @tparam particle_t is the particle information type + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being scattered + /// @return a 3d scattering angle /// - /// @param[in] generator is the random number generator - /// @param[in] detector the detector information - /// @param[in] particle the particle which is being scattered - /// - /// @return a scattering angle in 3D - template <typename generator_t, typename detector_t, typename particle_t> - double operator()(generator_t &generator, const detector_t &detector, - particle_t &particle) const { - // scale the path length to the radiation length - // @todo path correction factor - double tInX0 = detector.thickness() / detector.material().X0(); - - // material properties - double Z = detector.material().Z(); // charge layer material - - double theta(0.); - - if (std::abs(particle.pdg()) != 11) { - /// Uniform distribution, will be sampled with generator - std::uniform_real_distribution<double> uniformDist(0., 1.); + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + double operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { + double theta = 0.0; + if (std::abs(particle.pdg()) != Acts::PdgParticle::eElectron) { //---------------------------------------------------------------------------- // see Mixture models of multiple scattering: computation and simulation. // - @@ -61,39 +54,37 @@ struct GeneralMixture { std::array<double, 4> scattering_params; // Decide which mixture is best double beta2 = (particle.beta() * particle.beta()); - double tob2 = tInX0 / beta2; - if (tob2 > 0.6 / std::pow(Z, 0.6)) { + double tInX0 = slab.thicknessInX0(); + double tob2 = slab.thicknessInX0() / beta2; + if (tob2 > 0.6 / std::pow(slab.material().Z(), 0.6)) { // Gaussian mixture or pure Gaussian if (tob2 > 10) { - scattering_params = getGaussian(particle.beta(), particle.p(), tInX0, - genMixtureScalor); + scattering_params = getGaussian(particle.beta(), particle.momentum(), + tInX0, genMixtureScalor); } else { scattering_params = - getGaussmix(particle.beta(), particle.p(), tInX0, - detector.material().Z(), genMixtureScalor); + getGaussmix(particle.beta(), particle.momentum(), tInX0, + slab.material().Z(), genMixtureScalor); } // Simulate - theta = gaussmix(uniformDist, generator, scattering_params); + theta = gaussmix(generator, scattering_params); } else { // Semigaussian mixture - get parameters auto scattering_params_sg = - getSemigauss(particle.beta(), particle.p(), tInX0, - detector.material().Z(), genMixtureScalor); + getSemigauss(particle.beta(), particle.momentum(), tInX0, + slab.material().Z(), genMixtureScalor); // Simulate - theta = semigauss(uniformDist, generator, scattering_params_sg); + theta = semigauss(generator, scattering_params_sg); } } else { - /// Gauss distribution, will be sampled with generator - std::normal_distribution<double> gaussDist(0., 1.); - // for electrons we fall back to the Highland (extension) // return projection factor times sigma times gauss random - double qop = particle.q() / particle.p(); - double theta0 = Acts::computeMultipleScatteringTheta0( - detector, particle.pdg(), particle.m(), qop); - theta = theta0 * gaussDist(generator); + const auto theta0 = Acts::computeMultipleScatteringTheta0( + slab, particle.pdg(), particle.mass(), particle.chargeOverMomentum(), + particle.charge()); + theta = std::normal_distribution<double>(0.0, theta0)(generator); } - // return scaled by sqare root of two + // scale from planar to 3d angle return M_SQRT2 * theta; } @@ -163,8 +154,9 @@ struct GeneralMixture { /// /// @return a double value that represents the gaussian mixture template <typename generator_t> - double gaussmix(UniformDist &udist, generator_t &generator, + double gaussmix(generator_t &generator, const std::array<double, 4> &scattering_params) const { + std::uniform_real_distribution<double> udist(0.0, 1.0); double sigma_tot = scattering_params[0]; double var1 = scattering_params[1]; double var2 = scattering_params[2]; @@ -186,8 +178,9 @@ struct GeneralMixture { /// /// @return a double value that represents the gaussian mixture template <typename generator_t> - double semigauss(UniformDist &udist, generator_t &generator, + double semigauss(generator_t &generator, const std::array<double, 6> &scattering_params) const { + std::uniform_real_distribution<double> udist(0.0, 1.0); double a = scattering_params[0]; double b = scattering_params[1]; double var1 = scattering_params[2]; diff --git a/Fatras/include/ActsFatras/Physics/Scattering/Highland.hpp b/Fatras/include/ActsFatras/Physics/Scattering/Highland.hpp index 256c9c54e94ef55755aec477e73c518c803a51b6..211f2045c8ad3c680fb197e514cada4af3cf4aa2 100644 --- a/Fatras/include/ActsFatras/Physics/Scattering/Highland.hpp +++ b/Fatras/include/ActsFatras/Physics/Scattering/Highland.hpp @@ -11,36 +11,34 @@ #include <random> #include "Acts/Material/Interactions.hpp" +#include "Acts/Material/MaterialProperties.hpp" +#include "ActsFatras/EventData/Particle.hpp" namespace ActsFatras { -/// @The struct for the Highland-based scattering +/// Generate scattering angles using the Highland/PDG parametrization. /// -/// This will scatter particles with a single gaussian distribution -/// according to the highland formula. +/// The angles are drawn from a single normal distribution with a width +/// given by the Highland/PDG formula. struct Highland { - /// @brief Call operator to perform this scattering + /// Generate a single 3D scattering angle. /// - /// @tparam generator_t is a random number generator type - /// @tparam detector_t is the detector information type - /// @tparam particle_t is the particle information type + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being scattered + /// @return a 3d scattering angle /// - /// @param[in] generator is the random number generator - /// @param[in] detector the detector information - /// @param[in] particle the particle which is being scattered - /// - /// @return a scattering angle in 3D - template <typename generator_t, typename detector_t, typename particle_t> - double operator()(generator_t &generator, const detector_t &detector, - particle_t &particle) const { - // Gauss distribution, will be sampled sampled with generator - std::normal_distribution<double> gaussDist(0., 1.); - - double qop = particle.q() / particle.p(); - double theta0 = Acts::computeMultipleScatteringTheta0( - detector, particle.pdg(), particle.m(), qop, particle.q()); - // Return projection factor times sigma times grauss random - return M_SQRT2 * theta0 * gaussDist(generator); + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + double operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { + // compute the planar scattering angle + const auto theta0 = Acts::computeMultipleScatteringTheta0( + slab, particle.pdg(), particle.mass(), particle.chargeOverMomentum(), + particle.charge()); + // draw from the normal distribution representing the 3d angle distribution + return std::normal_distribution<double>(0.0, M_SQRT2 * theta0)(generator); } }; diff --git a/Fatras/include/ActsFatras/Physics/Scattering/Scattering.hpp b/Fatras/include/ActsFatras/Physics/Scattering/Scattering.hpp index 3ede5644e18754efbe4900f1bd84b903418bd80f..96989f212f072006fdbfa2ce66603a55e9d9d827 100644 --- a/Fatras/include/ActsFatras/Physics/Scattering/Scattering.hpp +++ b/Fatras/include/ActsFatras/Physics/Scattering/Scattering.hpp @@ -10,53 +10,56 @@ #include <cmath> #include <random> +#include <vector> +#include "Acts/Material/MaterialProperties.hpp" #include "Acts/Utilities/Definitions.hpp" #include "Acts/Utilities/Helpers.hpp" +#include "ActsFatras/EventData/Particle.hpp" namespace ActsFatras { -/// @class Scatterering +/// Simulate (multiple) scattering using a configurable scattering model. /// -/// This is the (multiple) scattering plugin to the -/// Physics list. It needs a scattering formula in order -/// to provide the the scattering angle in 3D space. -/// -/// There's two options to apply the scattering -/// - a parametric action that relates phi and theta (default: off) -/// - an actuall out of direction scattering applying two random numbers -template <typename formula_t> +/// @tparam scattering_model_t Model implementation to draw a scattering angle. +template <typename scattering_model_t> struct Scattering { /// The flag to include scattering or not bool scattering = true; - /// Include the log term bool parametric = false; - double projectionFactor = 1. / std::sqrt(2.); - /// The scattering formula - formula_t angle; - - /// This is the scattering call operator - template <typename generator_t, typename detector_t, typename particle_t> - std::vector<particle_t> operator()(generator_t &gen, const detector_t &det, - particle_t &in) const { + scattering_model_t angle; + + /// Simulate scattering and update the particle parameters. + /// + /// @param[in] generator is the random number generator + /// @param[in] slab defines the passed material + /// @param[in,out] particle is the particle being updated + /// @return Empty secondaries containers. + /// + /// @tparam generator_t is a RandomNumberEngine + template <typename generator_t> + std::vector<Particle> operator()(generator_t &generator, + const Acts::MaterialProperties &slab, + Particle &particle) const { // Do nothing if the flag is set to false if (not scattering) { return {}; } // 3D scattering angle - double angle3D = angle(gen, det, in); + double angle3D = angle(generator, slab, particle); // parametric scattering if (parametric) { // the initial values - double theta = Acts::VectorHelpers::theta(in.momentum()); - double phi = Acts::VectorHelpers::phi(in.momentum()); + double theta = Acts::VectorHelpers::theta(particle.direction()); + double phi = Acts::VectorHelpers::phi(particle.direction()); double sinTheta = (sin(theta) * sin(theta) > 10e-10) ? sin(theta) : 1.; // sample them in an independent way + const double projectionFactor = 1. / std::sqrt(2.); double deltaTheta = projectionFactor * angle3D; double numDetlaPhi = 0.; //?? @THIS IS WRONG HERE ! double deltaPhi = projectionFactor * numDetlaPhi / sinTheta; @@ -81,16 +84,17 @@ struct Scattering { double ctheta = std::cos(theta); // assign the new values - in.scatter(in.p() * Acts::Vector3D(cphi * stheta, sphi * stheta, ctheta)); + particle.setDirection( + Acts::Vector3D(cphi * stheta, sphi * stheta, ctheta)); } else { /// uniform distribution std::uniform_real_distribution<double> uniformDist(0., 1.); // Create a random uniform distribution between in the intervall [0,1] - double psi = 2. * M_PI * uniformDist(gen); + double psi = 2. * M_PI * uniformDist(generator); // more complex but "more true" - Acts::Vector3D pDirection(in.momentum().normalized()); + Acts::Vector3D pDirection(particle.direction()); double x = -pDirection.y(); double y = pDirection.x(); double z = 0.; @@ -107,7 +111,7 @@ struct Scattering { rotation = Acts::AngleAxis3D(psi, pDirection) * Acts::AngleAxis3D(angle3D, deflector); // rotate and set a new direction to the cache - in.scatter(in.p() * rotation * pDirection.normalized()); + particle.setDirection(rotation * pDirection); } // scattering always returns an empty list // - it is a non-distructive process diff --git a/Fatras/include/ActsFatras/Selectors/ChargeSelectors.hpp b/Fatras/include/ActsFatras/Selectors/ChargeSelectors.hpp index 254df7da56ae725ece0f334137e5181f0edf4a54..66cc9cd6f20d05bff7f68f2817f5294d4ce903ca 100644 --- a/Fatras/include/ActsFatras/Selectors/ChargeSelectors.hpp +++ b/Fatras/include/ActsFatras/Selectors/ChargeSelectors.hpp @@ -8,37 +8,39 @@ #pragma once +#include "ActsFatras/EventData/Particle.hpp" + namespace ActsFatras { -struct ChargedSelector { - /// Return true for all particles with charge != 0. - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.q() * particle.q() > 0.); +/// Select neutral particles. +struct NeutralSelector { + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (particle.charge() == Particle::Scalar(0)); } }; -struct NeutralSelector { - /// Return true for all particles with charge == 0. - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.q() == 0.); +/// Select all charged particles. +struct ChargedSelector { + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (particle.charge() != Particle::Scalar(0)); } }; +/// Select positively charged particles. struct PositiveSelector { - /// Return true for all particles with charge > 0. - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.q() > 0.); + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (Particle::Scalar(0) < particle.charge()); } }; +/// Select negatively charged particles. struct NegativeSelector { - /// Return true for all particles with charge<> 0. - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.q() * particle.q() > 0.); + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (particle.charge() < Particle::Scalar(0)); } }; diff --git a/Fatras/include/ActsFatras/Selectors/KinematicCasts.hpp b/Fatras/include/ActsFatras/Selectors/KinematicCasts.hpp index 357a2d70ffb6f8e10d57528e2d62918dedcd3394..1c25b17ac36230fe6dc37aeab90cca1354556f9e 100644 --- a/Fatras/include/ActsFatras/Selectors/KinematicCasts.hpp +++ b/Fatras/include/ActsFatras/Selectors/KinematicCasts.hpp @@ -11,71 +11,65 @@ #include <cmath> #include "Acts/Utilities/Helpers.hpp" +#include "ActsFatras/EventData/Particle.hpp" namespace ActsFatras { namespace Casts { -/// The Eta cast operator -struct eta { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return Acts::VectorHelpers::eta(particle.momentum()); +/// Retrieve the transverse absolute distance of the vertex to the origin. +struct Vrho { + double operator()(const Particle& particle) const { + return Acts::VectorHelpers::perp(particle.position()); } }; -/// The Eta cast operator -struct absEta { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return std::abs(Acts::VectorHelpers::eta(particle.momentum())); +/// Retrieve the longitudinal distance of the vertex to the origin. +struct Vz { + double operator()(const Particle& particle) const { + return particle.position().z(); } }; -/// The Pt cast operator -struct pT { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return Acts::VectorHelpers::perp(particle.momentum()); +/// Retrieve the longitudinal absolute distance of the vertex to the origin. +struct AbsVz { + double operator()(const Particle& particle) const { + return std::abs(particle.position().z()); } }; -/// The P cast operator -struct p { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return particle.momentum().norm(); +/// Retrieve the direction pseudo-rapidity. +struct Eta { + double operator()(const Particle& particle) const { + return Acts::VectorHelpers::eta(particle.direction()); } }; -/// The E cast operator -struct E { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return particle.E(); +/// Retrieve the direction absolute pseudo-rapidity. +struct AbsEta { + double operator()(const Particle& particle) const { + return std::abs(Acts::VectorHelpers::eta(particle.direction())); } }; -/// The E cast operator -struct vR { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return Acts::VectorHelpers::perp(particle.position()); +/// Retrieve the transverse momentum. +struct Pt { + double operator()(const Particle& particle) const { + return particle.momentum() * + Acts::VectorHelpers::perp(particle.direction()); } }; -/// The E cast operator -struct vZ { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return particle.position().z(); +/// Retrieve the absolute momentum. +struct P { + double operator()(const Particle& particle) const { + return particle.momentum(); } }; -/// The E cast operator -struct AbsVz { - template <typename particle_t> - double operator()(const particle_t &particle) const { - return std::abs(particle.position().z()); +/// Retrieve the total energy. +struct E { + double operator()(const Particle& particle) const { + return particle.energy(); } }; diff --git a/Fatras/include/ActsFatras/Selectors/LimitSelectors.hpp b/Fatras/include/ActsFatras/Selectors/LimitSelectors.hpp index 36d4eb23f1d9fc51a3f7e46b65994a799e3563cf..8ebad10eae8982db206e52a39690df73cb23dc38 100644 --- a/Fatras/include/ActsFatras/Selectors/LimitSelectors.hpp +++ b/Fatras/include/ActsFatras/Selectors/LimitSelectors.hpp @@ -8,31 +8,26 @@ #pragma once +#include "Acts/Material/MaterialProperties.hpp" +#include "ActsFatras/EventData/Particle.hpp" + namespace ActsFatras { -struct X0Limit { - /// Return true if the limit in X0 is reached - /// @todo modify, return what's left from the detector: needs non-const - /// detector - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &detector, - const particle_t &particle) const { - return particle.pathInX0() + - detector.thickness() / detector.material().X0() >= - particle.limitInX0(); +/// Select particles whose X0 limit would be reached after material passage. +struct PathLimitX0 { + bool operator()(const Acts::MaterialProperties &slab, + const Particle &particle) const { + return particle.pathLimitX0() < + (particle.pathInX0() + slab.thicknessInX0()); } }; -struct L0Limit { - /// Return true if the limit in X0 is reached - /// @todo modify, return what's left from the detector: needs non-const - /// detector - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &detector, - const particle_t &particle) const { - return particle.pathInL0() + - detector.thickness() / detector.material().L0() >= - particle.limitInL0(); +/// Select particles whose L0 limit would be reached after material passage. +struct PathLimitL0 { + bool operator()(const Acts::MaterialProperties &slab, + const Particle &particle) const { + return particle.pathLimitL0() < + (particle.pathInL0() + slab.thicknessInL0()); } }; diff --git a/Fatras/include/ActsFatras/Selectors/PdgSelectors.hpp b/Fatras/include/ActsFatras/Selectors/PdgSelectors.hpp index 99fcdfc8e8a075312cc8471c0d5f9f82a96286fb..9017340800601e46e9555c625d5ce6157406c0b4 100644 --- a/Fatras/include/ActsFatras/Selectors/PdgSelectors.hpp +++ b/Fatras/include/ActsFatras/Selectors/PdgSelectors.hpp @@ -8,57 +8,49 @@ #pragma once -namespace ActsFatras { +#include <cstdlib> -template <int pdg_t> -struct AbsPdgSelector { - // absolute Pdg selection - const int saPDG = pdg_t; +#include "ActsFatras/EventData/Particle.hpp" - /// Return true for all particles with | pdg | matching - /// the selection criteria - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.pdg() * particle.pdg() == saPDG * saPDG); - } -}; +namespace ActsFatras { -template <int pdg_t> +/// Select particles of one specific type. +/// +/// Particle and Antiparticle are treated as two separate types. +template <int Pdg> struct PdgSelector { - // Pdg selection - const int saPDG = pdg_t; - - /// Return true for all particles with pdg matching - /// the selection criteria - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return (particle.pdg() == saPDG); + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (static_cast<int>(particle.pdg()) == Pdg); } }; -template <int pdg_t> -struct AbsPdgExcluder { - // absolute Pdg selection - const int saPDG = pdg_t; - - /// Return true for all particles with | pdg | matching - /// the selection criteria - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return !(particle.pdg() * particle.pdg() == saPDG * saPDG); +/// Select particles and antiparticles of one specific type. +template <int Pdg> +struct AbsPdgSelector { + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (std::abs(static_cast<int>(particle.pdg())) == std::abs(Pdg)); } }; -template <int pdg_t> +/// Select all particles except one specific type. +/// +/// Particle and Antiparticle are treated as two separate types. +template <int Pdg> struct PdgExcluder { - // Pdg selection - const int saPDG = pdg_t; + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (static_cast<int>(particle.pdg()) != Pdg); + } +}; - /// Return true for all particles with pdg matching - /// the selection criteria - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - return !(particle.pdg() == saPDG); +/// Select all particles except for (anti-)particles of one specific type. +template <int Pdg> +struct AbsPdgExcluder { + template <typename detector_t> + bool operator()(const detector_t &, const Particle &particle) const { + return (std::abs(static_cast<int>(particle.pdg())) != std::abs(Pdg)); } }; diff --git a/Fatras/include/ActsFatras/Selectors/SelectorHelpers.hpp b/Fatras/include/ActsFatras/Selectors/SelectorHelpers.hpp index 05f16fdb91db364bca04465982f9cbf2b0072626..c5d47b54719f4e4ad7805420531a7966d95cc846 100644 --- a/Fatras/include/ActsFatras/Selectors/SelectorHelpers.hpp +++ b/Fatras/include/ActsFatras/Selectors/SelectorHelpers.hpp @@ -10,49 +10,44 @@ #include <climits> +#include "ActsFatras/EventData/Particle.hpp" + namespace ActsFatras { -// static selectors +/// Select all objects with an extracted value equal or larger than the cut. template <typename cast_t> struct Min { - cast_t cast; double valMin = 0.; - /// Return true for all particles with transverse momentum - /// bigger than the specified minimum value - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - double val = cast(particle); - return (val >= valMin); + template <typename detector_t, typename T> + bool operator()(const detector_t &, const T &thing) const { + return (valMin <= cast_t()(thing)); } }; +/// Select all objects with an extracted value below the cut. template <typename cast_t> struct Max { - cast_t cast; double valMax = std::numeric_limits<double>::max(); - /// Return true for all particles with transverse momentum - /// bigger than the specified minimum value - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - double val = cast(particle); - return (val <= valMax); + template <typename detector_t, typename T> + bool operator()(const detector_t &, const T &thing) const { + return (cast_t()(thing) < valMax); } }; +/// Select all objects with an extracted value within the range. +/// +/// The range is defined as the left, half-open interval within the cuts. template <typename cast_t> struct Range { - cast_t cast; - double valMin = 0.; + double valMin = std::numeric_limits<double>::lowest(); double valMax = std::numeric_limits<double>::max(); - /// Return true for all particles with transverse momentum - /// within the specified range - template <typename detector_t, typename particle_t> - bool operator()(const detector_t &, const particle_t &particle) const { - double val = cast(particle); - return (val >= valMin && val <= valMax); + template <typename detector_t, typename T> + bool operator()(const detector_t &, const T &thing) const { + const auto val = cast_t()(thing); + return ((valMin <= val) and (val < valMax)); } }; diff --git a/Fatras/include/ActsFatras/Kernel/LandauDistribution.hpp b/Fatras/include/ActsFatras/Utilities/LandauDistribution.hpp similarity index 80% rename from Fatras/include/ActsFatras/Kernel/LandauDistribution.hpp rename to Fatras/include/ActsFatras/Utilities/LandauDistribution.hpp index 7f6becece237ded613145980da01870fde6681a4..54f1a0ef4b3be1102ce45f9a91717a7a44b3d9ab 100644 --- a/Fatras/include/ActsFatras/Kernel/LandauDistribution.hpp +++ b/Fatras/include/ActsFatras/Utilities/LandauDistribution.hpp @@ -22,13 +22,16 @@ class LandauDistribution { /// Parameters must link back to the host distribution. using distribution_type = LandauDistribution; - /// Mean of the Landau distribution - double mean = 0.; - /// Scale of the Landau distribution - double scale = 1.; + /// Location parameter. + /// + /// @warning This is neither the mean nor the most probable value. + double location = 0.0; + /// Scale parameter. + double scale = 1.0; /// Construct from parameters. - param_type(double mean_, double scale_) : mean(mean_), scale(scale_) {} + param_type(double location_, double scale_) + : location(location_), scale(scale_) {} // Explicitlely defaulted construction and assignment param_type() = default; param_type(const param_type &) = default; @@ -38,7 +41,7 @@ class LandauDistribution { /// Parameters should be EqualityComparable friend bool operator==(const param_type &lhs, const param_type &rhs) { - return (lhs.mean == rhs.mean) and (lhs.scale == rhs.scale); + return (lhs.location == rhs.location) and (lhs.scale == rhs.scale); } friend bool operator!=(const param_type &lhs, const param_type &rhs) { return not(lhs == rhs); @@ -48,7 +51,7 @@ class LandauDistribution { using result_type = double; /// Construct directly from the distribution parameters. - LandauDistribution(double mean, double scale) : m_cfg(mean, scale) {} + LandauDistribution(double location, double scale) : m_cfg(location, scale) {} /// Construct from a parameter object. LandauDistribution(const param_type &cfg) : m_cfg(cfg) {} // Explicitlely defaulted construction and assignment @@ -72,14 +75,14 @@ class LandauDistribution { /// Generate a random number from the configured Landau distribution. template <typename Generator> - result_type operator()(Generator &engine) { - return (*this)(engine, m_cfg); + result_type operator()(Generator &generator) { + return (*this)(generator, m_cfg); } /// Generate a random number from the given Landau distribution. template <typename Generator> - result_type operator()(Generator &engine, const param_type ¶ms) { - double x = std::generate_canonical<double, 10>(engine); - return params.mean + quantile(x, params.scale); + result_type operator()(Generator &generator, const param_type ¶ms) { + const auto z = std::uniform_real_distribution<double>()(generator); + return params.location + params.scale * quantile(z); } /// Provide standard comparison operators @@ -95,7 +98,7 @@ class LandauDistribution { private: param_type m_cfg; - static double quantile(double z, double xi); + static double quantile(double z); }; } // namespace ActsFatras diff --git a/Fatras/src/LandauDistribution.cpp b/Fatras/src/LandauDistribution.cpp index 1c605f35d009e6e7b150b012e19538a17df0ab5f..c9166bc6fc3beb6f1c9605c7c034ed995455b10d 100644 --- a/Fatras/src/LandauDistribution.cpp +++ b/Fatras/src/LandauDistribution.cpp @@ -6,11 +6,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -#include "ActsFatras/Kernel/LandauDistribution.hpp" +#include "ActsFatras/Utilities/LandauDistribution.hpp" -double ActsFatras::LandauDistribution::quantile(double z, double xi) { +double ActsFatras::LandauDistribution::quantile(double z) { // LANDAU quantile : algorithm from CERNLIB G110 ranlan - // with scale parameter xi // Converted by Rene Brun from CERNLIB routine ranlan(G110), // Moved and adapted to QuantFuncMathCore by B. List 29.4.2010 @@ -180,8 +179,6 @@ double ActsFatras::LandauDistribution::quantile(double z, double xi) { 40.157721, 41.622399, 43.202525, 44.912465, 46.769077, 48.792279, 51.005773, 53.437996, 56.123356, 59.103894}; - if (xi <= 0) - return 0; if (z <= 0) return -std::numeric_limits<double>::infinity(); if (z >= 1) @@ -214,5 +211,5 @@ double ActsFatras::LandauDistribution::quantile(double z, double xi) { ((1 + 6.06511919E3 * u + 6.94021044E5 * v) * u); } } - return xi * ranlan; + return ranlan; } diff --git a/Tests/UnitTests/Fatras/CMakeLists.txt b/Tests/UnitTests/Fatras/CMakeLists.txt index f78d03b06ecb9402d278abd34673168b2e61e5be..d9bcfa9550ea8558f74ede0f1d1825e0c8e4ff85 100644 --- a/Tests/UnitTests/Fatras/CMakeLists.txt +++ b/Tests/UnitTests/Fatras/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(EventData) add_subdirectory(Kernel) add_subdirectory(Physics) add_subdirectory(Selectors) diff --git a/Tests/UnitTests/Fatras/EventData/BarcodeTests.cpp b/Tests/UnitTests/Fatras/EventData/BarcodeTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..254f5ace6fe134d64561c6381ed26e7210797b0c --- /dev/null +++ b/Tests/UnitTests/Fatras/EventData/BarcodeTests.cpp @@ -0,0 +1,57 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2018-2020 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <boost/test/unit_test.hpp> + +#include <random> + +#include "ActsFatras/EventData/Barcode.hpp" + +using ActsFatras::Barcode; + +BOOST_AUTO_TEST_SUITE(FatrasBarcode) + +BOOST_AUTO_TEST_CASE(MakeDescendant) { + // initial barcode with primary particle + auto p3 = Barcode().setVertexPrimary(1).setVertexSecondary(2).setParticle(3); + BOOST_TEST(p3.vertexPrimary() == 1u); + BOOST_TEST(p3.vertexSecondary() == 2u); + BOOST_TEST(p3.particle() == 3u); + BOOST_TEST(p3.generation() == 0u); + BOOST_TEST(p3.subParticle() == 0u); + + // generate two first-generation descendants + auto p30 = p3.makeDescendant(0); + auto p31 = p3.makeDescendant(1); + BOOST_TEST(p30.vertexPrimary() == 1u); + BOOST_TEST(p30.vertexSecondary() == 2u); + BOOST_TEST(p30.particle() == 3u); + BOOST_TEST(p30.generation() == 1u); + BOOST_TEST(p30.subParticle() == 0u); + BOOST_TEST(p31.vertexPrimary() == 1u); + BOOST_TEST(p31.vertexSecondary() == 2u); + BOOST_TEST(p31.particle() == 3u); + BOOST_TEST(p31.generation() == 1u); + BOOST_TEST(p31.subParticle() == 1u); + + // generate two (overlapping) second-generation descendants. + auto p300 = p30.makeDescendant(0); + auto p310 = p31.makeDescendant(0); + BOOST_TEST(p300.vertexPrimary() == 1u); + BOOST_TEST(p300.vertexSecondary() == 2u); + BOOST_TEST(p300.particle() == 3u); + BOOST_TEST(p300.generation() == 2u); + BOOST_TEST(p300.subParticle() == 0u); + BOOST_TEST(p310.vertexPrimary() == 1u); + BOOST_TEST(p310.vertexSecondary() == 2u); + BOOST_TEST(p310.particle() == 3u); + BOOST_TEST(p310.generation() == 2u); + BOOST_TEST(p310.subParticle() == 0u); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/EventData/CMakeLists.txt b/Tests/UnitTests/Fatras/EventData/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0f5147602f28c3dbef7b5d675e712e68ab00151 --- /dev/null +++ b/Tests/UnitTests/Fatras/EventData/CMakeLists.txt @@ -0,0 +1,4 @@ +set(unittest_extra_libraries ActsFatras) + +add_unittest(FatrasBarcode BarcodeTests.cpp) +add_unittest(FatrasParticle ParticleTests.cpp) diff --git a/Tests/UnitTests/Fatras/EventData/ParticleTests.cpp b/Tests/UnitTests/Fatras/EventData/ParticleTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c2be28f87499261488ce53562f0b9ef8561934cc --- /dev/null +++ b/Tests/UnitTests/Fatras/EventData/ParticleTests.cpp @@ -0,0 +1,76 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2018-2020 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <boost/test/unit_test.hpp> + +#include <random> + +#include "Acts/Utilities/Units.hpp" +#include "ActsFatras/EventData/Particle.hpp" + +using Acts::PdgParticle; +using ActsFatras::Barcode; +using ActsFatras::Particle; +using namespace Acts::UnitLiterals; + +BOOST_AUTO_TEST_SUITE(FatrasParticle) + +BOOST_AUTO_TEST_CASE(Construct) { + const auto id = Barcode().setVertexPrimary(1).setParticle(42); + const auto particle = Particle(id, PdgParticle::eProton, 1_GeV, 1_e); + + BOOST_TEST(particle.id() == id); + BOOST_TEST(particle.pdg() == PdgParticle::eProton); + // particle is at rest at the origin + BOOST_TEST(particle.position4() == Particle::Vector4::Zero()); + BOOST_TEST(particle.position() == Particle::Vector3::Zero()); + BOOST_TEST(particle.time() == Particle::Scalar(0)); + BOOST_TEST(particle.position4().x() == particle.position().x()); + BOOST_TEST(particle.position4().y() == particle.position().y()); + BOOST_TEST(particle.position4().z() == particle.position().z()); + BOOST_TEST(particle.position4().w() == particle.time()); + // particle direction is undefined + BOOST_TEST(particle.momentum() == Particle::Scalar(0)); + // particle is created at rest and thus not alive + BOOST_TEST(not particle); +} + +BOOST_AUTO_TEST_CASE(CorrectEnergy) { + const auto id = Barcode().setVertexPrimary(1).setParticle(42); + auto particle = Particle(id, PdgParticle::eProton, 1_GeV, 1_e) + .setDirection(Particle::Vector3::UnitX()) + .setMomentum(2_GeV); + + BOOST_TEST(particle.mass() == 1_GeV); + // check that the particle has some input energy + BOOST_TEST(particle.momentum4().x() == 2_GeV); + BOOST_TEST(particle.momentum4().y() == 0_GeV); + BOOST_TEST(particle.momentum4().z() == 0_GeV); + BOOST_TEST(particle.momentum4().w() == std::hypot(1_GeV, 2_GeV)); + BOOST_TEST(particle.momentum() == 2_GeV); + BOOST_TEST(particle.energy() == std::hypot(1_GeV, 2_GeV)); + // loose some energy + particle.correctEnergy(-100_MeV); + BOOST_TEST(particle.momentum() < 2_GeV); + BOOST_TEST(particle.energy() == + Particle::Scalar(std::hypot(1_GeV, 2_GeV) - 100_MeV)); + // particle is still alive + BOOST_TEST(particle); + // loose a lot of energy + particle.correctEnergy(-3_GeV); + BOOST_TEST(particle.momentum() == Particle::Scalar(0)); + BOOST_TEST(particle.energy() == particle.mass()); + // lossing even more energy does nothing + particle.correctEnergy(-10_GeV); + BOOST_TEST(particle.momentum() == Particle::Scalar(0)); + BOOST_TEST(particle.energy() == particle.mass()); + // particle is not alive anymore + BOOST_TEST(not particle); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/Physics/Dataset.hpp b/Tests/UnitTests/Fatras/Physics/Dataset.hpp index 9406efd00d7465cca9019dc8349cec36ea8c1686..7591d824c9b213bcc5fe0fd3efb8e2236975acea 100644 --- a/Tests/UnitTests/Fatras/Physics/Dataset.hpp +++ b/Tests/UnitTests/Fatras/Physics/Dataset.hpp @@ -57,12 +57,14 @@ const auto particleParameters = momentumPhi * momentumLambda * momentumAbs * // utility function to build a particle from the dataset parameters inline ActsFatras::Particle makeParticle(double phi, double lambda, double p, - int pdg, double m, double q) { - Acts::Vector3D pos(0, 0, 0); - Acts::Vector3D mom(p * std::cos(lambda) * std::cos(phi), - p * std::cos(lambda) * std::sin(phi), - p * std::sin(lambda)); - return {pos, mom, m, q, pdg}; + Acts::PdgParticle pdg, double m, + double q) { + const auto id = ActsFatras::Barcode().setVertexPrimary(1).setParticle(1); + return ActsFatras::Particle(id, pdg, m, q) + .setPosition4(0, 0, 0, 0) + .setDirection(std::cos(lambda) * std::cos(phi), + std::cos(lambda) * std::sin(phi), std::sin(lambda)) + .setMomentum(p); } } // namespace diff --git a/Tests/UnitTests/Fatras/Physics/EnergyLossTests.cpp b/Tests/UnitTests/Fatras/Physics/EnergyLossTests.cpp index 18dfb89375d13d435d50857593c1c378b98cf349..06561bec6bd6a86bf3cacbbf6f94e4a4b011e0ef 100644 --- a/Tests/UnitTests/Fatras/Physics/EnergyLossTests.cpp +++ b/Tests/UnitTests/Fatras/Physics/EnergyLossTests.cpp @@ -30,10 +30,8 @@ BOOST_DATA_TEST_CASE(BetheBloch, Dataset::particleParameters, phi, lambda, p, ActsFatras::BetheBloch process; const auto outgoing = process(gen, Dataset::thickSlab, after); // energy loss changes momentum and energy - // TODO fix process computation - // BOOST_TEST(after.pT() < before.pT()); - // BOOST_TEST(after.p() < before.p()); - // BOOST_TEST(after.E() < before.E()); + BOOST_TEST(after.momentum() < before.momentum()); + BOOST_TEST(after.energy() < before.energy()); // energy loss creates no new particles BOOST_TEST(outgoing.empty()); } @@ -48,10 +46,8 @@ BOOST_DATA_TEST_CASE(BetheHeitler, Dataset::particleParameters, phi, lambda, p, ActsFatras::BetheHeitler process; const auto outgoing = process(gen, Dataset::thickSlab, after); // energy loss changes momentum and energy - // TODO fix process computation - // BOOST_TEST(after.pT() < before.pT()); - // BOOST_TEST(after.p() < before.p()); - // BOOST_TEST(after.E() < before.E()); + BOOST_TEST(after.momentum() < before.momentum()); + BOOST_TEST(after.energy() < before.energy()); // energy loss creates no new particles BOOST_TEST(outgoing.empty()); } diff --git a/Tests/UnitTests/Fatras/Physics/ScatteringTests.cpp b/Tests/UnitTests/Fatras/Physics/ScatteringTests.cpp index 8ff257b8fe9c246eba18fa2908f3881931891bcf..d8c8186dc63b0db6bbf35872f78825ccf7c45199 100644 --- a/Tests/UnitTests/Fatras/Physics/ScatteringTests.cpp +++ b/Tests/UnitTests/Fatras/Physics/ScatteringTests.cpp @@ -15,33 +15,55 @@ #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" #include "Acts/Tests/CommonHelpers/PredefinedMaterials.hpp" #include "ActsFatras/EventData/Particle.hpp" +#include "ActsFatras/Physics/Scattering/GaussianMixture.hpp" +#include "ActsFatras/Physics/Scattering/GeneralMixture.hpp" #include "ActsFatras/Physics/Scattering/Highland.hpp" #include "ActsFatras/Physics/Scattering/Scattering.hpp" #include "Dataset.hpp" -// TODO decrease this after removing computation round trip from particle -static constexpr double eps = 1e-4; +namespace { -BOOST_AUTO_TEST_SUITE(FatrasScattering) +constexpr double eps = 1e-10; -/// Test the scattering implementation -BOOST_DATA_TEST_CASE(HighlandScattering, Dataset::particleParameters, phi, - lambda, p, pdg, m, q) { - using HighlandScattering = ActsFatras::Scattering<ActsFatras::Highland>; +using GeneralMixtureScattering = + ActsFatras::Scattering<ActsFatras::GeneralMixture>; +using GaussianMixtureScattering = + ActsFatras::Scattering<ActsFatras::GaussianMixture>; +using HighlandScattering = ActsFatras::Scattering<ActsFatras::Highland>; +// Common test method that will be instantiated for each scattering model. +template <typename Scattering> +void run(const Scattering& scattering, const ActsFatras::Particle& before) { std::default_random_engine gen; - ActsFatras::Particle before = - Dataset::makeParticle(phi, lambda, p, pdg, m, q); ActsFatras::Particle after = before; - HighlandScattering scattering; const auto outgoing = scattering(gen, Dataset::thinSlab, after); // scattering leaves absolute energy/momentum unchanged - CHECK_CLOSE_REL(after.pT(), before.pT(), eps); - CHECK_CLOSE_REL(after.p(), before.p(), eps); - CHECK_CLOSE_REL(after.E(), before.E(), eps); + CHECK_CLOSE_REL(after.momentum(), before.momentum(), eps); + CHECK_CLOSE_REL(after.energy(), before.energy(), eps); // scattering creates no new particles BOOST_TEST(outgoing.empty()); } +} // namespace + +BOOST_AUTO_TEST_SUITE(FatrasScattering) + +BOOST_DATA_TEST_CASE(GeneralMixture, Dataset::particleParameters, phi, lambda, + p, pdg, m, q) { + run(GeneralMixtureScattering(), + Dataset::makeParticle(phi, lambda, p, pdg, m, q)); +} + +BOOST_DATA_TEST_CASE(GaussianMixture, Dataset::particleParameters, phi, lambda, + p, pdg, m, q) { + run(GaussianMixtureScattering(), + Dataset::makeParticle(phi, lambda, p, pdg, m, q)); +} + +BOOST_DATA_TEST_CASE(Highland, Dataset::particleParameters, phi, lambda, p, pdg, + m, q) { + run(HighlandScattering(), Dataset::makeParticle(phi, lambda, p, pdg, m, q)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/Selectors/CMakeLists.txt b/Tests/UnitTests/Fatras/Selectors/CMakeLists.txt index 86a4a0f82cdcc77177c4f4d16770a59b49ab4100..50112c684dce66d6a490b3a9b0cf6f84245bbce3 100644 --- a/Tests/UnitTests/Fatras/Selectors/CMakeLists.txt +++ b/Tests/UnitTests/Fatras/Selectors/CMakeLists.txt @@ -1,5 +1,6 @@ set(unittest_extra_libraries ActsFatras) +add_unittest(FatrasChargeSelectors ChargeSelectorsTests.cpp) add_unittest(FatrasKinematicCasts KinematicCastsTests.cpp) add_unittest(FatrasLimitSelectors LimitSelectorsTests.cpp) add_unittest(FatrasPdgSelectors PdgSelectorsTests.cpp) diff --git a/Tests/UnitTests/Fatras/Selectors/ChargeSelectorsTests.cpp b/Tests/UnitTests/Fatras/Selectors/ChargeSelectorsTests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5695ebe886f5ebb9751bf1973b3497ab061e6877 --- /dev/null +++ b/Tests/UnitTests/Fatras/Selectors/ChargeSelectorsTests.cpp @@ -0,0 +1,47 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2018-2020 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <boost/test/unit_test.hpp> + +#include "ActsFatras/Selectors/ChargeSelectors.hpp" +#include "Dataset.hpp" + +using namespace ActsFatras; + +static const auto& detector = Dataset::thinSlab; + +BOOST_AUTO_TEST_SUITE(FatrasChargeSelectors) + +BOOST_AUTO_TEST_CASE(NegativeParticle) { + const auto& particle = Dataset::centralElectron; + + BOOST_TEST(not NeutralSelector()(detector, particle)); + BOOST_TEST(ChargedSelector()(detector, particle)); + BOOST_TEST(not PositiveSelector()(detector, particle)); + BOOST_TEST(NegativeSelector()(detector, particle)); +} + +BOOST_AUTO_TEST_CASE(NeutralParticle) { + const auto& particle = Dataset::centralNeutron; + + BOOST_TEST(NeutralSelector()(detector, particle)); + BOOST_TEST(not ChargedSelector()(detector, particle)); + BOOST_TEST(not PositiveSelector()(detector, particle)); + BOOST_TEST(not NegativeSelector()(detector, particle)); +} + +BOOST_AUTO_TEST_CASE(PositiveParticle) { + const auto& particle = Dataset::centralPositron; + + BOOST_TEST(not NeutralSelector()(detector, particle)); + BOOST_TEST(ChargedSelector()(detector, particle)); + BOOST_TEST(PositiveSelector()(detector, particle)); + BOOST_TEST(not NegativeSelector()(detector, particle)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/Selectors/Dataset.hpp b/Tests/UnitTests/Fatras/Selectors/Dataset.hpp index af9b15cce4ce8f260b6c95d9161f5e3ccc04a26c..1287661c020c2d19068c092fea97cb6eb33e87cb 100644 --- a/Tests/UnitTests/Fatras/Selectors/Dataset.hpp +++ b/Tests/UnitTests/Fatras/Selectors/Dataset.hpp @@ -27,25 +27,31 @@ constexpr auto massElectron = 0.51099891_MeV; constexpr auto massMuon = 105.658367_MeV; constexpr auto massPion = 134.9766_MeV; -ActsFatras::Particle centralElectron({0_mm, 0_mm, 0_mm}, - {0_GeV, 1.5_GeV, 0_GeV}, massElectron, - -1_e, Acts::PdgParticle::eElectron); -ActsFatras::Particle centralPositron({0_mm, 0_mm, 0_mm}, - {0_GeV, 1.5_GeV, 0_GeV}, massElectron, 1_e, - Acts::PdgParticle::ePositron); -ActsFatras::Particle centralMuon({0_mm, 0_mm, 0_mm}, {0_GeV, 1.5_GeV, 0_GeV}, - massMuon, -1_e, Acts::PdgParticle::eMuon); -ActsFatras::Particle centralAntiMuon({0_mm, 0_mm, 0_mm}, - {0_GeV, 1.5_GeV, 0_GeV}, massMuon, 1_e, - Acts::PdgParticle::eAntiMuon); -ActsFatras::Particle backwardPion({0_mm, 0_mm, -100_mm}, - {10_MeV, 10_MeV, -1.5_GeV}, massPion, -1_e, - Acts::PdgParticle::ePionMinus); -ActsFatras::Particle centralPion({0_mm, 0_mm, 0_mm}, {0_GeV, 1.5_GeV, 0_GeV}, - massPion, -1_e, Acts::PdgParticle::ePionMinus); -ActsFatras::Particle forwardPion({0_mm, 0_mm, 100_mm}, - {10_MeV, 10_MeV, 1.5_GeV}, massPion, -1_e, - Acts::PdgParticle::ePionMinus); +ActsFatras::Particle makeParticle(double z, double eta, Acts::PdgParticle pdg, + double m, double q) { + const auto id = ActsFatras::Barcode().setVertexPrimary(1).setParticle(1); + return ActsFatras::Particle(id, pdg, m, q) + .setPosition4(0.0, 0.0, z, 0.0) + .setDirection(1.0 / std::cosh(eta), 0.0, std::tanh(eta)) + .setMomentum(1.5_GeV); +} + +const auto centralElectron = + makeParticle(0_mm, 0.0, Acts::PdgParticle::eElectron, massElectron, -1_e); +const auto centralPositron = + makeParticle(0_mm, 0.0, Acts::PdgParticle::ePositron, massElectron, 1_e); +const auto centralMuon = + makeParticle(0_mm, 0.0, Acts::PdgParticle::eMuon, massMuon, -1_e); +const auto centralAntiMuon = + makeParticle(0_mm, 0.0, Acts::PdgParticle::eAntiMuon, massMuon, 1_e); +const auto backwardPion = + makeParticle(-100_mm, -4.5, Acts::PdgParticle::ePionMinus, massPion, -1_e); +const auto centralPion = + makeParticle(0_mm, 0.0, Acts::PdgParticle::ePionMinus, massPion, -1_e); +const auto forwardPion = + makeParticle(100_mm, 4.5, Acts::PdgParticle::ePionMinus, massPion, -1_e); +const auto centralNeutron = + makeParticle(1_mm, 0.1, Acts::PdgParticle::eNeutron, 1_GeV, 0_e); } // namespace } // namespace Dataset diff --git a/Tests/UnitTests/Fatras/Selectors/KinematicCastsTests.cpp b/Tests/UnitTests/Fatras/Selectors/KinematicCastsTests.cpp index eacafa5c2dca61a8599ae7a8372790b0c51a822e..c427323c670c1f49832bdf036cf879a0eccf4f82 100644 --- a/Tests/UnitTests/Fatras/Selectors/KinematicCastsTests.cpp +++ b/Tests/UnitTests/Fatras/Selectors/KinematicCastsTests.cpp @@ -19,31 +19,43 @@ static constexpr double eps = 1e-10; BOOST_AUTO_TEST_SUITE(FatrasKinematicCasts) +BOOST_AUTO_TEST_CASE(BackwardParticle) { + const auto& particle = Dataset::backwardPion; + + CHECK_SMALL(Vrho()(particle), eps); + CHECK_CLOSE_REL(Vz()(particle), -100_mm, eps); + CHECK_CLOSE_REL(AbsVz()(particle), 100_mm, eps); + CHECK_CLOSE_REL(Eta()(particle), -4.5, eps); + CHECK_CLOSE_REL(AbsEta()(particle), 4.5, eps); + CHECK_CLOSE_REL(Pt()(particle), 1.5_GeV / std::cosh(4.5), eps); + CHECK_CLOSE_REL(P()(particle), 1.5_GeV, eps); + CHECK_CLOSE_REL(E()(particle), std::hypot(1.5_GeV, Dataset::massPion), eps); +} + BOOST_AUTO_TEST_CASE(CentralParticle) { const auto& particle = Dataset::centralPion; - CHECK_SMALL(vR()(particle), eps); - CHECK_SMALL(vZ()(particle), eps); + CHECK_SMALL(Vrho()(particle), eps); + CHECK_SMALL(Vz()(particle), eps); CHECK_SMALL(AbsVz()(particle), eps); - CHECK_SMALL(eta()(particle), eps); - CHECK_SMALL(absEta()(particle), eps); - CHECK_CLOSE_REL(pT()(particle), 1.5_GeV, eps); - CHECK_CLOSE_REL(p()(particle), 1.5_GeV, eps); - // TODO energy + CHECK_SMALL(Eta()(particle), eps); + CHECK_SMALL(AbsEta()(particle), eps); + CHECK_CLOSE_REL(Pt()(particle), 1.5_GeV, eps); + CHECK_CLOSE_REL(P()(particle), 1.5_GeV, eps); + CHECK_CLOSE_REL(E()(particle), std::hypot(1.5_GeV, Dataset::massPion), eps); } BOOST_AUTO_TEST_CASE(ForwardParticle) { const auto& particle = Dataset::forwardPion; - CHECK_SMALL(vR()(particle), eps); - CHECK_CLOSE_REL(vZ()(particle), 100_mm, eps); + CHECK_SMALL(Vrho()(particle), eps); + CHECK_CLOSE_REL(Vz()(particle), 100_mm, eps); CHECK_CLOSE_REL(AbsVz()(particle), 100_mm, eps); - // TODO use value comparisons instead of relative checks - BOOST_TEST(4.0 < eta()(particle)); - BOOST_TEST(4.0 < absEta()(particle)); - BOOST_TEST(10_MeV < pT()(particle)); - BOOST_TEST(1.5_GeV < p()(particle)); - // TODO energy + CHECK_CLOSE_REL(Eta()(particle), 4.5, eps); + CHECK_CLOSE_REL(AbsEta()(particle), 4.5, eps); + CHECK_CLOSE_REL(Pt()(particle), 1.5_GeV / std::cosh(4.5), eps); + CHECK_CLOSE_REL(P()(particle), 1.5_GeV, eps); + CHECK_CLOSE_REL(E()(particle), std::hypot(1.5_GeV, Dataset::massPion), eps); } BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/Selectors/LimitSelectorsTests.cpp b/Tests/UnitTests/Fatras/Selectors/LimitSelectorsTests.cpp index 111148198e7be44efb9f11c09db01b287190e759..f032ee9bb5be29061c6651544e360c901333b513 100644 --- a/Tests/UnitTests/Fatras/Selectors/LimitSelectorsTests.cpp +++ b/Tests/UnitTests/Fatras/Selectors/LimitSelectorsTests.cpp @@ -16,21 +16,35 @@ using namespace Acts::UnitLiterals; BOOST_AUTO_TEST_SUITE(LimitSelectors) -BOOST_AUTO_TEST_CASE(X0L0Limits) { - ActsFatras::X0Limit selectX0; - ActsFatras::L0Limit selectL0; - +// Construct a particle that is close to its X0/L0 path limit. +// +// Passing a thin slab should still be Ok, but the thick slab should not. +static ActsFatras::Particle makeParticleCloseToLimit() { // create particle and move it close to the X0/L0 limit auto particle = Dataset::centralPion; - particle.setLimits(0.15, 0.45); - particle.update(particle.position(), particle.momentum(), 0.10, 0.34); + particle.addPassedMaterial(0.125, 0.0125); + particle.setMaterialLimits( + 0.125 + 1.125 * Dataset::thinSlab.thicknessInX0(), + 0.0125 + 1.125 * Dataset::thinSlab.thicknessInL0()); + return particle; +} + +BOOST_AUTO_TEST_CASE(PathLimitX0) { + ActsFatras::PathLimitX0 select; + auto particle = makeParticleCloseToLimit(); + // particle is still within limits for thin block + BOOST_TEST(not select(Dataset::thinSlab, particle)); + // particle would pass limits for thick block + BOOST_TEST(select(Dataset::thickSlab, particle)); +} +BOOST_AUTO_TEST_CASE(PathLimitL0) { + ActsFatras::PathLimitL0 select; + auto particle = makeParticleCloseToLimit(); // particle is still within limits for thin block - BOOST_TEST(not selectX0(Dataset::thinSlab, particle)); - BOOST_TEST(not selectL0(Dataset::thinSlab, particle)); + BOOST_TEST(not select(Dataset::thinSlab, particle)); // particle would pass limits for thick block - BOOST_TEST(selectX0(Dataset::thickSlab, particle)); - BOOST_TEST(selectL0(Dataset::thickSlab, particle)); + BOOST_TEST(select(Dataset::thickSlab, particle)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/Tests/UnitTests/Fatras/Selectors/SelectorHelpersTests.cpp b/Tests/UnitTests/Fatras/Selectors/SelectorHelpersTests.cpp index c43ea4505817486cdcb17ac6b8818ab4641e77b1..aa4a859d3e8e93edf1678b5b31f7f1e385bca5a1 100644 --- a/Tests/UnitTests/Fatras/Selectors/SelectorHelpersTests.cpp +++ b/Tests/UnitTests/Fatras/Selectors/SelectorHelpersTests.cpp @@ -23,17 +23,13 @@ BOOST_AUTO_TEST_SUITE(FatrasSelectorHelpers) BOOST_AUTO_TEST_CASE(Min) { // require a minimum eta value of 0.5 - ActsFatras::Min<ActsFatras::Casts::eta> minEta; - minEta.valMin = 0.5; - + ActsFatras::Min<ActsFatras::Casts::Eta> minEta{0.5}; BOOST_TEST(not minEta(detector, backward)); BOOST_TEST(not minEta(detector, central)); BOOST_TEST(minEta(detector, forward)); // require a mininum absolute eta value of 0.5 - ActsFatras::Min<ActsFatras::Casts::absEta> minAbsEta; - minAbsEta.valMin = 0.5; - + ActsFatras::Min<ActsFatras::Casts::AbsEta> minAbsEta{0.5}; BOOST_TEST(minAbsEta(detector, backward)); BOOST_TEST(not minAbsEta(detector, central)); BOOST_TEST(minAbsEta(detector, forward)); @@ -41,35 +37,25 @@ BOOST_AUTO_TEST_CASE(Min) { BOOST_AUTO_TEST_CASE(Max) { // require a maximum eta value of 0.5 - ActsFatras::Max<ActsFatras::Casts::eta> maxEta; - maxEta.valMax = 0.5; - + ActsFatras::Max<ActsFatras::Casts::Eta> maxEta{0.5}; BOOST_TEST(maxEta(detector, backward)); BOOST_TEST(maxEta(detector, central)); BOOST_TEST(not maxEta(detector, forward)); // require a maximum absolute eta value of 0.5 - ActsFatras::Max<ActsFatras::Casts::absEta> maxAbsEta; - maxAbsEta.valMax = 0.5; - + ActsFatras::Max<ActsFatras::Casts::AbsEta> maxAbsEta{0.5}; BOOST_TEST(not maxAbsEta(detector, backward)); BOOST_TEST(maxAbsEta(detector, central)); BOOST_TEST(not maxAbsEta(detector, forward)); } BOOST_AUTO_TEST_CASE(Range) { - ActsFatras::Range<ActsFatras::Casts::eta> rangeEta; - rangeEta.valMin = -6.; - rangeEta.valMax = -0.5; - + ActsFatras::Range<ActsFatras::Casts::Eta> rangeEta{-6.0, -0.5}; BOOST_TEST(rangeEta(detector, backward)); BOOST_TEST(not rangeEta(detector, central)); BOOST_TEST(not rangeEta(detector, forward)); - ActsFatras::Range<ActsFatras::Casts::absEta> rangeAbsEta; - rangeAbsEta.valMin = 0.5; - rangeAbsEta.valMax = 6.0; - + ActsFatras::Range<ActsFatras::Casts::AbsEta> rangeAbsEta{0.5, 6.0}; BOOST_TEST(rangeAbsEta(detector, backward)); BOOST_TEST(not rangeAbsEta(detector, central)); BOOST_TEST(rangeAbsEta(detector, forward));