Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-22 10:31:30

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