Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 10:31:07

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/global/Stepper.hh
0006 //! \sa Stepper.test.cc
0007 //---------------------------------------------------------------------------//
0008 #pragma once
0009 
0010 #include <array>
0011 #include <memory>
0012 #include <vector>
0013 
0014 #include "corecel/Types.hh"
0015 #include "corecel/cont/Span.hh"
0016 #include "corecel/data/PinnedAllocator.hh"
0017 #include "corecel/data/StateDataStore.hh"
0018 #include "corecel/random/params/RngParamsFwd.hh"
0019 #include "corecel/sys/DeviceEvent.hh"
0020 #include "celeritas/Types.hh"
0021 #include "celeritas/geo/GeoFwd.hh"
0022 #include "celeritas/phys/Primary.hh"
0023 #include "celeritas/track/TrackInitData.hh"
0024 #include "celeritas/user/ActionTimes.hh"
0025 
0026 #include "ActionSequence.hh"
0027 #include "CoreState.hh"
0028 #include "CoreTrackData.hh"
0029 
0030 namespace celeritas
0031 {
0032 //---------------------------------------------------------------------------//
0033 class CoreParams;
0034 struct Primary;
0035 class ExtendFromPrimariesAction;
0036 
0037 //---------------------------------------------------------------------------//
0038 /*!
0039  * State-specific options for the stepper.
0040  *
0041  * - \c params : Problem definition
0042  * - \c actions : Ordered sequence of transport actions
0043  * - \c stream_id : Unique thread or task ID for this state
0044  * - \c num_track_slots : Maximum number of tracks transported in parallel
0045  *   (optional, defaults to the per-stream value from \c params)
0046  * - \c primary_capacity : Maximum primaries in either owned host buffer
0047  *   (optional, defaults to the total primary capacity in \c params divided by
0048  *   the number of streams)
0049  */
0050 struct StepperInput
0051 {
0052     std::shared_ptr<CoreParams const> params;
0053     std::shared_ptr<ActionSequence> actions;
0054     StreamId stream_id{};
0055     size_type num_track_slots{};
0056     size_type primary_capacity{};
0057 
0058     //! True if defined
0059     explicit operator bool() const { return params && actions && stream_id; }
0060 };
0061 
0062 //---------------------------------------------------------------------------//
0063 /*!
0064  * Track counters for a step.
0065  */
0066 struct StepperResult
0067 {
0068     size_type generated{};  //!< New primaries added
0069     size_type queued{};  //!< Pending track initializers at end of step
0070     size_type active{};  //!< Active tracks at start of step
0071     size_type alive{};  //!< Active and alive at end of step
0072     size_type cut{};  //!< Tracks killed by tracking cuts during step
0073     size_type errored{};  //!< Tracks killed due to errors during step
0074 
0075     //! True if more steps need to be run
0076     explicit operator bool() const { return queued > 0 || alive > 0; }
0077 };
0078 
0079 //---------------------------------------------------------------------------//
0080 /*!
0081  * Interface class for stepper classes.
0082  *
0083  * This allows higher-level classes not to care whether the stepper operates on
0084  * host or device.
0085  *
0086  * A stepper initially has no asynchronous result, so \c has_outstanding_result
0087  * returns false.  Calling \c async, with or without primaries, starts one step
0088  * and creates an outstanding result. No other step can be started until \c get
0089  * returns the result and restores the initial state. The \c ready function
0090  * queries completion without blocking, \c wait blocks until completion without
0091  * retrieving the result, and \c get waits if necessary before retrieving the
0092  * result and restoring the initial state. All three require an outstanding
0093  * result. An outstanding result does not imply that execution is still in
0094  * progress: the step may already be complete and waiting for its result to be
0095  * retrieved.
0096  *
0097  * Primary input is accumulated in a fixed-capacity producer buffer owned by
0098  * the stepper. Calling \c stage_primaries transfers that batch into the core
0099  * state and makes it available to the next call to \c async. The producer can
0100  * then accept another batch, including while a previous step has an
0101  * outstanding result. At most one unsubmitted batch can be staged. The span
0102  * overload copies its input into stepper-owned storage, so the caller's span
0103  * need not remain valid after the call returns. The span returned by \c
0104  * staged_primaries represents only the current unsubmitted batch and should
0105  * not be retained after a call that changes the staging state.
0106  *
0107  * Calling \c async submits staged primaries, if present, and otherwise
0108  * advances existing tracks without changing the producer buffer. This allows
0109  * prior transport to be drained while a later primary batch remains buffered.
0110  * Primaries can be pushed and staged while a previous result is valid, but the
0111  * next step cannot start until \c get consumes that result. Thus result
0112  * completion and primary production have independent lifecycles.
0113  *
0114  * Host steps execute synchronously and are immediately ready. The deprecated
0115  * call operators preserve synchronous behavior by calling \c async followed
0116  * by \c get.
0117  *
0118  * Before destroying a device Stepper, the caller must ensure that any
0119  * outstanding asynchronous operation has completed by calling \c wait or \c
0120  * get, and that any staged primary batch has been submitted with \c async.
0121  * Destruction does not add hidden synchronization.
0122  *
0123  * \note This interface and its implementations may be removed soon to
0124  * facilitate step gathering.
0125  */
0126 class StepperInterface
0127 {
0128   public:
0129     //!@{
0130     //! \name Type aliases
0131     using Input = StepperInput;
0132     using SpanConstPrimary = Span<Primary const>;
0133     using result_type = StepperResult;
0134     using SPState = std::shared_ptr<CoreStateInterface>;
0135     //!@}
0136 
0137   public:
0138     // Default virtual destructor
0139     virtual ~StepperInterface();
0140 
0141     // Warm up before stepping
0142     virtual void warm_up() = 0;
0143 
0144     // Start a step with existing states and any staged primary batch
0145     virtual void async() = 0;
0146 
0147     // Copy new primaries into owned storage and start a step with them
0148     virtual void async(SpanConstPrimary primaries) = 0;
0149 
0150     //! Whether an asynchronous step result can be retrieved
0151     virtual bool has_outstanding_result() const noexcept = 0;
0152 
0153     // Whether the asynchronous step has completed
0154     virtual bool ready() const = 0;
0155 
0156     // Wait for the asynchronous step to complete
0157     virtual void wait() const = 0;
0158 
0159     // Wait for and return the asynchronous step result
0160     virtual StepperResult get() = 0;
0161 
0162     //! Fixed capacity of each stepper-owned primary buffer
0163     virtual size_type primary_capacity() const noexcept = 0;
0164 
0165     //! Fixed capacity of the track initializer queue
0166     virtual size_type initializer_capacity() const noexcept = 0;
0167 
0168     //! Fixed capacity of the per-step secondary stack
0169     virtual size_type secondary_capacity() const noexcept = 0;
0170 
0171     //! Number of primaries accumulated in the producer buffer
0172     virtual size_type num_buffered_primaries() const noexcept = 0;
0173 
0174     // Add a primary to the producer buffer; a prior result may remain valid
0175     virtual void push_primary(Primary const& primary) = 0;
0176 
0177     // Stage the nonempty producer buffer for the next step
0178     virtual void stage_primaries() = 0;
0179 
0180     //! Access the unsubmitted primary batch staged for the next step
0181     virtual SpanConstPrimary staged_primaries() const noexcept = 0;
0182 
0183     //! \deprecated Transport existing states
0184     virtual StepperResult operator()() = 0;
0185 
0186     // Copy primaries into owned storage and stage them for the next step
0187     virtual void stage_primaries(SpanConstPrimary primaries) = 0;
0188 
0189     // Transport existing states and these new primaries
0190     //! \deprecated Transport existing states and these new primaries
0191     virtual StepperResult operator()(SpanConstPrimary primaries) = 0;
0192 
0193     // Kill all tracks in flight to debug "stuck" tracks
0194     virtual void kill_active() = 0;
0195 
0196     // Reseed the RNGs at the start of an event for reproducibility
0197     virtual void reseed(UniqueEventId event_id) = 0;
0198 
0199     //! Get action sequence for timing diagnostics
0200     virtual ActionSequence const& actions() const = 0;
0201 
0202     //! Get the core state interface
0203     virtual CoreStateInterface const& state() const = 0;
0204 
0205     //! Get a shared pointer to the state (TEMPORARY)
0206     virtual SPState sp_state() = 0;
0207 
0208   protected:
0209     StepperInterface() = default;
0210     CELER_DEFAULT_COPY_MOVE(StepperInterface);
0211 };
0212 
0213 //---------------------------------------------------------------------------//
0214 /*!
0215  * Manage a state vector and execute a single step on all of them.
0216  *
0217  * \note This is likely to be removed and refactored since we're changing how
0218  * primaries are created and how multithread state ownership is managed.
0219  *
0220  * Device steps have separate start and result phases. Calling \c async
0221  * enqueues the action sequence, a counter snapshot, and a completion event on
0222  * the state stream. Diagnostic action or step timing can still synchronize the
0223  * stream. Other synchronization within the action sequence is being removed
0224  * separately.
0225  *
0226  * The two primary buffers are reserved to \c primary_capacity at construction,
0227  * so accumulating and staging primaries within capacity does not reallocate.
0228  * Device buffers use pinned host memory so inserting a staged batch can enqueue
0229  * its host-to-device copy. The submitted source storage is retained until that
0230  * copy completes; same-stream ordering ensures the step actions see the copied
0231  * input. Reusing a submitted source may wait for its copy event, but does not
0232  * wait for the full step to complete.
0233  *
0234  * The following sequence overlaps production of a later primary batch with an
0235  * outstanding result:
0236  * \code
0237    Stepper<MemSpace::device> step(input);
0238 
0239    step.async(make_span(first_primaries));
0240    for (Primary primary : second_primaries)
0241    {
0242        step.push_primary(std::move(primary));
0243    }
0244    step.stage_primaries();
0245    StepperResult result = step.get();
0246    do
0247    {
0248        // Submit the staged batch while continuing any active tracks
0249        step.async();
0250        result = step.get();
0251    } while (result);
0252    \endcode
0253  *
0254  * \internal
0255  *
0256  * \par Asynchronous state
0257  *
0258  * The \c has_outstanding_result_ flag tracks whether an asynchronous step has
0259  * a result that has not yet been retrieved with \c get, whereas \c step_done_
0260  * tracks completion of device work. Their states after successful calls are:
0261  *
0262  * | Lifecycle point | \c has_outstanding_result_ | CPU \c step_done_ | GPU \c
0263  step_done_ |
0264  * | --------------- | -------------------------- | ----------------- |
0265  ----------------- |
0266  * | Construction or after \c get | false | Null | Allocated and ready |
0267  * | After \c async | true | Null and ready | Recorded; pending or ready |
0268  * | After \c ready returns false | true | Not possible | Recorded and pending |
0269  * | After \c ready is true or \c wait | true | Null/ready | Recorded/ready |
0270  *
0271  * A host step executes synchronously, so its null event is always ready. A
0272  * device event is allocated once and re-recorded after each counter snapshot.
0273  * Calling \c get first waits for completion and then clears \c
0274  * has_outstanding_result_; it does not reset or replace the event. Primary
0275  * buffering has an independent state, tracked by \c primary_phase_ and \c
0276  * primary_copy_done_. The staged storage remains internal while a copy source
0277  * is submitted, but the public \c staged_primaries accessor returns only an
0278  * unsubmitted batch:
0279  *
0280  * | Primary phase | Producer | Staged storage | Accessor | Copy event |
0281  * | ------------- | -------- | -------------- | -------- | ---------- |
0282  * | \c empty | May fill | Empty | Empty | Null/ready or allocated/ready |
0283  * | \c staged | May fill | Next input | Next input | Recorded after H2D copy |
0284  * | \c submitted | May fill | Prior copy source | Empty | Pending or ready |
0285  *
0286  * Calling \c stage_primaries changes \c empty to \c staged. Calling \c async
0287  * changes \c staged to \c submitted after the actions have been enqueued. A
0288  * later \c stage_primaries call may reclaim a submitted source after waiting
0289  * only for its copy event, then stage the producer buffer. Calling \c get also
0290  * reclaims a submitted source, but leaves a next \c staged batch unchanged.
0291  * Host copies complete synchronously, so \c primary_copy_done_ is null and
0292  * reclaiming the source does not wait.
0293  *
0294  * The expected state transitions are
0295  * \code
0296  *   no result + producer -- async() --> outstanding result + same producer
0297  *   no result + no queued input -- async(primaries) --> outstanding result
0298  *   outstanding result -- ready() or wait() --> outstanding result
0299  *   outstanding result -- get() --> no result
0300  *
0301  *   producer -- stage_primaries() --> staged
0302  *   outstanding result + producer -- stage_primaries() --> outstanding result
0303  + staged
0304  *   staged + no result -- async() --> submitted + outstanding result
0305  *   submitted + producer -- stage_primaries() --> staged
0306  * \endcode
0307  * Calling \c ready or \c wait repeatedly with an outstanding result is
0308  * allowed. The next \c async call is allowed only after \c get consumes the
0309  * previous result.  Primaries may be pushed and staged while a result is
0310  * outstanding, and the producer may begin filling again while that next batch
0311  * is staged. The staged batch cannot be submitted until the prior result is
0312  * consumed. Calls to \c warm_up, \c reset_state, and \c reseed are rejected
0313  * while an outstanding result or queued primary batch exists. Calling \c
0314  * kill_active permits buffered primaries but rejects an outstanding result or
0315  * staged batch. The synchronous call operators perform \c async followed
0316  * immediately by \c get.
0317  */
0318 template<MemSpace M>
0319 class Stepper final : public StepperInterface
0320 {
0321   public:
0322     //!@{
0323     //! \name Type aliases
0324     using StateRef = CoreStateData<Ownership::reference, M>;
0325     //!@}
0326 
0327   public:
0328     // Construct with problem parameters and setup options
0329     explicit Stepper(Input input);
0330 
0331     // Default destructor
0332     ~Stepper() final;
0333 
0334     // Warm up before stepping
0335     void warm_up() final;
0336 
0337     // Start a step with existing states and any staged primary batch
0338     void async() final;
0339 
0340     // Copy new primaries into owned storage and start a step with them
0341     void async(SpanConstPrimary primaries) final;
0342 
0343     //! Whether an asynchronous step result can be retrieved
0344     bool has_outstanding_result() const noexcept final
0345     {
0346         return has_outstanding_result_;
0347     }
0348 
0349     // Whether the asynchronous step has completed
0350     bool ready() const final;
0351 
0352     // Wait for the asynchronous step to complete
0353     void wait() const final;
0354 
0355     // Wait for and return the asynchronous step result
0356     StepperResult get() final;
0357 
0358     //! Fixed capacity of each stepper-owned primary buffer
0359     size_type primary_capacity() const noexcept final
0360     {
0361         return primary_capacity_;
0362     }
0363 
0364     // Fixed capacity of the track initializer queue
0365     size_type initializer_capacity() const noexcept final;
0366 
0367     //! Fixed capacity of the per-step secondary stack
0368     size_type secondary_capacity() const noexcept final
0369     {
0370         return state_->ref().physics.secondaries.capacity();
0371     }
0372 
0373     //! Number of primaries accumulated in the producer buffer
0374     size_type num_buffered_primaries() const noexcept final
0375     {
0376         return primary_buffer_.size();
0377     }
0378 
0379     // Add a primary to the producer buffer; a prior result may remain valid
0380     void push_primary(Primary const& primary) final;
0381 
0382     // Stage the nonempty producer buffer for the next step
0383     void stage_primaries() final;
0384 
0385     //! Access the unsubmitted primary batch staged for the next step
0386     SpanConstPrimary staged_primaries() const noexcept final;
0387 
0388     // Transport existing states
0389     StepperResult operator()() final;
0390 
0391     // Copy primaries into owned storage and stage them for the next step
0392     void stage_primaries(SpanConstPrimary primaries) final;
0393 
0394     // Transport existing states and these new primaries
0395     StepperResult operator()(SpanConstPrimary primaries) final;
0396 
0397     // Kill all tracks in flight to debug "stuck" tracks
0398     void kill_active() final;
0399 
0400     // Reseed the RNGs at the start of an event for reproducibility
0401     void reseed(UniqueEventId event_id) final;
0402 
0403     //! Get action sequence for timing diagnostics
0404     ActionSequence const& actions() const final { return *actions_; }
0405 
0406     //! Access core data, primarily for debugging
0407     StateRef const& state_ref() const { return state_->ref(); }
0408 
0409     //! Get the core state interface for diagnostic output
0410     CoreStateInterface const& state() const final { return *state_; }
0411 
0412     // Reset the core state counters and data so it can be reused
0413     void reset_state();
0414 
0415     //! Get a shared pointer to the state (TEMPORARY, DO NOT USE)
0416     SPState sp_state() final { return state_; }
0417 
0418   private:
0419     enum class PrimaryPhase
0420     {
0421         empty,
0422         staged,
0423         submitted
0424     };
0425 
0426     using VecPrimary = std::vector<Primary>;
0427     using PinnedVecPrimary = std::vector<Primary, PinnedAllocator<Primary>>;
0428     using PrimaryStorage = MemSpaceCond_t<M, VecPrimary, PinnedVecPrimary>;
0429 
0430     // Params data
0431     std::shared_ptr<CoreParams const> params_;
0432     // Call sequence
0433     std::shared_ptr<ActionSequence> actions_;
0434     // Primary initialization
0435     std::shared_ptr<ExtendFromPrimariesAction const> primaries_action_;
0436     // State data
0437     std::shared_ptr<CoreState<M>> state_;
0438     // Maximum number of primaries in each host buffer
0439     size_type primary_capacity_{};
0440     // Primaries being accumulated by the producer
0441     PrimaryStorage primary_buffer_;
0442     // Host source for a staged or submitted primary copy
0443     PrimaryStorage staged_primaries_;
0444     // Completion of the H2D copy that uses staged_primaries_ as its source
0445     DeviceEvent primary_copy_done_{nullptr};
0446     // Logical state of staged_primaries_
0447     PrimaryPhase primary_phase_{PrimaryPhase::empty};
0448     // Completion of device work and the result-counter snapshot
0449     DeviceEvent step_done_{nullptr};
0450     // Whether an asynchronous step result can be retrieved
0451     bool has_outstanding_result_{false};
0452 
0453     // Whether an operation would conflict with queued primaries
0454     bool has_queued_primaries() const noexcept;
0455 
0456     // Release a submitted primary source after its copy completes
0457     void reclaim_submitted_primaries();
0458 
0459     // Calculate an upper bound on the number of initializers required
0460     size_type calc_max_initializers() const;
0461 };
0462 
0463 //---------------------------------------------------------------------------//
0464 // EXPLICIT INSTANTIATION
0465 //---------------------------------------------------------------------------//
0466 
0467 extern template class Stepper<MemSpace::host>;
0468 extern template class Stepper<MemSpace::device>;
0469 
0470 //---------------------------------------------------------------------------//
0471 }  // namespace celeritas