Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-09 08:40:06

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/PhysicsTrackView.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include "corecel/Config.hh"
0010 
0011 #include "corecel/Assert.hh"
0012 #include "corecel/Macros.hh"
0013 #include "corecel/Types.hh"
0014 #include "celeritas/Quantities.hh"
0015 #include "celeritas/Types.hh"
0016 #include "celeritas/em/xs/EPlusGGMacroXsCalculator.hh"
0017 #include "celeritas/em/xs/LivermorePEMicroXsCalculator.hh"
0018 #include "celeritas/grid/GridIdFinder.hh"
0019 #include "celeritas/grid/XsCalculator.hh"
0020 #include "celeritas/mat/MaterialView.hh"
0021 #include "celeritas/neutron/xs/NeutronElasticMicroXsCalculator.hh"
0022 #include "celeritas/random/TabulatedElementSelector.hh"
0023 
0024 #include "MacroXsCalculator.hh"
0025 #include "ParticleTrackView.hh"
0026 #include "PhysicsData.hh"
0027 
0028 namespace celeritas
0029 {
0030 //---------------------------------------------------------------------------//
0031 /*!
0032  * Physics data for a track.
0033  *
0034  * The physics track view provides an interface for data and operations
0035  * common to most processes and models.
0036  */
0037 class PhysicsTrackView
0038 {
0039   public:
0040     //!@{
0041     //! \name Type aliases
0042     using Initializer_t = PhysicsTrackInitializer;
0043     using PhysicsParamsRef = NativeCRef<PhysicsParamsData>;
0044     using PhysicsStateRef = NativeRef<PhysicsStateData>;
0045     using Energy = units::MevEnergy;
0046     using ModelFinder = GridIdFinder<Energy, ParticleModelId>;
0047     using UniformTableId = OpaqueId<UniformTable>;
0048     //!@}
0049 
0050   public:
0051     // Construct from params, states, and per-state IDs
0052     inline CELER_FUNCTION PhysicsTrackView(PhysicsParamsRef const& params,
0053                                            PhysicsStateRef const& states,
0054                                            ParticleTrackView const& particle,
0055                                            PhysMatId material,
0056                                            TrackSlotId tid);
0057 
0058     // Initialize the track view
0059     inline CELER_FUNCTION PhysicsTrackView& operator=(Initializer_t const&);
0060 
0061     // Set the remaining MFP to interaction
0062     inline CELER_FUNCTION void interaction_mfp(real_type);
0063 
0064     // Reset the remaining MFP to interaction
0065     inline CELER_FUNCTION void reset_interaction_mfp();
0066 
0067     // Set the energy loss range for the current material and particle energy
0068     inline CELER_FUNCTION void dedx_range(real_type);
0069 
0070     // Set the range properties for multiple scattering
0071     inline CELER_FUNCTION void msc_range(MscRange const&);
0072 
0073     //// DYNAMIC PROPERTIES (pure accessors, free) ////
0074 
0075     // Whether the remaining MFP has been calculated
0076     CELER_FORCEINLINE_FUNCTION bool has_interaction_mfp() const;
0077 
0078     // Remaining MFP to interaction [1]
0079     CELER_FORCEINLINE_FUNCTION real_type interaction_mfp() const;
0080 
0081     // Energy loss range for the current material and particle energy
0082     CELER_FORCEINLINE_FUNCTION real_type dedx_range() const;
0083 
0084     // Range properties for multiple scattering
0085     CELER_FORCEINLINE_FUNCTION MscRange const& msc_range() const;
0086 
0087     // Current material identifier
0088     CELER_FORCEINLINE_FUNCTION PhysMatId material_id() const;
0089 
0090     //// PROCESSES (depend on particle type and possibly material) ////
0091 
0092     // Number of processes that apply to this track
0093     inline CELER_FUNCTION ParticleProcessId::size_type
0094     num_particle_processes() const;
0095 
0096     // Process ID for the given within-particle process index
0097     inline CELER_FUNCTION ProcessId process(ParticleProcessId) const;
0098 
0099     // Get macro xs table, null if not present for this particle/material
0100     inline CELER_FUNCTION XsGridId macro_xs_grid(ParticleProcessId) const;
0101 
0102     // Get energy loss table, null if not present for this particle/material
0103     inline CELER_FUNCTION UniformGridId energy_loss_grid() const;
0104 
0105     // Get range table, null if not present for this particle/material
0106     inline CELER_FUNCTION UniformGridId range_grid() const;
0107 
0108     // Get inverse range table, null if not present
0109     inline CELER_FUNCTION UniformGridId inverse_range_grid() const;
0110 
0111     // Get data for processes that use the integral approach
0112     inline CELER_FUNCTION IntegralXsProcess const&
0113     integral_xs_process(ParticleProcessId ppid) const;
0114 
0115     // Calculate macroscopic cross section for the process
0116     inline CELER_FUNCTION real_type calc_xs(ParticleProcessId ppid,
0117                                             MaterialView const& material,
0118                                             Energy energy) const;
0119 
0120     // Estimate maximum macroscopic cross section for the process over the step
0121     inline CELER_FUNCTION real_type calc_max_xs(IntegralXsProcess const& process,
0122                                                 ParticleProcessId ppid,
0123                                                 MaterialView const& material,
0124                                                 Energy energy) const;
0125 
0126     // Models that apply to the given process ID
0127     inline CELER_FUNCTION
0128         ModelFinder make_model_finder(ParticleProcessId) const;
0129 
0130     // Return CDF table data for the given particle/model/material
0131     inline CELER_FUNCTION UniformTableId cdf_table(ParticleModelId) const;
0132 
0133     // Construct an element selector
0134     inline CELER_FUNCTION
0135         TabulatedElementSelector make_element_selector(UniformTableId,
0136                                                        Energy) const;
0137 
0138     // ID of the particle's at-rest process
0139     inline CELER_FUNCTION ParticleProcessId at_rest_process() const;
0140 
0141     //// PARAMETER DATA ////
0142 
0143     // Convert an action to a model ID for diagnostics, empty if not a model
0144     inline CELER_FUNCTION ModelId action_to_model(ActionId) const;
0145 
0146     // Convert a selected model ID into a simulation action ID
0147     inline CELER_FUNCTION ActionId model_to_action(ModelId) const;
0148 
0149     // Get the model ID corresponding to the given ParticleModelId
0150     inline CELER_FUNCTION ModelId model_id(ParticleModelId) const;
0151 
0152     // Calculate scaled step range
0153     inline CELER_FUNCTION real_type range_to_step(real_type range) const;
0154 
0155     // Access scalar properties
0156     CELER_FORCEINLINE_FUNCTION PhysicsParamsScalars const& scalars() const;
0157 
0158     // Access particle-dependent scalar properties
0159     CELER_FORCEINLINE_FUNCTION ParticleScalars const& particle_scalars() const;
0160 
0161     // Number of particle types
0162     inline CELER_FUNCTION size_type num_particles() const;
0163 
0164     // Construct a grid calculator from a physics table
0165     template<class T>
0166     inline CELER_FUNCTION T make_calculator(UniformGridId) const;
0167 
0168     //// HACKS ////
0169 
0170     // Get hardwired model, null if not present
0171     inline CELER_FUNCTION ModelId hardwired_model(ParticleProcessId ppid,
0172                                                   Energy energy) const;
0173 
0174   private:
0175     PhysicsParamsRef const& params_;
0176     PhysicsStateRef const& states_;
0177     ParticleId const particle_;
0178     PhysMatId const material_;
0179     TrackSlotId const track_slot_;
0180     bool is_heavy_;
0181 
0182     //// IMPLEMENTATION HELPER FUNCTIONS ////
0183 
0184     CELER_FORCEINLINE_FUNCTION PhysicsTrackState& state();
0185     CELER_FORCEINLINE_FUNCTION PhysicsTrackState const& state() const;
0186     CELER_FORCEINLINE_FUNCTION ProcessGroup const& process_group() const;
0187     inline CELER_FUNCTION UniformGridId uniform_grid(UniformTable const&) const;
0188 };
0189 
0190 //---------------------------------------------------------------------------//
0191 // INLINE DEFINITIONS
0192 //---------------------------------------------------------------------------//
0193 /*!
0194  * Construct from shared and state data.
0195  *
0196  * Particle and material IDs are derived from other class states.
0197  */
0198 CELER_FUNCTION
0199 PhysicsTrackView::PhysicsTrackView(PhysicsParamsRef const& params,
0200                                    PhysicsStateRef const& states,
0201                                    ParticleTrackView const& particle,
0202                                    PhysMatId mid,
0203                                    TrackSlotId tid)
0204     : params_(params)
0205     , states_(states)
0206     , particle_(particle.particle_id())
0207     , material_(mid)
0208     , track_slot_(tid)
0209     , is_heavy_(particle.is_heavy())
0210 {
0211     CELER_EXPECT(track_slot_);
0212 }
0213 
0214 //---------------------------------------------------------------------------//
0215 /*!
0216  * Initialize the track view.
0217  */
0218 CELER_FUNCTION PhysicsTrackView&
0219 PhysicsTrackView::operator=(Initializer_t const&)
0220 {
0221     this->state().interaction_mfp = 0;
0222     this->state().msc_range = {};
0223     return *this;
0224 }
0225 
0226 //---------------------------------------------------------------------------//
0227 /*!
0228  * Set the distance to the next interaction, in mean free paths.
0229  *
0230  * This value will be decremented at each step.
0231  */
0232 CELER_FUNCTION void PhysicsTrackView::interaction_mfp(real_type mfp)
0233 {
0234     CELER_EXPECT(mfp > 0);
0235     this->state().interaction_mfp = mfp;
0236 }
0237 
0238 //---------------------------------------------------------------------------//
0239 /*!
0240  * Set the distance to the next interaction, in mean free paths.
0241  *
0242  * This value will be decremented at each step.
0243  */
0244 CELER_FUNCTION void PhysicsTrackView::reset_interaction_mfp()
0245 {
0246     this->state().interaction_mfp = 0;
0247 }
0248 
0249 //---------------------------------------------------------------------------//
0250 /*!
0251  * Set the energy loss range for the current material and particle energy.
0252  *
0253  * This value will be calculated once at the beginning of each step.
0254  */
0255 CELER_FUNCTION void PhysicsTrackView::dedx_range(real_type range)
0256 {
0257     CELER_EXPECT(range > 0);
0258     this->state().dedx_range = range;
0259 }
0260 
0261 //---------------------------------------------------------------------------//
0262 /*!
0263  * Set the range properties for multiple scattering.
0264  *
0265  * These values will be calculated at the first step in every tracking volume.
0266  */
0267 CELER_FUNCTION void PhysicsTrackView::msc_range(MscRange const& msc_range)
0268 {
0269     this->state().msc_range = msc_range;
0270 }
0271 
0272 //---------------------------------------------------------------------------//
0273 /*!
0274  * Current material identifier.
0275  */
0276 CELER_FUNCTION PhysMatId PhysicsTrackView::material_id() const
0277 {
0278     return material_;
0279 }
0280 
0281 //---------------------------------------------------------------------------//
0282 /*!
0283  * Whether the remaining MFP has been calculated.
0284  */
0285 CELER_FUNCTION bool PhysicsTrackView::has_interaction_mfp() const
0286 {
0287     return this->state().interaction_mfp > 0;
0288 }
0289 
0290 //---------------------------------------------------------------------------//
0291 /*!
0292  * Remaining MFP to interaction.
0293  */
0294 CELER_FUNCTION real_type PhysicsTrackView::interaction_mfp() const
0295 {
0296     real_type mfp = this->state().interaction_mfp;
0297     CELER_ENSURE(mfp >= 0);
0298     return mfp;
0299 }
0300 
0301 //---------------------------------------------------------------------------//
0302 /*!
0303  * Energy loss range.
0304  */
0305 CELER_FUNCTION real_type PhysicsTrackView::dedx_range() const
0306 {
0307     real_type range = this->state().dedx_range;
0308     CELER_ENSURE(range > 0);
0309     return range;
0310 }
0311 
0312 //---------------------------------------------------------------------------//
0313 /*!
0314  * Persistent range properties for multiple scattering within a same volume.
0315  */
0316 CELER_FUNCTION MscRange const& PhysicsTrackView::msc_range() const
0317 {
0318     return this->state().msc_range;
0319 }
0320 
0321 //---------------------------------------------------------------------------//
0322 /*!
0323  * Number of processes that apply to this track.
0324  */
0325 CELER_FUNCTION ParticleProcessId::size_type
0326 PhysicsTrackView::num_particle_processes() const
0327 {
0328     return this->process_group().size();
0329 }
0330 
0331 //---------------------------------------------------------------------------//
0332 /*!
0333  * Process ID for the given within-particle process index.
0334  */
0335 CELER_FUNCTION ProcessId PhysicsTrackView::process(ParticleProcessId ppid) const
0336 {
0337     CELER_EXPECT(ppid < this->num_particle_processes());
0338     return params_.process_ids[this->process_group().processes[ppid.get()]];
0339 }
0340 
0341 //---------------------------------------------------------------------------//
0342 /*!
0343  * Return macro xs value grid data for the given process if available.
0344  */
0345 CELER_FUNCTION XsGridId
0346 PhysicsTrackView::macro_xs_grid(ParticleProcessId ppid) const
0347 {
0348     CELER_EXPECT(ppid < this->num_particle_processes());
0349     auto table_id = this->process_group().macro_xs[ppid.get()];
0350     CELER_ASSERT(table_id);
0351     auto const& table = params_.xs_tables[table_id];
0352     if (!table)
0353     {
0354         // No table for this process
0355         return {};
0356     }
0357     CELER_ASSERT(material_ < table.grids.size());
0358     auto grid_id = table.grids[material_.get()];
0359     if (!grid_id)
0360     {
0361         // No table for this particular material
0362         return {};
0363     }
0364     return params_.xs_grid_ids[grid_id];
0365 }
0366 
0367 //---------------------------------------------------------------------------//
0368 /*!
0369  * Return the energy loss grid data if available.
0370  */
0371 CELER_FUNCTION UniformGridId PhysicsTrackView::energy_loss_grid() const
0372 {
0373     return this->uniform_grid(this->process_group().energy_loss);
0374 }
0375 
0376 //---------------------------------------------------------------------------//
0377 /*!
0378  * Return the range grid data if available.
0379  */
0380 CELER_FUNCTION UniformGridId PhysicsTrackView::range_grid() const
0381 {
0382     return this->uniform_grid(this->process_group().range);
0383 }
0384 
0385 //---------------------------------------------------------------------------//
0386 /*!
0387  * Return the inverse range grid data if available.
0388  *
0389  * If spline interpolation is used, the inverse grid is explicitly stored with
0390  * the derivatives calculated using the range as the x values and the energy as
0391  * the y values.
0392  *
0393  * The grid and values are identical to the range grid (i.e., not inverted)
0394  * even if the inverse grid is explicitly stored: the inversion is done in the
0395  * \c InverseRangeCalculator .
0396  */
0397 CELER_FUNCTION UniformGridId PhysicsTrackView::inverse_range_grid() const
0398 {
0399     if (auto const& grid
0400         = this->uniform_grid(this->process_group().inverse_range))
0401     {
0402         return grid;
0403     }
0404     return this->range_grid();
0405 }
0406 
0407 //---------------------------------------------------------------------------//
0408 /*!
0409  * Get data for processes that use the integral approach.
0410  *
0411  * Particles that have energy loss processes will have a different energy at
0412  * the pre- and post-step points. This means the assumption that the cross
0413  * section is constant along the step is no longer valid. Instead, Monte Carlo
0414  * integration can be used to sample the interaction length for the discrete
0415  * process with the correct probability from the exact distribution,
0416  * \f[
0417      p = 1 - \exp \left( -\int_{E_0}^{E_1} n \sigma(E) \dif s \right),
0418  * \f]
0419  * where \f$ E_0 \f$ is the pre-step energy, \f$ E_1 \f$ is the post-step
0420  * energy, \em n is the atom density, and \em s is the interaction length.
0421  *
0422  * At the start of the step, the maximum value of the cross section over the
0423  * step \f$ \sigma_{max} \f$ is estimated and used as the macroscopic cross
0424  * section for the process rather than \f$ \sigma_{E_0} \f$. After the step,
0425  * the new value of the cross section \f$ \sigma(E_1) \f$ is calculated, and
0426  * the discrete interaction for the process occurs with probability
0427  * \f[
0428      p = \frac{\sigma(E_1)}{\sigma_{\max}}.
0429  * \f]
0430  *
0431  * See section 7.4 of the Geant4 Physics Reference (release 10.6) for details.
0432  */
0433 CELER_FUNCTION auto
0434 PhysicsTrackView::integral_xs_process(ParticleProcessId ppid) const
0435     -> IntegralXsProcess const&
0436 {
0437     CELER_EXPECT(ppid < this->num_particle_processes());
0438     return params_.integral_xs[this->process_group().integral_xs[ppid.get()]];
0439 }
0440 
0441 //---------------------------------------------------------------------------//
0442 /*!
0443  * Calculate macroscopic cross section for the process.
0444  */
0445 CELER_FUNCTION real_type PhysicsTrackView::calc_xs(ParticleProcessId ppid,
0446                                                    MaterialView const& material,
0447                                                    Energy energy) const
0448 {
0449     real_type result = 0;
0450 
0451     if (auto model_id = this->hardwired_model(ppid, energy))
0452     {
0453         // Calculate macroscopic cross section on the fly for special
0454         // hardwired processes.
0455         if (model_id == params_.hardwired.ids.livermore_pe)
0456         {
0457             result = MacroXsCalculator<LivermorePEMicroXsCalculator>(
0458                 params_.hardwired.livermore_pe, material)(energy);
0459         }
0460         else if (model_id == params_.hardwired.ids.eplusgg)
0461         {
0462             result = EPlusGGMacroXsCalculator(params_.hardwired.eplusgg,
0463                                               material)(energy);
0464         }
0465         else if (model_id == params_.hardwired.ids.chips)
0466         {
0467             result = MacroXsCalculator<NeutronElasticMicroXsCalculator>(
0468                 params_.hardwired.chips, material)(energy);
0469         }
0470     }
0471     else if (auto grid_id = this->macro_xs_grid(ppid))
0472     {
0473         // Calculate cross section from the tabulated data
0474         CELER_ASSERT(grid_id < params_.xs_grids.size());
0475         result = XsCalculator(params_.xs_grids[grid_id], params_.reals)(energy);
0476     }
0477 
0478     CELER_ENSURE(result >= 0);
0479     return result;
0480 }
0481 
0482 //---------------------------------------------------------------------------//
0483 /*!
0484  * Estimate maximum macroscopic cross section for the process over the step.
0485  *
0486  * If this is a particle with an energy loss process, this returns the
0487  * estimate of the maximum cross section over the step. If the energy of the
0488  * global maximum of the cross section (calculated at initialization) is in the
0489  * interval \f$ [\xi E_0, E_0) \f$, where \f$ E_0 \f$ is the pre-step energy
0490  * and \f$ \xi \f$ is \c min_eprime_over_e (defined by default as \f$ \xi = 1 -
0491  * \alpha \f$, where \f$ \alpha \f$ is \c max_step_over_range), \f$
0492  * \sigma_{\max} \f$ is set to the global maximum.  Otherwise, \f$
0493  * \sigma_{\max} = \max( \sigma(E_0), \sigma(\xi E_0) ) \f$. If the cross
0494  * section is not monotonic in the interval \f$ [\xi E_0, E_0) \f$ and the
0495  * interval does not contain the global maximum, the post-step cross section
0496  * \f$ \sigma(E_1) \f$ may be larger than \f$ \sigma_{\max} \f$.
0497  */
0498 CELER_FUNCTION real_type
0499 PhysicsTrackView::calc_max_xs(IntegralXsProcess const& process,
0500                               ParticleProcessId ppid,
0501                               MaterialView const& material,
0502                               Energy energy) const
0503 {
0504     CELER_EXPECT(process);
0505     CELER_EXPECT(material_ < process.energy_max_xs.size());
0506 
0507     real_type energy_max_xs
0508         = params_.reals[process.energy_max_xs[material_.get()]];
0509     real_type energy_xi = energy.value() * params_.scalars.min_eprime_over_e;
0510     if (energy_max_xs >= energy_xi && energy_max_xs < energy.value())
0511     {
0512         return this->calc_xs(ppid, material, Energy{energy_max_xs});
0513     }
0514     return max(this->calc_xs(ppid, material, energy),
0515                this->calc_xs(ppid, material, Energy{energy_xi}));
0516 }
0517 
0518 //---------------------------------------------------------------------------//
0519 /*!
0520  * Get a hardwired model for on-the-fly cross section calculation.
0521  *
0522  * This returns the model ID that applies to the given process ID and energy if
0523  * the process is hardwired to calculate macroscopic cross sections on the fly.
0524  * If the result is null, tables should be used for this process/energy.
0525  */
0526 CELER_FUNCTION ModelId PhysicsTrackView::hardwired_model(ParticleProcessId ppid,
0527                                                          Energy energy) const
0528 {
0529     ProcessId process = this->process(ppid);
0530     if ((process == params_.hardwired.ids.photoelectric
0531          && energy < LivermoreElement::tabulated_threshold())
0532         || (process == params_.hardwired.ids.annihilation)
0533         || (process == params_.hardwired.ids.neutron_elastic))
0534     {
0535         auto find_model = this->make_model_finder(ppid);
0536         return this->model_id(find_model(energy));
0537     }
0538     // Not a hardwired process
0539     return {};
0540 }
0541 
0542 //---------------------------------------------------------------------------//
0543 /*!
0544  * Models that apply to the given process ID.
0545  */
0546 CELER_FUNCTION auto
0547 PhysicsTrackView::make_model_finder(ParticleProcessId ppid) const
0548     -> ModelFinder
0549 {
0550     CELER_EXPECT(ppid < this->num_particle_processes());
0551     ModelGroup const& md
0552         = params_.model_groups[this->process_group().models[ppid.get()]];
0553     return ModelFinder(params_.reals[md.energy], params_.pmodel_ids[md.model]);
0554 }
0555 
0556 //---------------------------------------------------------------------------//
0557 /*!
0558  * Return value table data for the given particle/model/material.
0559  *
0560  * A null result means either the model is material independent or the material
0561  * only has one element, so no cross section CDF tables are stored.
0562  */
0563 CELER_FUNCTION
0564 auto PhysicsTrackView::cdf_table(ParticleModelId pmid) const -> UniformTableId
0565 {
0566     CELER_EXPECT(pmid < params_.model_cdf.size());
0567 
0568     // Get the CDF table for the given particle and model
0569     ModelCdfTable const& model_cdf = params_.model_cdf[pmid];
0570     if (!model_cdf)
0571     {
0572         // No tables stored for this model
0573         return {};
0574     }
0575 
0576     // Get the value table ID for the current material
0577     CELER_ASSERT(material_ < model_cdf.tables.size());
0578     auto table_id = model_cdf.tables[material_.get()];
0579     CELER_ASSERT(table_id < params_.uniform_tables.size());
0580     if (!params_.uniform_tables[table_id])
0581     {
0582         // No tables stored for this material
0583         return {};
0584     }
0585     return table_id;
0586 }
0587 
0588 //---------------------------------------------------------------------------//
0589 /*!
0590  * Construct an element selector to sample an element from tabulated xs data.
0591  */
0592 CELER_FUNCTION
0593 TabulatedElementSelector
0594 PhysicsTrackView::make_element_selector(UniformTableId table_id,
0595                                         Energy energy) const
0596 {
0597     CELER_EXPECT(table_id < params_.uniform_tables.size());
0598     auto const& table = params_.uniform_tables[table_id];
0599     return TabulatedElementSelector{table,
0600                                     params_.uniform_grids,
0601                                     params_.uniform_grid_ids,
0602                                     params_.reals,
0603                                     energy};
0604 }
0605 
0606 //---------------------------------------------------------------------------//
0607 /*!
0608  * ID of the particle's at-rest process.
0609  *
0610  * If the particle can have a discrete interaction at rest, this returns the \c
0611  * ParticleProcessId of that process. Otherwise, it returns an invalid ID.
0612  */
0613 CELER_FUNCTION ParticleProcessId PhysicsTrackView::at_rest_process() const
0614 {
0615     return this->process_group().at_rest;
0616 }
0617 
0618 //---------------------------------------------------------------------------//
0619 /*!
0620  * Convert an action to a model ID for diagnostics, false if not a model.
0621  */
0622 CELER_FUNCTION ModelId PhysicsTrackView::action_to_model(ActionId action) const
0623 {
0624     if (!action)
0625         return ModelId{};
0626 
0627     // Rely on unsigned rollover if action ID is less than the first model
0628     ModelId::size_type result = action.unchecked_get()
0629                                 - params_.scalars.model_to_action;
0630     if (result >= params_.scalars.num_models)
0631         return ModelId{};
0632 
0633     return ModelId{result};
0634 }
0635 
0636 //---------------------------------------------------------------------------//
0637 /*!
0638  * Convert a selected model ID into a simulation action ID.
0639  */
0640 CELER_FUNCTION ActionId PhysicsTrackView::model_to_action(ModelId model) const
0641 {
0642     CELER_ASSERT(model < params_.scalars.num_models);
0643     return ActionId{model.unchecked_get() + params_.scalars.model_to_action};
0644 }
0645 
0646 //---------------------------------------------------------------------------//
0647 /*!
0648  * Get the model ID corresponding to the given ParticleModelId.
0649  */
0650 CELER_FUNCTION ModelId PhysicsTrackView::model_id(ParticleModelId pmid) const
0651 {
0652     CELER_EXPECT(pmid < params_.model_ids.size());
0653     return params_.model_ids[pmid];
0654 }
0655 
0656 //---------------------------------------------------------------------------//
0657 /*!
0658  * Calculate scaled step range.
0659  *
0660  * This is the updated step function given by Eq. 7.4 of Geant4 Physics
0661  * Reference Manual, Release 10.6: \f[
0662    s = \alpha r + \rho (1 - \alpha) (2 - \frac{\rho}{r})
0663  \f]
0664  * where alpha is \c max_step_over_range and rho is \c min_range .
0665  *
0666  * Below \c min_range, no step scaling is applied, but the step can still
0667  * be arbitrarily small.
0668  */
0669 CELER_FUNCTION real_type PhysicsTrackView::range_to_step(real_type range) const
0670 {
0671     CELER_ASSERT(range >= 0);
0672     auto const& scalars = this->particle_scalars();
0673     real_type const rho = scalars.min_range;
0674     if (range < rho * (1 + celeritas::sqrt_tol()))
0675     {
0676         // Small range returns the step. The fudge factor avoids floating point
0677         // error in the interpolation below while preserving the near-linear
0678         // behavior for range = rho + epsilon.
0679         return range;
0680     }
0681 
0682     real_type const alpha = scalars.max_step_over_range;
0683     real_type step = alpha * range + rho * (1 - alpha) * (2 - rho / range);
0684     CELER_ENSURE(step > 0 && step <= range);
0685     return step;
0686 }
0687 
0688 //---------------------------------------------------------------------------//
0689 /*!
0690  * Access scalar properties (options, IDs).
0691  */
0692 CELER_FORCEINLINE_FUNCTION PhysicsParamsScalars const&
0693 PhysicsTrackView::scalars() const
0694 {
0695     return params_.scalars;
0696 }
0697 
0698 //---------------------------------------------------------------------------//
0699 /*!
0700  * Access particle-dependent scalar properties.
0701  *
0702  * These properties are different for light particles (electrons/positrons) and
0703  * heavy particles (muons/hadrons).
0704  */
0705 CELER_FORCEINLINE_FUNCTION ParticleScalars const&
0706 PhysicsTrackView::particle_scalars() const
0707 {
0708     if (is_heavy_)
0709     {
0710         return params_.scalars.heavy;
0711     }
0712     return params_.scalars.light;
0713 }
0714 
0715 //---------------------------------------------------------------------------//
0716 /*!
0717  * Number of particle types.
0718  */
0719 CELER_FUNCTION size_type PhysicsTrackView::num_particles() const
0720 {
0721     return params_.process_groups.size();
0722 }
0723 
0724 //---------------------------------------------------------------------------//
0725 /*!
0726  * Construct a grid calculator of the given type.
0727  *
0728  * The calculator must take two arguments: a reference to \c UniformGridRecord
0729  * and a reference to the backend reals storage.
0730  */
0731 template<class T>
0732 CELER_FUNCTION T PhysicsTrackView::make_calculator(UniformGridId id) const
0733 {
0734     CELER_EXPECT(id < params_.uniform_grids.size());
0735     return T{params_.uniform_grids[id], params_.reals};
0736 }
0737 
0738 //---------------------------------------------------------------------------//
0739 // IMPLEMENTATION HELPER FUNCTIONS
0740 //---------------------------------------------------------------------------//
0741 /*!
0742  * Return value grid data for the given table ID if available.
0743  *
0744  * If the result is not null, it can be used to instantiate a
0745  * grid Calculator.
0746  *
0747  * If the result is null, it's likely because the process doesn't have the
0748  * associated value (e.g. if the table type is "energy_loss" and the process is
0749  * not a slowing-down process).
0750  */
0751 CELER_FUNCTION UniformGridId
0752 PhysicsTrackView::uniform_grid(UniformTable const& table) const
0753 {
0754     if (!table)
0755     {
0756         // No table for this process
0757         return {};
0758     }
0759     CELER_ASSERT(material_ < table.grids.size());
0760     auto grid_id = table.grids[material_.get()];
0761     if (!grid_id)
0762     {
0763         // No table for this particular material
0764         return {};
0765     }
0766     return params_.uniform_grid_ids[grid_id];
0767 }
0768 
0769 //! Get the thread-local state (mutable)
0770 CELER_FUNCTION PhysicsTrackState& PhysicsTrackView::state()
0771 {
0772     return states_.state[track_slot_];
0773 }
0774 
0775 //! Get the thread-local state (const)
0776 CELER_FUNCTION PhysicsTrackState const& PhysicsTrackView::state() const
0777 {
0778     return states_.state[track_slot_];
0779 }
0780 
0781 //! Get the group of processes that apply to the particle
0782 CELER_FUNCTION ProcessGroup const& PhysicsTrackView::process_group() const
0783 {
0784     CELER_EXPECT(particle_ < params_.process_groups.size());
0785     return params_.process_groups[particle_];
0786 }
0787 
0788 //---------------------------------------------------------------------------//
0789 }  // namespace celeritas