Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 08:52:26

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file celeritas/phys/PhysicsData.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include "corecel/cont/Array.hh"
0010 #include "corecel/data/Collection.hh"
0011 #include "corecel/data/CollectionBuilder.hh"
0012 #include "corecel/data/StackAllocatorData.hh"
0013 #include "celeritas/Quantities.hh"
0014 #include "celeritas/Types.hh"
0015 #include "celeritas/em/data/AtomicRelaxationData.hh"
0016 #include "celeritas/em/data/EPlusGGData.hh"
0017 #include "celeritas/em/data/LivermorePEData.hh"
0018 #include "celeritas/grid/XsGridData.hh"
0019 #include "celeritas/neutron/data/NeutronElasticData.hh"
0020 
0021 #include "Interaction.hh"
0022 #include "Secondary.hh"
0023 
0024 namespace celeritas
0025 {
0026 //---------------------------------------------------------------------------//
0027 // TYPES
0028 //---------------------------------------------------------------------------//
0029 //! Currently all value grids are cross section grids
0030 using ValueGrid = XsGridRecord;
0031 using ValueGridId = OpaqueId<XsGridRecord>;
0032 using ValueTableId = OpaqueId<struct ValueTable>;
0033 
0034 //---------------------------------------------------------------------------//
0035 // PARAMS
0036 //---------------------------------------------------------------------------//
0037 /*!
0038  * Set of value grids for all elements or materials.
0039  *
0040  * It is allowable for this to be "false" (i.e. no materials assigned)
0041  * indicating that the value table doesn't apply in the context -- for
0042  * example, an empty ValueTable macro_xs means that the process doesn't have a
0043  * discrete interaction.
0044  */
0045 struct ValueTable
0046 {
0047     ItemRange<ValueGridId> grids;  //!< Value grid by element or material index
0048 
0049     //! True if assigned
0050     explicit CELER_FUNCTION operator bool() const { return !grids.empty(); }
0051 };
0052 
0053 //---------------------------------------------------------------------------//
0054 /*!
0055  * Set of cross section CDF tables for a model.
0056  *
0057  * Each material has a set of value grids for its constituent elements; these
0058  * are used to sample an element from a material when required by a discrete
0059  * interaction. A null ValueTableId means the material only has a single
0060  * element, so no cross sections need to be stored. An empty ModelXsTable means
0061  * no element selection is required for the model.
0062  */
0063 struct ModelXsTable
0064 {
0065     ItemRange<ValueTableId> material;  //!< Value table by material index
0066 
0067     //! True if assigned
0068     explicit CELER_FUNCTION operator bool() const { return !material.empty(); }
0069 };
0070 
0071 //---------------------------------------------------------------------------//
0072 /*!
0073  * Energy-dependent model IDs for a single process and particle type.
0074  *
0075  * For a given particle type, a single process should be divided into multiple
0076  * models as a function of energy. The ModelGroup represents this with an
0077  * energy grid, and each cell of the grid corresponding to a particular
0078  * ModelId.
0079  */
0080 struct ModelGroup
0081 {
0082     using Energy = units::MevEnergy;
0083 
0084     ItemRange<real_type> energy;  //!< Energy grid bounds [MeV]
0085     ItemRange<ParticleModelId> model;  //!< Corresponding models
0086 
0087     //! True if assigned
0088     explicit CELER_FUNCTION operator bool() const
0089     {
0090         return (energy.size() >= 2) && (model.size() + 1 == energy.size());
0091     }
0092 };
0093 
0094 //---------------------------------------------------------------------------//
0095 /*!
0096  * Particle-process that uses MC integration to sample interaction length.
0097  *
0098  * This is needed for the integral approach for correctly sampling the discrete
0099  * interaction length after a particle loses energy along a step. An \c
0100  * IntegralXsProcess is stored for each particle-process. This will be "false"
0101  * (i.e. no energy_max assigned) if the particle associated with the process
0102  * does not have energy loss processes or if \c use_integral_xs is false.
0103  */
0104 struct IntegralXsProcess
0105 {
0106     ItemRange<real_type> energy_max_xs;  //!< Energy of the largest xs [mat]
0107 
0108     //! True if assigned
0109     explicit CELER_FUNCTION operator bool() const
0110     {
0111         return !energy_max_xs.empty();
0112     }
0113 };
0114 
0115 //---------------------------------------------------------------------------//
0116 /*!
0117  * Processes for a single particle type.
0118  *
0119  * Each index should be accessed with type ParticleProcessId. \c macro_xs
0120  * stores the cross section tables for each process, while \c energy_loss and
0121  * \c range are the process-integrated dE/dx and range for the particle.  \c
0122  * integral_xs will only be assigned if the integral approach is used and the
0123  * particle has continuous-discrete processes.
0124  *
0125  * \todo If it's possible for a particle to have multiple at-rest processes, \c
0126  * at_rest should be the process with the smallest lifetime. This is used in \c
0127  * select_discrete_interaction to choose the at-rest process with the smallest
0128  * time to interaction if the particle is stopped and has a process that
0129  * applies at rest.
0130  */
0131 struct ProcessGroup
0132 {
0133     ItemRange<ProcessId> processes;  //!< Processes that apply [ppid]
0134     ItemRange<ModelGroup> models;  //!< Model applicability [ppid]
0135     ItemRange<IntegralXsProcess> integral_xs;  //!< [ppid]
0136     ItemRange<ValueTable> macro_xs;  //!< [ppid]
0137     ValueTableId energy_loss;  //!< Process-integrated energy loss
0138     ValueTableId range;  //!< Process-integrated range
0139     ValueTableId inverse_range;  //!< Inverse process-integrated range
0140     ParticleProcessId at_rest;  //!< ID of the particle's at-rest process
0141 
0142     //! True if assigned and valid
0143     explicit CELER_FUNCTION operator bool() const
0144     {
0145         return !processes.empty() && models.size() == processes.size();
0146     }
0147 
0148     //! Number of processes that apply
0149     CELER_FUNCTION ParticleProcessId::size_type size() const
0150     {
0151         return processes.size();
0152     }
0153 };
0154 
0155 //---------------------------------------------------------------------------//
0156 /*!
0157  * Model data for special hardwired cases (on-the-fly xs calculations).
0158  *
0159  * TODO: livermore/relaxation are owned by other classes, but
0160  * because we assign <host, value> -> { <host, cref> ; <device, value> ->
0161  * <device, cref> }
0162  */
0163 template<Ownership W, MemSpace M>
0164 struct HardwiredModels
0165 {
0166     //// DATA ////
0167 
0168     // Photoelectric effect
0169     ProcessId photoelectric;
0170     units::MevEnergy photoelectric_table_thresh;
0171     ModelId livermore_pe;
0172     LivermorePEData<W, M> livermore_pe_data;
0173     AtomicRelaxParamsData<W, M> relaxation_data;
0174 
0175     // Positron annihilation
0176     ProcessId positron_annihilation;
0177     ModelId eplusgg;
0178     EPlusGGData eplusgg_data;
0179 
0180     // Neutron elastic
0181     ProcessId neutron_elastic;
0182     ModelId chips;
0183     NeutronElasticData<W, M> chips_data;
0184 
0185     //// MEMBER FUNCTIONS ////
0186 
0187     //! Assign from another set of hardwired models
0188     template<Ownership W2, MemSpace M2>
0189     HardwiredModels& operator=(HardwiredModels<W2, M2> const& other)
0190     {
0191         // Note: don't require the other set of hardwired models to be assigned
0192         photoelectric = other.photoelectric;
0193         if (photoelectric)
0194         {
0195             // Only assign photoelectric data if that process is present
0196             photoelectric_table_thresh = other.photoelectric_table_thresh;
0197             livermore_pe = other.livermore_pe;
0198             livermore_pe_data = other.livermore_pe_data;
0199         }
0200         relaxation_data = other.relaxation_data;
0201         positron_annihilation = other.positron_annihilation;
0202         eplusgg = other.eplusgg;
0203         eplusgg_data = other.eplusgg_data;
0204 
0205         neutron_elastic = other.neutron_elastic;
0206         if (neutron_elastic)
0207         {
0208             // Only assign neutron_elastic data if that process is present
0209             chips = other.chips;
0210             chips_data = other.chips_data;
0211         }
0212 
0213         return *this;
0214     }
0215 };
0216 
0217 //---------------------------------------------------------------------------//
0218 /*!
0219  * User-configurable particle-dependent physics constants.
0220  *
0221  * These scalar quantities can have different values for electrons/positrons
0222  * and muons/hadrons. They are described in \c PhysicsParams .
0223  */
0224 struct ParticleScalars
0225 {
0226     using Energy = units::MevEnergy;
0227 
0228     // Energy loss/range options
0229     real_type min_range{};  //!< rho [len]
0230     real_type max_step_over_range{};  //!< alpha [unitless]
0231     Energy lowest_energy{};  //!< Lowest kinetic energy
0232 
0233     // Multiple scattering options
0234     bool displaced{};  //!< Whether lateral displacement is enabled
0235     real_type range_factor{};
0236     MscStepLimitAlgorithm step_limit_algorithm{MscStepLimitAlgorithm::size_};
0237 
0238     //! True if assigned
0239     explicit CELER_FUNCTION operator bool() const
0240     {
0241         return min_range > 0 && max_step_over_range > 0
0242                && lowest_energy > zero_quantity() && range_factor > 0
0243                && range_factor < 1
0244                && step_limit_algorithm != MscStepLimitAlgorithm::size_;
0245     }
0246 };
0247 
0248 //---------------------------------------------------------------------------//
0249 /*!
0250  * Scalar (no template needed) quantities used by physics.
0251  *
0252  * The user-configurable constants and multiple scattering options are
0253  * described in \c PhysicsParams .
0254  *
0255  * The \c model_to_action value corresponds to the \c ActionId for the first \c
0256  * ModelId . Additionally it implies (by construction in physics_params) the
0257  * action IDs of several other physics actions.
0258  */
0259 struct PhysicsParamsScalars
0260 {
0261     using Energy = units::MevEnergy;
0262 
0263     //! Highest number of processes for any particle type
0264     ProcessId::size_type max_particle_processes{};
0265     //! Offset to create an ActionId from a ModelId
0266     ActionId::size_type model_to_action{};
0267     //! Number of physics models
0268     ModelId::size_type num_models{};
0269 
0270     // User-configurable constants
0271     real_type min_eprime_over_e{};  //!< xi [unitless]
0272     real_type linear_loss_limit{};  //!< For scaled range calculation
0273     real_type fixed_step_limiter{};  //!< Global charged step size limit [len]
0274 
0275     // User-configurable multiple scattering options
0276     real_type lambda_limit{};  //!< lambda limit
0277     real_type safety_factor{};  //!< safety factor
0278 
0279     // Particle-dependent user-configurable constants
0280     ParticleScalars light;
0281     ParticleScalars heavy;
0282 
0283     real_type secondary_stack_factor = 3;  //!< Secondary storage per state
0284                                            //!< size
0285     // When fixed step limiter is used, this is the corresponding action ID
0286     ActionId fixed_step_action{};
0287 
0288     //! True if assigned
0289     explicit CELER_FUNCTION operator bool() const
0290     {
0291         return max_particle_processes > 0 && model_to_action >= 4
0292                && num_models > 0 && min_eprime_over_e > 0
0293                && linear_loss_limit > 0 && secondary_stack_factor > 0
0294                && ((fixed_step_limiter > 0)
0295                    == static_cast<bool>(fixed_step_action))
0296                && lambda_limit > 0 && safety_factor >= 0.1 && light && heavy;
0297     }
0298 
0299     //! Stop early due to MSC limitation
0300     CELER_FORCEINLINE_FUNCTION ActionId msc_action() const
0301     {
0302         return ActionId{model_to_action - 4};
0303     }
0304 
0305     //! Stop early due to range limitation
0306     CELER_FORCEINLINE_FUNCTION ActionId range_action() const
0307     {
0308         return ActionId{model_to_action - 3};
0309     }
0310 
0311     //! Undergo a discrete interaction
0312     CELER_FORCEINLINE_FUNCTION ActionId discrete_action() const
0313     {
0314         return ActionId{model_to_action - 2};
0315     }
0316 
0317     //! Indicate a discrete interaction was rejected by the integral method
0318     CELER_FORCEINLINE_FUNCTION ActionId integral_rejection_action() const
0319     {
0320         return ActionId{model_to_action - 1};
0321     }
0322 
0323     //! Indicate an interaction failed to allocate memory
0324     CELER_FORCEINLINE_FUNCTION ActionId failure_action() const
0325     {
0326         return ActionId{model_to_action + num_models};
0327     }
0328 };
0329 
0330 //---------------------------------------------------------------------------//
0331 /*!
0332  * Persistent shared physics data.
0333  *
0334  * This includes macroscopic cross section tables ordered by
0335  * [particle][process][material][energy] and process-integrated energy loss and
0336  * range tables ordered by [particle][material][energy].
0337  *
0338  * So the first applicable process (ProcessId{0}) for an arbitrary particle
0339  * (ParticleId{1}) in material 2 (PhysMatId{2}) will have the following
0340  * ID and cross section grid: \code
0341    ProcessId proc_id = params.particle[1].processes[0];
0342    const UniformGridData& grid
0343        = params.particle[1].macro_xs[0].material[2].log_energy;
0344  * \endcode
0345  *
0346  * \todo Energy loss, range, and model xs grids should use \c UniformGridRecord
0347  * instead of \c XsGridRecord because they have no scaled values.
0348  */
0349 template<Ownership W, MemSpace M>
0350 struct PhysicsParamsData
0351 {
0352     //// TYPES ////
0353 
0354     template<class T>
0355     using Items = Collection<T, W, M>;
0356     template<class T>
0357     using ParticleItems = Collection<T, W, M, ParticleId>;
0358     template<class T>
0359     using ParticleModelItems = Collection<T, W, M, ParticleModelId>;
0360 
0361     //// DATA ////
0362 
0363     // Backend storage
0364     Items<real_type> reals;
0365     Items<ParticleModelId> pmodel_ids;
0366     Items<ValueGrid> value_grids;
0367     Items<ValueGridId> value_grid_ids;
0368     Items<ProcessId> process_ids;
0369     Items<ValueTable> value_tables;
0370     Items<ValueTableId> value_table_ids;
0371     Items<IntegralXsProcess> integral_xs;
0372     Items<ModelGroup> model_groups;
0373     ParticleItems<ProcessGroup> process_groups;
0374     ParticleModelItems<ModelId> model_ids;
0375     ParticleModelItems<ModelXsTable> model_xs;
0376 
0377     // Special data
0378     HardwiredModels<W, M> hardwired;
0379 
0380     // Non-templated data
0381     PhysicsParamsScalars scalars;
0382 
0383     //// METHODS ////
0384 
0385     //! True if assigned
0386     explicit CELER_FUNCTION operator bool() const
0387     {
0388         return !process_groups.empty() && !model_ids.empty() && scalars;
0389     }
0390 
0391     //! Assign from another set of data
0392     template<Ownership W2, MemSpace M2>
0393     PhysicsParamsData& operator=(PhysicsParamsData<W2, M2> const& other)
0394     {
0395         CELER_EXPECT(other);
0396 
0397         reals = other.reals;
0398         pmodel_ids = other.pmodel_ids;
0399         value_grids = other.value_grids;
0400         value_grid_ids = other.value_grid_ids;
0401         process_ids = other.process_ids;
0402         value_tables = other.value_tables;
0403         value_table_ids = other.value_table_ids;
0404         integral_xs = other.integral_xs;
0405         model_groups = other.model_groups;
0406         process_groups = other.process_groups;
0407         model_ids = other.model_ids;
0408         model_xs = other.model_xs;
0409 
0410         hardwired = other.hardwired;
0411 
0412         scalars = other.scalars;
0413 
0414         return *this;
0415     }
0416 };
0417 
0418 //---------------------------------------------------------------------------//
0419 // STATE
0420 //---------------------------------------------------------------------------//
0421 /*!
0422  * Physics state data for a single track.
0423  *
0424  * State that's persistent across steps:
0425  * - Remaining number of mean free paths to the next discrete interaction
0426  *
0427  * State that is reset at every step:
0428  * - Current macroscopic cross section
0429  * - Within-step energy deposition
0430  * - Within-step energy loss range
0431  * - Secondaries emitted from an interaction
0432  * - Discrete process element selection
0433  */
0434 struct PhysicsTrackState
0435 {
0436     real_type interaction_mfp;  //!< Remaining MFP to interaction
0437 
0438     // TEMPORARY STATE
0439     real_type macro_xs;  //!< Total cross section for discrete interactions
0440     real_type energy_deposition;  //!< Local energy deposition in a step [MeV]
0441     real_type dedx_range;  //!< Local energy loss range [len]
0442     MscRange msc_range;  //!< Range properties for multiple scattering
0443     Span<Secondary> secondaries;  //!< Emitted secondaries
0444     ElementComponentId element;  //!< Element sampled for interaction
0445 };
0446 
0447 //---------------------------------------------------------------------------//
0448 /*!
0449  * Initialize a physics track state.
0450  *
0451  * Currently no data is required at initialization -- it all must be evaluated
0452  * by the physics kernels itself.
0453  */
0454 struct PhysicsTrackInitializer
0455 {
0456 };
0457 
0458 //---------------------------------------------------------------------------//
0459 /*!
0460  * Dynamic physics (models, processes) state data.
0461  *
0462  * The "xs scratch space" is a 2D array of reals, indexed with
0463  * [track_id][el_component_id], where the fast-moving dimension has the
0464  * greatest number of element components of any material in the problem. This
0465  * can be used for the physics to calculate microscopic cross sections.
0466  */
0467 template<Ownership W, MemSpace M>
0468 struct PhysicsStateData
0469 {
0470     //// TYPES ////
0471 
0472     template<class T>
0473     using StateItems = celeritas::StateCollection<T, W, M>;
0474     template<class T>
0475     using Items = celeritas::Collection<T, W, M>;
0476 
0477     //// DATA ////
0478 
0479     StateItems<PhysicsTrackState> state;  //!< Track state [track]
0480     StateItems<MscStep> msc_step;  //!< Internal MSC data [track]
0481 
0482     Items<real_type> per_process_xs;  //!< XS [track][particle process]
0483 
0484     AtomicRelaxStateData<W, M> relaxation;  //!< Scratch data
0485     StackAllocatorData<Secondary, W, M> secondaries;  //!< Secondary stack
0486 
0487     //// METHODS ////
0488 
0489     //! True if assigned
0490     explicit CELER_FUNCTION operator bool() const
0491     {
0492         return !state.empty() && secondaries;
0493     }
0494 
0495     //! State size
0496     CELER_FUNCTION size_type size() const { return state.size(); }
0497 
0498     //! Assign from another set of states
0499     template<Ownership W2, MemSpace M2>
0500     PhysicsStateData& operator=(PhysicsStateData<W2, M2>& other)
0501     {
0502         CELER_EXPECT(other);
0503         state = other.state;
0504         msc_step = other.msc_step;
0505 
0506         per_process_xs = other.per_process_xs;
0507 
0508         relaxation = other.relaxation;
0509         secondaries = other.secondaries;
0510 
0511         return *this;
0512     }
0513 };
0514 
0515 //---------------------------------------------------------------------------//
0516 /*!
0517  * Resize the state in host code.
0518  */
0519 template<MemSpace M>
0520 inline void resize(PhysicsStateData<Ownership::value, M>* state,
0521                    HostCRef<PhysicsParamsData> const& params,
0522                    size_type size)
0523 {
0524     CELER_EXPECT(size > 0);
0525     CELER_EXPECT(params.scalars.max_particle_processes > 0);
0526     resize(&state->state, size);
0527     resize(&state->msc_step, size);
0528     resize(&state->per_process_xs,
0529            size * params.scalars.max_particle_processes);
0530     resize(&state->relaxation, params.hardwired.relaxation_data, size);
0531     resize(
0532         &state->secondaries,
0533         static_cast<size_type>(size * params.scalars.secondary_stack_factor));
0534 }
0535 
0536 //---------------------------------------------------------------------------//
0537 }  // namespace celeritas