From 735cbc61538b8e1cbdaa40f434426376fd434b96 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Mon, 1 Dec 2025 11:18:53 -0500 Subject: [PATCH 01/60] Optical track offloadin inherited from LocalOffloadInterface --- src/accel/LocalOpticalTrackOffload.cc | 201 ++++++++++++++++++++++++++ src/accel/LocalOpticalTrackOffload.hh | 89 ++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 src/accel/LocalOpticalTrackOffload.cc create mode 100644 src/accel/LocalOpticalTrackOffload.hh diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc new file mode 100644 index 0000000000..2e547f9adf --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -0,0 +1,201 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/LocalOpticalTrackOffload.cc +//---------------------------------------------------------------------------// +#include "LocalOpticalTrackOffload.hh" + +#include +#include + +#include "corecel/sys/ScopedProfiling.hh" +#include "geocel/GeantUtils.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/Transporter.hh" + +#include "SetupOptions.hh" +#include "SharedParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * + */ +LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, + SharedParams& params) +{ + CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, + << "cannot create local optical track offload when " + "Celeritas " + "offloading is disabled"); + + // Check the thread ID and MT model + validate_geant_threading(params.Params()->max_streams()); + + // Save a pointer to the optical transporter + transport_ = params.optical_transporter(); + CELER_ASSERT(transport_); + + CELER_ASSERT(transport_->params()); + auto const& optical_params = *transport_->params(); + + // Number of optical tracks to buffer before offloading + auto const& capacity = options.optical->capacity; + auto_flush_ = capacity.tracks; + + auto stream_id = id_cast(get_geant_thread_id()); + + // Allocate thread-local state data + auto memspace = celeritas::device() ? MemSpace::device : MemSpace::host; + if (memspace == MemSpace::device) + { + state_ = std::make_shared>( + optical_params, stream_id, capacity.tracks); + } + else + { + state_ = std::make_shared>( + optical_params, stream_id, capacity.tracks); + } + + // Allocate auxiliary data + if (params.Params()->aux_reg()) + { + state_->aux() = std::make_shared( + *params.Params()->aux_reg(), memspace, stream_id, capacity.tracks); + } + + CELER_ENSURE(*this); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize with options and shared data. + */ +void LocalOpticalTrackOffload::Initialize(SetupOptions const& options, + SharedParams& params) +{ + *this = LocalOpticalTrackOffload(options, params); +} + +//---------------------------------------------------------------------------// +/*! + * Set the event ID and reseed the Celeritas RNG at the start of an event. + */ +void LocalOpticalTrackOffload::InitializeEvent(int id) +{ + CELER_EXPECT(*this); + CELER_EXPECT(id >= 0); + + event_id_ = id_cast(id); + + if (!(G4Threading::IsMultithreadedApplication() + && G4MTRunManager::SeedOncePerCommunication())) + { + // Since Geant4 schedules events dynamically, reseed the Celeritas RNGs + // using the Geant4 event ID for reproducibility. This guarantees that + // an event can be reproduced given the event ID. + state_->reseed(transport_->params()->rng(), id_cast(id)); + } +} + +//---------------------------------------------------------------------------// +/*! + * Buffer distribution data for generating optical photons. + */ +void LocalOpticalTrackOffload::Push(TrackData const& track) +{ + CELER_EXPECT(*this); + + ScopedProfiling profile_this{"push"}; + + buffer_.push_back(track); + pending_tracks_++; + + if (pending_tracks_ >= auto_flush_) + { + this->Flush(); + } +} +//---------------------------------------------------------------------------// +/*! + * Generate and transport optical photons from the buffered distribution data. + */ +void LocalOpticalTrackOffload::Flush() +{ + CELER_EXPECT(*this); + + if (buffer_.empty()) + { + return; + } + + ScopedProfiling profile_this("flush"); + + //! \todo Duplicated in \c LocalTransporter + if (event_manager_ || !event_id_) + { + if (CELER_UNLIKELY(!event_manager_)) + { + // Save the event manager pointer, thereby marking that + // *subsequent* events need to have their IDs checked as well + event_manager_ = G4EventManager::GetEventManager(); + CELER_ASSERT(event_manager_); + } + + G4Event const* event = event_manager_->GetConstCurrentEvent(); + CELER_ASSERT(event); + if (event_id_ != id_cast(event->GetEventID())) + { + // The event ID has changed: reseed it + this->InitializeEvent(event->GetEventID()); + } + } + CELER_ASSERT(event_id_); + + if (celeritas::device()) + { + CELER_LOG_LOCAL(debug) + << "Transporting " << pending_tracks_ + << " optical track from event " << event_id_.unchecked_get() + << " with Celeritas"; + } + + pending_tracks_ = 0; + buffer_.clear(); + + // Generate optical photons and transport to completion + (*transport_)(*state_); +} + +//---------------------------------------------------------------------------// +/*! + * Get the accumulated action times. + */ +auto LocalOpticalTrackOffload::GetActionTime() const -> MapStrDbl +{ + CELER_EXPECT(*this); + return transport_->get_action_times(*state_->aux()); +} +//---------------------------------------------------------------------------// +/*! + * Clear local data. + */ +void LocalOpticalTrackOffload::Finalize() +{ + CELER_EXPECT(*this); + + CELER_VALIDATE(buffer_.empty(), + << pending_tracks_ << " optical tracks were not flushed"); + // Reset all data + *this = {}; + + CELER_ENSURE(!*this); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh new file mode 100644 index 0000000000..57fc663bf7 --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -0,0 +1,89 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/LocalOpticalTrackOffload.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Types.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/TrackInitializer.hh" +#include "celeritas/optical/Transporter.hh" + +#include "LocalOffloadInterface.hh" + +class G4EventManager; + +namespace celeritas +{ +namespace optical +{ +class CoreStateBase; +class Transporter; +} // namespace optical + +struct SetupOptions; +class SharedParams; + +//---------------------------------------------------------------------------// +/*! + * Brief class description. + * + * Optional detailed class description, and possibly example usage: + * \code + LocalOpticalTrackOffload ...; + \endcode + */ +class LocalOpticalTrackOffload final : public LocalOffloadInterface +{ + public: + using TrackData = optical::TrackInitializer; + // Construct in an invalid state + LocalOpticalTrackOffload() = default; + + // Construct with shared (across threads) params + LocalOpticalTrackOffload(SetupOptions const& options, SharedParams& params); + + //!@{ + //! \name Type aliases + void Initialize(SetupOptions const&, SharedParams&) final; + + // Set the event ID and reseed the Celeritas RNG at the start of an event + void InitializeEvent(int) final; + + // Transport all buffered tracks to completion + void Flush() final; + + // Clear local data and return to an invalid state + void Finalize() final; + + // Whether the class instance is initialized + bool Initialized() const final { return static_cast(state_); } + // Offload optical distribution data to Celeritas + void Push(TrackData const&); + // Number of buffered tracks + size_type GetBufferSize() const final { return pending_tracks_; } + + // Get accumulated action times + MapStrDbl GetActionTime() const final; + //!@} + + private: + // Transport pending optical tracks + std::shared_ptr transport_; + // Thread-local state data + std::shared_ptr state_; + + std::vector buffer_; + size_type pending_tracks_{}; + // Number of photons to buffer before offloading + size_type auto_flush_{}; + + // Current event ID or manager for obtaining it + UniqueEventId event_id_; + G4EventManager* event_manager_{nullptr}; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas From 2eb5c367de8018f19756ef7cad73068cc9762aa7 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 3 Dec 2025 17:42:45 -0500 Subject: [PATCH 02/60] Add track-offload option and replace LocalOffloadinterface with TrackOffloadInterface --- src/accel/LocalTransporter.hh | 6 ++--- src/accel/TrackOffloadInterface.hh | 35 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 src/accel/TrackOffloadInterface.hh diff --git a/src/accel/LocalTransporter.hh b/src/accel/LocalTransporter.hh index b9510d564b..42c4f9fd32 100644 --- a/src/accel/LocalTransporter.hh +++ b/src/accel/LocalTransporter.hh @@ -15,7 +15,7 @@ #include "celeritas/Types.hh" #include "celeritas/phys/Primary.hh" -#include "LocalOffloadInterface.hh" +#include "TrackOffloadInterface.hh" class G4EventManager; class G4Track; @@ -52,7 +52,7 @@ class StepperInterface; * * \todo Rename \c LocalOffload or something? */ -class LocalTransporter final : public LocalOffloadInterface +class LocalTransporter final : public TrackOffloadInterface { public: // Construct in an invalid state @@ -88,7 +88,7 @@ class LocalTransporter final : public LocalOffloadInterface //!@} // Offload this track - void Push(G4Track&); + void Push(G4Track const&) override; // Access core state data for user diagnostics CoreStateInterface const& GetState() const; diff --git a/src/accel/TrackOffloadInterface.hh b/src/accel/TrackOffloadInterface.hh new file mode 100644 index 0000000000..0dfa11539b --- /dev/null +++ b/src/accel/TrackOffloadInterface.hh @@ -0,0 +1,35 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/TrackOffloadInterface.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "LocalOffloadInterface.hh" + +class G4Track; + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Brief class description. + * + * Optional detailed class description, and possibly example usage: + * \code + TrackOffloadInterface ...; + \endcode + */ +class TrackOffloadInterface : public LocalOffloadInterface +{ + public: + // Construct with defaults + virtual ~TrackOffloadInterface() = default; + + // Push a full Geant4 track to Celeritas + virtual void Push(G4Track const&) = 0; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas From 29c8c140d3bc8488945a6d68316081b28734f404 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 3 Dec 2025 17:44:52 -0500 Subject: [PATCH 03/60] Add optical track-offload option and integrate with constructor --- src/accel/SetupOptions.hh | 3 +- src/accel/TrackingManager.cc | 3 +- src/accel/TrackingManager.hh | 8 ++-- src/accel/TrackingManagerConstructor.cc | 4 +- src/accel/TrackingManagerConstructor.hh | 8 ++-- src/accel/detail/IntegrationSingleton.cc | 49 +++++++++++++++++++++++- src/accel/detail/IntegrationSingleton.hh | 9 +++++ 7 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index de110f9beb..d4d40e60c1 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -129,6 +129,8 @@ struct OpticalSetupOptions size_type max_steps{inp::TrackingLimits::unlimited}; //! Limit on number of optical step iterations before aborting size_type max_step_iters{inp::TrackingLimits::unlimited}; + + bool offload_tracks{false}; }; //---------------------------------------------------------------------------// @@ -193,7 +195,6 @@ struct SetupOptions //!@{ //! \name Optical photon options - std::optional optical; //!@} diff --git a/src/accel/TrackingManager.cc b/src/accel/TrackingManager.cc index 9ce9b86949..bd3f28efe8 100644 --- a/src/accel/TrackingManager.cc +++ b/src/accel/TrackingManager.cc @@ -16,6 +16,7 @@ #include "ExceptionConverter.hh" #include "LocalTransporter.hh" #include "SharedParams.hh" +#include "TrackOffloadInterface.hh" namespace celeritas { @@ -27,7 +28,7 @@ namespace celeritas * run. */ TrackingManager::TrackingManager(SharedParams const* params, - LocalTransporter* local) + TrackOffloadInterface* local) : params_(params), transport_(local) { CELER_EXPECT(params_); diff --git a/src/accel/TrackingManager.hh b/src/accel/TrackingManager.hh index 6eba5170dc..5314e836ef 100644 --- a/src/accel/TrackingManager.hh +++ b/src/accel/TrackingManager.hh @@ -18,7 +18,7 @@ namespace celeritas //---------------------------------------------------------------------------// class SharedParams; -class LocalTransporter; +class TrackOffloadInterface; //---------------------------------------------------------------------------// /*! @@ -40,7 +40,7 @@ class TrackingManager final : public G4VTrackingManager public: // Construct with shared (across threads) params, and thread-local // transporter. - TrackingManager(SharedParams const* params, LocalTransporter* local); + TrackingManager(SharedParams const* params, TrackOffloadInterface* local); // Prepare cross-section tables for rebuild (e.g. if new materials have // been defined). @@ -62,12 +62,12 @@ class TrackingManager final : public G4VTrackingManager SharedParams const* shared_params() const { return params_; } //! Get the thread-local transporter - LocalTransporter* local_transporter() const { return transport_; } + TrackOffloadInterface* local_transporter() const { return transport_; } private: bool validated_{false}; SharedParams const* params_{nullptr}; - LocalTransporter* transport_{nullptr}; + TrackOffloadInterface* transport_{nullptr}; }; //---------------------------------------------------------------------------// diff --git a/src/accel/TrackingManagerConstructor.cc b/src/accel/TrackingManagerConstructor.cc index e589115347..285206268b 100644 --- a/src/accel/TrackingManagerConstructor.cc +++ b/src/accel/TrackingManagerConstructor.cc @@ -55,7 +55,7 @@ TrackingManagerConstructor::TrackingManagerConstructor( : TrackingManagerConstructor( &detail::IntegrationSingleton::instance().shared_params(), [](int) { return &detail::IntegrationSingleton::instance() - .local_transporter(); + .local_track_offload(); }) { CELER_EXPECT(tmi == &TrackingManagerIntegration::Instance()); @@ -134,7 +134,7 @@ void TrackingManagerConstructor::ConstructProcess() /*! * Get the local transporter associated with the current thread ID. */ -LocalTransporter* TrackingManagerConstructor::get_local_transporter() const +TrackOffloadInterface* TrackingManagerConstructor::get_local_transporter() const { CELER_EXPECT(get_local_); return this->get_local_(G4Threading::G4GetThreadId()); diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 0da6799f04..52c30daf07 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -11,12 +11,13 @@ #include #include "corecel/cont/Span.hh" +#include "accel/TrackOffloadInterface.hh" #include "detail/IntegrationSingleton.hh" namespace celeritas { -class LocalTransporter; +class TrackOffloadInterface; class SharedParams; class TrackingManagerIntegration; @@ -51,7 +52,8 @@ class TrackingManagerConstructor final : public G4VPhysicsConstructor public: //!@{ //! \name Type aliases - using LocalTransporterFromThread = std::function; + using LocalTransporterFromThread + = std::function; using VecG4PD = SetupOptions::VecG4PD; //!@} @@ -75,7 +77,7 @@ class TrackingManagerConstructor final : public G4VPhysicsConstructor SharedParams const* shared_params() const { return shared_; } // Get the local transporter associated with the current thread ID - LocalTransporter* get_local_transporter() const; + TrackOffloadInterface* get_local_transporter() const; private: SharedParams const* shared_{nullptr}; diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 69b357e17c..ddc80cea3d 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -78,6 +78,15 @@ IntegrationSingleton& IntegrationSingleton::instance() /*! * Static THREAD-LOCAL Celeritas state data. */ +TrackOffloadInterface& IntegrationSingleton::local_track_offload() +{ + if (this->optical_track_offload()) + { + return IntegrationSingleton::local_optical_track_offload(); + } + return IntegrationSingleton::local_transporter(); +} + LocalTransporter& IntegrationSingleton::local_transporter() { auto& offload = IntegrationSingleton::local_offload_ptr(); @@ -110,17 +119,48 @@ LocalOpticalOffload& IntegrationSingleton::local_optical_offload() return *lt; } +//---------------------------------------------------------------------------// +/*! + * Static THREAD-LOCAL Celeritas optical state data. + */ +LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() +{ + auto& offload = IntegrationSingleton::local_offload_ptr(); + if (!offload) + { + offload = std::make_unique(); + } + auto* lt = dynamic_cast(offload.get()); + CELER_VALIDATE(lt, + << "Cannot access LocalOpticalTrtackOffload when " + "LocalTransporter is being used"); + return *lt; +} + //---------------------------------------------------------------------------// /*! * Access the thread-local offload interface. */ LocalOffloadInterface& IntegrationSingleton::local_offload() { - if (this->optical_offload()) + CELER_VALIDATE(!(this->optical_track_offload() && this->optical_offload()), + << "Cannot enable both optical generator offload and " + "optical " + "track offload at the same time"); + + if (this->optical_track_offload()) + { + return IntegrationSingleton::local_optical_track_offload(); + } + + else if (this->optical_offload()) { return IntegrationSingleton::local_optical_offload(); } - return IntegrationSingleton::local_transporter(); + else + { + return IntegrationSingleton::local_transporter(); + } } //---------------------------------------------------------------------------// @@ -364,6 +404,11 @@ bool IntegrationSingleton::optical_offload() const options_.optical->generator); } +bool IntegrationSingleton::optical_track_offload() const +{ + return options_.optical && options_.optical->offload_tracks; +} + //---------------------------------------------------------------------------// /*! * Create or update the number of threads for the logger. diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index 41e29f0a68..5faddb3325 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -9,6 +9,7 @@ #include #include "corecel/sys/Stopwatch.hh" +#include "accel/LocalOpticalTrackOffload.hh" #include "../LocalOpticalOffload.hh" #include "../LocalTransporter.hh" @@ -54,6 +55,12 @@ class IntegrationSingleton // Static THREAD-LOCAL Celeritas optical state data static LocalOpticalOffload& local_optical_offload(); + // Static Thread-local Celeritas optical track offload + static LocalOpticalTrackOffload& local_optical_track_offload(); + + // Thread-local offload object for particles handled by the tracking + // manager + TrackOffloadInterface& local_track_offload(); //// ACCESSORS //// // Access the thread-local offload interface @@ -122,6 +129,8 @@ class IntegrationSingleton // Whether offloading optical distribution data is enabled bool optical_offload() const; + bool optical_track_offload() const; + // Set up or update logging if the run manager is enabled void update_logger(); }; From a96adc1a141e08c759a5e9ad57299252636f420c Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 3 Dec 2025 17:45:54 -0500 Subject: [PATCH 04/60] Modify the track info in push --- src/accel/LocalOpticalTrackOffload.cc | 30 +++++++++++++++++++++++++-- src/accel/LocalOpticalTrackOffload.hh | 7 ++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index 2e547f9adf..ae475bb878 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -107,13 +107,36 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) /*! * Buffer distribution data for generating optical photons. */ -void LocalOpticalTrackOffload::Push(TrackData const& track) +void LocalOpticalTrackOffload::Push(G4Track const& g4track) { CELER_EXPECT(*this); + TrackData init; + + // Sanity check: this path is meant for optical photons + CELER_EXPECT(g4track.GetDefinition()); + CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); + + // Energy: convert Geant4 energy [MeV] to Celeritas MevEnergy + init.energy = units::MevEnergy{g4track.GetTotalEnergy() / CLHEP::MeV}; + + // Position: Geant4 uses mm; Celeritas uses cm + auto const& pos = g4track.GetPosition(); + init.position + = Real3{pos.x() / CLHEP::cm, pos.y() / CLHEP::cm, pos.z() / CLHEP::cm}; + + auto const& dir = g4track.GetMomentumDirection(); + init.direction = Real3{dir.x(), dir.y(), dir.z()}; + + // Polarization: directly from G4 + auto const& pol = g4track.GetPolarization(); + init.polarization = Real3{pol.x(), pol.y(), pol.z()}; + + // Time: Geant4 uses ns; Celeritas uses seconds + init.time = g4track.GetGlobalTime() / CLHEP::s; ScopedProfiling profile_this{"push"}; - buffer_.push_back(track); + buffer_.push_back(init); pending_tracks_++; if (pending_tracks_ >= auto_flush_) @@ -164,6 +187,9 @@ void LocalOpticalTrackOffload::Flush() << " optical track from event " << event_id_.unchecked_get() << " with Celeritas"; } + // Inject buffered tracks into optical state + + state_->insert_primaries(make_span(buffer_)); pending_tracks_ = 0; buffer_.clear(); diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index 57fc663bf7..d3881ef097 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -10,8 +10,9 @@ #include "celeritas/Types.hh" #include "celeritas/optical/TrackInitializer.hh" #include "celeritas/optical/Transporter.hh" +#include "accel/TrackOffloadInterface.hh" -#include "LocalOffloadInterface.hh" +#include "TrackOffloadInterface.hh" class G4EventManager; @@ -35,7 +36,7 @@ class SharedParams; LocalOpticalTrackOffload ...; \endcode */ -class LocalOpticalTrackOffload final : public LocalOffloadInterface +class LocalOpticalTrackOffload final : public TrackOffloadInterface { public: using TrackData = optical::TrackInitializer; @@ -61,7 +62,7 @@ class LocalOpticalTrackOffload final : public LocalOffloadInterface // Whether the class instance is initialized bool Initialized() const final { return static_cast(state_); } // Offload optical distribution data to Celeritas - void Push(TrackData const&); + void Push(G4Track const&) final; // Number of buffered tracks size_type GetBufferSize() const final { return pending_tracks_; } From 7603078bfc1590af37805f7b8ba132854fae3d4d Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 2 Dec 2025 06:53:14 -0500 Subject: [PATCH 05/60] Fix vecgeom 2.x with CUDA (#2136) * Fix vecgeom 2 CUDA build failure due to IWYU in nav state/tuple * Add hudson vg2 profiles * Fix vecgeom failures by initializing navstatetuple on device * Fix build with navpath * CudaManager is only valid when CUDA --- scripts/cmake-presets/hudson.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/cmake-presets/hudson.json b/scripts/cmake-presets/hudson.json index 0f2348961a..42ce399ade 100644 --- a/scripts/cmake-presets/hudson.json +++ b/scripts/cmake-presets/hudson.json @@ -75,6 +75,26 @@ "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} } }, + { + "name": "release-vecgeom2-surface", + "displayName": "Build with full optimizations (VecGeom 2 with surface geo)", + "inherits": ["release-vecgeom2-solid"], + "cacheVariables": { + "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "ON"}, + "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "ON"} + } + }, + { + "name": "release-vecgeom2-solid", + "displayName": "Build with full optimizations (VecGeom 2 with solid geo)", + "inherits": ["release"], + "environment": { + "CMAKE_PREFIX_PATH": "$env{SPACK_ROOT}/var/spack/environments/celeritas-vg2/.spack-env/view" + }, + "cacheVariables": { + "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} + } + }, { "name": "release-vecgeom2-surface", "displayName": "Build with full optimizations (VecGeom 2 with surface geo)", From 1544ae792ed6faacaffc229afab1785df3cc8203 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Mon, 8 Dec 2025 17:14:49 -0500 Subject: [PATCH 06/60] WIP --- src/celeritas/setup/Problem.cc | 24 +++++++++++++++++++++++- src/celeritas/setup/Problem.hh | 5 ++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 1de82d1ac2..05dfeee421 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -724,12 +724,34 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) }, }, p.physics.optical_generator); + + if (p.physics.optical && p.physics.optical_generat) + { + if (!optical_transporter_) + { + optical::Transporter::Input inp; + inp.params = optical_params; + inp.max_step_iters = p.tracking.limits.optical_step_iters; + + if (action_times) + { + inp.action_times = ActionTimes::make_and_insert( + optical_params->action_reg(), + core_params->aux_reg(), + "optical-action-times"); + } + + result.optical_transporter + = std::make_shared(std::move(inp)); + } + } } else { CELER_VALIDATE(imported.optical_models.empty(), << "optical physics models were imported but no " - "optical capacity was set. Either define optical " + "optical capacity was set. Either define " + "optical " "tracking loop parameters, or ignore optical " "physics"); } diff --git a/src/celeritas/setup/Problem.hh b/src/celeritas/setup/Problem.hh index c41daddbb5..19066faff9 100644 --- a/src/celeritas/setup/Problem.hh +++ b/src/celeritas/setup/Problem.hh @@ -45,7 +45,10 @@ struct ProblemLoaded //! Step collector std::shared_ptr step_collector; //! Optical-only offload management - std::shared_ptr optical_transporter; + std::shared_ptr optical_transporter; // optical + // primary w/ + // haydens + // class //! Combined EM and optical offload management std::shared_ptr optical_collector; //! Geant4 SD interface From 89c46013663c306cd9bfc7ccbc1a2ee24881efdc Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Tue, 16 Dec 2025 09:55:58 -0500 Subject: [PATCH 07/60] Add OpticalTrackOffload option in problem --- src/celeritas/inp/Events.hh | 11 ++++++++- src/celeritas/setup/Problem.cc | 41 +++++++++++++++------------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index 117774e32c..7d391d35c2 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -119,11 +119,20 @@ struct OpticalOffloadGenerator { }; +//---------------------------------------------------------------------------// +/*! + * Generate optical photons track. + */ +struct OpticalTrackOffload +{ +}; + //---------------------------------------------------------------------------// //! Mechanism for generating optical photons using OpticalGenerator = std::variant; + OpticalPrimaryGenerator, + OpticalTrackOffload>; //---------------------------------------------------------------------------// /*! diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 05dfeee421..9f6c526cfd 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -718,40 +718,35 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) = std::make_shared( std::move(inp)); }, + [&](inp::OpticalTrackOffload) { + // Build optical track transporter + optical::Transporter::Input inp; + inp.params = optical_params; + inp.max_step_iters = p.tracking.limits.optical_step_iters; + if (action_times) + { + inp.action_times = ActionTimes::make_and_insert( + optical_params->action_reg(), + core_params->aux_reg(), + "optical-action-times"); + } + + result.optical_transporter + = std::make_shared( + std::move(inp)); + }, [](inp::OpticalPrimaryGenerator) { //! \todo Enable optical primary generator CELER_NOT_IMPLEMENTED("optical primary generator"); }, }, p.physics.optical_generator); - - if (p.physics.optical && p.physics.optical_generat) - { - if (!optical_transporter_) - { - optical::Transporter::Input inp; - inp.params = optical_params; - inp.max_step_iters = p.tracking.limits.optical_step_iters; - - if (action_times) - { - inp.action_times = ActionTimes::make_and_insert( - optical_params->action_reg(), - core_params->aux_reg(), - "optical-action-times"); - } - - result.optical_transporter - = std::make_shared(std::move(inp)); - } - } } else { CELER_VALIDATE(imported.optical_models.empty(), << "optical physics models were imported but no " - "optical capacity was set. Either define " - "optical " + "optical capacity was set. Either define optical " "tracking loop parameters, or ignore optical " "physics"); } From db5ea623fbc64ea04fab2ad300c1dce4271b0566 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Tue, 16 Dec 2025 10:03:29 -0500 Subject: [PATCH 08/60] change push parameter --- src/accel/CMakeLists.txt | 1 + src/accel/LocalOpticalTrackOffload.cc | 2 +- src/accel/LocalOpticalTrackOffload.hh | 2 +- src/accel/LocalTransporter.hh | 2 +- src/accel/TrackOffloadInterface.hh | 2 +- src/accel/cmake_install.cmake | 62 +++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/accel/cmake_install.cmake diff --git a/src/accel/CMakeLists.txt b/src/accel/CMakeLists.txt index 0800349f4f..d374f68840 100644 --- a/src/accel/CMakeLists.txt +++ b/src/accel/CMakeLists.txt @@ -31,6 +31,7 @@ list(APPEND SOURCES GeantStepDiagnostic.cc IntegrationBase.cc LocalOpticalOffload.cc + LocalOpticalTrackOffload.cc LocalTransporter.cc Logger.cc PGPrimaryGeneratorAction.cc diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index ae475bb878..3c61f43b68 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -107,7 +107,7 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) /*! * Buffer distribution data for generating optical photons. */ -void LocalOpticalTrackOffload::Push(G4Track const& g4track) +void LocalOpticalTrackOffload::Push(G4Track& g4track) { CELER_EXPECT(*this); TrackData init; diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index d3881ef097..3ae0eb850b 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -62,7 +62,7 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface // Whether the class instance is initialized bool Initialized() const final { return static_cast(state_); } // Offload optical distribution data to Celeritas - void Push(G4Track const&) final; + void Push(G4Track&) final; // Number of buffered tracks size_type GetBufferSize() const final { return pending_tracks_; } diff --git a/src/accel/LocalTransporter.hh b/src/accel/LocalTransporter.hh index 42c4f9fd32..383c26a47e 100644 --- a/src/accel/LocalTransporter.hh +++ b/src/accel/LocalTransporter.hh @@ -88,7 +88,7 @@ class LocalTransporter final : public TrackOffloadInterface //!@} // Offload this track - void Push(G4Track const&) override; + void Push(G4Track&) override; // Access core state data for user diagnostics CoreStateInterface const& GetState() const; diff --git a/src/accel/TrackOffloadInterface.hh b/src/accel/TrackOffloadInterface.hh index 0dfa11539b..004af0bcb6 100644 --- a/src/accel/TrackOffloadInterface.hh +++ b/src/accel/TrackOffloadInterface.hh @@ -28,7 +28,7 @@ class TrackOffloadInterface : public LocalOffloadInterface virtual ~TrackOffloadInterface() = default; // Push a full Geant4 track to Celeritas - virtual void Push(G4Track const&) = 0; + virtual void Push(G4Track&) = 0; }; //---------------------------------------------------------------------------// diff --git a/src/accel/cmake_install.cmake b/src/accel/cmake_install.cmake new file mode 100644 index 0000000000..8e23f7226e --- /dev/null +++ b/src/accel/cmake_install.cmake @@ -0,0 +1,62 @@ +# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src/accel + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "Release") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "runtime" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" TYPE SHARED_LIBRARY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/lib/libaccel.dylib") + if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib" AND + NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") + execute_process(COMMAND /usr/bin/install_name_tool + -delete_rpath "/Users/r1i/Desktop/project/forked/celeritas/lib" + -add_rpath "/usr/local/lib" + "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") + if(CMAKE_INSTALL_DO_STRIP) + execute_process(COMMAND "/usr/bin/strip" -x "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") + endif() + endif() +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "runtime" OR NOT CMAKE_INSTALL_COMPONENT) +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/accel/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() From 3054a37eeda5bfe595e2e55d82e3d20e0139105a Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 17 Dec 2025 14:42:11 -0500 Subject: [PATCH 09/60] Flush and Push logs --- src/accel/LocalOpticalTrackOffload.cc | 37 +++++++----- src/accel/SharedParams.cc | 8 +++ src/accel/UserActionIntegration.cc | 21 ++++++- src/accel/detail/IntegrationSingleton.cc | 5 +- src/cmake_install.cmake | 74 ++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 src/cmake_install.cmake diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index 3c61f43b68..aaef2949ea 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -92,12 +92,14 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) CELER_EXPECT(id >= 0); event_id_ = id_cast(id); - + CELER_LOG(debug) << "Entering the init evt : "; if (!(G4Threading::IsMultithreadedApplication() && G4MTRunManager::SeedOncePerCommunication())) { - // Since Geant4 schedules events dynamically, reseed the Celeritas RNGs - // using the Geant4 event ID for reproducibility. This guarantees that + // Since Geant4 schedules events dynamically, reseed the Celeritas + // RNGs + // using the Geant4 event ID for reproducibility. This guarantees + // that // an event can be reproduced given the event ID. state_->reseed(transport_->params()->rng(), id_cast(id)); } @@ -109,9 +111,12 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) */ void LocalOpticalTrackOffload::Push(G4Track& g4track) { + CELER_LOG(info) << "Transport pointer: " << transport_; CELER_EXPECT(*this); TrackData init; - + CELER_LOG(info) << "Optical track offloaded to Celeritas: " + << "E=" << g4track.GetTotalEnergy() / CLHEP::eV << " eV, "; + //<< "event=" << g4track.GetEventID(); // Sanity check: this path is meant for optical photons CELER_EXPECT(g4track.GetDefinition()); CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); @@ -151,7 +156,8 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) void LocalOpticalTrackOffload::Flush() { CELER_EXPECT(*this); - + CELER_LOG(info) << "Flushing " << buffer_.size() + << " optical tracks to Celeritas"; if (buffer_.empty()) { return; @@ -180,22 +186,21 @@ void LocalOpticalTrackOffload::Flush() } CELER_ASSERT(event_id_); - if (celeritas::device()) - { - CELER_LOG_LOCAL(debug) - << "Transporting " << pending_tracks_ - << " optical track from event " << event_id_.unchecked_get() - << " with Celeritas"; - } + // if (celeritas::device()) + // { + CELER_LOG(debug) << "Transporting " << pending_tracks_ + << " optical track from event " + << event_id_.unchecked_get() << " with Celeritas"; + //} // Inject buffered tracks into optical state - state_->insert_primaries(make_span(buffer_)); - - pending_tracks_ = 0; + // state_->insert_primaries(make_span(buffer_)); + CELER_LOG(info) << "Skipping optical transport (WIP)"; buffer_.clear(); + pending_tracks_ = 0; // Generate optical photons and transport to completion - (*transport_)(*state_); + // (*transport_)(*state_); } //---------------------------------------------------------------------------// diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 33f23ec135..4450379f73 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -337,6 +337,14 @@ SharedParams::SharedParams(SetupOptions const& options) optical_collector_ = std::move(loaded.problem.optical_collector); CELER_ASSERT(optical_collector_); } + else if (std::holds_alternative( + opt->generator)) + { + optical_transporter_ + = std::move(loaded.problem.optical_transporter); + CELER_LOG(info) << "could break here in optical offload"; + CELER_ASSERT(optical_transporter_); + } else { CELER_VALIDATE(false, diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index e83cd7fd93..9a4a5214a9 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -7,6 +7,7 @@ #include "UserActionIntegration.hh" #include +#include #include #include @@ -83,6 +84,19 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) if (mode == SharedParams::Mode::disabled) return; + // Optical track offload path + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + CELER_LOG(debug) << "Entering optical offload in user action"; + auto& opt_local + = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt_local) + { + opt_local.Push(*track); + track->SetTrackStatus(fStopAndKill); + return; + } + } auto const& particles = singleton.shared_params().OffloadParticles(); if (std::find(particles.begin(), particles.end(), track->GetDefinition()) != particles.end()) @@ -116,7 +130,12 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) CELER_TRY_HANDLE( local.Flush(), ExceptionConverter("celer.event.flush", &singleton.shared_params())); - + auto& opt = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt && opt.GetBufferSize() > 0) + { + CELER_LOG(info) << "EOE flushing optical tracks"; + opt.Flush(); + } // Record the time for this event singleton.shared_params().timer()->RecordEventTime(get_event_time_()); } diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index ddc80cea3d..e1ce0e066c 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -125,9 +125,11 @@ LocalOpticalOffload& IntegrationSingleton::local_optical_offload() */ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() { + CELER_LOG(info) << "Entered integration singleton"; auto& offload = IntegrationSingleton::local_offload_ptr(); if (!offload) { + CELER_LOG(info) << "Optical offload is not empty"; offload = std::make_unique(); } auto* lt = dynamic_cast(offload.get()); @@ -189,7 +191,8 @@ void IntegrationSingleton::setup_options(SetupOptions&& opts) << R"(SetOptions called with incomplete input: you must use the UI to update before /run/initialize)"; } - CELER_ENSURE(!offloaded_.empty() || this->optical_offload()); + CELER_ENSURE(!offloaded_.empty() || this->optical_offload() + || this->optical_track_offload()); } //---------------------------------------------------------------------------// diff --git a/src/cmake_install.cmake b/src/cmake_install.cmake new file mode 100644 index 0000000000..e58d66a914 --- /dev/null +++ b/src/cmake_install.cmake @@ -0,0 +1,74 @@ +# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "Release") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/corecel/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/geocel/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/orange/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/celeritas/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/accel/cmake_install.cmake") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "development" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/src/" FILES_MATCHING REGEX ".*\\.hh?$") +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() From 0b4436743dbb950f87d7bffa49c5fe1f9a1745ac Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 17 Dec 2025 14:43:20 -0500 Subject: [PATCH 10/60] Optical track offload test --- test/accel/UserActionIntegration.test.cc | 74 ++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 50bbdc3af3..1f4cda3223 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -7,6 +7,7 @@ #include "accel/UserActionIntegration.hh" #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include "corecel/math/ArrayUtils.hh" +#include "corecel/math/Quantity.hh" #include "geocel/ScopedGeantExceptionHandler.hh" #include "geocel/UnitUtils.hh" #include "geocel/g4/Convert.hh" @@ -290,6 +292,78 @@ TEST_F(LarSphereOpticalOffload, run) rm.BeamOn(2); } +//---------------------------------------------------------------------------// +// LAR SPHERE WITH OPTICAL TRACK OFFLOAD +//---------------------------------------------------------------------------// +class LarSphereOpticalTrackOffload : public LarSphere +{ + public: + PhysicsInput make_physics_input() const override; + SetupOptions make_setup_options() override; + UPTrackAction make_tracking_action() override + { + return std::make_unique(); + } +}; + +auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput +{ + auto result = LarSphereIntegrationMixin::make_physics_input(); + + auto& optical = result.optical; + optical = {}; + + optical.cherenkov.stack_photons = true; + optical.scintillation.stack_photons = true; + + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); + + return result; +} + +auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions +{ + auto result = LarSphereIntegrationMixin::make_setup_options(); + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 1; + opt.capacity.generators = opt.capacity.tracks * 8; + opt.capacity.primaries = opt.capacity.tracks * 16; + opt.generator = inp::OpticalTrackOffload{}; + opt.offload_tracks = true; + + return opt; + }(); + // result.offload_particles = {}; + // Don't offload any particles + result.offload_particles = SetupOptions::VecG4PD{}; + + return result; +} + +// sIntegrationTestBase::UPTrackAction +// sLarSphereOpticalTrackOffload::make_tracking_action() +// s{ +// s CELER_LOG(info) << "Optical photon seen in G4"; +// s return std::make_unique(); +// s} + +TEST_F(LarSphereOpticalTrackOffload, run) +{ + auto& rm = this->run_manager(); + UAI::Instance().SetOptions(this->make_setup_options()); + + rm.Initialize(); + rm.BeamOn(1); + + // auto& local + // = + // detail::IntegrationSingleton::instance().local_optical_track_offload(); + // EXPECT_FALSE(local); // flushed after event +} + //---------------------------------------------------------------------------// // TEST EM3 //---------------------------------------------------------------------------// From 4d6d60003693ca307a0a74efa59f8c89d1b11e63 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 17 Dec 2025 22:10:51 -0500 Subject: [PATCH 11/60] log number of optical tracks --- src/accel/LocalOpticalTrackOffload.cc | 26 +++++++++----------------- src/accel/LocalOpticalTrackOffload.hh | 12 +++++++++--- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index aaef2949ea..cd81fb8466 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -39,7 +39,6 @@ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, // Save a pointer to the optical transporter transport_ = params.optical_transporter(); CELER_ASSERT(transport_); - CELER_ASSERT(transport_->params()); auto const& optical_params = *transport_->params(); @@ -92,7 +91,6 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) CELER_EXPECT(id >= 0); event_id_ = id_cast(id); - CELER_LOG(debug) << "Entering the init evt : "; if (!(G4Threading::IsMultithreadedApplication() && G4MTRunManager::SeedOncePerCommunication())) { @@ -111,13 +109,10 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) */ void LocalOpticalTrackOffload::Push(G4Track& g4track) { - CELER_LOG(info) << "Transport pointer: " << transport_; CELER_EXPECT(*this); + ++num_pushed_; TrackData init; - CELER_LOG(info) << "Optical track offloaded to Celeritas: " - << "E=" << g4track.GetTotalEnergy() / CLHEP::eV << " eV, "; - //<< "event=" << g4track.GetEventID(); - // Sanity check: this path is meant for optical photons + CELER_EXPECT(g4track.GetDefinition()); CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); @@ -156,8 +151,6 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) void LocalOpticalTrackOffload::Flush() { CELER_EXPECT(*this); - CELER_LOG(info) << "Flushing " << buffer_.size() - << " optical tracks to Celeritas"; if (buffer_.empty()) { return; @@ -186,16 +179,10 @@ void LocalOpticalTrackOffload::Flush() } CELER_ASSERT(event_id_); - // if (celeritas::device()) - // { - CELER_LOG(debug) << "Transporting " << pending_tracks_ - << " optical track from event " - << event_id_.unchecked_get() << " with Celeritas"; - //} // Inject buffered tracks into optical state - + ++num_flushed_; // state_->insert_primaries(make_span(buffer_)); - CELER_LOG(info) << "Skipping optical transport (WIP)"; + // ToDo Skipping optical transport (WIP) buffer_.clear(); pending_tracks_ = 0; @@ -222,6 +209,11 @@ void LocalOpticalTrackOffload::Finalize() CELER_VALIDATE(buffer_.empty(), << pending_tracks_ << " optical tracks were not flushed"); + + CELER_LOG(info) << "Finalizing Celeritas after " << num_pushed_ + << " optical tracks pushed (over " << num_flushed_ + << " ) flushes"; + // Reset all data *this = {}; diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index 3ae0eb850b..6aef13e92f 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -66,6 +66,8 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface // Number of buffered tracks size_type GetBufferSize() const final { return pending_tracks_; } + // Optical tracks pushed + size_type num_pushed() const { return num_pushed_; } // Get accumulated action times MapStrDbl GetActionTime() const final; //!@} @@ -78,10 +80,14 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface std::vector buffer_; size_type pending_tracks_{}; - // Number of photons to buffer before offloading + // Number of photons tracks to buffer before offloading size_type auto_flush_{}; - - // Current event ID or manager for obtaining it + // size_type num_pushed_{}; + // Diagnostics (thread-local) + size_type num_pushed_{0}; + size_type num_flushed_{0}; + // size_type num_events_{0}; + // Current event ID or manager for obtaining it UniqueEventId event_id_; G4EventManager* event_manager_{nullptr}; }; From d8f1b5821c4b97bf9c931f1c23d1bc3e1c8b5a52 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 17 Dec 2025 22:11:43 -0500 Subject: [PATCH 12/60] remove debug statements --- src/accel/UserActionIntegration.cc | 8 -------- src/accel/detail/IntegrationSingleton.cc | 3 +-- test/accel/UserActionIntegration.test.cc | 15 ++------------- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index 9a4a5214a9..c9d5e3f393 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -87,7 +87,6 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) // Optical track offload path if (track->GetDefinition() == G4OpticalPhoton::Definition()) { - CELER_LOG(debug) << "Entering optical offload in user action"; auto& opt_local = detail::IntegrationSingleton::local_optical_track_offload(); if (opt_local) @@ -130,13 +129,6 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) CELER_TRY_HANDLE( local.Flush(), ExceptionConverter("celer.event.flush", &singleton.shared_params())); - auto& opt = detail::IntegrationSingleton::local_optical_track_offload(); - if (opt && opt.GetBufferSize() > 0) - { - CELER_LOG(info) << "EOE flushing optical tracks"; - opt.Flush(); - } - // Record the time for this event singleton.shared_params().timer()->RecordEventTime(get_event_time_()); } diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index e1ce0e066c..600c301a5d 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -125,11 +125,10 @@ LocalOpticalOffload& IntegrationSingleton::local_optical_offload() */ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() { - CELER_LOG(info) << "Entered integration singleton"; auto& offload = IntegrationSingleton::local_offload_ptr(); if (!offload) { - CELER_LOG(info) << "Optical offload is not empty"; + CELER_LOG(info) << "Optical track offload enabled"; offload = std::make_unique(); } auto* lt = dynamic_cast(offload.get()); diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 1f4cda3223..6c71e564ca 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -328,7 +328,7 @@ auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions auto result = LarSphereIntegrationMixin::make_setup_options(); result.optical = [] { OpticalSetupOptions opt; - opt.capacity.tracks = 1; + opt.capacity.tracks = 32; opt.capacity.generators = opt.capacity.tracks * 8; opt.capacity.primaries = opt.capacity.tracks * 16; opt.generator = inp::OpticalTrackOffload{}; @@ -343,25 +343,14 @@ auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions return result; } -// sIntegrationTestBase::UPTrackAction -// sLarSphereOpticalTrackOffload::make_tracking_action() -// s{ -// s CELER_LOG(info) << "Optical photon seen in G4"; -// s return std::make_unique(); -// s} - TEST_F(LarSphereOpticalTrackOffload, run) { auto& rm = this->run_manager(); + rm.SetNumberOfThreads(1); UAI::Instance().SetOptions(this->make_setup_options()); rm.Initialize(); rm.BeamOn(1); - - // auto& local - // = - // detail::IntegrationSingleton::instance().local_optical_track_offload(); - // EXPECT_FALSE(local); // flushed after event } //---------------------------------------------------------------------------// From e1d4ab547509c7d43f9183b44389b038abf3e832 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Thu, 18 Dec 2025 14:41:43 -0500 Subject: [PATCH 13/60] Move tracking action to test case --- src/accel/UserActionIntegration.cc | 15 ++--------- test/accel/UserActionIntegration.test.cc | 32 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index c9d5e3f393..e83cd7fd93 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -7,7 +7,6 @@ #include "UserActionIntegration.hh" #include -#include #include #include @@ -84,18 +83,6 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) if (mode == SharedParams::Mode::disabled) return; - // Optical track offload path - if (track->GetDefinition() == G4OpticalPhoton::Definition()) - { - auto& opt_local - = detail::IntegrationSingleton::local_optical_track_offload(); - if (opt_local) - { - opt_local.Push(*track); - track->SetTrackStatus(fStopAndKill); - return; - } - } auto const& particles = singleton.shared_params().OffloadParticles(); if (std::find(particles.begin(), particles.end(), track->GetDefinition()) != particles.end()) @@ -129,6 +116,8 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) CELER_TRY_HANDLE( local.Flush(), ExceptionConverter("celer.event.flush", &singleton.shared_params())); + + // Record the time for this event singleton.shared_params().timer()->RecordEventTime(get_event_time_()); } diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 6c71e564ca..40700e0396 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -294,6 +294,12 @@ TEST_F(LarSphereOpticalOffload, run) //---------------------------------------------------------------------------// // LAR SPHERE WITH OPTICAL TRACK OFFLOAD +//---------------------------------------------------------------------------// +class LSOOTrackingAction final : public G4UserTrackingAction +{ + void PreUserTrackingAction(G4Track const* track) final; +}; + //---------------------------------------------------------------------------// class LarSphereOpticalTrackOffload : public LarSphere { @@ -302,7 +308,7 @@ class LarSphereOpticalTrackOffload : public LarSphere SetupOptions make_setup_options() override; UPTrackAction make_tracking_action() override { - return std::make_unique(); + return std::make_unique(); } }; @@ -343,6 +349,30 @@ auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions return result; } +void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) +{ + CELER_EXPECT(track); + + auto& singleton = detail::IntegrationSingleton::instance(); + auto const mode = singleton.shared_params().mode(); + if (mode == SharedParams::Mode::disabled) + return; + + // Optical track offload path + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + auto& opt_local + = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt_local) + { + auto* mutable_track = const_cast(track); + opt_local.Push(*mutable_track); + mutable_track->SetTrackStatus(fStopAndKill); + return; + } + } +} + TEST_F(LarSphereOpticalTrackOffload, run) { auto& rm = this->run_manager(); From e6272d0b46865fba09f7aca9aace36945b61f583 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Thu, 18 Dec 2025 14:43:20 -0500 Subject: [PATCH 14/60] fixes for floating precision --- src/accel/LocalOpticalTrackOffload.cc | 16 ++++-- src/accel/cmake_install.cmake | 62 ---------------------- src/celeritas/setup/Problem.cc | 1 - src/cmake_install.cmake | 74 --------------------------- 4 files changed, 11 insertions(+), 142 deletions(-) delete mode 100644 src/accel/cmake_install.cmake delete mode 100644 src/cmake_install.cmake diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index cd81fb8466..e7ec7d10b1 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -117,19 +117,25 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); // Energy: convert Geant4 energy [MeV] to Celeritas MevEnergy - init.energy = units::MevEnergy{g4track.GetTotalEnergy() / CLHEP::MeV}; + init.energy = units::MevEnergy{ + static_cast(g4track.GetTotalEnergy() / CLHEP::MeV)}; // Position: Geant4 uses mm; Celeritas uses cm auto const& pos = g4track.GetPosition(); - init.position - = Real3{pos.x() / CLHEP::cm, pos.y() / CLHEP::cm, pos.z() / CLHEP::cm}; + init.position = Real3{static_cast(pos.x() / CLHEP::cm), + static_cast(pos.y() / CLHEP::cm), + static_cast(pos.z() / CLHEP::cm)}; auto const& dir = g4track.GetMomentumDirection(); - init.direction = Real3{dir.x(), dir.y(), dir.z()}; + init.direction = Real3{static_cast(dir.x()), + static_cast(dir.y()), + static_cast(dir.z())}; // Polarization: directly from G4 auto const& pol = g4track.GetPolarization(); - init.polarization = Real3{pol.x(), pol.y(), pol.z()}; + init.polarization = Real3{static_cast(pol.x()), + static_cast(pol.y()), + static_cast(pol.z())}; // Time: Geant4 uses ns; Celeritas uses seconds init.time = g4track.GetGlobalTime() / CLHEP::s; diff --git a/src/accel/cmake_install.cmake b/src/accel/cmake_install.cmake deleted file mode 100644 index 8e23f7226e..0000000000 --- a/src/accel/cmake_install.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src/accel - -# Set the install prefix -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local") -endif() -string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - -# Set the install configuration name. -if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) - if(BUILD_TYPE) - string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" - CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") - else() - set(CMAKE_INSTALL_CONFIG_NAME "Release") - endif() - message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") -endif() - -# Set the component getting installed. -if(NOT CMAKE_INSTALL_COMPONENT) - if(COMPONENT) - message(STATUS "Install component: \"${COMPONENT}\"") - set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") - else() - set(CMAKE_INSTALL_COMPONENT) - endif() -endif() - -# Is this installation the result of a crosscompile? -if(NOT DEFINED CMAKE_CROSSCOMPILING) - set(CMAKE_CROSSCOMPILING "FALSE") -endif() - -# Set path to fallback-tool for dependency-resolution. -if(NOT DEFINED CMAKE_OBJDUMP) - set(CMAKE_OBJDUMP "/usr/bin/objdump") -endif() - -if(CMAKE_INSTALL_COMPONENT STREQUAL "runtime" OR NOT CMAKE_INSTALL_COMPONENT) - file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" TYPE SHARED_LIBRARY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/lib/libaccel.dylib") - if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib" AND - NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") - execute_process(COMMAND /usr/bin/install_name_tool - -delete_rpath "/Users/r1i/Desktop/project/forked/celeritas/lib" - -add_rpath "/usr/local/lib" - "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") - if(CMAKE_INSTALL_DO_STRIP) - execute_process(COMMAND "/usr/bin/strip" -x "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libaccel.dylib") - endif() - endif() -endif() - -if(CMAKE_INSTALL_COMPONENT STREQUAL "runtime" OR NOT CMAKE_INSTALL_COMPONENT) -endif() - -string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT - "${CMAKE_INSTALL_MANIFEST_FILES}") -if(CMAKE_INSTALL_LOCAL_ONLY) - file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/accel/install_local_manifest.txt" - "${CMAKE_INSTALL_MANIFEST_CONTENT}") -endif() diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 9f6c526cfd..887ce1de80 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -722,7 +722,6 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) // Build optical track transporter optical::Transporter::Input inp; inp.params = optical_params; - inp.max_step_iters = p.tracking.limits.optical_step_iters; if (action_times) { inp.action_times = ActionTimes::make_and_insert( diff --git a/src/cmake_install.cmake b/src/cmake_install.cmake deleted file mode 100644 index e58d66a914..0000000000 --- a/src/cmake_install.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src - -# Set the install prefix -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local") -endif() -string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - -# Set the install configuration name. -if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) - if(BUILD_TYPE) - string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" - CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") - else() - set(CMAKE_INSTALL_CONFIG_NAME "Release") - endif() - message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") -endif() - -# Set the component getting installed. -if(NOT CMAKE_INSTALL_COMPONENT) - if(COMPONENT) - message(STATUS "Install component: \"${COMPONENT}\"") - set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") - else() - set(CMAKE_INSTALL_COMPONENT) - endif() -endif() - -# Is this installation the result of a crosscompile? -if(NOT DEFINED CMAKE_CROSSCOMPILING) - set(CMAKE_CROSSCOMPILING "FALSE") -endif() - -# Set path to fallback-tool for dependency-resolution. -if(NOT DEFINED CMAKE_OBJDUMP) - set(CMAKE_OBJDUMP "/usr/bin/objdump") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/corecel/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/geocel/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/orange/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/celeritas/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/accel/cmake_install.cmake") -endif() - -if(CMAKE_INSTALL_COMPONENT STREQUAL "development" OR NOT CMAKE_INSTALL_COMPONENT) - file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/src/" FILES_MATCHING REGEX ".*\\.hh?$") -endif() - -string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT - "${CMAKE_INSTALL_MANIFEST_FILES}") -if(CMAKE_INSTALL_LOCAL_ONLY) - file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/install_local_manifest.txt" - "${CMAKE_INSTALL_MANIFEST_CONTENT}") -endif() From d62f9dfa5655d6189a6522ce3b79fa0f310209d9 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Thu, 18 Dec 2025 16:15:42 -0500 Subject: [PATCH 15/60] Change name to LocalOpticalGenOffload from LocalOpticalOffload --- src/accel/CMakeLists.txt | 2 +- ...alOffload.cc => LocalOpticalGenOffload.cc} | 24 +++++++++---------- ...alOffload.hh => LocalOpticalGenOffload.hh} | 8 +++---- src/accel/SetupOptions.hh | 2 +- src/accel/detail/IntegrationSingleton.cc | 10 ++++---- src/accel/detail/IntegrationSingleton.hh | 4 ++-- 6 files changed, 25 insertions(+), 25 deletions(-) rename src/accel/{LocalOpticalOffload.cc => LocalOpticalGenOffload.cc} (91%) rename src/accel/{LocalOpticalOffload.hh => LocalOpticalGenOffload.hh} (92%) diff --git a/src/accel/CMakeLists.txt b/src/accel/CMakeLists.txt index d374f68840..4ffd78e95b 100644 --- a/src/accel/CMakeLists.txt +++ b/src/accel/CMakeLists.txt @@ -30,7 +30,7 @@ list(APPEND SOURCES GeantSimpleCalo.cc GeantStepDiagnostic.cc IntegrationBase.cc - LocalOpticalOffload.cc + LocalOpticalGenOffload.cc LocalOpticalTrackOffload.cc LocalTransporter.cc Logger.cc diff --git a/src/accel/LocalOpticalOffload.cc b/src/accel/LocalOpticalGenOffload.cc similarity index 91% rename from src/accel/LocalOpticalOffload.cc rename to src/accel/LocalOpticalGenOffload.cc index 8ef2870aae..ee6cc7ffc5 100644 --- a/src/accel/LocalOpticalOffload.cc +++ b/src/accel/LocalOpticalGenOffload.cc @@ -2,9 +2,9 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/LocalOpticalOffload.cc +//! \file accel/LocalOpticalGenOffload.cc //---------------------------------------------------------------------------// -#include "LocalOpticalOffload.hh" +#include "LocalOpticalGenOffload.hh" #include #include @@ -32,8 +32,8 @@ namespace celeritas /*! * Construct with options and shared data. */ -LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, - SharedParams& params) +LocalOpticalGenOffload::LocalOpticalGenOffload(SetupOptions const& options, + SharedParams& params) { CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, << "cannot create local optical offload when Celeritas " @@ -101,17 +101,17 @@ LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, /*! * Initialize with options and shared data. */ -void LocalOpticalOffload::Initialize(SetupOptions const& options, - SharedParams& params) +void LocalOpticalGenOffload::Initialize(SetupOptions const& options, + SharedParams& params) { - *this = LocalOpticalOffload(options, params); + *this = LocalOpticalGenOffload(options, params); } //---------------------------------------------------------------------------// /*! * Set the event ID and reseed the Celeritas RNG at the start of an event. */ -void LocalOpticalOffload::InitializeEvent(int id) +void LocalOpticalGenOffload::InitializeEvent(int id) { CELER_EXPECT(*this); CELER_EXPECT(id >= 0); @@ -132,7 +132,7 @@ void LocalOpticalOffload::InitializeEvent(int id) /*! * Buffer distribution data for generating optical photons. */ -void LocalOpticalOffload::Push(optical::GeneratorDistributionData const& data) +void LocalOpticalGenOffload::Push(optical::GeneratorDistributionData const& data) { CELER_EXPECT(*this); CELER_EXPECT(data); @@ -152,7 +152,7 @@ void LocalOpticalOffload::Push(optical::GeneratorDistributionData const& data) /*! * Generate and transport optical photons from the buffered distribution data. */ -void LocalOpticalOffload::Flush() +void LocalOpticalGenOffload::Flush() { CELER_EXPECT(*this); @@ -207,7 +207,7 @@ void LocalOpticalOffload::Flush() /*! * Get the accumulated action times. */ -auto LocalOpticalOffload::GetActionTime() const -> MapStrDbl +auto LocalOpticalGenOffload::GetActionTime() const -> MapStrDbl { CELER_EXPECT(*this); return transport_->get_action_times(*state_->aux()); @@ -217,7 +217,7 @@ auto LocalOpticalOffload::GetActionTime() const -> MapStrDbl /*! * Clear local data. */ -void LocalOpticalOffload::Finalize() +void LocalOpticalGenOffload::Finalize() { CELER_EXPECT(*this); diff --git a/src/accel/LocalOpticalOffload.hh b/src/accel/LocalOpticalGenOffload.hh similarity index 92% rename from src/accel/LocalOpticalOffload.hh rename to src/accel/LocalOpticalGenOffload.hh index 48039b849d..343c27860b 100644 --- a/src/accel/LocalOpticalOffload.hh +++ b/src/accel/LocalOpticalGenOffload.hh @@ -2,7 +2,7 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/LocalOpticalOffload.hh +//! \file accel/LocalOpticalGenOffload.hh //---------------------------------------------------------------------------// #pragma once @@ -33,7 +33,7 @@ class SharedParams; /*! * Manage offloading of optical distribution data to Celeritas. */ -class LocalOpticalOffload final : public LocalOffloadInterface +class LocalOpticalGenOffload final : public LocalOffloadInterface { public: //!@{ @@ -43,10 +43,10 @@ class LocalOpticalOffload final : public LocalOffloadInterface public: // Construct in an invalid state - LocalOpticalOffload() = default; + LocalOpticalGenOffload() = default; // Construct with shared (across threads) params - LocalOpticalOffload(SetupOptions const& options, SharedParams& params); + LocalOpticalGenOffload(SetupOptions const& options, SharedParams& params); //!@{ //! \name LocalOffload interface diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index d4d40e60c1..16d4f65b02 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -147,7 +147,7 @@ struct OpticalSetupOptions * disables Celeritas offloading and immediately kills the \c offload_particles * in Geant4. The only expected use case for an empty \c offload_particles * vector is when offloading optical distribution data to Celeritas through the - * \c LocalOpticalOffload. + * \c LocalOpticalGenOffload. * * Note that the Celeritas core capacity values (\c max_num_tracks, \c * initializer_capacity and \c auto_flush) are per \em stream while the \c diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 600c301a5d..60f433f485 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -97,7 +97,7 @@ LocalTransporter& IntegrationSingleton::local_transporter() auto* lt = dynamic_cast(offload.get()); CELER_VALIDATE(lt, << "Cannot access LocalTransporter when " - "LocalOpticalOffload is being used"); + "LocalOpticalGenOffload is being used"); return *lt; } @@ -105,16 +105,16 @@ LocalTransporter& IntegrationSingleton::local_transporter() /*! * Static THREAD-LOCAL Celeritas optical state data. */ -LocalOpticalOffload& IntegrationSingleton::local_optical_offload() +LocalOpticalGenOffload& IntegrationSingleton::local_optical_offload() { auto& offload = IntegrationSingleton::local_offload_ptr(); if (!offload) { - offload = std::make_unique(); + offload = std::make_unique(); } - auto* lt = dynamic_cast(offload.get()); + auto* lt = dynamic_cast(offload.get()); CELER_VALIDATE(lt, - << "Cannot access LocalOpticalOffload when " + << "Cannot access LocalOpticalGenOffload when " "LocalTransporter is being used"); return *lt; } diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index 5faddb3325..b5af63b24f 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -11,7 +11,7 @@ #include "corecel/sys/Stopwatch.hh" #include "accel/LocalOpticalTrackOffload.hh" -#include "../LocalOpticalOffload.hh" +#include "../LocalOpticalGenOffload.hh" #include "../LocalTransporter.hh" #include "../SetupOptions.hh" #include "../SharedParams.hh" @@ -53,7 +53,7 @@ class IntegrationSingleton static LocalTransporter& local_transporter(); // Static THREAD-LOCAL Celeritas optical state data - static LocalOpticalOffload& local_optical_offload(); + static LocalOpticalGenOffload& local_optical_offload(); // Static Thread-local Celeritas optical track offload static LocalOpticalTrackOffload& local_optical_track_offload(); From 91c9469dd864812c46669d67ac1e9d11b2f9f9e2 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Fri, 19 Dec 2025 15:06:08 -0500 Subject: [PATCH 16/60] cleanup --- src/accel/LocalOpticalTrackOffload.cc | 92 ++++++------------------ src/accel/LocalOpticalTrackOffload.hh | 51 ++++++------- src/accel/LocalTransporter.hh | 2 - src/accel/SetupOptions.hh | 2 +- src/accel/SharedParams.cc | 1 - src/accel/TrackOffloadInterface.hh | 11 ++- src/accel/TrackingManagerConstructor.hh | 2 +- src/accel/detail/IntegrationSingleton.cc | 10 ++- src/accel/detail/IntegrationSingleton.hh | 4 +- test/accel/CMakeLists.txt | 7 ++ test/accel/UserActionIntegration.test.cc | 20 +++++- 11 files changed, 88 insertions(+), 114 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index e7ec7d10b1..8bdef16cff 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -13,7 +13,6 @@ #include "geocel/GeantUtils.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/optical/CoreParams.hh" -#include "celeritas/optical/CoreState.hh" #include "celeritas/optical/Transporter.hh" #include "SetupOptions.hh" @@ -23,7 +22,7 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * + * Offload Geant4 optical photon tracks to Celeritas */ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, SharedParams& params) @@ -38,11 +37,13 @@ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, // Save a pointer to the optical transporter transport_ = params.optical_transporter(); + CELER_ASSERT(transport_); CELER_ASSERT(transport_->params()); + auto const& optical_params = *transport_->params(); - // Number of optical tracks to buffer before offloading + CELER_EXPECT(options.optical); auto const& capacity = options.optical->capacity; auto_flush_ = capacity.tracks; @@ -91,54 +92,23 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) CELER_EXPECT(id >= 0); event_id_ = id_cast(id); - if (!(G4Threading::IsMultithreadedApplication() - && G4MTRunManager::SeedOncePerCommunication())) - { - // Since Geant4 schedules events dynamically, reseed the Celeritas - // RNGs - // using the Geant4 event ID for reproducibility. This guarantees - // that - // an event can be reproduced given the event ID. - state_->reseed(transport_->params()->rng(), id_cast(id)); - } } //---------------------------------------------------------------------------// /*! - * Buffer distribution data for generating optical photons. + * Buffer optical tracks. */ void LocalOpticalTrackOffload::Push(G4Track& g4track) { CELER_EXPECT(*this); + ++num_pushed_; - TrackData init; CELER_EXPECT(g4track.GetDefinition()); CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); - // Energy: convert Geant4 energy [MeV] to Celeritas MevEnergy - init.energy = units::MevEnergy{ - static_cast(g4track.GetTotalEnergy() / CLHEP::MeV)}; - - // Position: Geant4 uses mm; Celeritas uses cm - auto const& pos = g4track.GetPosition(); - init.position = Real3{static_cast(pos.x() / CLHEP::cm), - static_cast(pos.y() / CLHEP::cm), - static_cast(pos.z() / CLHEP::cm)}; - - auto const& dir = g4track.GetMomentumDirection(); - init.direction = Real3{static_cast(dir.x()), - static_cast(dir.y()), - static_cast(dir.z())}; - - // Polarization: directly from G4 - auto const& pol = g4track.GetPolarization(); - init.polarization = Real3{static_cast(pol.x()), - static_cast(pol.y()), - static_cast(pol.z())}; - - // Time: Geant4 uses ns; Celeritas uses seconds - init.time = g4track.GetGlobalTime() / CLHEP::s; + // TODO : Populate optical::TrackInitializer from Geant4 Track + TrackData init; ScopedProfiling profile_this{"push"}; @@ -150,64 +120,43 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) this->Flush(); } } + //---------------------------------------------------------------------------// /*! - * Generate and transport optical photons from the buffered distribution data. + * Flush buffered optical photon tracks. */ void LocalOpticalTrackOffload::Flush() { CELER_EXPECT(*this); + if (buffer_.empty()) { return; } - ScopedProfiling profile_this("flush"); + // Number of flushed optical tracks + ++num_flushed_; - //! \todo Duplicated in \c LocalTransporter - if (event_manager_ || !event_id_) - { - if (CELER_UNLIKELY(!event_manager_)) - { - // Save the event manager pointer, thereby marking that - // *subsequent* events need to have their IDs checked as well - event_manager_ = G4EventManager::GetEventManager(); - CELER_ASSERT(event_manager_); - } - - G4Event const* event = event_manager_->GetConstCurrentEvent(); - CELER_ASSERT(event); - if (event_id_ != id_cast(event->GetEventID())) - { - // The event ID has changed: reseed it - this->InitializeEvent(event->GetEventID()); - } - } - CELER_ASSERT(event_id_); + // TODO insert buffered track into + // optical CoreState and execute optical transport. + // state_->insert_primaries(make_span(buffer_)); - // Inject buffered tracks into optical state - ++num_flushed_; - // state_->insert_primaries(make_span(buffer_)); - // ToDo Skipping optical transport (WIP) buffer_.clear(); pending_tracks_ = 0; - - // Generate optical photons and transport to completion - // (*transport_)(*state_); } //---------------------------------------------------------------------------// -/*! - * Get the accumulated action times. - */ auto LocalOpticalTrackOffload::GetActionTime() const -> MapStrDbl { CELER_EXPECT(*this); + // TODO Add Per-track optical transport action timing once + // optical track insertion and transport are implemented. return transport_->get_action_times(*state_->aux()); } + //---------------------------------------------------------------------------// /*! - * Clear local data. + * Finalize the local optical track offload state */ void LocalOpticalTrackOffload::Finalize() { @@ -220,7 +169,6 @@ void LocalOpticalTrackOffload::Finalize() << " optical tracks pushed (over " << num_flushed_ << " ) flushes"; - // Reset all data *this = {}; CELER_ENSURE(!*this); diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index 6aef13e92f..6200d3175e 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -9,13 +9,9 @@ #include "corecel/Types.hh" #include "celeritas/Types.hh" #include "celeritas/optical/TrackInitializer.hh" -#include "celeritas/optical/Transporter.hh" -#include "accel/TrackOffloadInterface.hh" #include "TrackOffloadInterface.hh" -class G4EventManager; - namespace celeritas { namespace optical @@ -29,17 +25,17 @@ class SharedParams; //---------------------------------------------------------------------------// /*! - * Brief class description. - * - * Optional detailed class description, and possibly example usage: - * \code - LocalOpticalTrackOffload ...; - \endcode + * Offload Geant4 optical photon tracks to Celeritas. */ class LocalOpticalTrackOffload final : public TrackOffloadInterface { public: + //!@{ + //! \name Type aliases using TrackData = optical::TrackInitializer; + //!@} + + public: // Construct in an invalid state LocalOpticalTrackOffload() = default; @@ -47,7 +43,9 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface LocalOpticalTrackOffload(SetupOptions const& options, SharedParams& params); //!@{ - //! \name Type aliases + //! \name TrackOffloadInterface + + // Initialize with options and shared data void Initialize(SetupOptions const&, SharedParams&) final; // Set the event ID and reseed the Celeritas RNG at the start of an event @@ -60,36 +58,41 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface void Finalize() final; // Whether the class instance is initialized - bool Initialized() const final { return static_cast(state_); } - // Offload optical distribution data to Celeritas - void Push(G4Track&) final; + bool Initialized() const final { return static_cast(transport_); } + // Number of buffered tracks size_type GetBufferSize() const final { return pending_tracks_; } - // Optical tracks pushed - size_type num_pushed() const { return num_pushed_; } // Get accumulated action times MapStrDbl GetActionTime() const final; //!@} + // Offload optical distribution track to Celeritas + void Push(G4Track&) final; + private: // Transport pending optical tracks std::shared_ptr transport_; + // Thread-local state data std::shared_ptr state_; + // Buffered tracks for offloading std::vector buffer_; - size_type pending_tracks_{}; + // Number of photons tracks to buffer before offloading size_type auto_flush_{}; - // size_type num_pushed_{}; - // Diagnostics (thread-local) - size_type num_pushed_{0}; - size_type num_flushed_{0}; - // size_type num_events_{0}; - // Current event ID or manager for obtaining it + + // Accumulated number of optical photon tracks + size_type num_pushed_{}; + + // Accumulated number of tracks pushed over flushes + size_type num_flushed_{}; + + size_type pending_tracks_{}; + + // Current event ID for obtaining it UniqueEventId event_id_; - G4EventManager* event_manager_{nullptr}; }; //---------------------------------------------------------------------------// diff --git a/src/accel/LocalTransporter.hh b/src/accel/LocalTransporter.hh index 383c26a47e..ef3e299b28 100644 --- a/src/accel/LocalTransporter.hh +++ b/src/accel/LocalTransporter.hh @@ -49,8 +49,6 @@ class StepperInterface; * * \warning Due to Geant4 thread-local allocators, this class \em must be * finalized or destroyed on the same CPU thread in which is created and used! - * - * \todo Rename \c LocalOffload or something? */ class LocalTransporter final : public TrackOffloadInterface { diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 16d4f65b02..7fd67512a9 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -129,7 +129,7 @@ struct OpticalSetupOptions size_type max_steps{inp::TrackingLimits::unlimited}; //! Limit on number of optical step iterations before aborting size_type max_step_iters{inp::TrackingLimits::unlimited}; - + //! Optical photon track offload option bool offload_tracks{false}; }; diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 4450379f73..23cfb8acee 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -342,7 +342,6 @@ SharedParams::SharedParams(SetupOptions const& options) { optical_transporter_ = std::move(loaded.problem.optical_transporter); - CELER_LOG(info) << "could break here in optical offload"; CELER_ASSERT(optical_transporter_); } else diff --git a/src/accel/TrackOffloadInterface.hh b/src/accel/TrackOffloadInterface.hh index 004af0bcb6..2ced703342 100644 --- a/src/accel/TrackOffloadInterface.hh +++ b/src/accel/TrackOffloadInterface.hh @@ -14,18 +14,15 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * Brief class description. - * - * Optional detailed class description, and possibly example usage: - * \code - TrackOffloadInterface ...; - \endcode + * Interface for offloading complete Geant4 tracks to Celeritas. + * It allows the Geant4 tracking manager to forward full + * track to Celeritas, such as EM or optical track transport. */ class TrackOffloadInterface : public LocalOffloadInterface { public: // Construct with defaults - virtual ~TrackOffloadInterface() = default; + ~TrackOffloadInterface() override = default; // Push a full Geant4 track to Celeritas virtual void Push(G4Track&) = 0; diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 52c30daf07..1efdc91f2b 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -76,7 +76,7 @@ class TrackingManagerConstructor final : public G4VPhysicsConstructor //! Get the shared params associated with this TM SharedParams const* shared_params() const { return shared_; } - // Get the local transporter associated with the current thread ID + // Get the track transporter associated with the current thread ID TrackOffloadInterface* get_local_transporter() const; private: diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 60f433f485..bdf3033294 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -121,14 +121,14 @@ LocalOpticalGenOffload& IntegrationSingleton::local_optical_offload() //---------------------------------------------------------------------------// /*! - * Static THREAD-LOCAL Celeritas optical state data. + * Static thread-local Celeritas optical track. */ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() { auto& offload = IntegrationSingleton::local_offload_ptr(); if (!offload) { - CELER_LOG(info) << "Optical track offload enabled"; + CELER_LOG(info) << "Optical track offload initialised"; offload = std::make_unique(); } auto* lt = dynamic_cast(offload.get()); @@ -397,7 +397,7 @@ auto IntegrationSingleton::local_offload_ptr() -> UPOffload& //---------------------------------------------------------------------------// /*! - * Whether the local optical offload is used. + * Whether the local optical generator offload is used. */ bool IntegrationSingleton::optical_offload() const { @@ -406,6 +406,10 @@ bool IntegrationSingleton::optical_offload() const options_.optical->generator); } +//---------------------------------------------------------------------------// +/*! + * Whether the local optical track offload is used. + */ bool IntegrationSingleton::optical_track_offload() const { return options_.optical && options_.optical->offload_tracks; diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index b5af63b24f..e17eff7f64 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -58,8 +58,7 @@ class IntegrationSingleton // Static Thread-local Celeritas optical track offload static LocalOpticalTrackOffload& local_optical_track_offload(); - // Thread-local offload object for particles handled by the tracking - // manager + // Access thread-local track offload interface TrackOffloadInterface& local_track_offload(); //// ACCESSORS //// @@ -129,6 +128,7 @@ class IntegrationSingleton // Whether offloading optical distribution data is enabled bool optical_offload() const; + // Whether offloading optical track is enabled bool optical_track_offload() const; // Set up or update logging if the run manager is enabled diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index 6ddd92f3f3..b98be49de4 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -122,4 +122,11 @@ celeritas_add_integration_tests( RMTYPE ${_optical_rm_type} ) +# Test with optical track offloading +celeritas_add_integration_tests( + UserActionIntegration LarSphereOpticalTrackOffload run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + #-----------------------------------------------------------------------------# diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 40700e0396..d1aa0db800 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -301,6 +301,9 @@ class LSOOTrackingAction final : public G4UserTrackingAction }; //---------------------------------------------------------------------------// +/*! + * Offload optical tracks. + */ class LarSphereOpticalTrackOffload : public LarSphere { public: @@ -312,10 +315,15 @@ class LarSphereOpticalTrackOffload : public LarSphere } }; +//---------------------------------------------------------------------------// +/*! + * Enable optical physics + */ auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput { auto result = LarSphereIntegrationMixin::make_physics_input(); + // Set default optical physics auto& optical = result.optical; optical = {}; @@ -329,6 +337,10 @@ auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput return result; } +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking offloading. + */ auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions { auto result = LarSphereIntegrationMixin::make_setup_options(); @@ -342,13 +354,17 @@ auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions return opt; }(); - // result.offload_particles = {}; + // Don't offload any particles result.offload_particles = SetupOptions::VecG4PD{}; return result; } +//---------------------------------------------------------------------------// +/*! + * Tracking action for pushing optical tracks to Celeritas. + */ void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) { CELER_EXPECT(track); @@ -361,6 +377,7 @@ void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) // Optical track offload path if (track->GetDefinition() == G4OpticalPhoton::Definition()) { + // optical photon track offloading is enabled auto& opt_local = detail::IntegrationSingleton::local_optical_track_offload(); if (opt_local) @@ -373,6 +390,7 @@ void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) } } +//---------------------------------------------------------------------------// TEST_F(LarSphereOpticalTrackOffload, run) { auto& rm = this->run_manager(); From fc4a18e0aa0e5d372050bbe89d99214df3f65e65 Mon Sep 17 00:00:00 2001 From: Elliott Biondo Date: Sat, 20 Dec 2025 06:42:49 -0500 Subject: [PATCH 17/60] Fix ExCL env script (#2171) Fix small bug in Excl evn script --- scripts/env/excl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/env/excl.sh b/scripts/env/excl.sh index 86e85ca332..ea18e61405 100755 --- a/scripts/env/excl.sh +++ b/scripts/env/excl.sh @@ -14,7 +14,7 @@ if ! command -v celerlog >/dev/null 2>&1; then } fi if test -z "${SYSTEM_NAME}"; then - SYSTEM_NAME=$(uname -s) + SYSTEM_NAME=$(hostname -s) celerlog debug "Set SYSTEM_NAME=${SYSTEM_NAME}" fi From 9fa3028d3bbf4508e8cc07d51dc7bf2399fa9853 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Sun, 21 Dec 2025 16:58:35 -0500 Subject: [PATCH 18/60] Add a `celer-config` app and milan2 config (#2172) * Add milan2 system * Add milan2 presets * IWYU * Rename output_to_json method * Add celer-config app * fixup! IWYU * Replace CMakeUserPresets symlink if invalid * Print configuration if any tests fail * Add CODATA constants to build output --- app/CMakeLists.txt | 18 ++++- app/celer-config.cc | 106 ++++++++++++++++++++++++++ app/celer-geo/celer-geo.cc | 4 +- app/celer-sim/celer-sim.cc | 2 +- app/orange-update.cc | 8 +- scripts/build.sh | 24 +++--- scripts/cmake-presets/milan2.json | 95 +++++++++++++++++++++++ scripts/env/milan2.sh | 19 +++++ src/corecel/CMakeLists.txt | 1 + src/corecel/io/BuildOutput.cc | 1 + src/corecel/io/JsonPimpl.hh | 5 +- src/corecel/io/OutputInterface.cc | 2 - src/orange/orangeinp/ObjectIO.json.cc | 2 +- 13 files changed, 261 insertions(+), 26 deletions(-) create mode 100644 app/celer-config.cc create mode 100644 scripts/cmake-presets/milan2.json create mode 100755 scripts/env/milan2.sh diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 5afc83a5ef..9250d28f00 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -48,9 +48,25 @@ celeritas_target_include_directories(celer_app_utils ) #-----------------------------------------------------------------------------# -# Geant4/ROOT data interface +# Helper apps #-----------------------------------------------------------------------------# +# Configuration output +celeritas_add_executable(celer-config celer-config.cc) +celeritas_target_link_libraries(celer-config + Celeritas::corecel + nlohmann_json::nlohmann_json + CLI11::CLI11 + celer_app_utils +) + +# Target to run celer-config +add_custom_target(get-config + COMMAND "$" "-d" "--indent" -1 + VERBATIM + DEPENDS celer-config +) + # Orange JSON updater script celeritas_add_executable(orange-update orange-update.cc) celeritas_target_link_libraries(orange-update diff --git a/app/celer-config.cc b/app/celer-config.cc new file mode 100644 index 0000000000..910c4c5804 --- /dev/null +++ b/app/celer-config.cc @@ -0,0 +1,106 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celer-config.cc +//! \brief Write the Celeritas JSON configuration to stdout +//---------------------------------------------------------------------------// +#include +#include +#include +#include +#include + +#include "corecel/Assert.hh" +#include "corecel/AssertIO.json.hh" // IWYU pragma keep +#include "corecel/io/BuildOutput.hh" +#include "corecel/io/ExceptionOutput.hh" +#include "corecel/io/JsonPimpl.hh" +#include "corecel/io/Logger.hh" +#include "corecel/sys/Device.hh" +#include "corecel/sys/DeviceIO.json.hh" // IWYU pragma keep +#include "corecel/sys/ScopedMpiInit.hh" + +#include "CliUtils.hh" + +namespace celeritas +{ +namespace app +{ +namespace +{ +//---------------------------------------------------------------------------// +struct Args +{ + int indent{1}; + bool show_device{false}; +}; + +//---------------------------------------------------------------------------// +nlohmann::json device_to_json() +{ + if (Device::num_devices() == 0) + { + CELER_LOG(info) << "No GPUs were detected"; + return nullptr; + } + + try + { + celeritas::activate_device(); + CELER_VALIDATE(celeritas::Device::num_devices() != 0, + << "no GPUs were detected"); + } + catch (...) + { + return output_to_json(ExceptionOutput{std::current_exception()}); + } + + return celeritas::device(); +} + +void run(Args const& args) +{ + CELER_VALIDATE(args.indent >= -1 && args.indent < 80, + << "invalid indentation " << args.indent); + + auto result = output_to_json(BuildOutput{}); + if (args.show_device) + { + result["device"] = device_to_json(); + } + + std::cout << result.dump(/* indent = */ args.indent) << std::endl; +} + +//---------------------------------------------------------------------------// +} // namespace +} // namespace app +} // namespace celeritas + +//---------------------------------------------------------------------------// +/*! + * Execute and run. + */ +int main(int argc, char* argv[]) +{ + using namespace celeritas::app; + + celeritas::ScopedMpiInit scoped_mpi(&argc, &argv); + if (scoped_mpi.is_world_multiprocess()) + { + CELER_LOG(critical) << "This app cannot run in parallel"; + return EXIT_FAILURE; + } + + auto& cli = cli_app(); + cli.description("Write the Celeritas build configuration to stdout"); + + Args args; + cli.add_flag("-d,--device", args.show_device, "Activate and query GPU"); + cli.add_option("-i,--indent", args.indent, "JSON indentation") + ->default_val(args.indent); + + CELER_CLI11_PARSE(argc, argv); + return run_safely(run, args); +} diff --git a/app/celer-geo/celer-geo.cc b/app/celer-geo/celer-geo.cc index 6880db078e..d6ad994ca8 100644 --- a/app/celer-geo/celer-geo.cc +++ b/app/celer-geo/celer-geo.cc @@ -97,7 +97,7 @@ void put_json_line(nlohmann::json const& j) */ void put_json_line(OutputInterface const& oi) { - return put_json_line(json_pimpl_output(oi)); + return put_json_line(output_to_json(oi)); } //---------------------------------------------------------------------------// @@ -336,7 +336,7 @@ void run(std::string const& filename) {"device", device()}, {"kernels", kernel_registry()}, {"environment", environment()}, - {"build", json_pimpl_output(BuildOutput{})}, + {"build", output_to_json(BuildOutput{})}, }, }, })); diff --git a/app/celer-sim/celer-sim.cc b/app/celer-sim/celer-sim.cc index f291c44fb9..8b03b959e3 100644 --- a/app/celer-sim/celer-sim.cc +++ b/app/celer-sim/celer-sim.cc @@ -166,7 +166,7 @@ std::string get_device_string() celeritas::activate_device(); CELER_VALIDATE(celeritas::Device::num_devices() != 0, - << "No GPUs were detected"); + << "no GPUs were detected"); return nlohmann::json(celeritas::device()).dump(1); } diff --git a/app/orange-update.cc b/app/orange-update.cc index c02130e182..4a0b4b7f3c 100644 --- a/app/orange-update.cc +++ b/app/orange-update.cc @@ -6,20 +6,14 @@ //! \brief Read in and write back an ORANGE JSON file //---------------------------------------------------------------------------// #include -#include -#include #include -#include #include #include -#include "corecel/Config.hh" - -#include "corecel/Assert.hh" #include "corecel/io/FileOrConsole.hh" #include "corecel/io/Logger.hh" #include "corecel/sys/ScopedMpiInit.hh" -#include "orange/OrangeInputIO.json.hh" +#include "orange/OrangeInputIO.json.hh" // IWYU pragma: keep #include "CliUtils.hh" diff --git a/scripts/build.sh b/scripts/build.sh index af430e639f..39a409b0be 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -98,11 +98,15 @@ ln_presets() { # Return early if it exists if [ -L "${dst}" ]; then - src="$(readlink "${dst}" 2>/dev/null || printf "")" - log debug "CMake preset already exists: ${dst} -> ${src}" - return - elif [ -e "${dst}" ]; then - log debug "CMake preset already exists: ${dst}" + actual="$(readlink "${dst}" 2>/dev/null || printf "")" + if [ "${src}" = "${actual}" ]; then + log debug "CMake preset already exists: ${dst} -> ${actual}" + return + else + log warning "${dst} points to ${actual}, not ${src}: overwriting" + fi + elif [ -e "${src}" ]; then + log warning "${dst} already exists but is not a symlink to ${src}" return fi @@ -113,7 +117,7 @@ ln_presets() { git add "${src}" || log error "Could not stage presets" fi log info "Linking presets to ${dst}" - ln -s "${src}" "${dst}" + ln -f -s "${src}" "${dst}" } # Check if ccache is full and warn user @@ -288,16 +292,18 @@ CMAKE_PRESET=$1 shift # Configure, build, and test -log info "Configuring with verbosity" +log info "Configuring with --preset=${CMAKE_PRESET} --log-level=VERBOSE $@" cmake --preset="${CMAKE_PRESET}" --log-level=VERBOSE "$@" -log info "Building" +log info "Building with --preset=${CMAKE_PRESET}" if cmake --build --preset="${CMAKE_PRESET}"; then - log info "Testing" + log info "Testing with --preset=${CMAKE_PRESET} --timeout 15" if ctest --preset="${CMAKE_PRESET}" --timeout 15; then log info "Celeritas was successfully built and tested for development!" else log warning "Celeritas built but some tests failed" log info "Ask the Celeritas team whether the failures indicate an actual error" + log info "Provide the system configuration:" + cmake --build-target get-config --preset=${CMAKE_PRESET} fi install_precommit_if_git diff --git a/scripts/cmake-presets/milan2.json b/scripts/cmake-presets/milan2.json new file mode 100644 index 0000000000..c2e2823076 --- /dev/null +++ b/scripts/cmake-presets/milan2.json @@ -0,0 +1,95 @@ +{ + "version": 3, + "cmakeMinimumRequired": {"major": 3, "minor": 21, "patch": 0}, + "configurePresets": [ + { + "name": ".base", + "hidden": true, + "inherits": [".cuda", "full", ".spack-base"], + "binaryDir": "/scratch/$env{USER}/build/celeritas-${presetName}", + "generator": "Ninja", + "cacheVariables": { + "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"}, + "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", + "CMAKE_CXX_STANDARD": "20", + "CMAKE_CXX_COMPILER": "/usr/bin/c++", + "CMAKE_CXX_EXTENSIONS": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CUDA_FLAGS": "-Werror all-warnings -Wno-deprecated-gpu-targets", + "CMAKE_CUDA_HOST_COMPILER": "/usr/bin/c++", + "CMAKE_CUDA_ARCHITECTURES": "70", + "CMAKE_INSTALL_PREFIX": "/scratch/$env{USER}/install/celeritas-${presetName}", + "CMAKE_EXPORT_COMPILE_COMMANDS": {"type": "BOOL", "value": "ON"} + } + }, + { + "name": ".orange", + "hidden": true, + "cacheVariables": { + "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, + "CMAKE_EXE_LINKER_FLAGS": "-Wl,-z,defs", + "CMAKE_SHARED_LINKER_FLAGS": "-Wl,-z,defs" + } + }, + { + "name": "release", + "displayName": "Build with full optimizations (VecGeom)", + "inherits": [".base", ".ndebug"] + }, + { + "name": "reldeb", + "displayName": "Build with optimizations, but enable host debugging (VecGeom)", + "inherits": [".base", ".reldeb"], + "cacheVariables": { + "CELERITAS_DEVICE_DEBUG":{"type": "BOOL", "value": "OFF"} + } + }, + { + "name": "release-orange", + "displayName": "Build with full optimizations (ORANGE)", + "inherits": [".orange", "release"] + }, + { + "name": "reldeb-orange", + "displayName": "Build with optimizations, but enable host debugging (ORANGE)", + "inherits": [".orange", "reldeb"] + }, + { + "name": "debug-orange", + "displayName": "Build without optimization, enabling full host debugging (ORANGE)", + "inherits": [".orange", ".base", ".debug"], + "cacheVariables": { + "CELERITAS_DEVICE_DEBUG":{"type": "BOOL", "value": "OFF"} + } + } + ], + "buildPresets": [ + { + "name": ".base", + "configurePreset": ".base", + "jobs": 48, + "nativeToolOptions": ["-k0"] + }, + {"name": "release", "configurePreset": "release", "inherits": ".base"}, + {"name": "reldeb", "configurePreset": "reldeb", "inherits": ".base"}, + {"name": "release-orange", "configurePreset": "release-orange", "inherits": ".base"}, + {"name": "reldeb-orange", "configurePreset": "reldeb-orange", "inherits": ".base"}, + {"name": "debug-orange", "configurePreset": "debug-orange", "inherits": ".base"} + ], + "testPresets": [ + { + "name": ".base", + "configurePreset": ".base", + "output": {"outputOnFailure": true}, + "execution": {"noTestsAction": "error", "stopOnFailure": false, "jobs": 8} + }, + {"name": "release", "configurePreset": "release", "inherits": ".base"}, + {"name": "reldeb", "configurePreset": "reldeb", "inherits": ".base"}, + {"name": "release-orange", "configurePreset": "release-orange", "inherits": ".base"}, + {"name": "reldeb-orange", "configurePreset": "reldeb-orange", "inherits": ".base"}, + {"name": "debug-orange", "configurePreset": "debug-orange", "inherits": ".base"} + ] +} diff --git a/scripts/env/milan2.sh b/scripts/env/milan2.sh new file mode 100755 index 0000000000..33e91372c8 --- /dev/null +++ b/scripts/env/milan2.sh @@ -0,0 +1,19 @@ +#!/bin/sh -ex +#-------------------------------- -*- sh -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# + +if ! command -v load_system_env >/dev/null 2>&1; then + printf "error: expected load_system_env helper function via build.sh or shell\n" >&2 + return 1 +fi + +# Redundant with cmake prefix but useful if this is being used for other env +export CUDAARCHS=70 +# Set C++ compiler +export CXX=/usr/bin/c++ +export CC=/usr/bin/cc + +# Dispatch common loading to the 'excl' system +load_system_env excl || return $? diff --git a/src/corecel/CMakeLists.txt b/src/corecel/CMakeLists.txt index dd644c2a3a..124a4844e5 100644 --- a/src/corecel/CMakeLists.txt +++ b/src/corecel/CMakeLists.txt @@ -76,6 +76,7 @@ else() string(APPEND CELERITAS_BUILD_TYPE ",pic") endif() endif() +set(CELERITAS_CONSTANTS "CODATA ${CELERITAS_CODATA}") foreach(_var BUILD_TYPE HOSTNAME REAL_TYPE UNITS CONSTANTS OPENMP CORE_GEO CORE_RNG) string(TOLOWER "${_var}" _lower) celeritas_append_cmake_string("${_lower}" "${CELERITAS_${_var}}") diff --git a/src/corecel/io/BuildOutput.cc b/src/corecel/io/BuildOutput.cc index ba96f93496..da311cc516 100644 --- a/src/corecel/io/BuildOutput.cc +++ b/src/corecel/io/BuildOutput.cc @@ -60,6 +60,7 @@ void BuildOutput::output(JsonPimpl* j) const CO_ADD_CFG(hostname); CO_ADD_CFG(real_type); CO_ADD_CFG(units); + CO_ADD_CFG(constants); CO_ADD_CFG(openmp); CO_ADD_CFG(core_geo); CO_ADD_CFG(core_rng); diff --git a/src/corecel/io/JsonPimpl.hh b/src/corecel/io/JsonPimpl.hh index 0fea40a717..c0c6c6c6ed 100644 --- a/src/corecel/io/JsonPimpl.hh +++ b/src/corecel/io/JsonPimpl.hh @@ -8,8 +8,6 @@ #include -#include "corecel/Config.hh" - #include "corecel/Assert.hh" namespace celeritas @@ -46,8 +44,9 @@ void to_json_pimpl(JsonPimpl* jp, T const& self) to_json(jp->obj, self); } +//! Get a JSON object from an OutputInterface template -nlohmann::json json_pimpl_output(T const& self) +nlohmann::json output_to_json(T const& self) { JsonPimpl jp; self.output(&jp); diff --git a/src/corecel/io/OutputInterface.cc b/src/corecel/io/OutputInterface.cc index 46f4864b1f..fffaef82a1 100644 --- a/src/corecel/io/OutputInterface.cc +++ b/src/corecel/io/OutputInterface.cc @@ -9,8 +9,6 @@ #include #include -#include "corecel/Config.hh" - #include "EnumStringMapper.hh" #include "JsonPimpl.hh" diff --git a/src/orange/orangeinp/ObjectIO.json.cc b/src/orange/orangeinp/ObjectIO.json.cc index d7d3c796e0..98eb33788c 100644 --- a/src/orange/orangeinp/ObjectIO.json.cc +++ b/src/orange/orangeinp/ObjectIO.json.cc @@ -349,7 +349,7 @@ namespace nlohmann void adl_serializer::to_json(json& j, CelerSPObjConst const& oi) { - j = oi ? celeritas::json_pimpl_output(*oi) : json(nullptr); + j = oi ? celeritas::output_to_json(*oi) : json(nullptr); } void adl_serializer::to_json(json& j, From d62a25cfe5700d7fe5b5d1826d70c788e6b7fabc Mon Sep 17 00:00:00 2001 From: Soon Yung Jun Date: Mon, 22 Dec 2025 13:04:49 -0600 Subject: [PATCH 19/60] Add an initial support for electro-nuclear process and model (#2170) * Add support for the electro-nuclear process/model * Add initial skeleton for the electro-nuclear interactor * Add initial tests for electro-nuclear cross section calculations * Add electro-nuclear cross section calculation * Add electro-nuclear data, ElectroNuclearData * Add support for the electro-nuclear process/model * Add ElectroNuclearProcess test * Fixed undefined reference to calc_electro_nuclear_xs * Replace test points near the lower-bound region * Remove version dependent test points * Add missing comment separator line * Remove redundant CELER_EXPECT check * Move cross-section tabulation documentation to the method description * Fix a typo: change photon to particle --------- Co-authored-by: Soon Yung Jun --- src/celeritas/CMakeLists.txt | 2 + src/celeritas/em/data/ElectroNuclearData.hh | 89 ++++++++++ .../em/executor/ElectroNuclearExecutor.hh | 61 +++++++ .../em/interactor/ElectroNuclearInteractor.hh | 65 ++++++++ .../em/interactor/GammaNuclearInteractor.hh | 1 + src/celeritas/em/model/ElectroNuclearModel.cc | 154 ++++++++++++++++++ src/celeritas/em/model/ElectroNuclearModel.cu | 24 +++ src/celeritas/em/model/ElectroNuclearModel.hh | 81 +++++++++ src/celeritas/em/model/GammaNuclearModel.cc | 22 +-- .../em/process/ElectroNuclearProcess.cc | 65 ++++++++ .../em/process/ElectroNuclearProcess.hh | 62 +++++++ .../em/xs/ElectroNuclearMicroXsCalculator.hh | 83 ++++++++++ src/celeritas/g4/EmExtraPhysicsHelper.cc | 18 ++ src/celeritas/g4/EmExtraPhysicsHelper.hh | 15 +- src/celeritas/io/ImportProcess.cc | 2 + src/celeritas/io/ImportProcess.hh | 1 + src/celeritas/phys/PhysicsData.hh | 5 + src/celeritas/phys/PhysicsParams.cc | 14 ++ src/celeritas/phys/PhysicsTrackView.hh | 7 + src/celeritas/phys/ProcessBuilder.cc | 9 + src/celeritas/phys/ProcessBuilder.hh | 1 + test/celeritas/CMakeLists.txt | 2 + test/celeritas/em/ElectroNuclear.test.cc | 134 +++++++++++++++ test/celeritas/phys/ProcessBuilder.test.cc | 41 +++++ 24 files changed, 944 insertions(+), 14 deletions(-) create mode 100644 src/celeritas/em/data/ElectroNuclearData.hh create mode 100644 src/celeritas/em/executor/ElectroNuclearExecutor.hh create mode 100644 src/celeritas/em/interactor/ElectroNuclearInteractor.hh create mode 100644 src/celeritas/em/model/ElectroNuclearModel.cc create mode 100644 src/celeritas/em/model/ElectroNuclearModel.cu create mode 100644 src/celeritas/em/model/ElectroNuclearModel.hh create mode 100644 src/celeritas/em/process/ElectroNuclearProcess.cc create mode 100644 src/celeritas/em/process/ElectroNuclearProcess.hh create mode 100644 src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh create mode 100644 test/celeritas/em/ElectroNuclear.test.cc diff --git a/src/celeritas/CMakeLists.txt b/src/celeritas/CMakeLists.txt index 0003c831d5..8d7cc5269d 100644 --- a/src/celeritas/CMakeLists.txt +++ b/src/celeritas/CMakeLists.txt @@ -46,6 +46,7 @@ list(APPEND SOURCES em/process/BremsstrahlungProcess.cc em/process/ComptonProcess.cc em/process/CoulombScatteringProcess.cc + em/process/ElectroNuclearProcess.cc em/process/EIonizationProcess.cc em/process/EPlusAnnihilationProcess.cc em/process/GammaConversionProcess.cc @@ -357,6 +358,7 @@ celeritas_polysource(alongstep/AlongStepCylMapFieldMscAction) celeritas_polysource(em/model/BetheHeitlerModel) celeritas_polysource(em/model/BetheBlochModel) celeritas_polysource(em/model/BraggModel) +celeritas_polysource(em/model/ElectroNuclearModel) celeritas_polysource(em/model/EPlusGGModel) celeritas_polysource(em/model/GammaNuclearModel) celeritas_polysource(em/model/ICRU73QOModel) diff --git a/src/celeritas/em/data/ElectroNuclearData.hh b/src/celeritas/em/data/ElectroNuclearData.hh new file mode 100644 index 0000000000..dd9593f917 --- /dev/null +++ b/src/celeritas/em/data/ElectroNuclearData.hh @@ -0,0 +1,89 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/data/ElectroNuclearData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/Types.hh" +#include "corecel/data/Collection.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Scalar data for the electro-nuclear model. + */ +struct ElectroNuclearScalars +{ + // Particle IDs + ParticleId electron_id; + ParticleId positron_id; + + //! Model's minimum energy limit [MeV] + static CELER_CONSTEXPR_FUNCTION units::MevEnergy min_valid_energy() + { + return units::MevEnergy{1e+2}; + } + + //! Model's maximum energy limit [MeV] + static CELER_CONSTEXPR_FUNCTION units::MevEnergy max_valid_energy() + { + return units::MevEnergy{1e+8}; + } + + //! Whether data are assigned + explicit CELER_FUNCTION operator bool() const + { + return electron_id && positron_id; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Device data for calculating micro (element) cross sections. + */ +template +struct ElectroNuclearData +{ + template + using Items = Collection; + template + using ElementItems = Collection; + + //// MEMBER DATA //// + + // Scalar data + ElectroNuclearScalars scalars; + + // Microscopic cross sections using parameterized data + ElementItems micro_xs; + Items reals; + + //! Whether the data are assigned + explicit CELER_FUNCTION operator bool() const + { + return scalars && !micro_xs.empty() && !reals.empty(); + } + + //! Assign from another set of data + template + ElectroNuclearData& operator=(ElectroNuclearData const& other) + { + CELER_EXPECT(other); + scalars = other.scalars; + micro_xs = other.micro_xs; + reals = other.reals; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/executor/ElectroNuclearExecutor.hh b/src/celeritas/em/executor/ElectroNuclearExecutor.hh new file mode 100644 index 0000000000..ee74b01c70 --- /dev/null +++ b/src/celeritas/em/executor/ElectroNuclearExecutor.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/executor/ElectroNuclearExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/em/interactor/ElectroNuclearInteractor.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" +#include "celeritas/global/CoreTrackView.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/random/ElementSelector.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +struct ElectroNuclearExecutor +{ + inline CELER_FUNCTION Interaction + operator()(celeritas::CoreTrackView const& track); + + ElectroNuclearRef params; +}; + +//---------------------------------------------------------------------------// +/*! + * Apply the ElectroNuclearInteractor to the current track. + */ +CELER_FUNCTION Interaction +ElectroNuclearExecutor::operator()(CoreTrackView const& track) +{ + auto particle = track.particle(); + + // Select a target element + auto material = track.material().material_record(); + auto elcomp_id = track.physics_step().element(); + if (!elcomp_id) + { + // Sample an element (based on element cross sections on the fly) + ElementSelector select_el( + material, + ElectroNuclearMicroXsCalculator{params, particle.energy()}, + track.material().element_scratch()); + elcomp_id = select_el(rng); + CELER_ASSERT(elcomp_id); + track.physics_step().element(elcomp_id); + } + + // Construct the interactor + ElectroNuclearInteractor interact(params, particle); + + // Execute the interactor + return interact(); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/interactor/ElectroNuclearInteractor.hh b/src/celeritas/em/interactor/ElectroNuclearInteractor.hh new file mode 100644 index 0000000000..3224f1560b --- /dev/null +++ b/src/celeritas/em/interactor/ElectroNuclearInteractor.hh @@ -0,0 +1,65 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/interactor/ElectroNuclearInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Handle the electro-nuclear interaction using G4ElectroVDNuclearModel. + * + * The electro-nuclear interaction requires hadronic models for the final + * state generation, as described in section 45.2 of the Geant4 physics manual. + * When the electro-nuclear process is selected, the electromagnetic vertex + * of the electro-nucleus reaction is computed and the virtual photon is + * generated. The status is set to onload::electro_nuclear and the post step + * action of the converted real photon will be handled by Geant4, while the + * primary electron or position continues to be tracked. + */ +class ElectroNuclearInteractor +{ + public: + // Construct from shared and state data + inline CELER_FUNCTION + ElectroNuclearInteractor(NativeCRef const& shared, + ParticleTrackView const& particle); + + // Sample an interaction + inline CELER_FUNCTION Interaction operator()(); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data, and a target nucleus. + */ +CELER_FUNCTION +ElectroNuclearInteractor::ElectroNuclearInteractor( + NativeCRef const& shared, + ParticleTrackView const& particle) +{ + CELER_EXPECT(particle.particle_id() == shared.scalars.electron_id + || particle.particle_id() == shared.scalars.positron_id); +} + +//---------------------------------------------------------------------------// +/*! + * Onload the electro-nuclear interaction. + */ +CELER_FUNCTION Interaction ElectroNuclearInteractor::operator()() +{ + return Interaction::from_onloaded(); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/interactor/GammaNuclearInteractor.hh b/src/celeritas/em/interactor/GammaNuclearInteractor.hh index 81354de514..46eda0f66d 100644 --- a/src/celeritas/em/interactor/GammaNuclearInteractor.hh +++ b/src/celeritas/em/interactor/GammaNuclearInteractor.hh @@ -36,6 +36,7 @@ class GammaNuclearInteractor //---------------------------------------------------------------------------// // INLINE DEFINITIONS +//---------------------------------------------------------------------------// /*! * Construct with shared and state data, and a target nucleus. */ diff --git a/src/celeritas/em/model/ElectroNuclearModel.cc b/src/celeritas/em/model/ElectroNuclearModel.cc new file mode 100644 index 0000000000..a77c24d399 --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.cc @@ -0,0 +1,154 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.cc +//---------------------------------------------------------------------------// +#include "ElectroNuclearModel.hh" + +#include "corecel/Types.hh" +#include "corecel/grid/VectorUtils.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/g4/EmExtraPhysicsHelper.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" +#include "celeritas/grid/NonuniformGridInserter.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/phys/InteractionApplier.hh" +#include "celeritas/phys/PDGNumber.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from model ID and other necessary data. + */ +ElectroNuclearModel::ElectroNuclearModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials) + : StaticConcreteAction(id, "electro-nuclear", "interact by electro-nuclear") +{ + CELER_EXPECT(id); + + HostVal data; + + helper_ = std::make_shared(); + + // Save IDs + data.scalars.electron_id = particles.find(pdg::electron()); + data.scalars.positron_id = particles.find(pdg::positron()); + + CELER_VALIDATE(data.scalars.electron_id && data.scalars.positron_id, + << "missing particles (required for " << this->description() + << ")"); + + // Electro-nuclear element cross section data + NonuniformGridInserter insert_micro_xs{&data.reals, &data.micro_xs}; + + double const emin = data.scalars.min_valid_energy().value(); + double const emax = data.scalars.max_valid_energy().value(); + for (auto el_id : range(ElementId{materials.num_elements()})) + { + AtomicNumber z = materials.get(el_id).atomic_number(); + + // Build element cross sections + insert_micro_xs(this->calc_micro_xs(z, emin, emax)); + } + CELER_ASSERT(data.micro_xs.size() == materials.num_elements()); + + // Move to mirrored data, copying to device + data_ = CollectionMirror{std::move(data)}; + CELER_ENSURE(this->data_); +} + +//---------------------------------------------------------------------------// +/*! + * Particle types and energy ranges that this model applies to. + */ +auto ElectroNuclearModel::applicability() const -> SetApplicability +{ + Applicability electron_nuclear_appli; + electron_nuclear_appli.particle = this->host_ref().scalars.electron_id; + electron_nuclear_appli.lower = this->host_ref().scalars.min_valid_energy(); + electron_nuclear_appli.upper = this->host_ref().scalars.max_valid_energy(); + + Applicability positron_nuclear_appli = electron_nuclear_appli; + positron_nuclear_appli.particle = this->host_ref().scalars.positron_id; + + return {electron_nuclear_appli, positron_nuclear_appli}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the microscopic cross sections for the given particle and material. + */ +auto ElectroNuclearModel::micro_xs(Applicability) const -> XsTable +{ + // Cross sections are calculated on the fly + return {}; +} + +//---------------------------------------------------------------------------// +//!@{ +/*! + * Apply the interaction kernel. + */ +void ElectroNuclearModel::step(CoreParams const&, CoreStateHost&) const +{ + CELER_NOT_IMPLEMENTED("Electro-nuclear inelastic interaction"); +} + +//---------------------------------------------------------------------------// +#if !CELER_USE_DEVICE +void ElectroNuclearModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +/*! + * Build electro-nuclear element cross sections using G4ElectroNuclearXS. + * + * Tabulate cross sections using separate parameterizations for the high energy + * region (emin < E < 50 GeV) and the ultra high energy region up to the + * maximum valid energy (emax). The numbers of bins are chosen to adequately + * capture both parameterized points (336 bins from 2.0612 MeV to 50 GeV) and + * calculations used in G4ElectroNuclearCrossSection, which can be made + * configurable if needed (TODO). + */ +inp::Grid ElectroNuclearModel::calc_micro_xs(AtomicNumber z, + double emin, + double emax) const +{ + CELER_EXPECT(z); + + inp::Grid result; + + // Upper limit of parameterizations of electro-nuclear cross section [Mev] + double const emid = 5e+4; + + size_type nbin_total = 300; + size_type nbin_ultra = 50; + result.y.resize(nbin_total); + + result.x = geomspace(emin, emid, nbin_total - nbin_ultra); + result.x.pop_back(); + auto ultra = geomspace(emid, emax, nbin_ultra + 1); + result.x.insert(result.x.end(), ultra.begin(), ultra.end()); + + // Tabulate the cross section from emin to emax + for (size_type i = 0; i < nbin_total; ++i) + { + auto xs = helper_->calc_electro_nuclear_xs( + z, MevEnergy{static_cast(result.x[i])}); + result.y[i] + = native_value_to(native_value_from(xs)).value(); + } + + return result; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/ElectroNuclearModel.cu b/src/celeritas/em/model/ElectroNuclearModel.cu new file mode 100644 index 0000000000..ff1820cfdc --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.cu @@ -0,0 +1,24 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.cu +//---------------------------------------------------------------------------// +#include "ElectroNuclearModel.hh" + +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Interact with device data. + */ +void ElectroNuclearModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_IMPLEMENTED("Electro-nuclear interaction"); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/ElectroNuclearModel.hh b/src/celeritas/em/model/ElectroNuclearModel.hh new file mode 100644 index 0000000000..c48e6eb31f --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.hh @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include + +#include "corecel/data/CollectionMirror.hh" +#include "corecel/inp/Grid.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/phys/AtomicNumber.hh" +#include "celeritas/phys/Model.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +class EmExtraPhysicsHelper; + +//---------------------------------------------------------------------------// +/*! + * Set up and launch the electro-nuclear model interaction. + */ +class ElectroNuclearModel final : public Model, public StaticConcreteAction +{ + public: + //!@{ + using MevEnergy = units::MevEnergy; + using HostRef = HostCRef; + using DeviceRef = DeviceCRef; + //!@} + + public: + // Construct from model ID and other necessary data + ElectroNuclearModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials); + + // Particle types and energy ranges that this model applies to + SetApplicability applicability() const final; + + // Get the microscopic cross sections for the given particle and material + XsTable micro_xs(Applicability) const final; + + //! Apply the interaction kernel to host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Apply the interaction kernel to device data + void step(CoreParams const&, CoreStateDevice&) const final; + + //!@{ + //! Access model data + HostRef const& host_ref() const { return data_.host_ref(); } + DeviceRef const& device_ref() const { return data_.device_ref(); } + //!@} + + private: + //// DATA //// + std::shared_ptr helper_; + + // Host/device storage and reference + CollectionMirror data_; + + //// TYPES //// + + using HostXsData = HostVal; + + //// HELPER FUNCTIONS //// + + inp::Grid + calc_micro_xs(AtomicNumber atomic_number, double emin, double emax) const; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/GammaNuclearModel.cc b/src/celeritas/em/model/GammaNuclearModel.cc index e4591a3615..1cff6e885b 100644 --- a/src/celeritas/em/model/GammaNuclearModel.cc +++ b/src/celeritas/em/model/GammaNuclearModel.cc @@ -44,9 +44,6 @@ GammaNuclearModel::GammaNuclearModel(ActionId id, << "missing gamma (required for " << this->description() << ")"); - // Save particle properties - CELER_EXPECT(data.scalars); - // Load gamma-nuclear element cross section data NonuniformGridInserter insert_xs_iaea{&data.reals, &data.xs_iaea}; NonuniformGridInserter insert_xs_chips{&data.reals, &data.xs_chips}; @@ -115,6 +112,15 @@ void GammaNuclearModel::step(CoreParams const&, CoreStateDevice&) const //---------------------------------------------------------------------------// /*! * Build CHIPS gamma-nuclear element cross sections using G4GammaNuclearXS. + * + * Tabulate cross sections using separate parameterizations for the high energy + * region (emin < E < 50 GeV) and the ultra high energy region up to the + * maximum valid energy (emax). The numbers of bins are chosen to adequately + * capture both the parameterized points (224 bins from 106 MeV to 50 GeV) and + * the calculations used in G4PhotoNuclearCrossSection, which can be made + * configurable if needed (TODO). Note that the linear interpolation between + * the upper limit of the IAEA cross-section data and 150 MeV, as used in + * G4GammaNuclearXS, is also included in the tabulation. */ inp::Grid GammaNuclearModel::calc_chips_xs(AtomicNumber z, double emin, double emax) const @@ -123,16 +129,6 @@ GammaNuclearModel::calc_chips_xs(AtomicNumber z, double emin, double emax) const inp::Grid result; - // Tabulate cross sections using separate parameterizations for the high - // energy region (emin < E < 50 GeV) and the ultra high energy region up - // to the maximum valid energy (emax). The numbers of bins are chosen to - // adequately capture both the parameterized points (224 bins from 106 MeV - // to 50 GeV) and the calculations used in G4PhotoNuclearCrossSection, - // which can be made configurable if needed (TODO). Note that the linear - // interpolation between the upper limit of the IAEA cross-section data - // and 150 MeV, as used in G4GammaNuclearXS, is also included in this - // tabulation. - // Upper limit of parameterizations for the high-energy region (50 GeV) double const emid = 5e+4; diff --git a/src/celeritas/em/process/ElectroNuclearProcess.cc b/src/celeritas/em/process/ElectroNuclearProcess.cc new file mode 100644 index 0000000000..7a652d4be9 --- /dev/null +++ b/src/celeritas/em/process/ElectroNuclearProcess.cc @@ -0,0 +1,65 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/process/ElectroNuclearProcess.cc +//---------------------------------------------------------------------------// +#include "ElectroNuclearProcess.hh" + +#include "corecel/Assert.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" +#include "celeritas/phys/PDGNumber.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from host data. + */ +ElectroNuclearProcess::ElectroNuclearProcess(SPConstParticles particles, + SPConstMaterials materials) + : particles_(std::move(particles)), materials_(std::move(materials)) +{ + CELER_EXPECT(particles_); + CELER_EXPECT(materials_); +} + +//---------------------------------------------------------------------------// +/*! + * Construct the models associated with this process. + */ +auto ElectroNuclearProcess::build_models(ActionIdIter id) const -> VecModel +{ + return {std::make_shared( + *id++, *particles_, *materials_)}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the interaction cross sections for the given energy range. + */ +auto ElectroNuclearProcess::macro_xs(Applicability) const -> XsGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the energy loss for the given energy range. + */ +auto ElectroNuclearProcess::energy_loss(Applicability) const -> EnergyLossGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Name of the process. + */ +std::string_view ElectroNuclearProcess::label() const +{ + return "Electro nuclear"; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/process/ElectroNuclearProcess.hh b/src/celeritas/em/process/ElectroNuclearProcess.hh new file mode 100644 index 0000000000..34c74073e4 --- /dev/null +++ b/src/celeritas/em/process/ElectroNuclearProcess.hh @@ -0,0 +1,62 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/process/ElectroNuclearProcess.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include + +#include "corecel/inp/Grid.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/phys/Applicability.hh" +#include "celeritas/phys/ParticleParams.hh" +#include "celeritas/phys/Process.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Electro-nuclear process. + */ +class ElectroNuclearProcess : public Process +{ + public: + //!@{ + //! \name Type aliases + using SPConstParticles = std::shared_ptr; + using SPConstMaterials = std::shared_ptr; + //!@} + + public: + // Construct from particle, material, and external cross section data + ElectroNuclearProcess(SPConstParticles particles, + SPConstMaterials materials); + + // Construct the models associated with this process + VecModel build_models(ActionIdIter start_id) const final; + + // Get the interaction cross sections for the given energy range + XsGrid macro_xs(Applicability range) const final; + + // Get the energy loss for the given energy range + EnergyLossGrid energy_loss(Applicability range) const final; + + //! Whether the integral method can be used to sample interaction length + bool supports_integral_xs() const final { return false; } + + //! Whether the process applies when the particle is stopped + bool applies_at_rest() const final { return false; } + + // Name of the process + std::string_view label() const final; + + private: + SPConstParticles particles_; + SPConstMaterials materials_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh b/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh new file mode 100644 index 0000000000..e707ca3aeb --- /dev/null +++ b/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh @@ -0,0 +1,83 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/Types.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/Types.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/grid/NonuniformGridCalculator.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Calculate electro-nuclear cross sections. + */ +class ElectroNuclearMicroXsCalculator +{ + public: + //!@{ + //! \name Type aliases + using ParamsRef = NativeCRef; + using Energy = units::MevEnergy; + using BarnXs = units::BarnXs; + //!@} + + public: + // Construct with shared and state data + inline CELER_FUNCTION + ElectroNuclearMicroXsCalculator(ParamsRef const& shared, Energy energy); + + // Compute cross section + inline CELER_FUNCTION BarnXs operator()(ElementId el_id) const; + + private: + // Shared cross section data + ParamsRef const& data_; + // Incident particle energy + real_type const inc_energy_; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +ElectroNuclearMicroXsCalculator::ElectroNuclearMicroXsCalculator( + ParamsRef const& data, Energy energy) + : data_(data), inc_energy_(energy.value()) +{ +} + +//---------------------------------------------------------------------------// +/*! + * Compute microscopic electro-nuclear cross section at the given particle + * energy. + */ +CELER_FUNCTION +auto ElectroNuclearMicroXsCalculator::operator()(ElementId el_id) const + -> BarnXs +{ + NonuniformGridRecord grid; + + // Use tabulated electro-nuclear micro cross sections + CELER_EXPECT(el_id < data_.micro_xs.size()); + grid = data_.micro_xs[el_id]; + + // Calculate micro cross section at the given energy + NonuniformGridCalculator calc_micro_xs(grid, data_.reals); + real_type result = calc_micro_xs(inc_energy_); + + return BarnXs{result}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/g4/EmExtraPhysicsHelper.cc b/src/celeritas/g4/EmExtraPhysicsHelper.cc index a087a5ef5d..45884359bb 100644 --- a/src/celeritas/g4/EmExtraPhysicsHelper.cc +++ b/src/celeritas/g4/EmExtraPhysicsHelper.cc @@ -6,6 +6,8 @@ //---------------------------------------------------------------------------// #include "EmExtraPhysicsHelper.hh" +#include +#include #include #include #include @@ -22,6 +24,8 @@ EmExtraPhysicsHelper::EmExtraPhysicsHelper() << "compiled version of Geant4 (" << G4VERSION_NUMBER << ") is too old for gamma-nuclear cross section " "calculation"); + particle_ = std::make_shared(); + en_xs_ = std::make_shared(); gn_xs_ = std::make_shared(); if constexpr (G4VERSION_NUMBER < 1120) @@ -30,6 +34,20 @@ EmExtraPhysicsHelper::EmExtraPhysicsHelper() } } +//---------------------------------------------------------------------------// +/*! + * Calculate the electro-nuclear element cross section using + * G4ElectroNuclearCrossSection. + */ +auto EmExtraPhysicsHelper::calc_electro_nuclear_xs(AtomicNumber z, + MevEnergy energy) const + -> MmSqXs +{ + particle_->SetKineticEnergy(energy.value()); + return MmSqXs{ + en_xs_->GetElementCrossSection(particle_.get(), z.get(), nullptr)}; +} + //---------------------------------------------------------------------------// /*! * Calculate the gamma-nuclear element cross section using G4GammaNuclearXS. diff --git a/src/celeritas/g4/EmExtraPhysicsHelper.hh b/src/celeritas/g4/EmExtraPhysicsHelper.hh index 8770addca3..d8a6e171b9 100644 --- a/src/celeritas/g4/EmExtraPhysicsHelper.hh +++ b/src/celeritas/g4/EmExtraPhysicsHelper.hh @@ -16,13 +16,15 @@ #include "celeritas/UnitTypes.hh" #include "celeritas/phys/AtomicNumber.hh" +class G4DynamicParticle; +class G4ElectroNuclearCrossSection; class G4GammaNuclearXS; namespace celeritas { //---------------------------------------------------------------------------// /*! - * Calculate Geant4 gamma-nuclear cross sections. + * Calculate Geant4 gamma-nuclear and electro-nuclear cross sections. * * This class primarily severs as a wrapper around Geant4 cross section * calculation methods, which are not directly accessible from Celeritas EM @@ -41,11 +43,16 @@ class EmExtraPhysicsHelper // Construct EM extra physics helper EmExtraPhysicsHelper(); + // Calculate electro-nuclear element cross section + MmSqXs calc_electro_nuclear_xs(AtomicNumber z, MevEnergy energy) const; + // Calculate gamma-nuclear element cross section MmSqXs calc_gamma_nuclear_xs(AtomicNumber z, MevEnergy energy) const; private: //// DATA //// + std::shared_ptr particle_; + std::shared_ptr en_xs_; std::shared_ptr gn_xs_; }; @@ -66,6 +73,12 @@ inline EmExtraPhysicsHelper::EmExtraPhysicsHelper() # endif } +inline EmExtraPhysicsHelper::MmSqXs +EmExtraPhysicsHelper::calc_electro_nuclear_xs(AtomicNumber, MevEnergy) const +{ + CELER_ASSERT_UNREACHABLE(); +} + inline EmExtraPhysicsHelper::MmSqXs EmExtraPhysicsHelper::calc_gamma_nuclear_xs(AtomicNumber, MevEnergy) const { diff --git a/src/celeritas/io/ImportProcess.cc b/src/celeritas/io/ImportProcess.cc index fa5e036b76..beb2ab3cb8 100644 --- a/src/celeritas/io/ImportProcess.cc +++ b/src/celeritas/io/ImportProcess.cc @@ -61,6 +61,7 @@ char const* to_cstring(ImportProcessClass value) "mu_ioni", "mu_brems", "mu_pair_prod", + "electro_nuclear", "gamma_general", "gamma_nuclear", "neutron_elastic", @@ -92,6 +93,7 @@ char const* to_geant_name(ImportProcessClass value) "muIoni", // mu_ioni, "muBrems", // mu_brems, "muPairProd", // mu_pair_prod, + "ElectroNuclearProc", // electro_nuclear, "GammaGeneralProc", // gamma_general, "GammaNuclearProc", // gamma_nuclear, "neutronElasticProc", // neutron_elastic, diff --git a/src/celeritas/io/ImportProcess.hh b/src/celeritas/io/ImportProcess.hh index 1415f0d5f9..0c535ca830 100644 --- a/src/celeritas/io/ImportProcess.hh +++ b/src/celeritas/io/ImportProcess.hh @@ -68,6 +68,7 @@ enum class ImportProcessClass mu_ioni, mu_brems, mu_pair_prod, + electro_nuclear, gamma_general, // Will be decomposed into other processes gamma_nuclear, // Neutron diff --git a/src/celeritas/phys/PhysicsData.hh b/src/celeritas/phys/PhysicsData.hh index 99200b3eaa..b631cd59ae 100644 --- a/src/celeritas/phys/PhysicsData.hh +++ b/src/celeritas/phys/PhysicsData.hh @@ -13,6 +13,7 @@ #include "celeritas/Types.hh" #include "celeritas/em/data/AtomicRelaxationData.hh" #include "celeritas/em/data/EPlusGGData.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" #include "celeritas/em/data/GammaNuclearData.hh" #include "celeritas/em/data/LivermorePEData.hh" #include "celeritas/grid/XsGridData.hh" @@ -156,6 +157,9 @@ struct HardwiredIds ProcessId annihilation; ModelId eplusgg; + ProcessId electro_nuclear; + ModelId electro_vd; + ProcessId gamma_nuclear; ModelId bertini_qgs; @@ -178,6 +182,7 @@ struct HardwiredModels // Model data EPlusGGData eplusgg; + ElectroNuclearData electro_vd; GammaNuclearData bertini_qgs; LivermorePEData livermore_pe; AtomicRelaxParamsData relaxation; diff --git a/src/celeritas/phys/PhysicsParams.cc b/src/celeritas/phys/PhysicsParams.cc index 621a28c523..692558900a 100644 --- a/src/celeritas/phys/PhysicsParams.cc +++ b/src/celeritas/phys/PhysicsParams.cc @@ -29,6 +29,7 @@ #include "corecel/sys/ScopedMem.hh" #include "celeritas/Types.hh" #include "celeritas/em/model/EPlusGGModel.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" #include "celeritas/em/model/GammaNuclearModel.hh" #include "celeritas/em/model/LivermorePEModel.hh" #include "celeritas/em/params/AtomicRelaxationParams.hh" // IWYU pragma: keep @@ -445,6 +446,11 @@ void PhysicsParams::build_ids(ParticleParams const& particles, data->hardwired.ids.eplusgg = model_id; data->hardwired.eplusgg = m->host_ref(); } + else if (dynamic_cast(&model)) + { + data->hardwired.ids.electro_nuclear = process_id; + data->hardwired.ids.electro_vd = model_id; + } else if (dynamic_cast(&model)) { data->hardwired.ids.gamma_nuclear = process_id; @@ -481,6 +487,14 @@ void PhysicsParams::build_hardwired() host_ref_.hardwired.livermore_pe = model->host_ref(); device_ref_.hardwired.livermore_pe = model->device_ref(); } + if (auto model_id = host_ref_.hardwired.ids.electro_vd) + { + auto const* model = dynamic_cast( + models_[model_id.get()].first.get()); + CELER_ASSERT(model); + host_ref_.hardwired.electro_vd = model->host_ref(); + device_ref_.hardwired.electro_vd = model->device_ref(); + } if (auto model_id = host_ref_.hardwired.ids.bertini_qgs) { auto const* model = dynamic_cast( diff --git a/src/celeritas/phys/PhysicsTrackView.hh b/src/celeritas/phys/PhysicsTrackView.hh index 5c764fb6fa..b52c94f0ce 100644 --- a/src/celeritas/phys/PhysicsTrackView.hh +++ b/src/celeritas/phys/PhysicsTrackView.hh @@ -14,6 +14,7 @@ #include "celeritas/Quantities.hh" #include "celeritas/Types.hh" #include "celeritas/em/xs/EPlusGGMacroXsCalculator.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" #include "celeritas/em/xs/GammaNuclearMicroXsCalculator.hh" #include "celeritas/em/xs/LivermorePEMicroXsCalculator.hh" #include "celeritas/grid/GridIdFinder.hh" @@ -458,11 +459,17 @@ CELER_FUNCTION real_type PhysicsTrackView::calc_xs(ParticleProcessId ppid, result = MacroXsCalculator( params_.hardwired.livermore_pe, material)(energy); } + else if (model_id == params_.hardwired.ids.eplusgg) { result = EPlusGGMacroXsCalculator(params_.hardwired.eplusgg, material)(energy); } + else if (model_id == params_.hardwired.ids.electro_vd) + { + result = MacroXsCalculator( + params_.hardwired.electro_vd, material)(energy); + } else if (model_id == params_.hardwired.ids.bertini_qgs) { result = MacroXsCalculator( diff --git a/src/celeritas/phys/ProcessBuilder.cc b/src/celeritas/phys/ProcessBuilder.cc index 8175e6dc3a..2d3249fb2e 100644 --- a/src/celeritas/phys/ProcessBuilder.cc +++ b/src/celeritas/phys/ProcessBuilder.cc @@ -18,6 +18,7 @@ #include "celeritas/em/process/CoulombScatteringProcess.hh" #include "celeritas/em/process/EIonizationProcess.hh" #include "celeritas/em/process/EPlusAnnihilationProcess.hh" +#include "celeritas/em/process/ElectroNuclearProcess.hh" #include "celeritas/em/process/GammaConversionProcess.hh" #include "celeritas/em/process/GammaNuclearProcess.hh" #include "celeritas/em/process/MuBremsstrahlungProcess.hh" @@ -114,6 +115,7 @@ auto ProcessBuilder::operator()(IPC ipc) -> SPProcess {IPC::coulomb_scat, &ProcessBuilder::build_coulomb}, {IPC::e_brems, &ProcessBuilder::build_ebrems}, {IPC::e_ioni, &ProcessBuilder::build_eioni}, + {IPC::electro_nuclear, &ProcessBuilder::build_electro_nuclear}, {IPC::gamma_nuclear, &ProcessBuilder::build_gamma_nuclear}, {IPC::mu_brems, &ProcessBuilder::build_mubrems}, {IPC::mu_ioni, &ProcessBuilder::build_muioni}, @@ -158,6 +160,13 @@ auto ProcessBuilder::build_ebrems() -> SPProcess options); } +//---------------------------------------------------------------------------// +auto ProcessBuilder::build_electro_nuclear() -> SPProcess +{ + return std::make_shared(this->particle(), + this->material()); +} + //---------------------------------------------------------------------------// auto ProcessBuilder::build_gamma_nuclear() -> SPProcess { diff --git a/src/celeritas/phys/ProcessBuilder.hh b/src/celeritas/phys/ProcessBuilder.hh index adce498370..ceaebe1ab2 100644 --- a/src/celeritas/phys/ProcessBuilder.hh +++ b/src/celeritas/phys/ProcessBuilder.hh @@ -114,6 +114,7 @@ class ProcessBuilder auto build_coulomb() -> SPProcess; auto build_ebrems() -> SPProcess; auto build_eioni() -> SPProcess; + auto build_electro_nuclear() -> SPProcess; auto build_gamma_nuclear() -> SPProcess; auto build_mubrems() -> SPProcess; auto build_muioni() -> SPProcess; diff --git a/test/celeritas/CMakeLists.txt b/test/celeritas/CMakeLists.txt index ce6e8ab5d6..d2acaf3cd4 100644 --- a/test/celeritas/CMakeLists.txt +++ b/test/celeritas/CMakeLists.txt @@ -156,6 +156,8 @@ celeritas_add_test(em/xs/ScreeningFunctions.test.cc) celeritas_add_test(em/BetheHeitler.test.cc ${_needs_double}) celeritas_add_test(em/BetheBloch.test.cc ${_needs_double}) celeritas_add_test(em/BraggICRU73QO.test.cc ${_needs_double}) +celeritas_add_test(em/ElectroNuclear.test.cc ${_needs_double} + ${_needs_geant4_11}) celeritas_add_test(em/EPlusGG.test.cc ${_needs_double}) celeritas_add_test(em/GammaNuclear.test.cc ${_needs_double} ${_needs_geant4_11}) celeritas_add_test(em/KleinNishina.test.cc ${_needs_double}) diff --git a/test/celeritas/em/ElectroNuclear.test.cc b/test/celeritas/em/ElectroNuclear.test.cc new file mode 100644 index 0000000000..0589b3f166 --- /dev/null +++ b/test/celeritas/em/ElectroNuclear.test.cc @@ -0,0 +1,134 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/ElectroNuclear.test.cc +//---------------------------------------------------------------------------// +#include + +#include "celeritas_test_config.h" + +#include "corecel/cont/Range.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/em/interactor/ElectroNuclearInteractor.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" +#include "celeritas/mat/MaterialTrackView.hh" +#include "celeritas/phys/InteractorHostTestBase.hh" +#include "celeritas/phys/MacroXsCalculator.hh" + +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace test +{ +//---------------------------------------------------------------------------// +class ElectroNuclearTest : public InteractorHostTestBase +{ + protected: + using MevEnergy = units::MevEnergy; + + void SetUp() override + { + using namespace units; + + // Set up the default particle: 1000 MeV electron along +z direction + auto const& particles = *this->particle_params(); + this->set_inc_particle(pdg::electron(), MevEnergy{1000}); + this->set_inc_direction({0, 0, 1}); + + // Build the model with the default material + MaterialParams::Input mi; + mi.elements = { + {AtomicNumber{8}, AmuMass{15.999}, {}, Label{"O"}}, + {AtomicNumber{74}, AmuMass{183.84}, {}, Label{"W"}}, + {AtomicNumber{82}, AmuMass{207.2}, {}, Label{"Pb"}}, + }; + + mi.materials = { + {native_value_from(MolCcDensity{8.28}), + 293., + MatterState::solid, + {{ElementId{0}, 0.14}, {ElementId{1}, 0.4}, {ElementId{2}, 0.46}}, + Label{"PbWO4"}}}; + this->set_material_params(mi); + + model_ = std::make_shared( + ActionId{0}, particles, *this->material_params()); + + this->set_material("PbWO4"); + } + + protected: + std::shared_ptr model_; +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +TEST_F(ElectroNuclearTest, micro_xs) +{ + // Calculate the electro-nuclear microscopic (element) cross section + using XsCalculator = ElectroNuclearMicroXsCalculator; + + // Set the target element: Pb + ElementId el_id{2}; + + // Check the size of the element cross section data + HostCRef shared = model_->host_ref(); + + // Calculate the electro-nuclear cross section using parameterizated data + + NonuniformGridRecord grid = shared.micro_xs[el_id]; + EXPECT_EQ(grid.grid.size(), 300); + + // Expected microscopic cross section (units::BarnXs) in [100:1e+8] (MeV) + std::vector> const energy_xs + = {{200, 0.0076866011595330607}, + {500, 0.010594901781001968}, + {1e+3, 0.012850271316595725}, + {5e+3, 0.018062295361634773}, + {5e+4, 0.025265243621752368}, + {1e+6, 0.035225631593952887}, + {1e+7, 0.044179324718181687}, + {1e+8, 0.05492003274983899}}; + + for (auto i : range(energy_xs.size())) + { + XsCalculator calc_micro_xs(shared, MevEnergy{energy_xs[i].first}); + EXPECT_SOFT_EQ(calc_micro_xs(el_id).value(), energy_xs[i].second); + } +} + +TEST_F(ElectroNuclearTest, macro_xs) +{ + // Calculate the electro-nuclear macroscopic cross section + auto material = this->material_track().material_record(); + auto calc_xs = MacroXsCalculator( + model_->host_ref(), material); + + // Expected macroscopic cross section (\f$ cm^{-1} \f$)} in [100:1e+8](MeV) + std::vector> const energy_xs + = {{200, 0.03099484247367704}, + {500, 0.042848174635210838}, + {1e+3, 0.05204849281161035}, + {5e+3, 0.073325144774783371}, + {5e+4, 0.10275850385625593}, + {1e+6, 0.14353937451738991}, + {1e+7, 0.1802859228767722}, + {1e+8, 0.22446330065829731}}; + + for (auto i : range(energy_xs.size())) + { + EXPECT_SOFT_EQ(native_value_to( + calc_xs(MevEnergy{energy_xs[i].first})) + .value(), + energy_xs[i].second); + } +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace celeritas diff --git a/test/celeritas/phys/ProcessBuilder.test.cc b/test/celeritas/phys/ProcessBuilder.test.cc index 7901812b2b..316be921c0 100644 --- a/test/celeritas/phys/ProcessBuilder.test.cc +++ b/test/celeritas/phys/ProcessBuilder.test.cc @@ -13,6 +13,7 @@ #include "celeritas/em/process/CoulombScatteringProcess.hh" #include "celeritas/em/process/EIonizationProcess.hh" #include "celeritas/em/process/EPlusAnnihilationProcess.hh" +#include "celeritas/em/process/ElectroNuclearProcess.hh" #include "celeritas/em/process/GammaConversionProcess.hh" #include "celeritas/em/process/GammaNuclearProcess.hh" #include "celeritas/em/process/MuBremsstrahlungProcess.hh" @@ -277,6 +278,46 @@ TEST_F(ProcessBuilderTest, gamma_conversion) } } +TEST_F(ProcessBuilderTest, electro_nuclear) +{ + if (!this->has_particle_xs_data()) + { + GTEST_SKIP() << "Missing ElectroNuclearData"; + } + + ProcessBuilder build_process( + this->import_data(), this->particle(), this->material()); + + // Create process + auto process = build_process(IPC::electro_nuclear); + EXPECT_PROCESS_TYPE(ElectroNuclearProcess, process.get()); + + // Test model + auto models = process->build_models(ActionIdIter{}); + ASSERT_EQ(1, models.size()); + ASSERT_TRUE(models.front()); + EXPECT_EQ("electro-nuclear", models.front()->label()); + auto all_applic = models.front()->applicability(); + ASSERT_EQ(2, all_applic.size()); + Applicability applic = *all_applic.begin(); + + for (auto mat_id : range(PhysMatId{this->material()->num_materials()})) + { + // Test step limits + { + applic.material = mat_id; + EXPECT_FALSE(process->macro_xs(applic)); + EXPECT_FALSE(process->energy_loss(applic)); + } + + // Test micro xs + for (auto const& model : models) + { + EXPECT_TRUE(model->micro_xs(applic).empty()); + } + } +} + TEST_F(ProcessBuilderTest, gamma_nuclear) { if (!this->has_particle_xs_data()) From 340eabe9fdc933c6c93a0aa931dbcf9c523b6518 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Fri, 2 Jan 2026 17:30:53 -0500 Subject: [PATCH 20/60] Remove use of G4Backtrace (#2176) --- app/celer-g4/celer-g4.cc | 1 - src/celeritas/ext/GeantSetup.cc | 6 ------ src/geocel/GeantGeoParams.cc | 10 ++-------- src/geocel/GeantUtils.cc | 18 ------------------ src/geocel/GeantUtils.hh | 6 ------ test/accel/IntegrationTestBase.cc | 3 --- 6 files changed, 2 insertions(+), 42 deletions(-) diff --git a/app/celer-g4/celer-g4.cc b/app/celer-g4/celer-g4.cc index 83950bad68..5234fcf0e6 100644 --- a/app/celer-g4/celer-g4.cc +++ b/app/celer-g4/celer-g4.cc @@ -99,7 +99,6 @@ void run(std::string_view filename, std::shared_ptr params) // Disable external error handlers ScopedRootErrorHandler scoped_root_errors; - disable_geant_signal_handler(); // Set the random seed *before* the run manager is instantiated // (G4MTRunManager constructor uses the RNG) diff --git a/src/celeritas/ext/GeantSetup.cc b/src/celeritas/ext/GeantSetup.cc index 56381b97d6..28f071b74e 100644 --- a/src/celeritas/ext/GeantSetup.cc +++ b/src/celeritas/ext/GeantSetup.cc @@ -18,9 +18,6 @@ #include #include #include -#if G4VERSION_NUMBER >= 1070 -# include -#endif #if G4VERSION_NUMBER >= 1100 # include #endif @@ -91,9 +88,6 @@ GeantSetup::GeantSetup(std::string const& gdml_filename, Options options) "execution"); ++geant_launch_count; - // Disable signal handling - disable_geant_signal_handler(); - #if G4VERSION_NUMBER >= 1100 run_manager_.reset( G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)); diff --git a/src/geocel/GeantGeoParams.cc b/src/geocel/GeantGeoParams.cc index 87b9288080..81544a1ae7 100644 --- a/src/geocel/GeantGeoParams.cc +++ b/src/geocel/GeantGeoParams.cc @@ -27,20 +27,16 @@ #include #include -#include "corecel/Assert.hh" -#include "geocel/inp/Model.hh" -#if G4VERSION_NUMBER >= 1070 -# include -#endif - #include "corecel/Config.hh" +#include "corecel/Assert.hh" #include "corecel/cont/Range.hh" #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" #include "corecel/sys/Device.hh" #include "corecel/sys/ScopedMem.hh" #include "corecel/sys/ScopedProfiling.hh" +#include "geocel/inp/Model.hh" #include "GeantGdmlLoader.hh" #include "GeantGeoUtils.hh" @@ -674,8 +670,6 @@ GeantGeoParams::from_gdml(std::string const& filename) ScopedGeantLogger logger(celeritas::world_logger()); ScopedGeantExceptionHandler exception_handler; - disable_geant_signal_handler(); - if (!ends_with(filename, ".gdml")) { CELER_LOG(warning) << "Expected '.gdml' extension for GDML input"; diff --git a/src/geocel/GeantUtils.cc b/src/geocel/GeantUtils.cc index 5c3bc5a819..ce3c29b73e 100644 --- a/src/geocel/GeantUtils.cc +++ b/src/geocel/GeantUtils.cc @@ -15,9 +15,6 @@ #if G4VERSION_NUMBER < 1070 # include #endif -#if G4VERSION_NUMBER >= 1070 -# include -#endif #ifdef _OPENMP # include @@ -30,21 +27,6 @@ namespace celeritas { -//---------------------------------------------------------------------------// -/*! - * Clear Geant4's signal handlers that get installed on startup/activation. - * - * This should be called before instantiating a run manager. - */ -void disable_geant_signal_handler() -{ -#if G4VERSION_NUMBER >= 1070 - CELER_LOG(debug) << "Disabling Geant4 signal handlers"; - // Disable geant4 signal interception - G4Backtrace::DefaultSignals() = {}; -#endif -} - //---------------------------------------------------------------------------// /*! * Get the number of threads in a version-portable way. diff --git a/src/geocel/GeantUtils.hh b/src/geocel/GeantUtils.hh index 3feb373df2..9a8631a917 100644 --- a/src/geocel/GeantUtils.hh +++ b/src/geocel/GeantUtils.hh @@ -16,10 +16,6 @@ class G4RunManager; namespace celeritas { -//---------------------------------------------------------------------------// -// Clear Geant4's signal handlers that get installed when linking 11+ -void disable_geant_signal_handler(); - //---------------------------------------------------------------------------// // Get the number of threads in a version-portable way int get_geant_num_threads(G4RunManager const&); @@ -50,8 +46,6 @@ std::ostream& operator<<(std::ostream& os, StreamablePD const& pd); // INLINE DEFINITIONS //---------------------------------------------------------------------------// #if !CELERITAS_USE_GEANT4 -inline void disable_geant_signal_handler() {} - inline int get_geant_num_threads(G4RunManager const&) { CELER_NOT_CONFIGURED("Geant4"); diff --git a/test/accel/IntegrationTestBase.cc b/test/accel/IntegrationTestBase.cc index 3ae1e56297..7401545e22 100644 --- a/test/accel/IntegrationTestBase.cc +++ b/test/accel/IntegrationTestBase.cc @@ -294,9 +294,6 @@ G4RunManager& IntegrationTestBase::run_manager() }; CELER_ASSERT(rm); - // Disable signal handling - disable_geant_signal_handler(); - // Set up detector rm->SetUserInitialization(new DetectorConstruction{ this->test_data_path("geocel", basename + ".gdml"), From cc553f569c3f13dd8e80a76d833abfff77aef82e Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Mon, 5 Jan 2026 09:59:04 -0500 Subject: [PATCH 21/60] Delete unused run input conversion code (#2178) --- app/celer-g4/RunInput.cc | 199 --------------------------------------- app/celer-g4/RunInput.hh | 2 - 2 files changed, 201 deletions(-) diff --git a/app/celer-g4/RunInput.cc b/app/celer-g4/RunInput.cc index f9f46c9bff..4551290126 100644 --- a/app/celer-g4/RunInput.cc +++ b/app/celer-g4/RunInput.cc @@ -6,175 +6,12 @@ //---------------------------------------------------------------------------// #include "RunInput.hh" -#include - -#include "corecel/Config.hh" - #include "corecel/io/EnumStringMapper.hh" -#include "corecel/io/Logger.hh" -#include "corecel/math/ArrayUtils.hh" -#include "geocel/GeantUtils.hh" -#include "celeritas/inp/StandaloneInput.hh" -#include "celeritas/phys/PrimaryGeneratorOptions.hh" -#include "accel/SharedParams.hh" namespace celeritas { namespace app { -namespace -{ -//---------------------------------------------------------------------------// -inp::System load_system(RunInput const& ri) -{ - inp::System s; - - if (celeritas::Device::num_devices()) - { - inp::Device d; - d.stack_size = ri.cuda_stack_size; - d.heap_size = ri.cuda_heap_size; - s.device = std::move(d); - } - - s.environment = {ri.environ.begin(), ri.environ.end()}; - - return s; -} - -//---------------------------------------------------------------------------// -inp::Problem load_problem(RunInput const& ri) -{ - inp::Problem p; - - // Model definition - p.model.geometry = ri.geometry_file; - - p.control.num_streams = get_geant_num_threads(); - - // NOTE: old SetupOptions input *per stream*, but inp::Problem needs - // *integrated* over streams - p.control.capacity = [&ri, num_streams = p.control.num_streams] { - inp::CoreStateCapacity c; - c.tracks = ri.num_track_slots * num_streams; - c.initializers = ri.initializer_capacity * num_streams; - c.secondaries = static_cast( - ri.secondary_stack_factor * ri.num_track_slots * num_streams); - c.primaries = ri.auto_flush; - return c; - }(); - - if (celeritas::Device::num_devices()) - { - inp::DeviceDebug dd; - dd.sync_stream = ri.action_times; - p.control.device_debug = std::move(dd); - } - - if (ri.track_order != TrackOrder::size_) - { - p.control.track_order = ri.track_order; - } - - { - inp::CoreTrackingLimits& limits = p.tracking.limits; - limits.steps = ri.max_steps; - } - - // Field setup - if (ri.field_type == "rzmap") - { - CELER_LOG(info) << "Loading RZMapField from " << ri.field_file; - std::ifstream inp(ri.field_file); - CELER_VALIDATE(inp, - << "failed to open field map file at '" << ri.field_file - << "'"); - - // Read RZ map from file - RZMapFieldInput rzmap; - inp >> rzmap; - - // Replace driver options with user options - rzmap.driver_options = ri.field_options; - - p.field = std::move(rzmap); - } - else if (ri.field_type == "uniform") - { - inp::UniformField field; - field.strength = ri.field; - - auto field_val = norm(field.strength); - if (field_val > 0) - { - CELER_LOG(info) << "Using a uniform field " << field_val << " [T]"; - field.driver_options = ri.field_options; - p.field = std::move(field); - } - } - else - { - CELER_VALIDATE(false, - << "invalid field type '" << ri.field_type << "'"); - } - - if (ri.sd_type != SensitiveDetectorType::none) - { - // Activate Geant4 SD callbacks - p.scoring.sd.emplace(); - } - - { - // Diagnostics - auto& d = p.diagnostics; - d.output_file = ri.output_file; - d.export_files.physics = ri.physics_output_file; - d.export_files.offload = ri.offload_output_file; - d.timers.action = ri.action_times; - d.perfetto_file = ri.tracing_file; - - if (!ri.slot_diagnostic_prefix.empty()) - { - inp::SlotDiagnostic slot_diag; - slot_diag.basename = ri.slot_diagnostic_prefix; - d.slot = std::move(slot_diag); - } - - if (ri.step_diagnostic) - { - inp::StepDiagnostic step; - step.bins = ri.step_diagnostic_bins; - d.step = std::move(step); - } - } - - CELER_VALIDATE(ri.macro_file.empty(), - << "macro file is no longer supported"); - - return p; -} - -//---------------------------------------------------------------------------// -inp::Events load_events(RunInput const& ri) -{ - CELER_VALIDATE(ri.event_file.empty() != !ri.primary_options, - << "either a event filename or options to generate " - "primaries must be provided (but not both)"); - - if (!ri.event_file.empty()) - { - inp::ReadFileEvents rfe; - rfe.event_file = ri.event_file; - return rfe; - } - - CELER_ASSERT(ri.primary_options); - return to_input(ri.primary_options); -} - -//---------------------------------------------------------------------------// -} // namespace - //---------------------------------------------------------------------------// /*! * Get a string corresponding to the physics list selection. @@ -218,42 +55,6 @@ RunInput::operator bool() const && (step_diagnostic_bins > 0 || !step_diagnostic); } -//---------------------------------------------------------------------------// -/*! - * Convert to standalone input format. - */ -inp::StandaloneInput to_input(RunInput const& ri) -{ - inp::StandaloneInput si; - - si.system = load_system(ri); - si.problem = load_problem(ri); - - // Set up Geant4 - if (ri.physics_list == PhysicsListSelection::celer_ftfp_bert) - { - // TODO: physics list is handled elsewhere - } - else - { - CELER_VALIDATE(ri.physics_list == PhysicsListSelection::celer_em, - << "invalid physics list selection '" - << to_cstring(ri.physics_list) << "' (must be 'celer')"); - } - - si.geant_setup = ri.physics_options; - - inp::PhysicsFromGeant geant_import; - geant_import.ignore_processes.push_back("CoulombScat"); - geant_import.data_selection.interpolation.type = ri.interpolation; - geant_import.data_selection.interpolation.order = ri.poly_spline_order; - si.physics_import = std::move(geant_import); - - si.events = load_events(ri); - - return si; -} - //---------------------------------------------------------------------------// } // namespace app } // namespace celeritas diff --git a/app/celer-g4/RunInput.hh b/app/celer-g4/RunInput.hh index 9620e610ce..711e44f7e1 100644 --- a/app/celer-g4/RunInput.hh +++ b/app/celer-g4/RunInput.hh @@ -128,8 +128,6 @@ struct RunInput char const* to_cstring(PhysicsListSelection value); char const* to_cstring(SensitiveDetectorType value); -inp::StandaloneInput to_input(RunInput const& run_input); - //---------------------------------------------------------------------------// } // namespace app } // namespace celeritas From 5bef20fa8565549810a987c1e68dc3366c022da9 Mon Sep 17 00:00:00 2001 From: Sakib Rahman Date: Tue, 6 Jan 2026 07:16:27 -0500 Subject: [PATCH 22/60] Implement Celeritas-DD4hep integration plugin (#1756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add dd4hep to list of optional cmake dependencies * Add initial ddceler plugin building mechanism to cmake * Add initial ddceler plugin layout * Update CMakeLists.txt to bring in latest changes from develop * Remove ddceler plugin test files. Will be uploaded later with proper formatting. * Add ddceler directory to list of subdirectories to build if DD4hep option enabled * Fix typo. Replace dd4hep with ddceler. * Add plugin to lib post install * Modify plugin name to match class name * Fix formatting for ddceler plugin code * Add DDG4 Run Action plugin for celeritas offload with proper formatting * Add DDG4 Run Action plugin for celeritas offload to cmake file * Minimal DDG4 steering file to demonstrate celeritas integration * Add a placeholder non-zero uniform magnetic field * First commit of simple detector for testing celeritas-dd4hep integration plugin * Fix style and set sensor type * Parse sensor type properly * Remove explicit definition of world volume and make the beampipe a passive volume * Add a placeholder sensor type for beampipe to passive * Fix style and move comment block into info section * Fix style and commit first iteration of benchmark plotting script to compare result between 2 different simulation runs * Add steering file to the example with proper formatting * Use a uniform 3 Tesla field everywhere * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Track zero energy deposition particles * Align celeritas magnetic field with the DD4hep field direction * Updates to the plotting script. Looking at only primaries from MCParticles. Changes to histogram ranges. And updated file nomenclature. * Move the steering file to the example directory * Fix comments and don't include unnecessary header file * Generalize info message for celeritas tracking and replace physics list specific header * Update steering file * Add messenger commands to pass celeritas configuration variables through ddg4 steering file. The integration right now assumes a uniform magnetic field along the z-direction. This needs to be generalized in the future. * First commit of workflow to test dd4hep integration under eic-shell environment * Temporarily trigger on every push to the branch * Remove download action * Build celeritas with dd4hep * Run with a general example from DD4hep upstream repo * Set correct relative path for steering file in action * Specify a single parameter to define the field map for DD4hep and celeritas in steering file. This should override the default field in the dd4hep example specified in xml * Use lowercase for dd4hep class name * Revert "Use lowercase for dd4hep class name" This reverts commit f7d60ee3e0dbbf6891e0f309eb496da4fcb9cd16. * Revert "Specify a single parameter to define the field map for DD4hep and celeritas in steering file. This should override the default field in the dd4hep example specified in xml" This reverts commit dabb8d3ef8861dd8ebefa79a5312ba419af3fdbd. * Using the upstream DD4hep example but replacing the field with a constant field * Remove custom example * Use the constant field example instead of solenoidal field * 1. DDcelerTMI.hh:34 - Changed m_uniformFieldStrength from float to std::vector 2. DDcelerTMI.cc:41-43 - Updated to use all three components [x, y, z] from the vector with validation 3. steeringFile.py:26-28 - Updated to pass a list of all three field components instead of just Z * Minor updates to variable names that align with the example output structure * 1. DDcelerTMI.cc:10 - Added #include to access the detector description 2. DDcelerTMI.cc:34-36 - Now retrieves the field directly from DD4hep detector description using context()->detectorDescription().field() 3. DDcelerTMI.cc:42-49 - The field strength is now calculated directly in C++ instead of being passed from Python 4. DDcelerTMI.hh:35 - Removed the m_uniformFieldStrength member variable 5. DDcelerTMI.hh:60 - Removed the property declaration for UniformFieldStrength 6. steeringFile.py:16-24 - Simplified the steering file by removing all field-related code The field is now automatically retrieved from the DD4hep XML description at the origin point (0,0,0) when makeOptions() is called, and the warning message is now shown from C++ instead of Python. * Strategy 1 (Fast path): DDcelerTMI.cc:54-82 - Checks if all magnetic components are ConstantField - If so, directly sums their directions - No sampling needed Strategy 2 (Non-elegant Fallback for when a summed field can be approximated to be constant): DDcelerTMI.cc:84-156 - For non-constant components (or mixed), samples the combined field - Uses 20 random sampling points (plus origin) within the detector volume - Fixed seed (12345) for reproducibility - Sample ranges: ±100 cm in x/y, ±200 cm in z - Verifies all sampled points match within 1e-6 tesla tolerance - Reports the exact location and magnitude of non-uniformity if detected * Build the magnetic field with only constant field types in local example * Only allow constant field components * Don't use debug mode in action workflow * Also run with no celeritas offload and produce a benchmark comparison plot * Add more options within steering file to simplify usage * Add a dd4hep property to pass celeritas ignored processes from steering file * Attempt at parametrizing the field driver options through the xml description that is passed to both geant4 and celeritas * Instead of resetting Geant4FieldManager, we query the DD4hep description to get the field driver option values set in the steeringFile and convert to celeritas units. There is a lack of 1-to-1 mapping between celeritas and dd4hep driver parameters. So, a little more code may be necessary to make the driver options identical * Use CELER_VALIDATE instead of using throw directly everywhere * Use CELER_LOG(info) instead of using this->info directly everywhere * Apply fixes from pre-commit hooks: see detailed commit message → This commit means that you must (please!) install pre-commit on your development machine and run `pre-commit install --install-hooks`. For more information, see https://celeritas-project.github.io/celeritas/user/development/style.html#formatting Autogenerated: https://pre-commit.ci * Configure DD4hep plugins to link against build libraries and copy components files - Set CMAKE_LIBRARY_OUTPUT_DIRECTORY to output plugin libraries to build/lib - Configure RPATH to prioritize build directory libraries over installed ones - Use BUILD_RPATH to point to build/lib during development - Use INSTALL_RPATH for installation directory - Add --disable-new-dtags linker flag to use RPATH instead of RUNPATH for correct library search priority - Add custom target to copy .components files to build/lib for DD4hep plugin discovery This ensures the plugins link against the newly built Celeritas libraries instead of any pre-existing installation (e.g., /opt/local). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Add VecGeom-specific linker flags for DD4hep plugins When VecGeom is enabled, add accel_final library with --no-as-needed linker flag to ensure proper symbol resolution for VecGeom geometry. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Additional condition for vecgeom based runs * Revert "Use CELER_LOG(info) instead of using this->info directly everywhere" This reverts commit 91207841892d6c6d3e7afed6bcf15428b919f199. * Use the ci nightly of eic-shell container * Doesn't need to run on every push to the branch Removed push trigger for feature-dd4hep-integration-plugin branch. * Use the container from github package registry instead of accessing from cvmfs * Revert "Doesn't need to run on every push to the branch" This reverts commit 1b437a639a6d33ecd5d0234cdbbd57ffd94a0ff9. * Source profile for eic-shell container environment * Use bash shell explicitly * Run Celeritas and Geant4 simulations in parallel Updated the workflow to run Celeritas and Geant4 simulations in parallel, capturing logs for both and checking exit codes. * Check removing source command in workflow Removed sourcing of /etc/profile in the CI workflow. * Enhance dd4hep integration workflow with error handling Added error handling and improved logging for dd4hep integration tests. * Reorganize plugin cmakefile to setup proper dependency chain * Add copyright header to steeringFile.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Add copyright header to SiD_ConstantField.xml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Make steeringFile.py PEP8 compliant - Rename RUNNER to runner (lowercase for module-level variable) - Rename setupPhysics to setup_physics (snake_case for functions) - Reformat docstring to be more concise - Split long comment line to stay within 79 character limit - Add function docstring - Move DDG4 imports inside function scope 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Rename parameter 'nam' to 'name' and remove from codespell ignore list - Update DDcelerTMI constructor parameter from nam to name - Update DDcelerRunAction constructor parameter from nam to name - Remove 'nam' from codespell-ignore.txt 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Initialize DDcelerTMI member variables and add validation Default-initialize m_maxNumTracks and m_initCapacity to 0 and add validation in makeOptions() to ensure they are set to positive values before use. This addresses PR feedback to ensure users explicitly configure these parameters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Refactor dd4hep integration workflow steps * Add error handling to dd4hep integration workflow * Rename upload step and update artifact paths * Export gdml and upload artifact * Update check-dd4hep-integration workflow for GDML output Removed geoConverter command and added GDML output option to ddsim for Celeritas simulation. * Update DD4hep steering file configuration - Add random seed flags to usage documentation - Increase number of events from 20 to 100 - Update particle gun energy from 5 GeV to 18 GeV - Add comments for CPU/GPU configuration of Celeritas capacity parameters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Narrow the eta range and make sure the beam mostly passes through the ecal endcap * Refactor the benchmark to do explicit checks on physics metrics 1) Similarity of initial (phi, eta and pT) distributions 2) Total energy deposited in ecal endcap 3) The shower profile at different percentiles in depth and radial spread * Add the benchmark as a ctest * Pass the unit definition into the lambda function. So, it doesn't need to be redefined * Run the benchmark as a ctest in github actions * Check for dependencies to build ddceler example in main cmake file * Fix spelling: use grammatically correct 'overlaid' instead of 'overlayed' Changed all instances of 'overlayed' to 'overlaid' in variables and comments where we have control. The DD4hep API type 'OverlayedField' remains unchanged as it is part of the external library interface. Updated codespell-ignore.txt to allow 'OverlayedField' (DD4hep API type) while removing 'overlayed' to catch future incorrect usage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Remove OverlayedField from words to ignore by codespell * Remove redundant initialization logs * Changes from discussion * Fix namespace, includes, formatting, etc * Further refactoring to have everything consistently under celeritas::ddceler namespace and attempt at fixing field driver properties propagation * Fix build on macos * cleanup tmi code * Fix dd4hep build on macos * Further refactoring to propagate magnetic field tracking properties from configure phase * celeritas requires delta intersection to be bigger than min step * Remove unnecessary code from CMake and update style * Avoid finding root twice * Update gitignore files * Simplify validation cmake and update app * Add wrapper script * Set root library path without environment variables * Refactor integration script * REVERTME: disable most builds * fixup! REVERTME: disable most builds * Try again * Use login shell to source profile and avoid settings * Fix target properties and add linker checking * Configure with verbosity * Revert top-level forcing of tests * Suppress obnoxious dd4hep debug messages * Update launcher script to work in general case * Use dd4hep in hudson and don't force dd4hep off for 'all' configuration * Fix cmake build command * Add check for DD4HEP_LIBRARY_PATH on macos * Remove validation scripts from ddceler example Validation benchmarks have been moved to the benchmarks repository. The example directory now only contains geometry and steering files for running simulations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Replace SiD example with Preshower example and update CI workflow - Remove SiD geometry and run script - Add Preshower geometry (Preshower.xml) and steering file to input/ directory - Add run-preshower.sh script with mode selection (celeritas/geant4) - Update CI workflow to run preshower simulation with 3 events and fixed random seed - Update artifact paths to match new results directory structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Use Celeritas_ROOT instead of CELER_INSTALL_DIR * Fix/improve run-example script * Fix 'set' call * Fix CMake generator expression for CUDA RDC final library Replace invalid $ generator expression with $ to correctly link the CUDA RDC final library into DD4hep plugins. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Adjust default CELER_SOURCE_DIR path * Delete obsolete comments * Use anonymous namespace and write gdml by default * Fix run script when `MODE` not given; rename results to output to match `input` * Fix comment * Eliminate gap to fix orange * Update integration class names and references * Explicitly define build order for linked libraries * Revert unrelated changes * Revert "REVERTME: disable most builds" This reverts commit d8ee573f385822783b6b3bbbdf90402b9c66afdc. * Remove branch name-based check --------- Co-authored-by: Sakib Rahman Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: Seth R Johnson --- .../workflows/check-dd4hep-integration.yml | 56 +++++ CMakeLists.txt | 46 +++- CMakePresets.json | 1 + cmake/CeleritasConfig.cmake.in | 4 + cmake/FindDD4hep.cmake | 32 +++ example/.gitignore | 2 + example/ddceler/.gitignore | 1 + example/ddceler/input/Preshower.xml | 89 ++++++++ example/ddceler/input/steeringFile.py | 61 +++++ example/ddceler/run-preshower.sh | 111 ++++++++++ example/geant4/.gitignore | 2 - example/minimal/.gitignore | 2 - example/offload-template/.gitignore | 3 - scripts/cmake-presets/hudson.json | 1 + scripts/spack.yaml | 5 +- src/CMakeLists.txt | 3 + src/ddceler/CMakeLists.txt | 128 +++++++++++ src/ddceler/CelerPhysics.cc | 209 ++++++++++++++++++ src/ddceler/CelerPhysics.hh | 47 ++++ src/ddceler/CelerRun.cc | 58 +++++ src/ddceler/CelerRun.hh | 39 ++++ 21 files changed, 880 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/check-dd4hep-integration.yml create mode 100644 cmake/FindDD4hep.cmake create mode 100644 example/.gitignore create mode 100644 example/ddceler/.gitignore create mode 100644 example/ddceler/input/Preshower.xml create mode 100644 example/ddceler/input/steeringFile.py create mode 100755 example/ddceler/run-preshower.sh delete mode 100644 example/geant4/.gitignore delete mode 100644 example/minimal/.gitignore delete mode 100644 example/offload-template/.gitignore create mode 100644 src/ddceler/CMakeLists.txt create mode 100644 src/ddceler/CelerPhysics.cc create mode 100644 src/ddceler/CelerPhysics.hh create mode 100644 src/ddceler/CelerRun.cc create mode 100644 src/ddceler/CelerRun.hh diff --git a/.github/workflows/check-dd4hep-integration.yml b/.github/workflows/check-dd4hep-integration.yml new file mode 100644 index 0000000000..c16b371cc6 --- /dev/null +++ b/.github/workflows/check-dd4hep-integration.yml @@ -0,0 +1,56 @@ +#-------------------------------- -*- yaml -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# +# Check that dd4hep plugin builds and runs on EIC containers +#-----------------------------------------------------------------------------# + +name: Check dd4hep integration + +on: + workflow_dispatch: + workflow_call: + +concurrency: + group: build-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}-${{github.workflow}} + cancel-in-progress: true + +jobs: + check-dd4hep-integration: + container: ghcr.io/eic/eic_ci:nightly + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l -o pipefail {0} + steps: + - name: Check out + uses: actions/checkout@v5 + - name: Configure + run: | + eic-info + cmake --log-level=verbose -S . -B build \ + -DCELERITAS_USE_DD4hep=ON -DCMAKE_INSTALL_PREFIX=install + - name: Build and install + id: build + working-directory: build + run: | + cmake --build . -j8 + cmake --install . + + - name: Run preshower simulation + working-directory: example/ddceler + run: | + ./run-preshower.sh celeritas \ + --numberOfEvents 3 \ + --random.seed=1 \ + --random.enableEventSeed + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: + ${{ + !cancelled() + && steps.build.outcome == 'success' + }} + with: + name: simulation-artifacts + path: example/ddceler/output/** diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bcb62bf5a..cc135a0c28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ celeritas_optional_package(PNG "Enable PNG output with libpng") celeritas_optional_package(Python "Use Python for documentation and testing") celeritas_optional_package(ROOT "Enable ROOT I/O") celeritas_optional_package(VecGeom "Use VecGeom geometry") +celeritas_optional_package(DD4hep "Enable DD4hep integration") option(CELERITAS_USE_Perfetto "Perfetto tracing library" OFF) # Components @@ -332,6 +333,21 @@ set(CELERITAS_RUNTIME_OUTPUT_DIRECTORY # ... #----------------------------------------------------------------------------# +# Model explicit package dependencies +foreach(_pkg LArSoft DD4hep) + if(CELERITAS_USE_${_pkg}) + foreach(_dep ROOT Geant4) + if(NOT CELERITAS_USE_${_dep}) + celeritas_error_incompatible_option( + "${_pkg} requires ${_dep}" CELERITAS_USE_${_dep} ON + ) + endif() + endforeach() + endif() +endforeach() + +#----------------------------------------------------------------------------# + include(CeleritasUtils) if(CELERITAS_USE_CLI11) @@ -344,14 +360,15 @@ if(BUILD_TESTING) endif() if(CELERITAS_USE_LArSoft) - foreach(_dep ROOT Geant4) - if(NOT CELERITAS_USE_${_dep}) - celeritas_error_incompatible_option( - "LArSoft (LArSoft) requires ${_dep}" CELERITAS_USE_${_dep} ON - ) - endif() - endforeach() - find_package(LArSoft REQUIRED) + if(NOT LArSoft_FOUND) + find_package(LArSoft REQUIRED) + endif() +endif() + +if(CELERITAS_USE_DD4hep) + if(NOT DD4hep_FOUND) + find_package(DD4hep REQUIRED COMPONENTS DDCore DDG4) + endif() endif() if(CELERITAS_USE_CUDA) @@ -389,8 +406,10 @@ endif() # Unconditionally set Thrust availability for downstream configuration celeritas_force_package(Thrust "${Thrust_FOUND}") -if(CELERITAS_USE_Geant4 AND NOT Geant4_FOUND) - find_package(Geant4 REQUIRED) +if(CELERITAS_USE_Geant4) + if(NOT Geant4_FOUND) + find_package(Geant4 REQUIRED) + endif() if(CELERITAS_OPENMP STREQUAL "track" AND Geant4_multithreaded_FOUND) message(WARNING "Track-level OpenMP will conflict with MT runs of geant4:" "consider setting CELERITAS_OPENMP to \"event\" or disabling OpenMP" @@ -453,8 +472,10 @@ if(CELERITAS_USE_ROOT) ON ) endif() - # ROOT requirement is due to missing CMake commands in old versions - find_package(ROOT 6.24 REQUIRED) + if(NOT ROOT_FOUND) + # ROOT requirement is due to missing CMake commands in old versions + find_package(ROOT 6.24 REQUIRED) + endif() if(ROOT_CXX_STANDARD AND CMAKE_CXX_STANDARD AND NOT (ROOT_CXX_STANDARD STREQUAL CMAKE_CXX_STANDARD)) message(WARNING "ROOT C++ standard (${ROOT_CXX_STANDARD}) " @@ -463,6 +484,7 @@ if(CELERITAS_USE_ROOT) endif() endif() + if(CELERITAS_USE_covfie) if(CELERITAS_USE_CUDA OR CELERITAS_USE_HIP) set(_min_covfie_version 0.14) diff --git a/CMakePresets.json b/CMakePresets.json index 48b9737e34..7d200675bb 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -47,6 +47,7 @@ "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} } }, diff --git a/cmake/CeleritasConfig.cmake.in b/cmake/CeleritasConfig.cmake.in index e81760356d..36fb209e22 100644 --- a/cmake/CeleritasConfig.cmake.in +++ b/cmake/CeleritasConfig.cmake.in @@ -167,6 +167,10 @@ if(CELERITAS_USE_VecGeom) endif() endif() +if(CELERITAS_USE_DD4hep) + find_dependency(DD4hep REQUIRED) +endif() + cmake_policy(POP) #-----------------------------------------------------------------------------# diff --git a/cmake/FindDD4hep.cmake b/cmake/FindDD4hep.cmake new file mode 100644 index 0000000000..9c3a72d77a --- /dev/null +++ b/cmake/FindDD4hep.cmake @@ -0,0 +1,32 @@ +#------------------------------- -*- cmake -*- -------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#[=======================================================================[.rst: + +FindDD4hep +---------- + +Find the Detector Description Toolkit for High Energy Physics. + +#]=======================================================================] + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + cmake_language(GET_MESSAGE_LOG_LEVEL _orig_log_lev) +else() + set(_orig_log_lev ${CMAKE_MESSAGE_LOG_LEVEL}) +endif() + +if(NOT _orig_log_lev OR _orig_log_lev STREQUAL "STATUS") + # Suppress verbose DD4hep cmake configuration output + set(CMAKE_MESSAGE_LOG_LEVEL WARNING) +endif() + +find_package(DD4hep QUIET CONFIG) + +set(CMAKE_MESSAGE_LOG_LEVEL ${_orig_log_lev}) +unset(_orig_log_lev) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DD4hep CONFIG_MODE) + +#-----------------------------------------------------------------------------# diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000000..cb58885a57 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,2 @@ +*/build/ +*/install/ diff --git a/example/ddceler/.gitignore b/example/ddceler/.gitignore new file mode 100644 index 0000000000..ea1472ec1f --- /dev/null +++ b/example/ddceler/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/example/ddceler/input/Preshower.xml b/example/ddceler/input/Preshower.xml new file mode 100644 index 0000000000..099f18c5eb --- /dev/null +++ b/example/ddceler/input/Preshower.xml @@ -0,0 +1,89 @@ + + + + + + + + Simple two-layer preshower detector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + system:8,x:24:-12,y:-12 + + + + + + + + + + diff --git a/example/ddceler/input/steeringFile.py b/example/ddceler/input/steeringFile.py new file mode 100644 index 0000000000..4b497c7cac --- /dev/null +++ b/example/ddceler/input/steeringFile.py @@ -0,0 +1,61 @@ +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""DDG4 steering file for Preshower benchmark with Celeritas integration.""" + +from DDSim.DD4hepSimulation import DD4hepSimulation + +runner = DD4hepSimulation() + +# Action configuration +runner.action.run = "CelerRun" +runner.action.tracker = "Geant4TrackerAction" +runner.action.trackerSDTypes = ["tracker"] +runner.action.calo = "Geant4CalorimeterAction" +runner.action.calorimeterSDTypes = ["calorimeter"] + +# Output configuration +runner.outputConfig.forceDD4HEP = True + +# Number of events +runner.numberOfEvents = 100 + +# Particle gun configuration +runner.enableGun = True +runner.gun.particle = "e-" +runner.gun.energy = "5*GeV" +runner.gun.distribution = "uniform" +runner.gun.etaMin = 5.0 +runner.gun.etaMax = 5.0 + +# Field tracking configuration - defined once, used by both +# DD4hep/Geant4 and Celeritas +runner.field.delta_chord = 0.025 # mm +runner.field.delta_intersection = 1e-2 # mm +runner.field.delta_one_step = 0.001 # mm +runner.field.eps_min = 5e-5 # mm +runner.field.eps_max = 0.001 # mm +runner.field.min_chord_step = 1e-6 # mm + + +def setup_physics(kernel): + """Configure physics list with Celeritas integration.""" + from DDG4 import Geant4, PhysicsList + + phys = Geant4(kernel).setupPhysics("QGSP_BERT") + celer_phys = PhysicsList(kernel, str("CelerPhysics")) + # MaxNumTracks: max number of tracks in flight + # InitCapacity: initial capacity for state data allocation + # CPU defaults: MaxNumTracks=2048, InitCapacity=245760 + # GPU recommendation: MaxNumTracks=262144, InitCapacity=8388608 + celer_phys.MaxNumTracks = 2048 + celer_phys.InitCapacity = 245760 + # Celeritas does not support single scattering + celer_phys.IgnoreProcesses = ["CoulombScat"] + phys.adopt(celer_phys) + phys.dump() + return None + + +runner.physics.setupUserPhysics(setup_physics) + +runner.part.userParticleHandler = "" diff --git a/example/ddceler/run-preshower.sh b/example/ddceler/run-preshower.sh new file mode 100755 index 0000000000..846edf6387 --- /dev/null +++ b/example/ddceler/run-preshower.sh @@ -0,0 +1,111 @@ +#!/bin/sh -e +#-------------------------------- -*- sh -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# +# Run Preshower benchmark with or without Celeritas offload +# +# Usage: +# ./run-preshower.sh [celeritas|geant4] [additional ddsim options] +# +# Examples: +# ./run-preshower.sh celeritas --numberOfEvents 1000 +# ./run-preshower.sh geant4 --random.seed=42 +#-----------------------------------------------------------------------------# + +log() { + printf "%s\n" "$1" >&2 +} + +# Resolve symlink chain to actual file/executable +resolve_symlinks() { + _path="$1" + while [ -L "$_path" ]; do + printf "Following symlink: %s -> " "$_path" >&2 + _path=$(readlink -f "$_path") + log "$_path" + done + printf "%s\n" "$_path" +} + +# Parse mode argument +MODE=$1 +if [ "$MODE" != "celeritas" ] && [ "$MODE" != "geant4" ]; then + log "Usage: $0 [celeritas|geant4] [additional ddsim options]" + log "" + log " celeritas - Run with Celeritas offload (default)" + log " geant4 - Run with Geant4 only" + exit 1 +fi +shift # Remove first argument + +# Disable Celeritas if running in geant4 mode +if [ "$MODE" = "geant4" ]; then + export CELER_DISABLE=1 +fi + +EXAMPLE_DIR=$(cd "$(dirname $0)" && pwd) + +if [ -z "${Celeritas_ROOT}" ]; then + Celeritas_ROOT=$(cd "$EXAMPLE_DIR"/../.. && pwd)/install + log "warning: Celeritas_ROOT is undefined: using ${Celeritas_ROOT}" +fi + +# Resolve ddsim +DDSIM=$(command -v "ddsim" 2>/dev/null || printf "") +if [ -z "$DDSIM" ]; then + log "error: ddsim: command not found" + exit 1 +fi +DDSIM=$(resolve_symlinks "$DDSIM" true) + +if [ -z "$DD4hepINSTALL" ]; then + log "error: DD4hepINSTALL environment variable is not set" + log " You must load DD4HEP's environment (including its PYTHONPATH and ROOT's)" + exit 1 +fi + +CELER_LIB_DIR=$(ls -1 -d "$Celeritas_ROOT"/lib 2>/dev/null | head -1) +if [ -z "$CELER_LIB_DIR" ]; then + log "error: celeritas installation not found inside $Celeritas_ROOT" + exit 1 +fi + +# Plugin must be available in the runtime library path for DD4hep to find it +if [ "$(uname -s)" = "Darwin" ]; then + _ld_prefix=DY + export DYLD_LIBRARY_PATH=${CELER_LIB_DIR}:${DYLD_LIBRARY_PATH} + if [ -z "$DD4HEP_LIBRARY_PATH" ]; then + # Needed by dd4hep load on macos + log "error: DD4HEP_LIBRARY_PATH environment variable is not set" + exit 1 + fi +else + _ld_prefix= + export LD_LIBRARY_PATH=${CELER_LIB_DIR}:${LD_LIBRARY_PATH} +fi +log "info: Prepended ${CELER_LIB_DIR} to ${_ld_prefix}LD_LIBRARY_PATH" + +# Find python interpreter (prefer python3) +PYTHON=$(command -v "python3" 2>/dev/null || command -v "python" 2>/dev/null || printf "") +if [ -z "$PYTHON" ]; then + log "error: failed to find python3 or python" + exit 1 +fi +PYTHON=$(resolve_symlinks "$PYTHON" true) + +# Set output file name based on run mode +log "Running with ${MODE} physics" +output_file="preshower.root" +log "info: Output will be written to ${output_file}" + +# Create mode-specific subdirectory and change to it +mkdir -p "${EXAMPLE_DIR}/output/${MODE}" +cd "${EXAMPLE_DIR}/output/${MODE}" + +set -x +exec "$PYTHON" "$DDSIM" \ + --compactFile="${EXAMPLE_DIR}/input/Preshower.xml" \ + --steering="${EXAMPLE_DIR}/input/steeringFile.py" \ + --outputFile="${output_file}" \ + "$@" diff --git a/example/geant4/.gitignore b/example/geant4/.gitignore deleted file mode 100644 index a084120796..0000000000 --- a/example/geant4/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/install diff --git a/example/minimal/.gitignore b/example/minimal/.gitignore deleted file mode 100644 index a084120796..0000000000 --- a/example/minimal/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/install diff --git a/example/offload-template/.gitignore b/example/offload-template/.gitignore deleted file mode 100644 index 39e512ccc0..0000000000 --- a/example/offload-template/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -/build* -/install diff --git a/scripts/cmake-presets/hudson.json b/scripts/cmake-presets/hudson.json index 42ce399ade..dee0188902 100644 --- a/scripts/cmake-presets/hudson.json +++ b/scripts/cmake-presets/hudson.json @@ -11,6 +11,7 @@ "cacheVariables": { "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", diff --git a/scripts/spack.yaml b/scripts/spack.yaml index 6514fbfa69..f6dec3607b 100644 --- a/scripts/spack.yaml +++ b/scripts/spack.yaml @@ -26,6 +26,7 @@ spack: - py-sphinx - py-sphinxcontrib-bibtex # ... plus experimental options + - "dd4hep@1.18:" - mpi view: true concretizer: @@ -34,7 +35,9 @@ spack: root: # Note: dd4hep requires +gsl+math # ROOT here is built without GUI to reduce build time - variants: ~aqua ~davix ~examples ~opengl ~x ~tbb + variants: ~aqua ~davix ~examples ~opengl ~x ~tbb ~webgui +root7 + dd4hep: + variants: ~utilityapps ~ddeve all: require: # Note: C++17 is the minimum required but we'll default to C++20 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d08ffc8bec..49b80b529f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,6 +180,9 @@ add_subdirectory(celeritas) if(CELERITAS_USE_Geant4) add_subdirectory(accel) endif() +if(CELERITAS_USE_DD4hep) + add_subdirectory(ddceler) +endif() if(CELERITAS_USE_LArSoft) add_subdirectory(larceler) endif() diff --git a/src/ddceler/CMakeLists.txt b/src/ddceler/CMakeLists.txt new file mode 100644 index 0000000000..c254f0572f --- /dev/null +++ b/src/ddceler/CMakeLists.txt @@ -0,0 +1,128 @@ +#------------------------------- -*- cmake -*- -------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#----------------------------------------------------------------------------# + +# Set variables used by internal dd4hep macros +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CELERITAS_LIBRARY_OUTPUT_DIRECTORY}") +set(LIBRARY_OUTPUT_PATH "${CELERITAS_LIBRARY_OUTPUT_DIRECTORY}") + +#----------------------------------------------------------------------------# +# >>> OVERRIDE DD4HEP TO FIX NEW/RPATH/MACOS ROOT +# see https://github.com/AIDASoft/DD4hep/pull/1545 +#----------------------------------------------------------------------------# + +function(dd4hep_generate_rootmap library) + if(APPLE) + set(_ld_var DYLD_LIBRARY_PATH) + else() + set(_ld_var LD_LIBRARY_PATH) + endif() + + set(DD4HEP_LIBRARY_PATH "$ENV{${_ld_var}}" + CACHE STRING + "Set ${_ld_var} when adding plugins" + ) + + if("$ENV{ROOT_LIBRARY_PATH}") + set(_default_root_lib_path "$ENV{ROOT_LIBRARY_PATH}") + else() + set(_default_root_lib_path "${ROOT_LIBRARY_DIR}") + endif() + set(DD4HEP_ROOT_LIBRARY_PATH "${_default_root_lib_path}" + CACHE STRING + "Set ROOT_LIBRARY_PATH when adding plugins" + ) + + string(JOIN ":" _ld_path + "$" + "$" + "${DD4HEP_LIBRARY_PATH}" + ) + + set(rootmapfile ${CMAKE_SHARED_MODULE_PREFIX}${library}.components) + + add_custom_command(OUTPUT ${rootmapfile} + DEPENDS ${library} + COMMAND + "${CMAKE_COMMAND}" -E env + "${_ld_var}=${_ld_path}" + "ROOT_LIBRARY_PATH=${DD4HEP_ROOT_LIBRARY_PATH}" + $ -o ${rootmapfile} $ + WORKING_DIRECTORY ${LIBRARY_OUTPUT_PATH} + ) + + add_custom_target(Components_${library} ALL DEPENDS ${rootmapfile}) + set(install_destination "lib") + if(CMAKE_INSTALL_LIBDIR) + set(install_destination ${CMAKE_INSTALL_LIBDIR}) + endif() + install(FILES $/${rootmapfile} + DESTINATION ${install_destination} + ) +endfunction() + +# <<< END OVERRIDE +#----------------------------------------------------------------------------# + +get_target_property(_final_lib Celeritas::accel CUDA_RDC_FINAL_LIBRARY) +if(_final_lib) + # Using CUDA RDC: check for other flags as well + if(NOT DEFINED CELERITAS_DISABLE_NEW_DTAGS) + set(_dtag_flag "--disable-new-dtags") + include(CheckLinkerFlag) + check_linker_flag(CXX "LINKER:${_dtag_flag}" CELERITAS_DISABLE_NEW_DTAGS) + if(CELERITAS_DISABLE_NEW_DTAGS) + set(CELERITAS_DISABLE_NEW_DTAGS "${_dtag_flag}" CACHE INTERNAL + "flag used to disable dtags") + endif() + endif() +endif() + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + cmake_language(GET_MESSAGE_LOG_LEVEL _orig_log_lev) +else() + set(_orig_log_lev STATUS) +endif() + +function(celer_dd4hep_plugin target) + # Suppress verbose dd4hep + if(_orig_log_lev STREQUAL STATUS) + set(CMAKE_MESSAGE_LOG_LEVEL WARNING) + endif() + + dd4hep_add_plugin(${target} ${ARGN}) + + if(_final_lib) + # Add dependency on the final library to ensure it's built first + add_dependencies(${target} ${_final_lib}) + + # Link CUDA RDC "final" library into plugin + target_link_options(${target} PRIVATE + "-Wl,--push-state,--no-as-needed" + "$" + "-Wl,--pop-state" + ) + if(CELERITAS_DISABLE_NEW_DTAGS) + set_property(TARGET ${_plugins} APPEND + PROPERTY LINK_FLAGS "-Wl,--disable-new-dtags" + ) + endif() + endif() +endfunction() + +#----------------------------------------------------------------------------# +# Add plugins + +celer_dd4hep_plugin(CelerPhysics + SOURCES CelerPhysics.cc + USES DD4hep::DDG4 Celeritas::accel Celeritas::celeritas Celeritas::corecel +) + +celer_dd4hep_plugin(CelerRun + SOURCES CelerRun.cc + USES DD4hep::DDG4 DD4hep::DDCore + Celeritas::accel Celeritas::celeritas Celeritas::corecel +) + +#----------------------------------------------------------------------------# diff --git a/src/ddceler/CelerPhysics.cc b/src/ddceler/CelerPhysics.cc new file mode 100644 index 0000000000..0d75664d17 --- /dev/null +++ b/src/ddceler/CelerPhysics.cc @@ -0,0 +1,209 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerPhysics.cc +//---------------------------------------------------------------------------// +#include "CelerPhysics.hh" + +#include +#include +#include +#include +#include + +#include "celeritas/field/FieldDriverOptions.hh" +#include "celeritas/inp/Field.hh" +#include "accel/TrackingManagerIntegration.hh" + +using TMI = celeritas::TrackingManagerIntegration; +using Geant4Context = dd4hep::sim::Geant4Context; +using Geant4PhysicsList = dd4hep::sim::Geant4PhysicsList; +using OverlayedField = dd4hep::OverlayedField; +using CartesianField = dd4hep::CartesianField; +using ConstantField = dd4hep::ConstantField; +using Direction = dd4hep::Direction; + +namespace celeritas +{ +namespace dd +{ +namespace +{ +//---------------------------------------------------------------------------// + +FieldDriverOptions load_driver_options(dd4hep::sim::Geant4Action* field_action) +{ + FieldDriverOptions driver_options; + constexpr auto celer_mm = units::millimeter; + + // Load field tracking parameters directly from DD4hep action properties + // Values are in DD4hep units (mm) + driver_options.delta_chord + = field_action->property("delta_chord").value() * celer_mm; + driver_options.delta_intersection + = field_action->property("delta_intersection").value() + * celer_mm; + driver_options.minimum_step + = field_action->property("delta_one_step").value() * celer_mm; + + return driver_options; +} + +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Standard constructor + */ +CelerPhysics::CelerPhysics(Geant4Context* ctxt, std::string const& name) + : Geant4PhysicsList(ctxt, name) +{ + declareProperty("MaxNumTracks", max_num_tracks_); + declareProperty("InitCapacity", init_capacity_); + declareProperty("IgnoreProcesses", ignore_processes_); +} + +//---------------------------------------------------------------------------// +SetupOptions CelerPhysics::make_options() +{ + SetupOptions opts; + + // Validate configuration parameters + CELER_VALIDATE(max_num_tracks_ > 0, + << "MaxNumTracks must be set to a positive value (got " + << max_num_tracks_ << ")"); + CELER_VALIDATE(init_capacity_ > 0, + << "InitCapacity must be set to a positive value (got " + << init_capacity_ << ")"); + + opts.max_num_tracks = max_num_tracks_; + opts.initializer_capacity = init_capacity_; + + // Set ignored processes from configuration + for (auto const& proc : ignore_processes_) + { + opts.ignore_processes.push_back(proc); + } + + // Get the field from DD4hep detector description and validate its type + auto& detector = context()->detectorDescription(); + auto field = detector.field(); + auto* overlaid_obj = field.data(); + + // Validate field configuration: no electric components + CELER_VALIDATE(overlaid_obj->electric_components.empty(), + << "Celeritas does not support electric field components. " + "Found " + << overlaid_obj->electric_components.size() + << " electric component(s)."); + + CELER_VALIDATE(!overlaid_obj->magnetic_components.empty(), + << "No magnetic field components found in DD4hep field " + "description."); + + // Check that all magnetic components are ConstantField and sum them + Direction field_direction(0, 0, 0); + for (auto const& mag_component : overlaid_obj->magnetic_components) + { + auto* cartesian_obj = mag_component.data(); + auto* const_field = dynamic_cast(cartesian_obj); + + CELER_VALIDATE(const_field, + << "Celeritas currently only supports ConstantField " + "magnetic " + << "fields. Found non-constant field component in " + "DD4hep " + << "description."); + field_direction += const_field->direction; + } + + // Print field strength + // Note: field_direction is already in DD4hep internal units (parsed from + // XML) DD4hep supports tesla, gauss, kilogauss, etc. in XML and converts + // to internal units + constexpr double dd4hep_tesla = dd4hep::tesla; + CELER_LOG(debug) << "Field strength: (" + << field_direction.X() / dd4hep_tesla << ", " + << field_direction.Y() / dd4hep_tesla << ", " + << field_direction.Z() / dd4hep_tesla << ") T"; + + // Get field tracking parameters from DD4hep FieldSetup action + // These parameters are set in the steering file (runner.field.*) + FieldDriverOptions driver_options; + + auto& kernel = context()->kernel(); + auto* config_phase = kernel.getPhase("configure"); + + dd4hep::sim::Geant4Action* field_action = nullptr; + if (config_phase) + { + // Find the MagFieldTrackingSetup action in the configure phase + for (auto const& [action, callback] : config_phase->members()) + { + if (action->name() == "MagFieldTrackingSetup") + { + field_action = action; + break; + } + } + } + + if (field_action) + { + driver_options = load_driver_options(field_action); + CELER_LOG(debug) << "Loaded field driver options from DD4hep " + "FieldSetup action"; + } + else + { + CELER_LOG(warning) << "MagFieldTrackingSetup action not found, using " + "default field parameters"; + } + + // Print field driver options + constexpr auto celer_mm = units::millimeter; + CELER_LOG(debug) + << "Field driver options: min_step=" + << driver_options.minimum_step / celer_mm + << " mm, delta_chord=" << driver_options.delta_chord / celer_mm + << " mm, delta_intersection=" + << driver_options.delta_intersection / celer_mm << " mm"; + + // Use a uniform magnetic field based on DD4hep ConstantField + auto make_field_input = [field_direction, driver_options] { + inp::UniformField input; + + // Convert from DD4hep (tesla) to Celeritas field units + input.strength = {field_direction.X() / dd4hep_tesla, + field_direction.Y() / dd4hep_tesla, + field_direction.Z() / dd4hep_tesla}; + input.driver_options = driver_options; + return input; + }; + opts.make_along_step = UniformAlongStepFactory(make_field_input); + opts.sd.ignore_zero_deposition = false; + + // Save diagnostic file to a unique name + opts.output_file = "ddceler.out.json"; + opts.geometry_output_file = "ddceler.out.gdml"; + return opts; +} + +//---------------------------------------------------------------------------// + +void CelerPhysics::constructPhysics(G4VModularPhysicsList* physics) +{ + // Register Celeritas tracking manager + auto& tmi = TMI::Instance(); + physics->RegisterPhysics(new TrackingManagerConstructor(&tmi)); + + // Configure Celeritas options + tmi.SetOptions(this->make_options()); +} + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas + +DECLARE_GEANT4ACTION_NS(celeritas::dd, CelerPhysics) diff --git a/src/ddceler/CelerPhysics.hh b/src/ddceler/CelerPhysics.hh new file mode 100644 index 0000000000..f99ccc14d5 --- /dev/null +++ b/src/ddceler/CelerPhysics.hh @@ -0,0 +1,47 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerPhysics.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * DDG4 action plugin for Celeritas tracking manager integration (TMI). + */ +class CelerPhysics final : public dd4hep::sim::Geant4PhysicsList +{ + public: + // Standard constructor + CelerPhysics(dd4hep::sim::Geant4Context* ctxt, std::string const& name); + + // Delete copy/move + DDG4_DEFINE_ACTION_CONSTRUCTORS(CelerPhysics); + + // constructPhysics callback + virtual void constructPhysics(G4VModularPhysicsList* physics) final; + + private: + int max_num_tracks_{0}; + int init_capacity_{0}; + std::vector ignore_processes_; + + // Make options for Celeritas tracking manager + SetupOptions make_options(); +}; + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas diff --git a/src/ddceler/CelerRun.cc b/src/ddceler/CelerRun.cc new file mode 100644 index 0000000000..7662b9c501 --- /dev/null +++ b/src/ddceler/CelerRun.cc @@ -0,0 +1,58 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerRun.cc +//---------------------------------------------------------------------------// +#include "CelerRun.hh" + +#include +#include + +#include "accel/TrackingManagerIntegration.hh" + +using TMI = celeritas::TrackingManagerIntegration; +using Geant4Context = dd4hep::sim::Geant4Context; +using Geant4RunAction = dd4hep::sim::Geant4RunAction; +using InstanceCount = dd4hep::InstanceCount; + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * Standard constructor. + */ +CelerRun::CelerRun(Geant4Context* ctxt, std::string const& name) + : Geant4RunAction(ctxt, name) +{ + InstanceCount::increment(this); +} + +//---------------------------------------------------------------------------// + +CelerRun::~CelerRun() +{ + InstanceCount::decrement(this); +} + +//---------------------------------------------------------------------------// + +void CelerRun::begin(G4Run const* run) +{ + TMI::Instance().BeginOfRunAction(run); +} + +//---------------------------------------------------------------------------// + +void CelerRun::end(G4Run const* run) +{ + TMI::Instance().EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas + +DECLARE_GEANT4ACTION_NS(celeritas::dd, CelerRun) diff --git a/src/ddceler/CelerRun.hh b/src/ddceler/CelerRun.hh new file mode 100644 index 0000000000..d2eb61af9d --- /dev/null +++ b/src/ddceler/CelerRun.hh @@ -0,0 +1,39 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerRun.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * DDG4 action plugin for Celeritas run action. + */ +class CelerRun final : public dd4hep::sim::Geant4RunAction +{ + public: + // Standard constructor + CelerRun(dd4hep::sim::Geant4Context* ctxt, std::string const& name); + + // Run action callbacks + void begin(G4Run const* run) final; + void end(G4Run const* run) final; + + protected: + // Define standard assignments and constructors + DDG4_DEFINE_ACTION_CONSTRUCTORS(CelerRun); + ~CelerRun() final; +}; + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas From 72489564fca460834acc8d1909ed4ca7167924bf Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 6 Jan 2026 07:23:45 -0500 Subject: [PATCH 23/60] Use a reasonable project version for shallow clones (#2175) * Update CgvFindVersion to handle fallback * Set fallback version * Update cache var name * Update version * Allow empty major/minor/patch * Fix remaining git describes and add dash before version string * Remove duplicate call command * Add comment about -dev and version fallback --- .github/workflows/build-fast.yml | 20 ++-- .github/workflows/build-ultralite.yml | 16 ++-- CMakeLists.txt | 3 + CMakePresets.json | 2 +- cmake/CgvFindVersion.cmake | 130 ++++++++++++++++++-------- doc/development/administration.rst | 4 +- src/corecel/Version.cc.in | 6 +- 7 files changed, 120 insertions(+), 61 deletions(-) diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml index ffe0287e54..451e57e137 100644 --- a/.github/workflows/build-fast.yml +++ b/.github/workflows/build-fast.yml @@ -81,11 +81,13 @@ jobs: run: | ln -fs scripts/cmake-presets/ci-ubuntu-github.json CMakeUserPresets.json cmake --version - cmake --preset=${CMAKE_PRESET} \ + cmake --preset=${CMAKE_PRESET} --log-level=VERBOSE \ ${{matrix.cxxstd && format('-DCMAKE_CXX_STANDARD={0} ', matrix.cxxstd) || ''}} \ - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### @@ -174,10 +176,12 @@ jobs: - name: Configure Celeritas run: | Copy-Item scripts/cmake-presets/ci-windows-github.json -Destination CMakeUserPresets.json - cmake --preset=$Env:CMAKE_PRESET ` - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + cmake --preset=$Env:CMAKE_PRESET --log-level=VERBOSE ` + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### diff --git a/.github/workflows/build-ultralite.yml b/.github/workflows/build-ultralite.yml index 37c6e1fbcc..b5e61e1017 100644 --- a/.github/workflows/build-ultralite.yml +++ b/.github/workflows/build-ultralite.yml @@ -41,9 +41,9 @@ jobs: ln -fs scripts/cmake-presets/ci-ubuntu-github.json CMakeUserPresets.json cmake --version cmake --preset=${CMAKE_PRESET} --log-level=VERBOSE \ - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};;;', github.event.pull_request.number) - || format(';-{0};;;', github.ref_name)}}" + -DCeleritas_VERSION_STRING="${{github.event.pull_request + && format('pr.{0}', github.event.pull_request.number) + || github.ref_name}}" ### BUILD ### @@ -138,10 +138,12 @@ jobs: run: | Copy-Item scripts/cmake-presets/ci-windows-github.json -Destination CMakeUserPresets.json cmake --version - cmake --preset=$Env:CMAKE_PRESET ` - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + cmake --preset=$Env:CMAKE_PRESET --log-level=VERBOSE ` + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### diff --git a/CMakeLists.txt b/CMakeLists.txt index cc135a0c28..f71c2c46c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ cmake_minimum_required(VERSION 3.18...4.1) +# Fallback version in case of shallow clone: update when tagging "-dev" +set(Celeritas_VERSION 0.7) +# Load actual version from git metadata include("${CMAKE_CURRENT_LIST_DIR}/cmake/CgvFindVersion.cmake") cgv_find_version(Celeritas) diff --git a/CMakePresets.json b/CMakePresets.json index 7d200675bb..6a2e5d4fbf 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,7 +8,7 @@ "binaryDir": "${sourceDir}/build-${presetName}", "generator": "Ninja", "cacheVariables": { - "Celeritas_GIT_DESCRIBE": "", + "Celeritas_CGV_CACHE": "", "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CMAKE_CXX_COMPILER_LAUNCHER": {"type": "STRING", "value": "$env{CCACHE_PROGRAM}"}, diff --git a/cmake/CgvFindVersion.cmake b/cmake/CgvFindVersion.cmake index 6d4edc4ae0..3a149acd94 100644 --- a/cmake/CgvFindVersion.cmake +++ b/cmake/CgvFindVersion.cmake @@ -1,5 +1,6 @@ #------------------------------- -*- cmake -*- -------------------------------# # SPDX-License-Identifier: Apache-2.0 +# SPDX-PackageName: "CGV: CMake Git Version" # # https://github.com/sethrj/cmake-git-version # @@ -65,6 +66,11 @@ CgvFindVersion metadata, and it will re-run cmake if that file changes, and re-run the associated git commands only if the file changes. + .. note:: Since there are cases where no metadata is available (e.g., a + shallow clone), you can provide a *fallback* version by specifying + ``${PROJECT}_VERSION`` and (optionally) ``${PROJECT}_VERSION_STRING`` at the + top level of the cmake or (if using a package manager) via the command line. + .. note:: In order for this script to work properly with archived git repositories (generated with ``git-archive`` or GitHub's release tarball feature), it's necessary to add to your ``.gitattributes`` file:: @@ -109,8 +115,8 @@ macro(_cgv_git_call_output output_var) COMMAND "${GIT_EXECUTABLE}" ${ARGN} WORKING_DIRECTORY "${CGV_SOURCE_DIR}" OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_VARIABLE GIT_ERR - RESULT_VARIABLE GIT_RESULT + ERROR_VARIABLE ${output_var}_ERR + RESULT_VARIABLE ${output_var}_RESULT OUTPUT_VARIABLE ${output_var} ) endmacro() @@ -119,9 +125,25 @@ endmacro() # Save the version with a timestamp to a cache variable function(_cgv_store_version vstring vsuffix vhash tsfile) + if(NOT vstring) + # Use fallback version 0.1.2 + set(vstring "${${CGV_PROJECT}_VERSION}") + if(vstring) + # Look for and use version string "0.1.2-x+yz" + string(REPLACE "${vstring}" "" vsuffix "${${CGV_PROJECT}_VERSION_STRING}") + message(VERBOSE "Using fallback version and string: " + "${CGV_PROJECT}_VERSION=${vstring}, " + "${CGV_PROJECT}_VERSION_STRING=${${CGV_PROJECT}_VERSION_STRING}" + ) + endif() + endif() if(NOT vstring) message(WARNING "The version metadata for ${CGV_PROJECT} could not " - "be determined: installed version number may be incorrect") + "be determined: installed version number may be incorrect. Try " + "downloading an official release tarball, using `git archive`, " + "using `git clone` without `--shallow` nor deleting `.git`, or " + "manually specifying a known version by configuring with " + "`-D${CGV_PROJECT}_VERSION=1.2.3`") endif() # Replace 11-03 with 11.3 string(REGEX REPLACE "-+0*" "." vstring "${vstring}") @@ -137,7 +159,7 @@ function(_cgv_store_version vstring vsuffix vhash tsfile) "${vstring}" "${vsuffix}" "${vhash}" "${tsfile}" "${_vtimestamp}" ) # Note: extra 'unset' is necessary if using CMake presets with - # ${CGV_PROJECT}_GIT_DESCRIBE="", even with INTERNAL/FORCE + # ${CGV_PROJECT}_CGV_CACHE="", even with INTERNAL/FORCE unset("${CGV_CACHE_VAR}" CACHE) set("${CGV_CACHE_VAR}" "${_CACHED_VERSION}" CACHE INTERNAL "Version string and hash for ${CGV_PROJECT}") @@ -146,15 +168,10 @@ endfunction() #-----------------------------------------------------------------------------# # Get the path to the git head used to describe the current repository -function(_cgv_git_path resultvar) - if(GIT_EXECUTABLE) - _cgv_git_call_output(_TSFILE "rev-parse" "--git-path" "HEAD") - else() - set(GIT_RESULT 1) - set(GIT_ERR "GIT_EXECUTABLE is not defined") - endif() - if(GIT_RESULT) - message(AUTHOR_WARNING "Failed to get path to git head: ${GIT_ERR}") +function(_cgv_git_path_head resultvar) + _cgv_git_call_output(_TSFILE "rev-parse" "--git-path" "HEAD") + if(_TSFILE_RESULT) + message(AUTHOR_WARNING "Failed to get path to git head: ${_TSFILE_ERR}") set(_TSFILE) else() get_filename_component(_TSFILE "${_TSFILE}" ABSOLUTE BASE_DIR @@ -192,23 +209,23 @@ function(_cgv_try_parse_git_describe version_string branch_string tsfile) if(CMAKE_MATCH_2) # After a pre-release, e.g. -rc.1, for SemVer compatibility - set(_prerelease "${CMAKE_MATCH_2}.${CMAKE_MATCH_4}") + set(_suffix "${CMAKE_MATCH_2}.${CMAKE_MATCH_4}") else() # After a release, e.g. -123 - set(_prerelease "-${CMAKE_MATCH_4}") + set(_suffix "-${CMAKE_MATCH_4}") endif() if(branch_string) - set(_suffix "${branch_string}.${CMAKE_MATCH_5}") + set(_hash "${branch_string}.${CMAKE_MATCH_5}") else() - set(_suffix "${CMAKE_MATCH_5}") + set(_hash "${CMAKE_MATCH_5}") endif() # Qualify the version number with the distance-to-tag and hash _cgv_store_version( "${CMAKE_MATCH_1}" # 1.2.3 - "${_prerelease}" # -rc.2.3, -beta.1, -123 - "${_suffix}" # abcdef + "${_suffix}" # -rc.2.3, -beta.1, -123 + "${_hash}" # abcdef "${tsfile}" # timestamp file ) endfunction() @@ -271,13 +288,8 @@ endfunction() #-----------------------------------------------------------------------------# # Try git's 'describe' function function(_cgv_try_git_describe) - # First time calling "git describe" if(NOT Git_FOUND) - find_package(Git QUIET) - if(NOT Git_FOUND) - message(WARNING "Could not find Git, needed to find the version tag") - return() - endif() + return() endif() if(CGV_TAG_REGEX MATCHES "^\\^?([a-z-]+)") @@ -288,12 +300,12 @@ function(_cgv_try_git_describe) # Load git description _cgv_git_call_output(_VERSION_STRING "describe" "--tags" ${_match}) - if(GIT_RESULT) - message(AUTHOR_WARNING "No suitable git tags found': ${GIT_ERR}") + if(_VERSION_STRING_RESULT) + message(AUTHOR_WARNING "No git tags match '${CGV_TAG_REGEX}': ${_VERSION_STRING_ERR}") return() endif() - if(GIT_ERR) - message(AUTHOR_WARNING "git describe warned: ${GIT_ERR}") + if(_VERSION_STRING_ERR) + message(AUTHOR_WARNING "git describe warned: ${_VERSION_STRING_ERR}") endif() if(NOT _VERSION_STRING) message(AUTHOR_WARNING "Failed to get ${CGV_PROJECT} version from git: " @@ -305,16 +317,17 @@ function(_cgv_try_git_describe) # the desired behavior _cgv_git_call_output(_BRANCH_STRING "symbolic-ref" "--short" "HEAD") - _cgv_git_path(_TSFILE) + _cgv_git_path_head(_TSFILE) _cgv_try_parse_git_describe("${_VERSION_STRING}" "${_BRANCH_STRING}" "${_TSFILE}") endfunction() #-----------------------------------------------------------------------------# function(_cgv_try_git_hash) - if(NOT GIT_EXECUTABLE) + if(NOT Git_FOUND) return() endif() + # Fall back to just getting the hash _cgv_git_call_output(_VERSION_HASH "log" "-1" "--format=%h" "HEAD") if(_VERSION_HASH_RESULT) @@ -323,10 +336,24 @@ function(_cgv_try_git_hash) return() endif() - _cgv_git_path(_TSFILE) + _cgv_git_path_head(_TSFILE) _cgv_store_version("" "" "${_VERSION_HASH}" "${_TSFILE}") endfunction() +#-----------------------------------------------------------------------------# + +function(_cgv_try_cmake) + if(NOT DEFINED "${${CGV_PROJECT}_VERSION}") + message(AUTHOR_WARNING + "No fallback version specified from ${CGV_PROJECT}_VERSION") + return() + endif() + + _cgv_store_version("" "" "" "${CMAKE_PARENT_LIST_FILE}") +endfunction() + +#-----------------------------------------------------------------------------# + function(_cgv_try_all) if(${CGV_CACHE_VAR}) # Previous configure already set the variable: check the timestamp @@ -335,7 +362,7 @@ function(_cgv_try_all) list(GET ${CGV_CACHE_VAR} 3 _tsfile) list(GET ${CGV_CACHE_VAR} 4 _timestamp) else() - message(VERBOSE "Old cache variable ${CGV_CACHE_VAR}: length=${_len}") + message(VERBOSE "Invalid cache variable ${CGV_CACHE_VAR}: length=${_len}") set(_tsfile) endif() if(_tsfile) @@ -358,6 +385,22 @@ function(_cgv_try_all) return() endif() + # Now check for git + if(NOT Git_FOUND) + find_package(Git QUIET) + if(NOT Git_FOUND) + message(WARNING "Could not find Git (needed to find the version tag)") + endif() + endif() + if(Git_FOUND) + _cgv_git_call_output(_GIT_DIR "rev-parse" "--git-dir") + if(_GIT_DIR_RESULT) + message(VERBOSE "git: ${_GIT_DIR_ERR}") + message(WARNING "Not a git repository (needed to find the version tag)") + set(Git_FOUND FALSE) + endif() + endif() + _cgv_try_git_describe() if(${CGV_CACHE_VAR}) return() @@ -368,8 +411,13 @@ function(_cgv_try_all) return() endif() + _cgv_try_cmake() + if(${CGV_CACHE_VAR}) + return() + endif() + # Fallback: no metadata detected - set(${CGV_CACHE_VAR} "" "-unknown" "") + _cgv_store_version("" "" "unknown" "none") endfunction() #-----------------------------------------------------------------------------# @@ -389,7 +437,7 @@ function(cgv_find_version) set(CGV_TAG_REGEX "v([0-9.]+)(-[a-z]+[0-9.]*)?") endif() - set(CGV_CACHE_VAR "${CGV_PROJECT}_GIT_DESCRIBE") + set(CGV_CACHE_VAR "${CGV_PROJECT}_CGV_CACHE") # Try all possible ways of obtaining metadata _cgv_try_all() @@ -427,17 +475,17 @@ if(CMAKE_SCRIPT_MODE_FILE) if(DEFINED SOURCE_DIR) set(CGV_SOURCE_DIR ${SOURCE_DIR}) endif() - cgv_find_version(TEMP) + cgv_find_version(LOCAL) if(DEFINED ONLY) # Print only the given variable, presumably VERSION or VERSION_STRING # (will print to stderr) - set(VERSION "${TEMP_VERSION}") - set(VERSION_STRING "${TEMP_VERSION_STRING}") + set(VERSION "${LOCAL_VERSION}") + set(VERSION_STRING "${LOCAL_VERSION_STRING}") message("${${ONLY}}") else() - message("VERSION=\"${TEMP_VERSION}\"") - message("VERSION_STRING=\"${TEMP_VERSION_STRING}\"") + message("VERSION=\"${LOCAL_VERSION}\"") + message("VERSION_STRING=\"${LOCAL_VERSION_STRING}\"") endif() endif() -# cmake-git-version 1.2.1-5+main.bdcc7d7 +# cmake-git-version 1.3.1 diff --git a/doc/development/administration.rst b/doc/development/administration.rst index c44cb07c33..3cf1c32729 100644 --- a/doc/development/administration.rst +++ b/doc/development/administration.rst @@ -462,7 +462,9 @@ commit on the ``develop`` branch that is *not* intended for version 1.0.1 (i.e., the first new feature) should be tagged with ``v1.1.0-dev``, so that ``git describe --tags --match 'v*'`` shows the new features as being part of the -``v1.1.0`` series. +``v1.1.0`` series. That pull request should *also* update the "fallback" +version number ``Celeritas_VERSION`` stored in the top-level +:file:`CMakeLists.txt`. .. _helper notebook: https://github.com/celeritas-project/celeritas-docs/blob/master/nb/admin/github-stats.ipynb .. _geant-val: https://geant-val.cern.ch diff --git a/src/corecel/Version.cc.in b/src/corecel/Version.cc.in index 8c17517dd5..4029b83218 100644 --- a/src/corecel/Version.cc.in +++ b/src/corecel/Version.cc.in @@ -14,11 +14,11 @@ namespace celeritas //! Celeritas version string with git metadata char const version_string[] = "@Celeritas_VERSION_STRING@"; //! Celeritas major version -unsigned int const version_major = @PROJECT_VERSION_MAJOR@; +unsigned int const version_major{@Celeritas_VERSION_MAJOR@}; //! Celeritas minor version -unsigned int const version_minor = @PROJECT_VERSION_MINOR@; +unsigned int const version_minor{@Celeritas_VERSION_MINOR@}; //! Celeritas patch version -unsigned int const version_patch = @PROJECT_VERSION_PATCH@; +unsigned int const version_patch{@Celeritas_VERSION_PATCH@}; //---------------------------------------------------------------------------// } // namespace celeritas From 7f4908682344f5507038ccf570ed069c59caf0cd Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 6 Jan 2026 19:00:14 -0500 Subject: [PATCH 24/60] Add NO_COLOR support and new env helper (#2179) * Add getenv_flag_lazy * Add support for NO_COLOR and rename `color_code` to `ansi_color` * Fix assert test * Use recursive mutex * Use localized locking to prevent recursive call * Minor fixes * Support `auto` keyword for env variables * Add test of invalid flag warning (HANGS due to lock) * Revert "Use localized locking to prevent recursive call" This reverts commit fae85dfd37dcb31d015a4a009dd57384712165b0. * Add tests of log messages * Add another comment --- doc/usage/execution/environment.rst | 2 + src/corecel/io/ColorUtils.cc | 66 ++++++++++++++-------- src/corecel/io/ColorUtils.hh | 13 ++++- src/corecel/sys/Environment.cc | 54 +++++++++++++++--- src/corecel/sys/Environment.hh | 4 ++ test/corecel/Assert.test.cc | 54 +++++++++--------- test/corecel/ScopedLogStorer.cc | 6 ++ test/corecel/ScopedLogStorer.hh | 2 +- test/corecel/sys/Environment.test.cc | 84 ++++++++++++++++++++++++++-- 9 files changed, 216 insertions(+), 69 deletions(-) diff --git a/doc/usage/execution/environment.rst b/doc/usage/execution/environment.rst index 1e99519ed6..05268304b4 100644 --- a/doc/usage/execution/environment.rst +++ b/doc/usage/execution/environment.rst @@ -93,6 +93,8 @@ Celeritas or its apps: thread access the ``celeritas::environment()`` struct (see :ref:`api_system`), and call ``insert`` for the desired key/value pairs. +.. doxygenfunction:: celeritas::use_color + .. _logging: Logging diff --git a/src/corecel/io/ColorUtils.cc b/src/corecel/io/ColorUtils.cc index 534bef7559..a96ed246c8 100644 --- a/src/corecel/io/ColorUtils.cc +++ b/src/corecel/io/ColorUtils.cc @@ -13,6 +13,7 @@ # include #endif +#include "corecel/Assert.hh" #include "corecel/sys/Environment.hh" namespace celeritas @@ -20,7 +21,7 @@ namespace celeritas namespace { //---------------------------------------------------------------------------// -// Get a default color based on the terminal settings +//! Get a default color based on the terminal settings bool default_term_color() { #ifndef _WIN32 @@ -28,9 +29,9 @@ bool default_term_color() return isatty(fileno(stderr)); #endif // Fall back to checking environment variable - if (char const* term_str = std::getenv("TERM")) + if (auto term_str = celeritas::getenv("TERM"); !term_str.empty()) { - if (std::string{term_str}.find("xterm") != std::string::npos) + if (term_str.find("xterm") != std::string::npos) { // 'xterm' is in the TERM type, so assume it uses colors return true; @@ -41,9 +42,9 @@ bool default_term_color() //---------------------------------------------------------------------------// /*! - * Get a default color based on the terminal/env settings. + * Get the preferred environment variable to use for color override. */ -char const* default_color_env_str() +char const* color_env_var() { auto hasenv = [](char const* key) { return std::getenv(key) != nullptr; }; static char const celer_env[] = "CELER_COLOR"; @@ -58,17 +59,26 @@ char const* default_color_env_str() //---------------------------------------------------------------------------// /*! - * Whether colors are enabled (currently read-only). + * Whether colors are enabled by the environment. * - * This checks the \c CELER_COLOR environment variable, or the \c - * GTEST_COLOR variable (if it is defined and CELER_COLOR is not), and defaults - * based on the terminal settings of \c stderr. + * The \c NO_COLOR environment variable, if set to a non-empty value, disables + * color output. If either of the \c CELER_COLOR or \c GTEST_COLOR variables is + * set, that value will be used. Failing that, the default is true if \c stderr + * is a tty. The result of this value is used by \c ansi_color . */ bool use_color() { - static bool const result - = celeritas::getenv_flag(default_color_env_str(), default_term_color()) - .value; + static bool const result = [] { + if (auto term_str = celeritas::getenv("NO_COLOR"); !term_str.empty()) + { + // See https://no-color.org + return false; + } + + // Check one environment variable and fall back to terminal color + auto flag = getenv_flag_lazy(color_env_var(), default_term_color); + return flag.value; + }(); return result; } @@ -83,35 +93,43 @@ bool use_color() * - [x] gray * - [R]ed bold * - [W]hite bold - * - [ ] default (reset color) + * - \c null reset color + * - [ ] reset color */ -char const* color_code(char abbrev) +char const* ansi_color(char abbrev) { if (!use_color()) return ""; switch (abbrev) { - case 'g': - return "\033[32m"; - case 'b': - return "\033[34m"; + case '\0': + [[fallthrough]]; + case ' ': + return "\033[0m"; case 'r': return "\033[31m"; - case 'x': - return "\033[37;2m"; + case 'g': + return "\033[32m"; case 'y': return "\033[33m"; + case 'b': + return "\033[34m"; case 'R': - return "\033[31;1m"; + return "\033[1;31m"; + case 'G': + return "\033[1;32m"; + case 'B': + return "\033[1;34m"; case 'W': - return "\033[37;1m"; + return "\033[1;37m"; + case 'x': + return "\033[2;37m"; default: return "\033[0m"; } - // Unknown color code: ignore - return ""; + CELER_ASSERT_UNREACHABLE(); } //---------------------------------------------------------------------------// diff --git a/src/corecel/io/ColorUtils.hh b/src/corecel/io/ColorUtils.hh index e79e706214..1435bb265c 100644 --- a/src/corecel/io/ColorUtils.hh +++ b/src/corecel/io/ColorUtils.hh @@ -7,6 +7,8 @@ //---------------------------------------------------------------------------// #pragma once +#include "corecel/Macros.hh" + namespace celeritas { //---------------------------------------------------------------------------// @@ -14,8 +16,15 @@ namespace celeritas bool use_color(); //---------------------------------------------------------------------------// -// Get an ANSI color code: [y]ellow / [r]ed / [ ]default / ... -char const* color_code(char abbrev); +// Get an ANSI color code: [y]ellow / [r]ed / [\0]reset / ... +char const* ansi_color(char abbrev = '\0'); + +//---------------------------------------------------------------------------// +// DEPRECATED (remove in v1.0): get an ANSI color code +CELER_FORCEINLINE char const* color_code(char abbrev) +{ + return ansi_color(abbrev); +} //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/sys/Environment.cc b/src/corecel/sys/Environment.cc index 2a88d97032..621f131049 100644 --- a/src/corecel/sys/Environment.cc +++ b/src/corecel/sys/Environment.cc @@ -14,16 +14,25 @@ #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" +using BoolFunc = std::function; + namespace celeritas { namespace { //---------------------------------------------------------------------------// -std::mutex& getenv_mutex() +/*! + * Use a recursive mutex due to "lazy" callbacks possibly using environment. + * + * Recursion (one getenv_lazy within another) can also be present due to the + * CELER_LOG calls. + */ +std::recursive_mutex& getenv_mutex() { - static std::mutex mu; + static std::recursive_mutex mu; return mu; } + //---------------------------------------------------------------------------// } // namespace @@ -77,6 +86,17 @@ std::string const& getenv(std::string const& key) */ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) { + return getenv_flag_lazy(key, [default_val]() { return default_val; }); +} + +//---------------------------------------------------------------------------// +/*! + * Like \c getenv_flag but calls a function only when a default is needed. + */ +GetenvFlagResult +getenv_flag_lazy(std::string const& key, BoolFunc const& get_default_value) +{ + CELER_EXPECT(get_default_value); std::scoped_lock lock_{getenv_mutex()}; // Get the string value from the existing environment *or* system @@ -102,8 +122,8 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) }(); GetenvFlagResult result; + bool invalid_str{false}; result.defaulted = str_value.empty(); - result.value = default_val; if (!result.defaulted) { str_value = tolower(str_value); @@ -111,8 +131,13 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) static char const* const true_str[] = {"1", "t", "yes", "true"}; static char const* const false_str[] = {"0", "f", "no", "false"}; - if (std::find(std::begin(true_str), std::end(true_str), str_value) - != std::end(true_str)) + if (str_value == "auto") + { + // User forcing default value + result.defaulted = true; + } + else if (std::find(std::begin(true_str), std::end(true_str), str_value) + != std::end(true_str)) { result.value = true; } @@ -123,14 +148,25 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) } else { + result.defaulted = true; + invalid_str = true; + } + } + + if (result.defaulted) + { + // Get automatic default + result.value = get_default_value(); + + if (invalid_str) + { + // Warn after getting value, before overwriting string CELER_LOG(warning) << "Invalid environment value " << key << "=" << str_value << " (expected a flag): using default=" << result.value; } - } - else - { - // Save string value to be added to environment + + // Save string value actually used in environment str_value = result.value ? "1" : "0"; } diff --git a/src/corecel/sys/Environment.hh b/src/corecel/sys/Environment.hh index 275dfa4073..aff88c673a 100644 --- a/src/corecel/sys/Environment.hh +++ b/src/corecel/sys/Environment.hh @@ -112,6 +112,10 @@ std::string const& getenv(std::string const& key); // Thread-safe flag access to environment variables GetenvFlagResult getenv_flag(std::string const& key, bool default_val); +// Thread-safe flag access to environment variables with lazy function default +GetenvFlagResult +getenv_flag_lazy(std::string const& key, std::function const&); + // Write the accessed environment variables to a stream std::ostream& operator<<(std::ostream&, Environment const&); diff --git a/test/corecel/Assert.test.cc b/test/corecel/Assert.test.cc index 51d11c48fa..4172904937 100644 --- a/test/corecel/Assert.test.cc +++ b/test/corecel/Assert.test.cc @@ -25,11 +25,10 @@ class AssertTest : public ::celeritas::test::Test protected: static void SetUpTestSuite() { - EXPECT_FALSE(celeritas::getenv("CELER_COLOR").empty() - && celeritas::getenv("GTEST_COLOR").empty()) + EXPECT_TRUE(celeritas::getenv("NO_COLOR").empty() + && (celeritas::getenv_flag("CELER_COLOR", true).value + || celeritas::getenv_flag("GTEST_COLOR", true).value)) << "Color must be enabled for this test"; - EXPECT_FALSE(celeritas::getenv("CELER_LOG").empty()) - << "Logging (for verbose output) must be enabled for this test"; } }; @@ -42,8 +41,8 @@ TEST_F(AssertTest, debug_error) details.line = 123; EXPECT_STREQ( - "\x1B[37;1mAssert.test.cc:123:\x1B[0m\nceleritas: \x1B[31;1minternal " - "assertion failed: \x1B[37;2m2 + 2 == 5\x1B[0m", + "\x1B[1;37mAssert.test.cc:123:\x1B[0m\nceleritas: \x1B[1;31minternal " + "assertion failed: \x1B[2;37m2 + 2 == 5\x1B[0m", DebugError{std::move(details)}.what()); } @@ -70,8 +69,8 @@ TEST_F(AssertTest, runtime_error) { EXPECT_TRUE(std::string{e.what()}.find("implementation error:") != std::string::npos); - EXPECT_TRUE(std::string{e.what()}.find("feature is not yet " - "implemented: bar") + EXPECT_TRUE(std::string{e.what()}.find( + R"(feature is not yet implemented: bar)") != std::string::npos) << e.what(); } @@ -81,10 +80,9 @@ TEST_F(AssertTest, runtime_error) } catch (RuntimeError const& e) { - EXPECT_TRUE(std::string{e.what()}.find("runtime error: \x1B[0mthis is " - "not OK") - != std::string::npos) - << e.what(); + char const expected[] = "runtime error: \x1B[0mthis is not OK"; + EXPECT_TRUE(std::string{e.what()}.find(expected) != std::string::npos) + << repr(e.what()); } } @@ -122,22 +120,22 @@ TEST_F(AssertTest, runtime_error_variations) // clang-format off static char const* const expected_messages[] = { - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;2mAssert.test.cc:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;2mAssert.test.cc:123:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;2mAssert.test.cc:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;2mAssert.test.cc:123:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;1mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;1mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;1mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;1mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[2;37mAssert.test.cc:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[2;37mAssert.test.cc:123:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[2;37mAssert.test.cc:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[2;37mAssert.test.cc:123:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[1;37mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[1;37mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[1;37mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[1;37mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", }; // clang-format on diff --git a/test/corecel/ScopedLogStorer.cc b/test/corecel/ScopedLogStorer.cc index b30ac182e7..316ecf7f0d 100644 --- a/test/corecel/ScopedLogStorer.cc +++ b/test/corecel/ScopedLogStorer.cc @@ -30,6 +30,12 @@ void debug_clog(LogProvenance, LogLevel lev, std::string msg) } } // namespace +//---------------------------------------------------------------------------// +/*! + * Construct null storer for disassociating before destruction. + */ +ScopedLogStorer::ScopedLogStorer() = default; + //---------------------------------------------------------------------------// /*! * Construct reference to log to temporarily replace. diff --git a/test/corecel/ScopedLogStorer.hh b/test/corecel/ScopedLogStorer.hh index af198509bc..df5805fb18 100644 --- a/test/corecel/ScopedLogStorer.hh +++ b/test/corecel/ScopedLogStorer.hh @@ -53,7 +53,7 @@ class ScopedLogStorer explicit ScopedLogStorer(Logger* orig); // Construct null storer for disassociating before destruction - ScopedLogStorer() = default; + ScopedLogStorer(); // Restore original logger on destruction ~ScopedLogStorer(); diff --git a/test/corecel/sys/Environment.test.cc b/test/corecel/sys/Environment.test.cc index 870c28cb64..e208bee2ca 100644 --- a/test/corecel/sys/Environment.test.cc +++ b/test/corecel/sys/Environment.test.cc @@ -10,6 +10,8 @@ #include "corecel/Config.hh" +#include "corecel/ScopedLogStorer.hh" +#include "corecel/io/Logger.hh" #include "corecel/sys/EnvironmentIO.json.hh" #include "celeritas_test.hh" @@ -57,10 +59,6 @@ TEST(EnvironmentTest, global) getenv_flag("ENVTEST_ZERO", false)); EXPECT_EQ((GetenvFlagResult{true, false}), getenv_flag("ENVTEST_ONE", false)); - EXPECT_EQ((GetenvFlagResult{false, true}), - getenv_flag("ENVTEST_EMPTY", false)); - EXPECT_EQ((GetenvFlagResult{true, true}), - getenv_flag("ENVTEST_EMPTY", true)); EXPECT_EQ((GetenvFlagResult{true, true}), getenv_flag("ENVTEST_NEW_T", true)); EXPECT_EQ((GetenvFlagResult{false, true}), @@ -77,6 +75,73 @@ TEST(EnvironmentTest, global) getenv_flag("ENVTEST_FALSE", false)); EXPECT_EQ((GetenvFlagResult{true, false}), getenv_flag("ENVTEST_TRUE", false)); + + environment().insert({"ENVTEST_AUTO", "auto"}); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_AUTO", true)); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_AUTO", false)); + + { + // Empty should act like auto + ScopedLogStorer scoped_log_{&world_logger()}; + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_EMPTY", false)); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_EMPTY", true)); + static char const* const expected_log_messages[] = { + R"(Already-set but empty environment value 'ENVTEST_EMPTY' is being ignored)", + R"(Already-set but empty environment value 'ENVTEST_EMPTY' is being ignored)"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning", "warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } + { + // Invalid flag should also act like auto + ScopedLogStorer scoped_log_{&world_logger()}; + environment().insert({"ENVTEST_NOTAFLAG", "notaflag"}); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_NOTAFLAG", false)); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_NOTAFLAG", true)); + static char const* const expected_log_messages[] = { + R"(Invalid environment value ENVTEST_NOTAFLAG=notaflag (expected a flag): using default=0)", + R"(Invalid environment value ENVTEST_NOTAFLAG=notaflag (expected a flag): using default=1)"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning", "warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } +} + +TEST(EnvironmentTest, lazy) +{ + environment() = {}; + + // Set did_call to true and return its previous value + bool did_call{false}; + auto get_default_false = [&did_call] { + did_call = true; + return false; + }; + auto get_default_true = [&did_call] { + did_call = true; + return true; + }; + + EXPECT_EQ((GetenvFlagResult{false, false}), + getenv_flag_lazy("ENVTEST_ZERO", get_default_false)); + EXPECT_FALSE(did_call); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag_lazy("ENVTEST_NEW_F", get_default_false)); + EXPECT_TRUE(did_call); + did_call = false; + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag_lazy("ENVTEST_NEW_T", get_default_true)); + EXPECT_TRUE(did_call); + did_call = false; + EXPECT_EQ((GetenvFlagResult{true, false}), + getenv_flag_lazy("ENVTEST_NEW_T", get_default_true)); + EXPECT_FALSE(did_call); } TEST(EnvironmentTest, global_overrides) @@ -105,7 +170,16 @@ TEST(EnvironmentTest, merge) Environment input; input.insert({"FOO", "foo2"}); input.insert({"BAZ", "baz"}); - sys.merge(input); + { + ScopedLogStorer scoped_log_{&world_logger()}; + sys.merge(input); + static char const* const expected_log_messages[] = { + R"(Ignoring new environment variable FOO=foo2: using existing value 'foo')", + }; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } std::ostringstream os; os << sys; From dfe964a3787f6909ecbfdd34d99d581bed1bd8b4 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 6 Jan 2026 19:38:22 -0500 Subject: [PATCH 25/60] Refactor along-step energy loss applier (#2173) * Simplify eloss appliers * Minor comments in field * Refactor along-step applier * Add comments * Change to operator() and add commentary * Restore deduction guide * Work around weird doxygen error * Check for zero-energy deposition * Account for loss of all energy during step * Add missing decorator * Add testing * Fix assertion in edge case with absurdly tiny distance to collision * Skip checks for single precision and reenable other tests * Remove unlikely --- src/celeritas/alongstep/AlongStep.hh | 2 +- .../alongstep/detail/AlongStepNeutralImpl.hh | 12 +- .../alongstep/detail/ElossApplier.hh | 179 ++++++++++++++---- src/celeritas/alongstep/detail/FluctELoss.hh | 122 +++++------- src/celeritas/alongstep/detail/MeanELoss.hh | 54 +----- .../field/DormandPrinceIntegrator.hh | 2 +- src/celeritas/field/FieldPropagator.hh | 1 + src/celeritas/grid/RangeCalculator.hh | 2 - src/celeritas/grid/RangeGridCalculator.hh | 4 + src/celeritas/phys/Interaction.hh | 9 +- src/celeritas/phys/InteractionApplier.hh | 25 ++- src/celeritas/phys/ParticleTrackView.hh | 15 -- src/celeritas/phys/PhysicsStepUtils.hh | 12 +- src/celeritas/phys/PhysicsStepView.hh | 23 ++- src/celeritas/phys/PhysicsTrackView.hh | 6 + .../phys/detail/TrackingCutExecutor.hh | 3 +- src/geocel/vg/VecgeomTrackView.hh | 2 +- test/celeritas/CMakeLists.txt | 19 +- test/celeritas/global/AlongStep.test.cc | 145 +++++++++++--- test/celeritas/phys/Particle.test.cc | 2 - test/celeritas/phys/Physics.test.cc | 7 +- 21 files changed, 379 insertions(+), 267 deletions(-) diff --git a/src/celeritas/alongstep/AlongStep.hh b/src/celeritas/alongstep/AlongStep.hh index d64bd2f0aa..184580205b 100644 --- a/src/celeritas/alongstep/AlongStep.hh +++ b/src/celeritas/alongstep/AlongStep.hh @@ -24,7 +24,7 @@ namespace celeritas * * \tparam MH MSC helper, e.g. \c detail::NoMsc * \tparam MP Propagator factory, e.g. \c detail::LinearPropagatorFactory - * \tparam EH Energy loss helper, e.g. \c detail::TrackNoEloss + * \tparam EH Energy loss helper, e.g. \c detail::NoELoss */ template struct AlongStep diff --git a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh index 08ab2654d1..9d2d2bfcb7 100644 --- a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh +++ b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh @@ -47,21 +47,11 @@ struct NoMsc */ struct NoELoss { - //! This calculator never returns energy loss - CELER_CONSTEXPR_FUNCTION bool is_applicable(CoreTrackView const&) - { - return false; - } - //! No energy loss - CELER_FUNCTION auto calc_eloss(CoreTrackView const&, real_type, bool) const - -> decltype(auto) + CELER_FUNCTION auto operator()(CoreTrackView const&) const -> decltype(auto) { return zero_quantity(); } - - //! No slowing down - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return false; } }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/detail/ElossApplier.hh b/src/celeritas/alongstep/detail/ElossApplier.hh index a6e64416bf..37fe3030c6 100644 --- a/src/celeritas/alongstep/detail/ElossApplier.hh +++ b/src/celeritas/alongstep/detail/ElossApplier.hh @@ -6,7 +6,12 @@ //---------------------------------------------------------------------------// #pragma once +#include "corecel/Assert.hh" +#include "corecel/math/Quantity.hh" +#include "corecel/math/detail/QuantityImpl.hh" #include "celeritas/global/CoreTrackView.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/PhysicsStepUtils.hh" namespace celeritas { @@ -14,65 +19,112 @@ namespace detail { //---------------------------------------------------------------------------// /*! - * Apply energy loss using the EnergyLossHandler interface. - * - * TODO: move apply-cut out of mean/fluct eloss to this function to reduce - * duplicate code? + * Whether the given track can lose energy along its step. */ -template -struct ElossApplier +inline CELER_FUNCTION bool is_eloss_applicable(CoreTrackView const& track) { - inline CELER_FUNCTION void operator()(CoreTrackView const& track); + auto sim = track.sim(); + if (sim.status() != TrackStatus::alive) + { + // Errored during propagation + return false; + } - EH eloss; -}; + if (track.particle().is_stopped()) + { + // No energy to lose + return false; + } -//---------------------------------------------------------------------------// -// DEDUCTION GUIDES -//---------------------------------------------------------------------------// -template -CELER_FUNCTION ElossApplier(EH&&) -> ElossApplier; + if (!track.physics().energy_loss_grid()) + { + // No energy loss for this particle/material + return false; + } + + return true; +} //---------------------------------------------------------------------------// -// INLINE DEFINITIONS -//---------------------------------------------------------------------------// -template -CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) +/*! + * Whether the given track should lose all energy over the step. + * + * Tracks should theoretically only slow to zero via the range limiter (and its + * associated post-step action), but spline interpolation and energy + * fluctuations are inconsistent and may lead to incorrectly long steps. + */ +inline CELER_FUNCTION bool lost_all_energy(CoreTrackView const& track) { - auto particle = track.particle(); - if (!eloss.is_applicable(track) || particle.is_stopped()) + bool const on_boundary = track.geometry().is_on_boundary(); + CELER_ASSERT( + on_boundary + == (track.sim().post_step_action() == track.boundary_action())); + if (on_boundary) { - return; + // Avoid stopping particles unphysically on the boundary + return false; } + auto phys = track.physics(); + if (track.sim().step_length() == phys.dedx_range()) + { + // Range limited step: particle has deposited all remaining energy by + // slowing down + return true; + } + + if (track.particle().energy() < phys.particle_scalars().lowest_energy) + { + // Particle *started* below the tracking cut: deposit all remaining + // energy along the step + return true; + } + return false; +} + +//---------------------------------------------------------------------------// +/*! + * Deposit energy along the particle's step and update the particle state. + * + * - Particles that end below the tracking cut distribute their remaining + * energy along the step, unless they end on the boundary + * - Energy loss is removed to the particle and added to the physics step + * - Stopped tracks are killed if they have no at-rest process + * - Stopped tracks with at-rest processes are forced to undergo an interaction + */ +inline CELER_FUNCTION void +apply_slowing_down(CoreTrackView const& track, ParticleTrackView::Energy eloss) +{ + auto particle = track.particle(); + auto phys = track.physics(); auto sim = track.sim(); - auto step = sim.step_length(); - auto post_step_action = sim.post_step_action(); + bool on_boundary = track.geometry().is_on_boundary(); - // Calculate energy loss, possibly applying tracking cuts - bool apply_cut = (post_step_action != track.boundary_action()); - auto deposited = eloss.calc_eloss(track, step, apply_cut); - CELER_ASSERT(deposited <= particle.energy()); - CELER_ASSERT(apply_cut || deposited != particle.energy()); + CELER_EXPECT(eloss > zero_quantity()); + CELER_EXPECT(eloss <= particle.energy()); + CELER_EXPECT(eloss != particle.energy() || !on_boundary + || sim.post_step_action() == phys.scalars().range_action()); - if (deposited > zero_quantity()) + if (!on_boundary + && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) + { + // Particle *ended* below the tracking cut: deposit all its energy + // (aka adjusting dE/dx upward a bit) + eloss = particle.energy(); + } + if (eloss > zero_quantity()) { // Deposit energy loss - auto phys_step = track.physics_step(); - phys_step.deposit_energy(deposited); - particle.subtract_energy(deposited); + track.physics_step().deposit_energy_from(eloss, particle); } - // Energy loss helper *must* apply the tracking cutoff - CELER_ASSERT(particle.energy() - >= track.physics().particle_scalars().lowest_energy - || !apply_cut || particle.is_stopped()); + // At this point, we shouldn't have any low-energy tracks *except* on the + // boundary + CELER_ASSERT(particle.energy() >= phys.particle_scalars().lowest_energy + || on_boundary || particle.is_stopped()); if (particle.is_stopped()) { - // Particle lost all energy over the step - CELER_ASSERT(post_step_action != track.boundary_action()); - auto const phys = track.physics(); if (!phys.at_rest_process()) { // Immediately kill stopped particles with no at rest processes @@ -87,6 +139,55 @@ CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) } } +//---------------------------------------------------------------------------// +/*! + * Apply energy loss using an energy loss calculator class. + * + * \todo Rename to \c ElossExecutor. + */ +template +struct ElossApplier +{ + inline CELER_FUNCTION void operator()(CoreTrackView const& track); + + EC calc_eloss; +}; + +//---------------------------------------------------------------------------// +// DEDUCTION GUIDES +//---------------------------------------------------------------------------// + +// Note: needed for C++17 support +template +CELER_FUNCTION ElossApplier(EC&&) -> ElossApplier; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +template +CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) +{ + CELER_EXPECT(track.sim().step_length() > 0); + if (!is_eloss_applicable(track)) + return; + + auto deposited = [&] { + if (lost_all_energy(track)) + { + // Particle was low energy or range-limited + return track.particle().energy(); + } + + // Calculate energy loss along the step + return ParticleTrackView::Energy{this->calc_eloss(track)}; + }(); + + if (deposited > zero_quantity()) + { + apply_slowing_down(track, deposited); + } +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/celeritas/alongstep/detail/FluctELoss.hh b/src/celeritas/alongstep/detail/FluctELoss.hh index 9692cbb8b2..f99123fe17 100644 --- a/src/celeritas/alongstep/detail/FluctELoss.hh +++ b/src/celeritas/alongstep/detail/FluctELoss.hh @@ -19,6 +19,11 @@ namespace detail //---------------------------------------------------------------------------// /*! * Apply energy loss (with fluctuations) to a track. + * + * \warning Because particle range is the integral of the \em mean energy loss, + * and this samples from a distribution, the sampled energy loss may be more + * than the particle's energy! We take care not to end a particle's life on a + * boundary, which is a nonphysical bias. */ class FluctELoss { @@ -33,16 +38,8 @@ class FluctELoss // Construct with fluctuation data inline explicit CELER_FUNCTION FluctELoss(ParamsRef const& params); - // Whether energy loss can be used for this track - inline CELER_FUNCTION bool is_applicable(CoreTrackView const&) const; - // Apply to the track - inline CELER_FUNCTION Energy calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut); - - //! Indicate that we can lose all energy before hitting the dE/dx range - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return true; } + inline CELER_FUNCTION Energy operator()(CoreTrackView const& track); private: //// DATA //// @@ -69,21 +66,6 @@ CELER_FUNCTION FluctELoss::FluctELoss(ParamsRef const& params) CELER_EXPECT(fluct_params_); } -//---------------------------------------------------------------------------// -/*! - * Whether energy loss is used for this track. - */ -CELER_FUNCTION bool FluctELoss::is_applicable(CoreTrackView const& track) const -{ - // The track can be marked as `errored` *within* the along-step kernel, - // during propagation - if (track.sim().status() == TrackStatus::errored) - return false; - - // Energy loss grid ID is 'false' - return static_cast(track.physics().energy_loss_grid()); -} - //---------------------------------------------------------------------------// /*! * Apply energy loss to the given track. @@ -96,81 +78,65 @@ CELER_FUNCTION bool FluctELoss::is_applicable(CoreTrackView const& track) const * energy, we reduce it to the particle energy (if energy cuts are to be * applied) or to the mean energy loss (if cuts are prohibited due to this * being a non-physics-based step). + * + * \todo The gamma and gaussian energy loss models are never called by + * positrons/electrons, only by muons */ -CELER_FUNCTION auto FluctELoss::calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut) -> Energy +CELER_FUNCTION auto FluctELoss::operator()(CoreTrackView const& track) -> Energy { - CELER_EXPECT(step > 0); - auto particle = track.particle(); auto phys = track.physics(); + auto sim = track.sim(); - if (apply_cut && particle.energy() < phys.particle_scalars().lowest_energy) + // Calculate mean energy loss + auto eloss = calc_mean_energy_loss(particle, phys, sim.step_length()); + if (eloss == zero_quantity()) { - // Deposit all energy immediately when we start below the tracking cut - return particle.energy(); + return eloss; } - // Calculate mean energy loss - auto eloss = calc_mean_energy_loss(particle, phys, step); + // Apply energy loss fluctuations + auto cutoffs = track.cutoff(); + auto material = track.material(); + EnergyLossHelper loss_helper( + fluct_params_, cutoffs, material, particle, eloss, sim.step_length()); - if (eloss < particle.energy() && eloss > zero_quantity()) + auto rng = track.rng(); + switch (loss_helper.model()) { - // Apply energy loss fluctuations - auto cutoffs = track.cutoff(); - auto material = track.material(); - - EnergyLossHelper loss_helper( - fluct_params_, cutoffs, material, particle, eloss, step); - - auto rng = track.rng(); - switch (loss_helper.model()) - { #define ASU_SAMPLE_ELOSS(MODEL) \ case EnergyLossFluctuationModel::MODEL: \ eloss = this->sample_energy_loss( \ loss_helper, rng); \ break - ASU_SAMPLE_ELOSS(none); - ASU_SAMPLE_ELOSS(gamma); - ASU_SAMPLE_ELOSS(gaussian); - ASU_SAMPLE_ELOSS(urban); + ASU_SAMPLE_ELOSS(none); + ASU_SAMPLE_ELOSS(gamma); + ASU_SAMPLE_ELOSS(gaussian); + ASU_SAMPLE_ELOSS(urban); #undef ASU_SAMPLE_ELOSS - } - - if (eloss >= particle.energy()) - { - // Sampled energy loss can be greater than actual remaining energy - // because the range calculation is based on the *mean* energy - // loss. - if (apply_cut) - { - // Clamp to actual particle energy so that it stops - eloss = particle.energy(); - } - else - { - // Don't go to zero energy at geometry boundaries: just use the - // mean loss which should be positive because this isn't a - // range-limited step. - eloss = loss_helper.mean_loss(); - CELER_ASSERT(eloss < particle.energy()); - } - } } - if (apply_cut - && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) + if (eloss >= particle.energy()) { - // Deposit all energy when we end below the tracking cut - return particle.energy(); + // Sampled energy loss can be greater than actual remaining energy + // because the range calculation is based on the *mean* energy + // loss. To fix this, we would need to sample the range from a + // distribution as well. + if (track.geometry().is_on_boundary()) + { + // Don't stop particles on geometry boundaries: just use the + // mean loss which should be positive because this isn't a + // range-limited step. + eloss = loss_helper.mean_loss(); + CELER_ASSERT(eloss < particle.energy()); + } + else + { + // Clamp to actual particle energy so that it stops + eloss = particle.energy(); + } } - CELER_ASSERT(eloss <= particle.energy()); - CELER_ENSURE(eloss != particle.energy() || apply_cut - || track.sim().post_step_action() - == phys.scalars().range_action()); return eloss; } diff --git a/src/celeritas/alongstep/detail/MeanELoss.hh b/src/celeritas/alongstep/detail/MeanELoss.hh index e0b42fea38..3087665ef2 100644 --- a/src/celeritas/alongstep/detail/MeanELoss.hh +++ b/src/celeritas/alongstep/detail/MeanELoss.hh @@ -27,69 +27,25 @@ class MeanELoss //!@} public: - // Whether energy loss is used for this track - inline CELER_FUNCTION bool is_applicable(CoreTrackView const&) const; - // Apply to the track - inline CELER_FUNCTION Energy calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut); - - //! Particle will slow down to zero only if range limited - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return false; } + inline CELER_FUNCTION Energy operator()(CoreTrackView const& track); }; //---------------------------------------------------------------------------// // INLINE DEFINITIONS -//---------------------------------------------------------------------------// -/*! - * Whether energy loss is used for this track. - */ -CELER_FUNCTION bool MeanELoss::is_applicable(CoreTrackView const& track) const -{ - // The track can be marked as `errored` *within* the along-step kernel, - // during propagation - if (track.sim().status() == TrackStatus::errored) - return false; - - // Energy loss grid ID is 'false' - return static_cast(track.physics().energy_loss_grid()); -} - //---------------------------------------------------------------------------// /*! * Apply energy loss to the given track. */ -CELER_FUNCTION auto MeanELoss::calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut) -> Energy +CELER_FUNCTION auto MeanELoss::operator()(CoreTrackView const& track) -> Energy { - CELER_EXPECT(step > 0); - auto particle = track.particle(); auto phys = track.physics(); - - if (apply_cut && particle.energy() < phys.particle_scalars().lowest_energy) - { - // Deposit all energy when we start below the tracking cut - return particle.energy(); - } + auto sim = track.sim(); + CELER_EXPECT(!particle.is_stopped() && sim.step_length() > 0); // Calculate the mean energy loss - Energy eloss = calc_mean_energy_loss(particle, phys, step); - - if (apply_cut - && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) - { - // Deposit all energy when we end below the tracking cut - return particle.energy(); - } - - CELER_ENSURE(eloss <= particle.energy()); - CELER_ENSURE(eloss != particle.energy() - || track.sim().post_step_action() - == phys.scalars().range_action()); - return eloss; + return calc_mean_energy_loss(particle, phys, sim.step_length()); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/field/DormandPrinceIntegrator.hh b/src/celeritas/field/DormandPrinceIntegrator.hh index 0508669577..47c927b92d 100644 --- a/src/celeritas/field/DormandPrinceIntegrator.hh +++ b/src/celeritas/field/DormandPrinceIntegrator.hh @@ -78,7 +78,7 @@ class DormandPrinceIntegrator { } - // Adaptive step size control + // Perform numerical integration over a step given an initial state CELER_FUNCTION result_type operator()(real_type step, OdeState const& beg_state) const; diff --git a/src/celeritas/field/FieldPropagator.hh b/src/celeritas/field/FieldPropagator.hh index 6ccece3eaf..0723344584 100644 --- a/src/celeritas/field/FieldPropagator.hh +++ b/src/celeritas/field/FieldPropagator.hh @@ -63,6 +63,7 @@ class FieldPropagator //! Whether it's possible to have tracks that are looping static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } + private: //! Limit on substeps inline CELER_FUNCTION short int max_substeps() const; diff --git a/src/celeritas/grid/RangeCalculator.hh b/src/celeritas/grid/RangeCalculator.hh index 90fe2ec0f4..0635fb4e76 100644 --- a/src/celeritas/grid/RangeCalculator.hh +++ b/src/celeritas/grid/RangeCalculator.hh @@ -31,8 +31,6 @@ namespace celeritas * \f[ r = r_\mathrm{min} \sqrt{\frac{E}{E_\mathrm{min}}} * \f] - * - * \todo Construct with \c UniformGridRecord */ class RangeCalculator { diff --git a/src/celeritas/grid/RangeGridCalculator.hh b/src/celeritas/grid/RangeGridCalculator.hh index 0fd8561ead..db8f8cfeaf 100644 --- a/src/celeritas/grid/RangeGridCalculator.hh +++ b/src/celeritas/grid/RangeGridCalculator.hh @@ -40,6 +40,10 @@ namespace celeritas * points is less than 5, linear interpolation will be used instead. * * \todo support polynomial interpolation as well? + * + * \todo The calculated range does not account for "tracking cuts" (hard limits + * below which tracks are killed). The range should be adjusted so that the + * lower integral limit is the energy cutoff \c T_c. */ class RangeGridCalculator { diff --git a/src/celeritas/phys/Interaction.hh b/src/celeritas/phys/Interaction.hh index 2578787217..0c3d78e4dc 100644 --- a/src/celeritas/phys/Interaction.hh +++ b/src/celeritas/phys/Interaction.hh @@ -29,6 +29,8 @@ namespace celeritas */ struct Interaction { + using Energy = units::MevEnergy; + //! Interaction result category enum class Action { @@ -39,11 +41,10 @@ struct Interaction failed, //!< Ran out of memory during sampling }; - units::MevEnergy energy; //!< Post-interaction energy + Energy energy; //!< Post-interaction energy Real3 direction; //!< Post-interaction direction Span secondaries; //!< Emitted secondaries - units::MevEnergy energy_deposition{0}; //!< Energy loss locally to - //!< material + Energy energy_deposition{0}; //!< Energy loss locally to material Action action{Action::scattered}; //!< Flags for interaction result // Return an interaction representing a recoverable error @@ -101,6 +102,8 @@ struct MscStep * * These values are calculated at the first step in every msc tracking volume * and reused at subsequent steps within the same volume. + * + * \todo move to physics step data */ struct MscRange { diff --git a/src/celeritas/phys/InteractionApplier.hh b/src/celeritas/phys/InteractionApplier.hh index 7a59f272da..18722b40e8 100644 --- a/src/celeritas/phys/InteractionApplier.hh +++ b/src/celeritas/phys/InteractionApplier.hh @@ -132,8 +132,7 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) if (result.action != Interaction::Action::absorbed) { // Update direction - auto geo = track.geometry(); - geo.set_dir(result.direction); + track.geometry().set_dir(result.direction); } else { @@ -141,11 +140,13 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) sim.status(TrackStatus::killed); } - real_type deposition = result.energy_deposition.value(); + using Energy = units::MevEnergy; + using MassCSq = units::MevMass; + + real_type deposition = value_as(result.energy_deposition); auto cutoff = track.cutoff(); if (cutoff.apply_post_interaction()) { - // Kill secondaries with energies below the production cut for (auto& secondary : result.secondaries) { if (cutoff.apply(secondary)) @@ -153,20 +154,28 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) // Secondary is an electron, positron or gamma with energy // below the production cut -- deposit the energy locally // and clear the secondary - deposition += secondary.energy.value() * sim.weight(); + deposition += value_as(secondary.energy) * sim.weight(); auto sec_par = track.particle_record(secondary.particle_id); if (sec_par.is_antiparticle()) { // Conservation of energy for positrons - deposition += 2 * sec_par.mass().value(); + deposition += 2 * value_as(sec_par.mass()); } secondary = {}; } } } + + // Deposit energy and save secondaries auto phys = track.physics_step(); - phys.deposit_energy(units::MevEnergy{deposition}); - phys.secondaries(result.secondaries); + if (deposition > 0) + { + phys.deposit_energy(Energy{deposition}); + } + if (!result.secondaries.empty()) + { + phys.secondaries(result.secondaries); + } } //---------------------------------------------------------------------------// diff --git a/src/celeritas/phys/ParticleTrackView.hh b/src/celeritas/phys/ParticleTrackView.hh index c360562fc0..5b570e9906 100644 --- a/src/celeritas/phys/ParticleTrackView.hh +++ b/src/celeritas/phys/ParticleTrackView.hh @@ -61,9 +61,6 @@ class ParticleTrackView // Change the particle's energy [MeV] inline CELER_FUNCTION void energy(Energy); - // Reduce the particle's energy [MeV] - inline CELER_FUNCTION void subtract_energy(Energy); - //// DYNAMIC PROPERTIES (pure accessors, free) //// // Unique particle type identifier @@ -167,18 +164,6 @@ void ParticleTrackView::energy(Energy quantity) states_.particle_energy[track_slot_] = quantity.value(); } -//---------------------------------------------------------------------------// -/*! - * Reduce the particle's energy without undergoing a collision [MeV]. - */ -CELER_FUNCTION void ParticleTrackView::subtract_energy(Energy eloss) -{ - CELER_EXPECT(eloss >= zero_quantity()); - CELER_EXPECT(eloss <= this->energy()); - // TODO: save a read/write by only saving if eloss is positive? - states_.particle_energy[track_slot_] -= eloss.value(); -} - //---------------------------------------------------------------------------// // DYNAMIC PROPERTIES //---------------------------------------------------------------------------// diff --git a/src/celeritas/phys/PhysicsStepUtils.hh b/src/celeritas/phys/PhysicsStepUtils.hh index 340b8544a6..1f976327b2 100644 --- a/src/celeritas/phys/PhysicsStepUtils.hh +++ b/src/celeritas/phys/PhysicsStepUtils.hh @@ -11,16 +11,15 @@ #include "corecel/Types.hh" #include "corecel/cont/Range.hh" #include "corecel/math/Algorithms.hh" -#include "corecel/math/NumericLimits.hh" +#include "corecel/math/Quantity.hh" #include "corecel/random/distribution/GenerateCanonical.hh" #include "corecel/random/distribution/Selector.hh" #include "celeritas/Types.hh" #include "celeritas/grid/EnergyLossCalculator.hh" #include "celeritas/grid/InverseRangeCalculator.hh" #include "celeritas/grid/RangeCalculator.hh" -#include "celeritas/grid/SplineCalculator.hh" -#include "celeritas/grid/XsCalculator.hh" #include "celeritas/mat/MaterialTrackView.hh" +#include "celeritas/track/SimTrackView.hh" #include "ParticleTrackView.hh" #include "PhysicsStepView.hh" @@ -206,13 +205,6 @@ calc_physics_step_limit(MaterialTrackView const& material, * \note The inverse range correction assumes range is always the integral of * the stopping power/energy loss. * - * \todo The GEANT3 manual \cite{geant3-1993} makes the point that linear - * interpolation of energy - * loss rate results in a piecewise constant energy deposition curve, which is - * why they use spline interpolation. Investigate higher-order reconstruction - * of energy loss curve, e.g. through spline-based interpolation or log-log - * interpolation. - * * \note See section 7.2.4 Run Time Energy Loss Computation of the Geant4 * physics manual \cite{g4prm}. See also the longer discussions in section 8 * of PHYS010 of the Geant3 manual. diff --git a/src/celeritas/phys/PhysicsStepView.hh b/src/celeritas/phys/PhysicsStepView.hh index c8a05dd502..97cf862f49 100644 --- a/src/celeritas/phys/PhysicsStepView.hh +++ b/src/celeritas/phys/PhysicsStepView.hh @@ -10,6 +10,7 @@ #include "corecel/Macros.hh" #include "corecel/data/StackAllocator.hh" #include "corecel/math/NumericLimits.hh" +#include "corecel/math/Quantity.hh" #include "corecel/sys/ThreadId.hh" #include "celeritas/em/interactor/AtomicRelaxationHelper.hh" @@ -68,6 +69,11 @@ class PhysicsStepView // Accumulate into local step's energy deposition inline CELER_FUNCTION void deposit_energy(Energy); + // Accumulate into local step's energy deposition from particle + template + inline CELER_FUNCTION void + deposit_energy_from(Energy deposited, PTV&& particle); + // Set secondaries during an interaction inline CELER_FUNCTION void secondaries(Span); @@ -184,11 +190,24 @@ CELER_FUNCTION void PhysicsStepView::reset_energy_deposition_debug() */ CELER_FUNCTION void PhysicsStepView::deposit_energy(Energy energy) { - CELER_EXPECT(energy >= zero_quantity()); - // TODO: save a memory read/write by skipping if energy is zero? + CELER_EXPECT(energy > zero_quantity()); this->state().energy_deposition += energy.value(); } +//---------------------------------------------------------------------------// +/*! + * Deposit energy from the particle. + */ +template +inline CELER_FUNCTION void +PhysicsStepView::deposit_energy_from(Energy deposited, PTV&& particle) +{ + CELER_EXPECT(deposited > zero_quantity()); + CELER_EXPECT(deposited <= particle.energy()); + this->deposit_energy(deposited); + particle.energy(particle.energy() - deposited); +} + //---------------------------------------------------------------------------// /*! * Set secondaries during an interaction, or clear them with an empty span. diff --git a/src/celeritas/phys/PhysicsTrackView.hh b/src/celeritas/phys/PhysicsTrackView.hh index b52c94f0ce..413569d733 100644 --- a/src/celeritas/phys/PhysicsTrackView.hh +++ b/src/celeritas/phys/PhysicsTrackView.hh @@ -678,6 +678,12 @@ CELER_FUNCTION ModelId PhysicsTrackView::model_id(ParticleModelId pmid) const * * Below \c min_range, no step scaling is applied, but the step can still * be arbitrarily small. + * + * \todo Rename \c calc_eloss_step_limit . This step limiter allows tuning the + * accuracy loss from approximating a constant cross section along the step. We + * should also split this into limiting the \em actual range (where the energy + * goes to zero or the minimum allowable tracking range) versus a dE/dx + * limiter. */ CELER_FUNCTION real_type PhysicsTrackView::range_to_step(real_type range) const { diff --git a/src/celeritas/phys/detail/TrackingCutExecutor.hh b/src/celeritas/phys/detail/TrackingCutExecutor.hh index eed10a67ad..2f31484520 100644 --- a/src/celeritas/phys/detail/TrackingCutExecutor.hh +++ b/src/celeritas/phys/detail/TrackingCutExecutor.hh @@ -7,6 +7,7 @@ #pragma once #include "corecel/Macros.hh" +#include "corecel/math/Quantity.hh" #include "celeritas/Types.hh" #include "celeritas/geo/GeoTrackView.hh" #include "celeritas/global/CoreTrackView.hh" @@ -77,7 +78,7 @@ TrackingCutExecutor::operator()(celeritas::CoreTrackView& track) #endif track.physics_step().deposit_energy(Energy{deposited}); - particle.subtract_energy(particle.energy()); + particle.energy(zero_quantity()); sim.status(TrackStatus::killed); } diff --git a/src/geocel/vg/VecgeomTrackView.hh b/src/geocel/vg/VecgeomTrackView.hh index 10a30e3c0c..f1645e553e 100644 --- a/src/geocel/vg/VecgeomTrackView.hh +++ b/src/geocel/vg/VecgeomTrackView.hh @@ -493,7 +493,7 @@ CELER_FUNCTION Propagation VecgeomTrackView::find_next_step(real_type max_step) // Soft equivalence between distance and max step is because the // BVH navigator subtracts and then re-adds a bump distance to the // step - CELER_ASSERT(soft_equal(next_step_, max_step)); + CELER_ASSERT(soft_equal(next_step_, max(max_step, this->extra_push()))); next_step_ = max_step; } diff --git a/test/celeritas/CMakeLists.txt b/test/celeritas/CMakeLists.txt index d2acaf3cd4..ad5cfc8113 100644 --- a/test/celeritas/CMakeLists.txt +++ b/test/celeritas/CMakeLists.txt @@ -298,7 +298,7 @@ celeritas_add_test(geo/GeoMaterial.test.cc #-----------------------------------------------------------------------------# # Global -if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double") +if(CELERITAS_USE_Geant4) set(_along_step_filter "-Em3*:SimpleCms*:LeadBox*:Mock*" "Mock*" @@ -328,20 +328,9 @@ if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double") "LeadBox*" ) endif() -elseif(CELERITAS_USE_Geant4) - set(_along_step_filter - "-Em3*:SimpleCms*:LeadBox*" - "Em3AlongStepTest.nofluct_nomsc" - "Em3AlongStepTest.fluct_nomsc" - "LeadBox*" - ) - set(_stepper_filter - "-TestEm*:LeadBox*" - "TestEm3Compton.*" - "TestEm3Msc.*" - "TestEm3MscNofluct.*" - "TestEm15FieldMsc.*" - ) + if(Geant4_VERSION VERSION_LESS 11.0) + list(REMOVE_ITEM _along_step_filter) + endif() else() set(_along_step_filter) set(_stepper_filter) diff --git a/test/celeritas/global/AlongStep.test.cc b/test/celeritas/global/AlongStep.test.cc index 32373f710c..00ed3456a9 100644 --- a/test/celeritas/global/AlongStep.test.cc +++ b/test/celeritas/global/AlongStep.test.cc @@ -297,7 +297,7 @@ TEST_F(MockAlongStepTest, basic) } } -TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) +TEST_F(MockAlongStepFieldTest, basic) { size_type num_tracks = 10; Input inp; @@ -305,12 +305,16 @@ TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) { inp.energy = MevEnergy{0.1}; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.087685148514851444, result.eloss); - EXPECT_SOFT_EQ(0.072154637489842119, result.displacement); - EXPECT_SOFT_EQ(-0.77818527618217903, result.angle); - EXPECT_SOFT_EQ(1.1701381163128199e-11, result.time); - EXPECT_SOFT_EQ(0.14614191419141928, result.step); - EXPECT_SOFT_EQ(0.00013152772277225111, result.mfp); + + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.087685148514851444, result.eloss); + EXPECT_SOFT_EQ(0.072154637489842119, result.displacement); + EXPECT_SOFT_EQ(-0.77818527618217903, result.angle); + EXPECT_SOFT_EQ(1.1701381163128199e-11, result.time); + EXPECT_SOFT_EQ(0.14614191419141928, result.step); + EXPECT_SOFT_EQ(0.00013152772277225111, result.mfp); + } EXPECT_SOFT_EQ(1, result.alive); EXPECT_EQ("eloss-range", result.action); } @@ -319,11 +323,14 @@ TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) inp.position = {0, 0, 7}; // Outside top sphere, heading out inp.phys_mfp = 100; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.001, result.eloss); - EXPECT_SOFT_NEAR(0.0036768333578785931, result.displacement, 1e-10); - EXPECT_SOFT_NEAR(0.65590801657964626, result.angle, 1e-10); - EXPECT_SOFT_EQ(6.9431339225049422e-10, result.time); - EXPECT_SOFT_EQ(0.930177246841563, result.step); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.001, result.eloss); + EXPECT_SOFT_NEAR(0.0036768333578785931, result.displacement, 1e-10); + EXPECT_SOFT_NEAR(0.65590801657964626, result.angle, 1e-10); + EXPECT_SOFT_EQ(6.9431339225049422e-10, result.time); + EXPECT_SOFT_EQ(0.930177246841563, result.step); + } EXPECT_SOFT_EQ(0, result.mfp); EXPECT_SOFT_EQ(0, result.alive); EXPECT_EQ("eloss-range", result.action); @@ -494,7 +501,10 @@ TEST_F(Em3AlongStepTest, msc_nofluct_finegrid) inp.phys_mfp = 0.469519866261640; auto result = this->run(inp, num_tracks); // Distance to interaction = 0.0499189990540797 - EXPECT_SOFT_NEAR(0.049721747266950993, result.step, 1e-8); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_NEAR(0.049721747266950993, result.step, 1e-8); + } EXPECT_EQ("geo-boundary", result.action); } } @@ -581,6 +591,10 @@ TEST_F(SimpleCmsFieldVolAlongStepTest, msc_field) { GTEST_SKIP() << "Not using reference RNG"; } + if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) + { + GTEST_SKIP() << "Not using double precision"; + } EXPECT_SOFT_NEAR(0.28064807889290933, result.displacement, tol); EXPECT_SOFT_NEAR(0.68629076604678063, result.angle, tol); @@ -628,7 +642,10 @@ TEST_F(SimpleCmsAlongStepTest, msc_field) auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(2.7199323076809536, result.step); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(2.7199323076809536, result.step); + } EXPECT_EQ(0, result.eloss); EXPECT_EQ(0, result.mfp); EXPECT_EQ("geo-propagation-limit", result.action); @@ -648,6 +665,10 @@ TEST_F(SimpleCmsAlongStepTest, msc_field) { GTEST_SKIP() << "Not using reference RNG"; } + if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) + { + GTEST_SKIP() << "Not using double precision"; + } EXPECT_SOFT_NEAR(0.28057298212898418, result.displacement, tol); EXPECT_SOFT_NEAR(0.6882027184831665, result.angle, tol); EXPECT_SOFT_NEAR(0.33775753626703175, result.step, tol); @@ -676,7 +697,7 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) { bpd_ = 56; - size_type num_tracks = 1024; + size_type num_tracks = 8; Input inp; { SCOPED_TRACE("range-limited electron in field near boundary"); @@ -694,14 +715,23 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) auto result = this->run(inp, num_tracks); if (is_ci_build()) { - // Range = 6.4161473386016025e-06 - EXPECT_SOFT_EQ(6.4161473386016025e-06, result.step); + constexpr double range = 6.4161473386016025e-06; + EXPECT_SOFT_EQ(range, result.step); } - else + else if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) { EXPECT_SOFT_EQ(inp.energy.value(), result.eloss); } - EXPECT_EQ("eloss-range", result.action); + + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_EQ("eloss-range", result.action); + } + else + { + // Step is too small for single precision + EXPECT_EQ("tracking-cut", result.action); + } EXPECT_REAL_EQ(0, result.alive); } } @@ -709,11 +739,6 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) // Test nearly tangent value nearly on the boundary TEST_F(SimpleCmsRZFieldAlongStepTest, msc_rzfield) { - if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) - { - GTEST_SKIP() << "This edge case only occurs with double"; - } - size_type num_tracks = 128; Input inp; { @@ -726,9 +751,12 @@ TEST_F(SimpleCmsRZFieldAlongStepTest, msc_rzfield) -0.0391118941072485030}; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.55155207893668967, result.displacement); - EXPECT_SOFT_NEAR(0.095305171122713847, result.angle, 1e-11); - EXPECT_EQ("geo-propagation-limit", result.action); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.55155207893668967, result.displacement); + EXPECT_SOFT_NEAR(0.095305171122713847, result.angle, 1e-11); + EXPECT_EQ("geo-propagation-limit", result.action); + } } } @@ -805,6 +833,69 @@ TEST_F(LeadBoxAlongStepTest, position_change) EXPECT_EQ("eloss-range", result.action); } } + +TEST_F(LeadBoxAlongStepTest, mfp_steps) +{ + size_type num_tracks = 128; + Input inp; + inp.particle_id = this->particle()->find(pdg::electron()); + inp.energy = MevEnergy{10000}; + inp.direction = {1, 0, 0}; + inp.position = {0, 0, 0}; + inp.phys_mfp = 1; + { + SCOPED_TRACE("near-zero mfp"); + inp.phys_mfp = 1e-20; + auto result = this->run(inp, num_tracks); + EXPECT_SOFT_EQ(0, result.eloss); + EXPECT_SOFT_EQ(1, result.angle); + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("tiny mfp"); + inp.phys_mfp = 1e-6; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(6.9164161686786e-07, result.eloss); + EXPECT_SOFT_EQ(1, result.angle); + EXPECT_SOFT_EQ(1.6943121845301e-18, result.time); + EXPECT_SOFT_EQ(5.0794201375653e-08, result.step); + } + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("single mfp"); + inp.phys_mfp = 1; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.68234478660682, result.eloss); + } + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("large mfp"); + inp.phys_mfp = 1e6; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(2001.409778094, result.eloss); + EXPECT_SOFT_EQ(4.9030072611395e-09, result.time); + EXPECT_SOFT_EQ(146.988459649, result.step); + EXPECT_SOFT_EQ(2893.8039317112, result.mfp); + } + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("eloss-range", result.action); + } +} + //---------------------------------------------------------------------------// } // namespace test } // namespace celeritas diff --git a/test/celeritas/phys/Particle.test.cc b/test/celeritas/phys/Particle.test.cc index 49ec9c06de..8eb914da55 100644 --- a/test/celeritas/phys/Particle.test.cc +++ b/test/celeritas/phys/Particle.test.cc @@ -193,8 +193,6 @@ TEST_F(ParticleTestHost, electron) // Stop the particle EXPECT_FALSE(particle.is_stopped()); - particle.subtract_energy(MevEnergy{0.25}); - EXPECT_REAL_EQ(0.25, particle.energy().value()); particle.energy(zero_quantity()); EXPECT_TRUE(particle.is_stopped()); EXPECT_REAL_EQ(0.0, particle.energy().value()); diff --git a/test/celeritas/phys/Physics.test.cc b/test/celeritas/phys/Physics.test.cc index 9e19179128..520605c4a8 100644 --- a/test/celeritas/phys/Physics.test.cc +++ b/test/celeritas/phys/Physics.test.cc @@ -399,8 +399,11 @@ TEST_F(PhysicsTrackViewHostTest, step_view) gamma.reset_energy_deposition(); gamma.deposit_energy(Energy(2.5)); EXPECT_REAL_EQ(2.5, value_as(gamma_cref.energy_deposition())); - // Allow zero-energy deposition - EXPECT_NO_THROW(gamma.deposit_energy(zero_quantity())); + // Forbid zero-energy deposition + if (CELERITAS_DEBUG) + { + EXPECT_THROW(gamma.deposit_energy(zero_quantity()), DebugError); + } EXPECT_REAL_EQ(2.5, value_as(gamma_cref.energy_deposition())); gamma.reset_energy_deposition(); EXPECT_REAL_EQ(0.0, value_as(gamma_cref.energy_deposition())); From 47378ee8ac5ec8f0debc70cb667e5b76e0b1fd6a Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Wed, 7 Jan 2026 13:02:46 -0500 Subject: [PATCH 26/60] Link against development LArSim (#2180) * Fix errors frmo #2154 reviewd by @amandalund * Load system includes for clangd via mrb's GCC * Allow upstream definition of IOpticalPropagation * Add comment * IWYU * Fix build error in pugin * Fix fallback setup filename * Fix topsy turvy errors in build script * Use larsoft tag and add verbose code --- doc/usage/installation.rst | 4 +- scripts/build.sh | 6 +- scripts/env/scisoftbuild01.sh | 63 ++++++++++++------- src/larceler/CMakeLists.txt | 22 +++++-- src/larceler/LarCelerStandalone.cc | 6 +- src/larceler/LarCelerStandalone.hh | 42 +++---------- src/larceler/inp/LarStandaloneRunner.cc | 8 +-- src/larceler/inp/LarStandaloneRunner.hh | 9 +-- .../IOpticalPropagation.h | 54 ++++++++++++++++ test/larceler/LarStandaloneRunner.test.cc | 3 +- 10 files changed, 134 insertions(+), 83 deletions(-) create mode 100644 src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index c0c988a1ba..f18edf4f45 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -233,7 +233,7 @@ It includes environment files for quickly getting started on systems including N $ cd celeritas $ ./scripts/build.sh base -You can, of course, manually build as a +You can, of course, build manually: .. code-block:: console @@ -245,7 +245,7 @@ You can, of course, manually build as a .. _zip file: https://github.com/celeritas-project/celeritas/archive/refs/heads/develop.zip .. _Perlmutter: https://docs.nersc.gov/systems/perlmutter/ -.. _ExCL: https://docs.olcf.ornl.gov/systems/excl_user_guide.html +.. _ExCL: https://docs.excl.ornl.gov .. _Frontier: https://docs.olcf.ornl.gov/systems/frontier_user_guide.html .. _JLSE: https://www.jlse.anl.gov/ diff --git a/scripts/build.sh b/scripts/build.sh index 39a409b0be..74238c5e69 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -105,8 +105,8 @@ ln_presets() { else log warning "${dst} points to ${actual}, not ${src}: overwriting" fi - elif [ -e "${src}" ]; then - log warning "${dst} already exists but is not a symlink to ${src}" + elif [ -e "${dst}" ]; then + log warning "${PWD}/${dst} already exists but is not a symlink (should link to ${src})" return fi @@ -277,7 +277,7 @@ setup_ccache # Check arguments and give presets if missing if [ $# -eq 0 ] ; then printf '%s\n' "Usage: $0 PRESET [config_args...]" >&2 - if [ -n "${CMAKE}" ]; then + if [ -z "${CMAKE}" ]; then log error "cmake unavailable: cannot call --list-presets" exit 1 fi diff --git a/scripts/env/scisoftbuild01.sh b/scripts/env/scisoftbuild01.sh index 227942b866..d1cd954bf1 100755 --- a/scripts/env/scisoftbuild01.sh +++ b/scripts/env/scisoftbuild01.sh @@ -48,12 +48,16 @@ if [ -n "${APPTAINER_CONTAINER}" ]; then export MRB_PROJECT_VERSION=v10_14_01 export MRB_QUALS=e26:prof celerlog info "Running in apptainer ${APPTAINER_CONTAINER}" - if [ -z "${UPS_DIR}" ]; then + if [ -n "${UPS_DIR}" ]; then + celerlog debug "Dune UPS already set up: ${UPS_DIR}" + else celerlog info "Setting up DUNE UPS" . /cvmfs/dune.opensciencegrid.org/products/dune/setup_dune.sh celerlog debug "Using UPS_OVERRIDE=${UPS_OVERRIDE}, MRB_PROJECT=${MRB_PROJECT}" fi - if [ -z "${SETUP_LARCORE}" ]; then + if [ -n "${SETUP_LARCORE}" ]; then + celerlog debug "LARCORE is already set up" + else # Set up larsoft build defaults with UPS celerlog info "Setting up ${MRB_PROJECT} ${MRB_PROJECT_VERSION} with qualifiers '${MRB_QUALS}'" setup ${MRB_PROJECT} ${MRB_PROJECT_VERSION} -q ${MRB_QUALS} || return $? @@ -76,19 +80,21 @@ done # Set up larsoft if running inside an apptainer if [ -n "${MRB_PROJECT}" ]; then LARSCRATCHDIR="${SCRATCHDIR}/${MRB_PROJECT}" - if ! [ -d "${LARSCRATCHDIR}" ]; then - celerlog info "Creating MRB dev area in ${LARSCRATCHDIR}..." + if [ -d "${LARSCRATCHDIR}" ]; then + celerlog debug "MRB dev area already exists at ${LARSCRATCHDIR}" + else + celerlog info "Creating MRB dev area in ${LARSCRATCHDIR}" mkdir -p "${LARSCRATCHDIR}" || return $? ( cd "${LARSCRATCHDIR}" mrb newDev - ) + ) || return 1 celerlog debug "MRB environment created" fi _setup_filename="${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}_${MRB_PROJECT_VERSION}_${MRB_QUALS//:/_}/setup" if ! [ -f "${_setup_filename}" ]; then - celerlog warn "Expected setup file at ${_setup_filename}: MRB may not have been set up correctly" - _setup_filename=$(print %s"${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}*/setup") + celerlog warning "Expected setup file at ${_setup_filename}: MRB may not have been set up correctly" + _setup_filename=$(printf %s "${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}"*/setup) if [ -f "${_setup_filename}" ]; then celerlog info "Found setup file ${_setup_filename}" fi @@ -100,39 +106,50 @@ fi if [ -n "${MRB_SOURCE}" ]; then _pkg=larsim if ! [ -d "${MRB_SOURCE}/${_pkg}" ]; then - celerlog info "Installing ${_pkg}" - mrb g ${_pkg} + _tag=LARSOFT_SUITE_${MRB_PROJECT_VERSION} + celerlog info "Installing ${_pkg} @${_tag}" + mrb g -t ${_tag} ${_pkg} fi # Now that a package exists in MRB source, cmake and dependencies can load celerlog info "Activating MRB environment" - mrbsetenv - celerlog debug "MRB setup complete" + # Note that this may be a shell script + if ! command -v mrbsetenv >/dev/null 2>&1 ; then + celerlog warning "mrbsetenv is not defined: run manually in shell" + else + celerlog debug "MRB setup complete" + fi fi if [ -n "$CELER_SOURCE_DIR" ]; then _clangd="$CELER_SOURCE_DIR/.clangd" if [ ! -e "${_clangd}" ]; then # Create clangd compatible with the system and build config - _gcc_version=$(gcc -dumpversion | cut -d. -f1) - celerlog info "Creating clangd config using GCC ${_gcc_version}: ${_clangd}" - cat > "${_clangd}" << EOF + _cxx=$GCC_FQ_DIR/bin/g++ + if [ ! -x "${_cxx}" ]; then + celerlog info "GCC isn't loaded as expected at \$GCC_FQ_DIR/bin/g++ = ${_cxx}" + else + celerlog info "Creating clangd config using ${_cxx}: ${_clangd}" + + # Extract include paths from GCC + _gcc_includes=$("${_cxx}" -E -x c++ - -v < /dev/null 2>&1 | \ + sed -n '/^#include <...> search starts here:/,/^End of search list\./p' | \ + grep '^ ' | sed 's/^ *//' | \ + awk '{printf " -isystem,\n %s,\n", $0}' | \ + sed '$s/,$//') + + cat > "${_clangd}" << EOF CompileFlags: CompilationDatabase: ${SCRATCHDIR}/build/celeritas-reldeb-orange Add: [ - -isystem, - /usr/include/c++/${_gcc_version}, - -isystem, - /usr/local/include, - -isystem, - /usr/include, - -isystem, - /usr/include/x86_64-linux-gnu/c++/${_gcc_version}, +${_gcc_includes} ] EOF + unset _gcc_includes + fi + unset _clangd fi - unset _clangd fi export XDG_CACHE_HOME="${SCRATCHDIR}/cache" diff --git a/src/larceler/CMakeLists.txt b/src/larceler/CMakeLists.txt index 7afc0dde94..604386d390 100644 --- a/src/larceler/CMakeLists.txt +++ b/src/larceler/CMakeLists.txt @@ -15,10 +15,23 @@ set(PUBLIC_DEPS Celeritas::BuildFlags ) -if(TARGET larsim::OpticalPropagationTools) - message(SEND_ERROR "This version of Celeritas is meant for testing larsim " - "before https://github.com/nuRiceLab/larsim/pull/1 : please implement or " - "update Celeritas") +#-----------------------------------------------------------------------------# +# Create mock upstream library for building against official LArSoft +#-----------------------------------------------------------------------------# + +set(_larsoft_tool_target larsim::OpticalPropagationTools) +if(NOT TARGET ${_larsoft_tool_target}) + message(WARNING "LArSoft version is too old for integrated Celeritas support: " + "defining fake plugin target for local testing " + "(missing target ${_larsoft_tool_target})" + ) + celeritas_add_interface_library(OpticalPropagationTools) + celeritas_target_include_directories(OpticalPropagationTools + SYSTEM INTERFACE + "$" + "$" + ) + set(_larsoft_tool_target Celeritas::OpticalPropagationTools) endif() #-----------------------------------------------------------------------------# @@ -48,6 +61,7 @@ celeritas_target_link_libraries(${_plugin_name} cetlib_except::cetlib_except larcoreobj::SimpleTypesAndConstants # Implicit dependency needed by MCBase lardataobj::MCBase + ${_larsoft_tool_target} PUBLIC ${PUBLIC_DEPS} ) diff --git a/src/larceler/LarCelerStandalone.cc b/src/larceler/LarCelerStandalone.cc index e4ef5cb300..719a15b324 100644 --- a/src/larceler/LarCelerStandalone.cc +++ b/src/larceler/LarCelerStandalone.cc @@ -14,8 +14,8 @@ #include "corecel/Assert.hh" -#include "larceler/LarStandaloneRunner.hh" -#include "larceler/inp/LarStandaloneRunner.hh" +#include "LarStandaloneRunner.hh" +#include "inp/LarStandaloneRunner.hh" namespace celeritas { @@ -47,6 +47,8 @@ auto LarCelerStandalone::executeEvent(VecSED const& edeps) -> UPVecBTR CELER_EXPECT(runner_); CELER_EXPECT(!edeps.empty()); + using VecBTR = LarStandaloneRunner::VecBTR; + // Calculate detector responsors for the input steps auto& run = *runner_; VecBTR result = run(edeps); diff --git a/src/larceler/LarCelerStandalone.hh b/src/larceler/LarCelerStandalone.hh index ace62bcade..cab866480a 100644 --- a/src/larceler/LarCelerStandalone.hh +++ b/src/larceler/LarCelerStandalone.hh @@ -8,8 +8,8 @@ #pragma once #include -#include #include +#include #include "LarStandaloneRunner.hh" @@ -21,34 +21,6 @@ class SimEnergyDeposit; class OpDetBacktrackerRecord; } // namespace sim -// TODO: This will be defined upstream: -// see https://github.com/nuRiceLab/larsim/pull/1 -namespace phot -{ -class OpticalSimInterface -{ - public: - //!@{ - //! \name Type aliases - using VecSED = std::vector; - using VecBTR = std::vector; - using UPVecBTR = std::unique_ptr; - ///@} - - // Enable polymorphic deletion - virtual ~OpticalSimInterface() = 0; - - // Set up execution - virtual void beginJob() = 0; - - // Process a single event, returning detector hits - virtual UPVecBTR executeEvent(VecSED const& edeps) = 0; - - // Tear down execution - virtual void endJob() = 0; -}; -} // namespace phot - namespace celeritas { //---------------------------------------------------------------------------// @@ -56,11 +28,11 @@ namespace celeritas * Run optical photons in a standalone simulation. * * This plugin implements a replacement for LArSim's \c phot::PDFastSimPAR - * class, taking a vector of energy-depositing steps and returning a vector - * is instantiated by a FHiCL workflow file with a set of - * parameters. It is executed after the detector simulation step (ionization, - * recombination, scintillation, etc.) with a vector of steps that contain - * energy deposition, and it returns a vector of detector responses. + * class. It is instantiated by a FHiCL workflow file with a set of + * parameters. It takes a vector of energy-depositing steps and returns a + * vector of detector responses. It is executed after the detector simulation + * module (ionization, recombination, scintillation, etc.) with a vector of + * steps that contain local energy deposition. * * The execution happens \em after LArG4 is complete, so it is completely * independent of the Geant4 run manager and execution. It requires an input @@ -75,7 +47,7 @@ namespace celeritas * * See \c celeritas::detail::LarCelerStandaloneConfig . */ -class LarCelerStandalone final : public phot::OpticalSimInterface +class LarCelerStandalone final : public phot::IOpticalPropagation { public: //!@{ diff --git a/src/larceler/inp/LarStandaloneRunner.cc b/src/larceler/inp/LarStandaloneRunner.cc index 2a7baa6100..4e5daf9459 100644 --- a/src/larceler/inp/LarStandaloneRunner.cc +++ b/src/larceler/inp/LarStandaloneRunner.cc @@ -6,8 +6,6 @@ //---------------------------------------------------------------------------// #include "LarStandaloneRunner.hh" -#include "corecel/io/Logger.hh" - #include "larceler/detail/LarCelerConfig.hh" namespace celeritas @@ -34,11 +32,9 @@ from_config(detail::LarCelerStandaloneConfig const& cfg) #endif out.geometry = cfg.geometry(); - out.optical_step_iters = cfg.optical_step_iters(); - if (out.optical_step_iters == 0) + if (auto step_iters = cfg.optical_step_iters()) { - CELER_LOG(debug) << "Using unlimited optical step iters"; - out.optical_step_iters = out.unlimited; + out.tracking_limits.step_iters = step_iters; } // Optical capacities diff --git a/src/larceler/inp/LarStandaloneRunner.hh b/src/larceler/inp/LarStandaloneRunner.hh index c84aee7e6b..eabb329631 100644 --- a/src/larceler/inp/LarStandaloneRunner.hh +++ b/src/larceler/inp/LarStandaloneRunner.hh @@ -6,12 +6,11 @@ //---------------------------------------------------------------------------// #pragma once -#include #include #include -#include "corecel/Types.hh" #include "celeritas/inp/Control.hh" +#include "celeritas/inp/Tracking.hh" namespace celeritas { @@ -36,10 +35,6 @@ namespace inp */ struct LarStandaloneRunner { - //! Don't limit the number of steps (from TrackingLimits) - static constexpr size_type unlimited - = std::numeric_limits::max(); - //// DATA //// //! Environment variables used for program setup/diagnostic @@ -49,7 +44,7 @@ struct LarStandaloneRunner std::string geometry; //! Step iterations before aborting the optical stepping loop - size_type optical_step_iters{unlimited}; + OpticalTrackingLimits tracking_limits; //! Optical buffer sizes OpticalStateCapacity optical_capacity; diff --git a/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h b/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h new file mode 100644 index 0000000000..535f2d1155 --- /dev/null +++ b/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h @@ -0,0 +1,54 @@ +//---------------------------------------------------------------------------// +//! \file IOpticalPropagation.h +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // +//! \brief Abstract interface for optical simulation libraries +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include +#include + +namespace phot +{ +class IOpticalPropagation; +} + +//-------------------------------------------------------------------------// +/*! + * Abstract interface for optical propagation. + * + * This interface allows the addition of different optical photon propagation + * tools. As an \c art::tool, the \c executeEvent function is called *once* + * during a \c art::EDProducer::produce module execution, and uses all \c + * sim::SimEnergyDeposits from an \c art::Event found by the \c art::Handle . + * + * I.e. a single \c executeEvent function call propagates all resulting + * optical photons from the existing batch of energy depositions on an + * event-by-event basis. It is currently expected to manage 3 methods: + * - \c PDFastSimPAR : already available in larsim + * - \c Celeritas : Full optical particle transport on CPU and GPU + * - \c Opticks : Full optical particle transport on Nvidia GPUs + * + * The interfaces takes a vector of \c sim::SimEnergyDeposit as input and + * produces a vector of \c sim::OpDetBacktrackerRecord from detector hits. + */ +class phot::IOpticalPropagation +{ + public: + //!@{ + //! \name Type aliases + using VecSED = std::vector; + using UPVecBTR = std::unique_ptr>; + ///@} + + // Initialize tool + virtual void beginJob() = 0; + + // Execute is called for every art::Event + virtual UPVecBTR executeEvent(VecSED const& edeps) = 0; + + // Bring tool back to invalid state + virtual void endJob() = 0; +}; diff --git a/test/larceler/LarStandaloneRunner.test.cc b/test/larceler/LarStandaloneRunner.test.cc index 4efc5a17a8..404edc65da 100644 --- a/test/larceler/LarStandaloneRunner.test.cc +++ b/test/larceler/LarStandaloneRunner.test.cc @@ -68,7 +68,7 @@ auto LarSphereTest::make_input() const -> Input { Input result; result.geometry = this->test_data_path("geocel", "lar-sphere.gdml"); - result.optical_step_iters = 10; + result.tracking_limits.steps = 10; result.optical_capacity.tracks = 16; return result; } @@ -103,6 +103,7 @@ TEST_F(LarSphereTest, single_photon) /* origTrackID = */ 123); auto response = run({sed}); + EXPECT_EQ(0, response.size()); } //---------------------------------------------------------------------------// From d4030a66a276fd6d056e6a8326478bbfafcd76e3 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Fri, 9 Jan 2026 06:46:59 -0500 Subject: [PATCH 27/60] Document and add checks about tracking manager construction (#2183) * Remove unused accessors * Avoid accessing local transporter on master thread and add comments about lifetimes * Update comment and add IWYU * Fix tid assertion --- src/accel/FastSimulationModel.hh | 3 --- src/accel/TrackingManager.cc | 24 ++++++++++++++++-------- src/accel/TrackingManager.hh | 21 ++++++++++++++------- src/accel/TrackingManagerConstructor.cc | 21 ++++++++++++++++++--- src/accel/TrackingManagerConstructor.hh | 1 - 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/accel/FastSimulationModel.hh b/src/accel/FastSimulationModel.hh index 4607a3d35d..13e03a3cf6 100644 --- a/src/accel/FastSimulationModel.hh +++ b/src/accel/FastSimulationModel.hh @@ -23,9 +23,6 @@ class LocalTransporter; * `G4VUserDetectorConstruction::ConstructSDandField()`. * * Note that the argument \c G4Envelope is a type alias to \c G4Region. - * - * \todo Maybe need a helper to create a single fast sim model for multiple - * regions? */ class FastSimulationModel final : public G4VFastSimulationModel { diff --git a/src/accel/TrackingManager.cc b/src/accel/TrackingManager.cc index bd3f28efe8..20d7a352ca 100644 --- a/src/accel/TrackingManager.cc +++ b/src/accel/TrackingManager.cc @@ -25,6 +25,7 @@ namespace celeritas * Construct a tracking manager with data needed to offload to Celeritas. * * \note The shared/local pointers must remain valid for the lifetime of the + * run. The local transporter should be null on the "master" thread of an MT * run. */ TrackingManager::TrackingManager(SharedParams const* params, @@ -32,7 +33,9 @@ TrackingManager::TrackingManager(SharedParams const* params, : params_(params), transport_(local) { CELER_EXPECT(params_); - CELER_EXPECT(transport_); + CELER_EXPECT(static_cast(transport_) + == !(G4Threading::IsMasterThread() + && G4Threading::IsMultithreadedApplication())); } //---------------------------------------------------------------------------// @@ -52,11 +55,11 @@ TrackingManager::TrackingManager(SharedParams const* params, */ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) { - CELER_LOG(debug) << "Building physics table for " << part.GetParticleName(); + CELER_EXPECT(params_->mode() != SharedParams::Mode::disabled); + + CELER_LOG_LOCAL(debug) << "Building physics table for " + << part.GetParticleName(); - CELER_VALIDATE(params_->mode() != SharedParams::Mode::disabled, - << "Celeritas tracking manager cannot be active when " - "Celeritas is disabled"); G4ProcessManager* pManagerShadow = part.GetMasterProcessManager(); G4ProcessManager* pManager = part.GetProcessManager(); CELER_ASSERT(pManager); @@ -83,7 +86,7 @@ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) * * Messaged by the \c G4ParticleDefinition who stores us whenever cross-section * tables have to be rebuilt (i.e. if new materials have been defined). As with - * BuildPhysicsTable, we override this to ensure all Geant4 + * \c BuildPhysicsTable, we override this to ensure all Geant4 * process/cross-section data is available for Celeritas to use. * * The implementation follows that in \c @@ -92,8 +95,10 @@ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) */ void TrackingManager::PreparePhysicsTable(G4ParticleDefinition const& part) { - CELER_LOG(debug) << "Preparing physics table for " - << part.GetParticleName(); + CELER_EXPECT(params_->mode() != SharedParams::Mode::disabled); + + CELER_LOG_LOCAL(debug) << "Preparing physics table for " + << part.GetParticleName(); G4ProcessManager* pManagerShadow = part.GetMasterProcessManager(); G4ProcessManager* pManager = part.GetProcessManager(); @@ -118,10 +123,13 @@ void TrackingManager::PreparePhysicsTable(G4ParticleDefinition const& part) //---------------------------------------------------------------------------// /*! * Offload the incoming track to Celeritas. + * + * This will \em not be called in the master thread of an MT run. */ void TrackingManager::HandOverOneTrack(G4Track* track) { CELER_EXPECT(track); + CELER_EXPECT(transport_); if (CELER_UNLIKELY(!validated_)) { diff --git a/src/accel/TrackingManager.hh b/src/accel/TrackingManager.hh index 5314e836ef..e659c3b158 100644 --- a/src/accel/TrackingManager.hh +++ b/src/accel/TrackingManager.hh @@ -24,16 +24,23 @@ class TrackOffloadInterface; /*! * Offload to Celeritas via the per-particle Geant4 "tracking manager". * - * Tracking managers are to be created during worker action initialization and - * are thus thread-local. Construction/addition to \c G4ParticleDefinition - * appears to take place on the master thread, typically - * in the ConstructProcess method, but the tracking manager pointer is part of - * the split-class data for the particle. It's observed that different threads - * have distinct pointers to a LocalTransporter instance, and that these match - * those of the global thread-local instances in test problems. + * Tracking managers are created by \c G4VUserPhysicsList::Construct during + * \c G4RunManager::Initialize on each thread. The tracking manager pointer is + * a \em thread-local part of the split-class data for a \em global G4Particle. + * This thread-local manager points to a corresponding thread-local + * transporter. + * + * Because physics initialization also happens on the master MT thread, where + * no events are processed, a custom tracking manager \em also exists for that + * thread. In that case, the local transporter should be null. * * \note As of Geant4 11.3, instances of this class (one per thread) will never * be deleted. + * + * \warning The physics does \em not reconstruct tracking managers on + * subsequent runs. Therefore the \c SharedParams and \c LocalTransporter \em + * must have lifetimes that span multiple runs (which is the case for using + * global/thread-local). */ class TrackingManager final : public G4VTrackingManager { diff --git a/src/accel/TrackingManagerConstructor.cc b/src/accel/TrackingManagerConstructor.cc index 285206268b..e6a9e49022 100644 --- a/src/accel/TrackingManagerConstructor.cc +++ b/src/accel/TrackingManagerConstructor.cc @@ -49,11 +49,19 @@ TrackingManagerConstructor::TrackingManagerConstructor( * * Since there's only ever one tracking manager integration, we can just use * the behind-the-hood objects. + * + * \note When calling from a serial run manager in a threaded G4 build, the + * thread ID is \c G4Threading::MASTER_ID (-1). When calling from the run + * manager of a non-threaded G4 build, the thread is \c + * G4Threading::SEQUENTIAL_ID (-2). */ TrackingManagerConstructor::TrackingManagerConstructor( TrackingManagerIntegration* tmi) : TrackingManagerConstructor( - &detail::IntegrationSingleton::instance().shared_params(), [](int) { + &detail::IntegrationSingleton::instance().shared_params(), + [](int tid) { + CELER_EXPECT(tid >= 0 + || !G4Threading::IsMultithreadedApplication()); return &detail::IntegrationSingleton::instance() .local_track_offload(); }) @@ -100,8 +108,15 @@ void TrackingManagerConstructor::ConstructProcess() shared_ && get_local_, << R"(invalid null inputs given to TrackingManagerConstructor)"); - auto* transporter = this->get_local_transporter(); - CELER_VALIDATE(transporter, << "invalid null local transporter"); + LocalTransporter* transporter{nullptr}; + + if (G4Threading::IsWorkerThread() + || !G4Threading::IsMultithreadedApplication()) + { + // Don't create or access local transporter on master thread + transporter = this->get_local_(G4Threading::G4GetThreadId()); + CELER_VALIDATE(transporter, << "invalid null local transporter"); + } #if G4VERSION_NUMBER >= 1100 // Create *thread-local* tracking manager with pointers to *global* diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 1efdc91f2b..31b0537e8e 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -7,7 +7,6 @@ #pragma once #include -#include #include #include "corecel/cont/Span.hh" From 02dcc19b42092c6820b5f4dd497c34cf743e7b9b Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Fri, 9 Jan 2026 16:26:13 -0500 Subject: [PATCH 28/60] Ignore `BUILTIN_X` when `CELERITAS_USE_X` is false (#2186) * Check for unused BUILTIN=ON before downloading and including * Force nljson on to fix build * Force g4vg on and disable uninstalled packages * Put dd4hep in correct place and fix g4vg force * Verbose force output * Fix reldeb preset name * Simplify the ci ubuntu presets --- .github/workflows/build-spack.yml | 4 +- CMakeLists.txt | 10 +- cmake/CeleritasConfig.cmake.in | 16 +-- cmake/CeleritasOptionUtils.cmake | 1 + external/CMakeLists.txt | 42 +++++--- scripts/cmake-presets/ci-ubuntu-github.json | 113 ++++++++++---------- 6 files changed, 104 insertions(+), 82 deletions(-) diff --git a/.github/workflows/build-spack.yml b/.github/workflows/build-spack.yml index 08f6be82b1..fef6a4e50b 100644 --- a/.github/workflows/build-spack.yml +++ b/.github/workflows/build-spack.yml @@ -55,7 +55,7 @@ jobs: geometry: "orange" special: "static" geant: "11.3" - - build_type: "reldebinfo" + - build_type: "reldeb" geometry: "orange" special: "asanlite" geant: null @@ -85,7 +85,7 @@ jobs: geant: "11.3" env: CCACHE_DIR: "${{github.workspace}}/.ccache" - CCACHE_MAXSIZE: "${{matrix.build_type == 'reldebinfo' && '500' || '300'}}Mi" + CCACHE_MAXSIZE: "${{matrix.build_type == 'reldeb' && '500' || '300'}}Mi" CMAKE_PRESET: >- ${{format('{0}-{1}{2}{3}', matrix.build_type, diff --git a/CMakeLists.txt b/CMakeLists.txt index f71c2c46c3..5191b4b019 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ endif() # Optional dependencies celeritas_optional_package(covfie "Use Covfie field integrator") +celeritas_optional_package(DD4hep "Enable DD4hep integration") celeritas_optional_package(Geant4 "Enable Geant4 adapter tools") celeritas_optional_package(HepMC3 "Enable HepMC3 event record reader") celeritas_optional_package(LArSoft "Build LArSoft data interfaces") @@ -42,7 +43,6 @@ celeritas_optional_package(PNG "Enable PNG output with libpng") celeritas_optional_package(Python "Use Python for documentation and testing") celeritas_optional_package(ROOT "Enable ROOT I/O") celeritas_optional_package(VecGeom "Use VecGeom geometry") -celeritas_optional_package(DD4hep "Enable DD4hep integration") option(CELERITAS_USE_Perfetto "Perfetto tracing library" OFF) # Components @@ -86,9 +86,17 @@ if(CELERITAS_BUILD_TESTS) mark_as_advanced(CELERITAS_TEST_XML) endif() +if(CELERITAS_USE_VecGeom AND CELERITAS_USE_Geant4) + set(_needs_g4vg ON) +else() + set(_needs_g4vg OFF) +endif() + # Automatic options celeritas_force_package(CLI11 ${CELERITAS_BUILD_APPS}) celeritas_force_package(GTest ${CELERITAS_BUILD_TESTS}) +celeritas_force_package(G4VG ${_needs_g4vg}) +celeritas_force_package(nlohmann_json ON) #----------------------------------------------------------------------------# # PACKAGE-SPECIFIC OPTIONS diff --git a/cmake/CeleritasConfig.cmake.in b/cmake/CeleritasConfig.cmake.in index 36fb209e22..75d426f41f 100644 --- a/cmake/CeleritasConfig.cmake.in +++ b/cmake/CeleritasConfig.cmake.in @@ -98,6 +98,10 @@ elseif(CELERITAS_USE_HIP) find_dependency(hip REQUIRED QUIET) endif() +if(CELERITAS_USE_DD4hep) + find_dependency(DD4hep REQUIRED) +endif() + if(CELERITAS_USE_Geant4) # Geant4 calls `include_directories` for CLHEP :( which is not what we want! # Save and restore include directories around the call -- even though as a @@ -110,6 +114,10 @@ if(CELERITAS_USE_Geant4) cmake_policy(VERSION 3.10...4.0) endif() +if(CELERITAS_USE_G4VG AND NOT CELERITAS_BUILTIN_G4VG) + find_dependency(G4VG @G4VG_VERSION@ REQUIRED) +endif() + if(CELERITAS_USE_HepMC3) find_dependency(HepMC3 @HepMC3_VERSION@ REQUIRED) endif() @@ -161,14 +169,6 @@ if(CELERITAS_USE_VecGeom) "and Celeritas (CELERITAS_USE_CUDA=${CELERITAS_USE_CUDA})" ) endif() - - if(CELERITAS_USE_Geant4 AND NOT CELERITAS_BUILTIN_G4VG) - find_dependency(G4VG @G4VG_VERSION@ REQUIRED) - endif() -endif() - -if(CELERITAS_USE_DD4hep) - find_dependency(DD4hep REQUIRED) endif() cmake_policy(POP) diff --git a/cmake/CeleritasOptionUtils.cmake b/cmake/CeleritasOptionUtils.cmake index db22647010..de5a58208e 100644 --- a/cmake/CeleritasOptionUtils.cmake +++ b/cmake/CeleritasOptionUtils.cmake @@ -248,6 +248,7 @@ endmacro() macro(celeritas_force_package package value) set(_var "CELERITAS_USE_${package}") set(${_var} ${value}) + message(VERBOSE "Celeritas: forced ${_var}=${value}") # Append to list of components _celeritas_append_optional_component(${package} "${value}") # Append to list of forced packages for export diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 0ffbf4e1c8..60120760a3 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -3,27 +3,36 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) #-----------------------------------------------------------------------------# -# Print extra information about downloading: hash failures, for example, are -# silent otherwise -set(CMAKE_MESSAGE_LOG_LEVEL VERBOSE) - # Download the package, save version, add to a list set(_celer_builtin_packages) macro(celer_fetch_external name version url sha) - # Save version and annotated string for corecel/Config.cc include - set(${name}_VERSION ${version} PARENT_SCOPE) - set(${name}_VERSION_STRING "${version}+builtin" PARENT_SCOPE) - - string(REGEX REPLACE "@VERSION@" "${version}" _url "${url}") - FetchContent_Declare( - "${name}" - URL "${_url}" - URL_HASH SHA256=${sha} - DOWNLOAD_NO_PROGRESS TRUE - ) - list(APPEND _celer_builtin_packages "${name}") + if(CELERITAS_BUILTIN_${name} AND NOT CELERITAS_USE_${name}) + message(WARNING "Ignoring CELERITAS_BUILTIN_${name} " + "since CELERITAS_USE_${name} is disabled") + else() + # Save version and annotated string for corecel/Config.cc include + # Note that since this a macro, PARENT_SCOPE will be the top-level + # celeritas/CMakeLists.txt + set(${name}_VERSION ${version} PARENT_SCOPE) + set(${name}_VERSION_STRING "${version}+builtin" PARENT_SCOPE) + + string(REGEX REPLACE "@VERSION@" "${version}" _url "${url}") + FetchContent_Declare( + "${name}" + URL "${_url}" + URL_HASH SHA256=${sha} + DOWNLOAD_NO_PROGRESS TRUE + ) + list(APPEND _celer_builtin_packages "${name}") + endif() endmacro() +#-----------------------------------------------------------------------------# + +# Print extra information about downloading: hash failures, for example, are +# silent otherwise +set(CMAKE_MESSAGE_LOG_LEVEL VERBOSE) + #-----------------------------------------------------------------------------# # CLI11 #-----------------------------------------------------------------------------# @@ -51,6 +60,7 @@ if(CELERITAS_BUILTIN_covfie) "https://github.com/acts-project/covfie/archive/refs/tags/v@VERSION@.tar.gz" 72da1147c44731caf9163f3931de78d7605a44f056f22a2f6ea024ad02a1ba71 ) + set(COVFIE_PLATFORM_CPU ON) set(COVFIE_PLATFORM_CUDA ${CELERITAS_USE_CUDA}) set(COVFIE_PLATFORM_HIP ${CELERITAS_USE_HIP}) diff --git a/scripts/cmake-presets/ci-ubuntu-github.json b/scripts/cmake-presets/ci-ubuntu-github.json index 2299dd876b..4c7ac8395b 100644 --- a/scripts/cmake-presets/ci-ubuntu-github.json +++ b/scripts/cmake-presets/ci-ubuntu-github.json @@ -11,11 +11,19 @@ "cacheVariables": { "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_G4VG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_nlohmann_json": {"type": "BOOL", "value": "OFF"}, "CELERITAS_TEST_VERBOSE":{"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HIP": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LarSoft": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Perfetto":{"type": "BOOL", "value": "OFF"}, @@ -23,7 +31,6 @@ "CELERITAS_USE_Python": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, "CELERITAS_HOSTNAME": "ubuntu-github", "CELERITAS_TEST_XML": "$env{GITHUB_WORKSPACE}/test-output/google/", "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", @@ -34,53 +41,61 @@ } }, { - "name": "spack", + "name": "tool-spack", "inherits": ["base", ".spack-base"], "displayName": "CONFIG: use full spack toolchain", "cacheVariables": { "CELERITAS_USE_covfie": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_ROOT": null, - "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_ROOT": null } }, { - "name": ".system", + "name": "tool-system", "inherits": ["base"], - "displayName": "CONFIG: build with apt-get dependencies", + "displayName": "TOOLCHAIN: build with apt-get dependencies", "cacheVariables": { + "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_G4VG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_nlohmann_json": {"type": "BOOL", "value": "OFF"} + "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "ON"} } }, { "name": ".vecgeom", + "hidden": true, "description": "CONFIG: options to enable VecGeom on Ubuntu", "cacheVariables": { "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "ON"} } }, + { + "name": ".nogtest", + "hidden": true, + "description": "CONFIG: disable googletest", + "cacheVariables": { + "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "OFF"} + } + }, { "name": "doc", - "inherits": [".system"], + "inherits": [".nogtest", "tool-system"], "displayName": "FINAL: build only documentation", "cacheVariables": { "BUILD_TESTING": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_BUILD_APPS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "ON"}, - "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILD_APPS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "ON"}, + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_DOXYGEN_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"} } }, { "name": "type-reldeb", + "hidden": true, "description": "TYPE: debug assertions but no debug symbols", "cacheVariables": { "BUILD_SHARED_LIBS":{"type": "BOOL", "value": "ON"}, @@ -90,17 +105,16 @@ }, { "name": "type-ndebug", - "description": "TYPE: release build for testing *only* apps", + "hidden": true, + "description": "TYPE: release build", "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, - "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"} + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"} } }, { "name": "fast", - "inherits": ["type-reldeb", ".system"], + "inherits": ["type-reldeb", "tool-system"], "displayName": "FINAL: fast build using apt-get", "cacheVariables": { "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"} @@ -108,36 +122,25 @@ }, { "name": "ultralite", - "inherits": ["type-ndebug", ".system"], - "displayName": "FINAL: ultralite build with only app tests", - "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} - } + "inherits": [".nogtest", "type-ndebug", "tool-system"], + "displayName": "FINAL: ultralite build with only app tests" }, { "name": "ultralite-codecov", - "inherits": ["type-ndebug", ".system", ".gcov"], - "displayName": "FINAL: ultralite code coverage with only app tests", - "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} - } + "inherits": [".nogtest", ".gcov", "type-ndebug", "tool-system"], + "displayName": "FINAL: ultralite code coverage with only app tests" }, { "name": "reldeb-orange", "description": "FINAL: ORANGE", - "inherits": ["type-reldeb", "spack"] + "inherits": ["type-reldeb", "tool-spack"] }, { "name": "reldeb-vecgeom-tidy", "description": "FINAL: VecGeom and clang-tidy checks", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".nogtest", ".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_DEBUG": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, "CMAKE_CXX_SCAN_FOR_MODULES": {"type": "BOOL", "value": "OFF"}, @@ -148,7 +151,7 @@ { "name": "reldeb-vecgeom-clhep", "description": "FINAL: VecGeom and CLHEP units", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_UNITS": "CLHEP" } @@ -156,17 +159,17 @@ { "name": "reldeb-vecgeom", "description": "FINAL: release, assertions, VecGeom", - "inherits": ["type-reldeb", ".vecgeom", "spack"] + "inherits": [".vecgeom", "type-reldeb", "tool-spack"] }, { "name": "reldeb-vecgeom-codecov", "description": "FINAL: VecGeom and code coverage", - "inherits": ["type-reldeb", ".vecgeom", ".gcov", "spack"] + "inherits": [".vecgeom", ".gcov", "type-reldeb", "tool-spack"] }, { "name": "reldeb-vgsurf", "description": "FINAL: release, assertions, VecGeom 2+surf", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "ON"} } @@ -174,7 +177,7 @@ { "name": "reldeb-vecgeom2", "description": "FINAL: release, assertions, VecGeom 2~surf", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} } @@ -182,12 +185,12 @@ { "name": "ndebug-vecgeom", "description": "FINAL: vecgeom app only", - "inherits": ["type-ndebug", ".vecgeom", "spack"] + "inherits": [".vecgeom", ".nogtest", "type-ndebug", "tool-spack"] }, { "name": "ndebug-orange-static", "description": "FINAL: static libraries and perfetto, no tests", - "inherits": ["type-ndebug", "spack"], + "inherits": ["type-ndebug", "tool-spack"], "cacheVariables": { "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Perfetto":{"type": "BOOL", "value": "ON"}, @@ -198,7 +201,7 @@ { "name": "reldeb-geant4", "description": "FINAL: Geant4 geometry", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_CORE_GEO": "Geant4", "CELERITAS_UNITS": "CLHEP" @@ -207,7 +210,7 @@ { "name": "reldeb-orange-minimal", "description": "FINAL: minimal reasonable dependencies", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "OFF"}, @@ -217,9 +220,9 @@ } }, { - "name": "reldebinfo-orange-asanlite", + "name": "reldeb-orange-asanlite", "description": "FINAL: address sanitizer flags and debug symbols", - "inherits": ["spack"], + "inherits": ["tool-spack"], "cacheVariables": { "BUILD_SHARED_LIBS":{"type": "BOOL", "value": "ON"}, "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, @@ -233,7 +236,7 @@ { "name": "reldeb-orange-float", "description": "FINAL: single-precision arithmetic", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_REAL_TYPE": "float" } @@ -295,12 +298,12 @@ }, { "name": "spack-unit", - "configurePreset": "spack", + "configurePreset": "tool-spack", "inherits": [".unit", ".base"] }, { "name": "spack-app", - "configurePreset": "spack", + "configurePreset": "tool-spack", "inherits": [".app", ".base"] }, { From b35ed47ce48a0d027f3e7f7ac0a966335a18a60e Mon Sep 17 00:00:00 2001 From: Amanda Lund Date: Sat, 10 Jan 2026 14:55:17 -0600 Subject: [PATCH 29/60] Centralize default capacity values (#2185) * Centralize default capacity values * clang-tidy --- app/celer-g4/RunInput.hh | 6 +-- app/celer-g4/RunInputIO.json.cc | 21 +++++++--- app/celer-sim/RunnerInput.hh | 10 ++--- app/celer-sim/RunnerInputIO.json.cc | 14 +++++-- src/accel/LocalTransporter.cc | 17 +++++++-- src/accel/SetupOptions.cc | 59 ++++++++++------------------- src/accel/SetupOptions.hh | 6 +-- src/celeritas/inp/Control.hh | 25 ++++++++++-- 8 files changed, 92 insertions(+), 66 deletions(-) diff --git a/app/celer-g4/RunInput.hh b/app/celer-g4/RunInput.hh index 711e44f7e1..dd083bdbbc 100644 --- a/app/celer-g4/RunInput.hh +++ b/app/celer-g4/RunInput.hh @@ -78,10 +78,10 @@ struct RunInput PrimaryGeneratorOptions primary_options; // Control - size_type num_track_slots{}; //!< Defaults to 2^18 on device, 2^10 on host + size_type num_track_slots{}; size_type max_steps{unspecified}; - size_type initializer_capacity{}; //!< Defaults to 8 * num_track_slots - real_type secondary_stack_factor{2}; + size_type initializer_capacity{}; + real_type secondary_stack_factor{}; size_type auto_flush{}; //!< Defaults to num_track_slots bool action_times{false}; diff --git a/app/celer-g4/RunInputIO.json.cc b/app/celer-g4/RunInputIO.json.cc index c4f35f24e8..4b7cf3a910 100644 --- a/app/celer-g4/RunInputIO.json.cc +++ b/app/celer-g4/RunInputIO.json.cc @@ -13,9 +13,11 @@ #include "corecel/io/StringEnumMapper.hh" #include "corecel/sys/Environment.hh" #include "corecel/sys/EnvironmentIO.json.hh" +#include "geocel/GeantUtils.hh" #include "celeritas/TypesIO.json.hh" #include "celeritas/ext/GeantPhysicsOptionsIO.json.hh" #include "celeritas/field/FieldDriverOptionsIO.json.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/PrimaryGeneratorOptionsIO.json.hh" namespace celeritas @@ -88,19 +90,28 @@ void from_json(nlohmann::json const& j, RunInput& v) // DEPRECATED: remove in v1.0 RI_LOAD_DEPRECATED(sync, action_times); + // Get default capacities *integrated* over streams + auto capacity = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + + // celer-g4 input capacities are *per-stream* + size_type num_streams = get_geant_num_threads(); + RI_LOAD_OPTION(num_track_slots); - RI_LOAD_DEFAULT(num_track_slots, - celeritas::Device::num_devices() ? 262144 : 1024); + RI_LOAD_DEFAULT(num_track_slots, capacity.tracks / num_streams); RI_LOAD_OPTION(max_steps); - RI_LOAD_DEFAULT(initializer_capacity, 8 * v.num_track_slots); - RI_LOAD_OPTION(secondary_stack_factor); + RI_LOAD_DEFAULT(initializer_capacity, capacity.initializers / num_streams); + CELER_ASSERT(capacity.secondaries); + RI_LOAD_DEFAULT( + secondary_stack_factor, + static_cast(*capacity.secondaries) / capacity.tracks); RI_LOAD_OPTION(action_times); if (j.count("default_stream")) { // DEPRECATED: remove in v1.0 CELER_LOG(warning) << "Ignoring removed option 'default_stream'"; } - RI_LOAD_DEFAULT(auto_flush, v.num_track_slots); + RI_LOAD_DEFAULT(auto_flush, capacity.primaries / num_streams); RI_LOAD_OPTION(track_order); diff --git a/app/celer-sim/RunnerInput.hh b/app/celer-sim/RunnerInput.hh index 42c843bc7f..49e80c6673 100644 --- a/app/celer-sim/RunnerInput.hh +++ b/app/celer-sim/RunnerInput.hh @@ -60,7 +60,7 @@ struct RunnerInput struct OpticalOptions { - // Sizes are divided among streams + // *Per-process* capacities size_type num_track_slots{}; //!< Number of optical loop tracks slots size_type buffer_capacity{}; //!< Number of steps that created photons size_type auto_flush{}; //!< Threshold number of primaries for @@ -114,14 +114,12 @@ struct RunnerInput // Control unsigned int seed{}; - size_type num_track_slots{}; //!< Divided among streams. Defaults to 2^20 - //!< on device, 2^12 on host - size_type initializer_capacity{}; //!< Divided among streams. Defaults to - //!< 8 * num_track_slots + size_type num_track_slots{}; //!< Per-process track slots + size_type initializer_capacity{}; //!< Per-process initializer buffer size + real_type secondary_stack_factor{}; size_type max_steps = static_cast(-1); //!< Step *iterations* InterpolationType interpolation{InterpolationType::linear}; size_type poly_spline_order{1}; - real_type secondary_stack_factor{2}; bool use_device{}; bool action_times{}; bool merge_events{false}; //!< Run all events at once on a single stream diff --git a/app/celer-sim/RunnerInputIO.json.cc b/app/celer-sim/RunnerInputIO.json.cc index 948a52a1b5..513072c601 100644 --- a/app/celer-sim/RunnerInputIO.json.cc +++ b/app/celer-sim/RunnerInputIO.json.cc @@ -21,6 +21,7 @@ #include "celeritas/TypesIO.json.hh" #include "celeritas/ext/GeantPhysicsOptionsIO.json.hh" #include "celeritas/field/FieldDriverOptionsIO.json.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/PrimaryGeneratorOptionsIO.json.hh" #include "celeritas/user/RootStepWriterIO.json.hh" @@ -90,10 +91,17 @@ void from_json(nlohmann::json const& j, RunnerInput& v) LDIO_LOAD_OPTION(seed); LDIO_LOAD_REQUIRED(use_device); - LDIO_LOAD_DEFAULT(num_track_slots, v.use_device ? 1048576 : 4096); + + // Get default capacities *integrated* over streams + auto capacity = inp::CoreStateCapacity::from_default(v.use_device); + + LDIO_LOAD_DEFAULT(num_track_slots, capacity.tracks); LDIO_LOAD_OPTION(max_steps); - LDIO_LOAD_DEFAULT(initializer_capacity, 16 * v.num_track_slots); - LDIO_LOAD_OPTION(secondary_stack_factor); + LDIO_LOAD_DEFAULT(initializer_capacity, capacity.initializers); + CELER_ASSERT(capacity.secondaries); + LDIO_LOAD_DEFAULT( + secondary_stack_factor, + static_cast(*capacity.secondaries) / capacity.tracks); LDIO_LOAD_OPTION(interpolation); LDIO_LOAD_OPTION(poly_spline_order); LDIO_LOAD_OPTION(action_times); diff --git a/src/accel/LocalTransporter.cc b/src/accel/LocalTransporter.cc index dea3bd074a..7e6a6a40ae 100644 --- a/src/accel/LocalTransporter.cc +++ b/src/accel/LocalTransporter.cc @@ -40,6 +40,7 @@ #include "celeritas/global/ActionSequence.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/Stepper.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/io/EventWriter.hh" #include "celeritas/io/JsonEventWriter.hh" #include "celeritas/io/RootEventWriter.hh" @@ -150,9 +151,7 @@ void trace(StepperResult const& track_counts) */ LocalTransporter::LocalTransporter(SetupOptions const& options, SharedParams& params) - : auto_flush_( - get_default(options, params.Params()->max_streams()).primaries) - , max_step_iters_(options.max_step_iters) + : max_step_iters_(options.max_step_iters) , dump_primaries_{params.offload_writer()} { CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, @@ -164,6 +163,18 @@ LocalTransporter::LocalTransporter(SetupOptions const& options, << "invalid optical photon generation mechanism for local " "transporter"); + if (options.auto_flush) + { + auto_flush_ = options.auto_flush; + } + else + { + // Get default *per-process* auto flush and divide by number of streams + auto capacity = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + auto_flush_ = capacity.primaries / params.Params()->max_streams(); + } + particles_ = params.Params()->particle(); CELER_ASSERT(particles_); bbox_ = params.bbox(); diff --git a/src/accel/SetupOptions.cc b/src/accel/SetupOptions.cc index b19ce1c1ba..61c11f6748 100644 --- a/src/accel/SetupOptions.cc +++ b/src/accel/SetupOptions.cc @@ -91,13 +91,27 @@ void ProblemSetup::operator()(inp::Problem& p) const // NOTE: old SetupOptions input *per stream*, but inp::Problem needs // *integrated* over streams p.control.capacity = [this, num_streams = p.control.num_streams] { - auto capacity = get_default(so, num_streams); - inp::CoreStateCapacity c; - c.tracks = capacity.tracks; - c.initializers = capacity.initializers; - c.primaries = capacity.primaries; - c.secondaries - = static_cast(so.secondary_stack_factor * c.tracks); + auto c = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + + // Override default values if capacities were specified + if (so.max_num_tracks) + { + c.tracks = so.max_num_tracks * num_streams; + } + if (so.initializer_capacity) + { + c.initializers = so.initializer_capacity * num_streams; + } + if (so.auto_flush) + { + c.primaries = so.auto_flush * num_streams; + } + if (so.secondary_stack_factor) + { + c.secondaries + = static_cast(so.secondary_stack_factor * c.tracks); + } return c; }(); @@ -286,36 +300,5 @@ inp::FrameworkInput to_inp(SetupOptions const& so) return result; } -//---------------------------------------------------------------------------// -/*! - * Get runtime-dependent default capacity values. - * - * \note This must be called after CUDA/MPI have been initialized. - */ -inp::CoreStateCapacity -get_default(SetupOptions const& so, size_type num_streams) -{ - inp::CoreStateCapacity result; - result.tracks = num_streams * [&so] { - if (so.max_num_tracks) - { - return static_cast(so.max_num_tracks); - } - if (celeritas::Device::num_devices()) - { - constexpr size_type device_default = 262144; - return device_default; - } - constexpr size_type host_default = 1024; - return host_default; - }(); - result.initializers = so.initializer_capacity - ? num_streams * so.initializer_capacity - : 8 * result.tracks; - result.primaries = so.auto_flush ? so.auto_flush - : result.tracks / num_streams; - return result; -} - //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 7fd67512a9..3dfcfb7f1d 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -210,7 +210,7 @@ struct SetupOptions //! Maximum number of track initializers (primaries+secondaries) size_type initializer_capacity{}; //! At least the average number of secondaries per track slot - real_type secondary_stack_factor{2.0}; + real_type secondary_stack_factor{}; //! Number of tracks to buffer before offloading (if unset: max num tracks) size_type auto_flush{}; //!@} @@ -292,9 +292,5 @@ inp::GeantSd to_inp(SDSetupOptions const& so); // Construct a framework input inp::FrameworkInput to_inp(SetupOptions const& so); -// Get runtime-dependent default capacity values -inp::CoreStateCapacity -get_default(SetupOptions const& so, size_type num_streams); - //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/celeritas/inp/Control.hh b/src/celeritas/inp/Control.hh index a3312c7bd0..baa9bf408b 100644 --- a/src/celeritas/inp/Control.hh +++ b/src/celeritas/inp/Control.hh @@ -75,10 +75,19 @@ struct CoreStateCapacity : StateCapacity size_type initializers{}; //! Maximum number of secondaries created per step std::optional secondaries; - - //! Maximum number of simultaneous events (zero for doing one event at a - //! time) + //! Maximum number of simultaneous events (zero for one event at a time) std::optional events; + + //! Return default values + static CoreStateCapacity from_default(bool use_device) + { + CoreStateCapacity result; + result.tracks = use_device ? 1048576 : 4096; + result.primaries = result.tracks; + result.initializers = 8 * result.tracks; + result.secondaries = 2 * result.tracks; + return result; + } }; //---------------------------------------------------------------------------// @@ -91,6 +100,16 @@ struct OpticalStateCapacity : StateCapacity { //! Maximum number of queued photon-generating steps size_type generators{}; + + //! Return default values + static OpticalStateCapacity from_default(bool use_device) + { + OpticalStateCapacity result; + result.tracks = use_device ? 1048576 : 4096; + result.primaries = 128 * result.tracks; + result.generators = 2 * result.tracks; + return result; + } }; //---------------------------------------------------------------------------// From 253797f7a8c5208424239319ef433b0d5f94e617 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Mon, 12 Jan 2026 09:51:21 -0500 Subject: [PATCH 30/60] Fix InitializedValue semantics (#2181) * Fix InitializedValue semantics * Improve test coverage * Improve other test coverage * Update docs and add assertions * move functions out of line --- src/corecel/cont/InitializedValue.hh | 138 +++++++++++---------- test/corecel/cont/InitializedValue.test.cc | 67 ++++++---- test/corecel/data/DeviceAllocation.test.cc | 7 ++ 3 files changed, 124 insertions(+), 88 deletions(-) diff --git a/src/corecel/cont/InitializedValue.hh b/src/corecel/cont/InitializedValue.hh index e1639a3a20..a14f8973b7 100644 --- a/src/corecel/cont/InitializedValue.hh +++ b/src/corecel/cont/InitializedValue.hh @@ -9,6 +9,8 @@ #include #include +#include "corecel/Macros.hh" + namespace celeritas { namespace detail @@ -17,27 +19,28 @@ namespace detail template struct DefaultFinalize { - void operator()(T&) const noexcept {} + CELER_FORCEINLINE void operator()(T&) const noexcept {} }; //---------------------------------------------------------------------------// } // namespace detail //---------------------------------------------------------------------------// /*! - * Clear the value of the object on initialization and moving. + * Clear and finalize default values when moving and destroying. * * This helper class is used to simplify the "rule of 5" for classes that have * to treat one member data specially but can use default assign/construct for * the other elements. The default behavior is just to default-initialize when * assigning and clearing the RHS when moving; this is useful for handling - * managed memory. The *finalizer* is useful when the type has a - * destructor-type method that has to be called before clearing it. + * managed memory. The \em finalizer is called every time a \em non-default + * value is lost by assignment or destruction. */ template> class InitializedValue { - private: - static constexpr bool ne_finalize_ + static_assert(std::is_default_constructible_v); + static_assert(std::is_default_constructible_v); + static constexpr bool noexcept_finalize_ = noexcept(std::declval()(std::declval())); public: @@ -48,76 +51,33 @@ class InitializedValue InitializedValue() = default; //! Implicit construct from lvalue InitializedValue(T const& value) : value_(value) {} - //! Implicit construct from lvalue and finalizer - InitializedValue(T const& value, Finalizer fin) - : value_(value), fin_(std::move(fin)) - { - } //! Implicit construct from rvalue InitializedValue(T&& value) : value_(std::move(value)) {} - //! Implicit construct from value type and finalizer - InitializedValue(T&& value, Finalizer fin) - : value_(std::move(value)), fin_(std::move(fin)) - { - } //! Default copy constructor InitializedValue(InitializedValue const&) noexcept( std::is_nothrow_copy_constructible_v) = default; - //! Clear other value on move construct + // Move constructor InitializedValue(InitializedValue&& other) noexcept( - std::is_nothrow_move_constructible_v) - : value_(std::exchange(other.value_, {})) - { - } + std::is_nothrow_move_constructible_v); - ~InitializedValue() = default; + // Destructor + ~InitializedValue() noexcept(noexcept_finalize_); //!@} + //!@{ //! \name Assignment - - //! Finalize our value when assigning + // Copy assignment InitializedValue& operator=(InitializedValue const& other) noexcept( - ne_finalize_ && std::is_nothrow_copy_assignable_v) - { - fin_(value_); - value_ = other.value_; - fin_ = other.fin_; - return *this; - } - - //! Clear other value on move assign + noexcept_finalize_ && std::is_nothrow_copy_assignable_v); + // Move assignment InitializedValue& operator=(InitializedValue&& other) noexcept( - ne_finalize_ && std::is_nothrow_move_assignable_v) - { - fin_(value_); - value_ = std::exchange(other.value_, {}); - fin_ = std::exchange(other.fin_, {}); - return *this; - } - - //! Implicit assign from type - InitializedValue& - operator=(T const& value) noexcept(ne_finalize_ - && std::is_nothrow_copy_assignable_v) - { - fin_(value_); - value_ = value; - return *this; - } - - //! Swap with another value - void swap(InitializedValue& other) noexcept - { - using std::swap; - swap(other.value_, value_); - swap(other.fin_, fin_); - } - + noexcept_finalize_ && std::is_nothrow_move_assignable_v); //!@} + //!@{ //! \name Conversion @@ -128,20 +88,64 @@ class InitializedValue //! Explicit reference to stored value T const& value() const& { return value_; } T& value() & { return value_; } - T&& value() && { return value_; } //!@} - //!@{ - //! Access finalizer - Finalizer const& finalizer() const { return fin_; } - void finalizer(Finalizer fin) { fin_ = std::move(fin); } - //!@} - private: T value_{}; - Finalizer fin_{}; // TODO: if C++20, [[no_unique_address]] }; +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +//! Exchange with other value on move construct +template +InitializedValue::InitializedValue( + InitializedValue&& other) noexcept(std::is_nothrow_move_constructible_v) + : value_(std::exchange(other.value_, {})) +{ +} + +//---------------------------------------------------------------------------// +//! Call finalizer on destruct +template +InitializedValue::~InitializedValue() noexcept(noexcept_finalize_) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } +} + +//---------------------------------------------------------------------------// +//! Finalize our value when assigning +template +InitializedValue& +InitializedValue::operator=(InitializedValue const& other) noexcept( + noexcept_finalize_ && std::is_nothrow_copy_assignable_v) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } + value_ = other.value_; + return *this; +} + +//---------------------------------------------------------------------------// +//! Clear other value on move assign +template +InitializedValue& +InitializedValue::operator=(InitializedValue&& other) noexcept( + noexcept_finalize_ && std::is_nothrow_move_assignable_v) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } + value_ = std::exchange(other.value_, T{}); + return *this; +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/test/corecel/cont/InitializedValue.test.cc b/test/corecel/cont/InitializedValue.test.cc index 1b334a1fea..cad98f4516 100644 --- a/test/corecel/cont/InitializedValue.test.cc +++ b/test/corecel/cont/InitializedValue.test.cc @@ -17,8 +17,7 @@ namespace test TEST(InitializedValue, no_finalizer) { using InitValueInt = InitializedValue; - // TODO: in C++20 use [[no_unique_address]] and restore this assertion - // static_assert(sizeof(InitValueInt) == sizeof(int), "Bad size"); + static_assert(sizeof(InitValueInt) == sizeof(int), "Bad size"); // Use operator new to test that the int is being initialized properly by // constructing into data space that's been set to a different value @@ -66,61 +65,87 @@ TEST(InitializedValue, no_finalizer) } //---------------------------------------------------------------------------// +using OptionalInt = std::optional; struct Finalizer { - static std::vector& finalized_values() + static OptionalInt& last_finalized() { - static std::vector result; + static OptionalInt result; return result; } - void operator()(int val) const { finalized_values().push_back(val); } + void operator()(int val) const { last_finalized() = val; } }; -TEST(InitializedValue, finalizer) +OptionalInt get_last_finalized() { - std::vector expected; - std::vector& actual = Finalizer::finalized_values(); - actual.clear(); + return std::exchange(Finalizer::last_finalized(), {}); +} +TEST(InitializedValue, finalizer) +{ using InitValueInt = InitializedValue; + // Test default value + InitValueInt{}; + EXPECT_EQ(std::nullopt, get_last_finalized()); + + { + // Test nondefault value + InitValueInt derp{1}; + EXPECT_EQ(1, derp); + EXPECT_EQ(std::nullopt, get_last_finalized()); + } + EXPECT_EQ(1, get_last_finalized()); + InitValueInt ival{}; EXPECT_EQ(0, ival); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(std::nullopt, get_last_finalized()); + + { + InitValueInt temp{2}; + ival = std::move(temp); + EXPECT_EQ(std::nullopt, get_last_finalized()); + EXPECT_EQ(0, temp); + EXPECT_EQ(2, ival); + } + EXPECT_EQ(std::nullopt, get_last_finalized()); + ival = {}; + EXPECT_EQ(2, get_last_finalized()); InitValueInt other{345}; EXPECT_EQ(345, other); ival = other; - expected.push_back(0); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(std::nullopt, get_last_finalized()); + EXPECT_EQ(345, ival); + EXPECT_EQ(345, other); + other = ival; + EXPECT_EQ(345, get_last_finalized()); EXPECT_EQ(345, ival); EXPECT_EQ(345, other); other = 1000; - expected.push_back(345); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(345, get_last_finalized()); ival = std::move(other); - expected.push_back(345); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(345, get_last_finalized()); EXPECT_EQ(1000, ival); EXPECT_EQ(0, other); InitValueInt third(std::move(ival)); EXPECT_EQ(0, ival); EXPECT_EQ(1000, third); + EXPECT_EQ(std::nullopt, get_last_finalized()); // Test const T& constructor int const cint = 1234; - other = InitValueInt(cint); - expected.push_back(0); - EXPECT_VEC_EQ(expected, actual); - EXPECT_EQ(1234, other); + third = InitValueInt(cint); + EXPECT_EQ(1000, get_last_finalized()); + EXPECT_EQ(1234, third); // Test implicit conversion int tempint; tempint = third; - EXPECT_EQ(1000, tempint); + EXPECT_EQ(1234, tempint); } //---------------------------------------------------------------------------// diff --git a/test/corecel/data/DeviceAllocation.test.cc b/test/corecel/data/DeviceAllocation.test.cc index ab2625395f..c648aa2b2c 100644 --- a/test/corecel/data/DeviceAllocation.test.cc +++ b/test/corecel/data/DeviceAllocation.test.cc @@ -23,7 +23,14 @@ TEST(ConstructionTest, should_work_always) { DeviceAllocation alloc; EXPECT_EQ(0, alloc.size()); + EXPECT_EQ(nullptr, alloc.data()); EXPECT_TRUE(alloc.empty()); + EXPECT_EQ(StreamId{}, alloc.stream_id()); + + DeviceAllocation alloc2; + swap(alloc, alloc2); + EXPECT_TRUE(alloc.empty()); + EXPECT_TRUE(alloc.device_ref().empty()); } #if !CELER_USE_DEVICE From 1ba716009fb625e1df3f47bea6710470d3f662a5 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Mon, 12 Jan 2026 12:30:52 -0500 Subject: [PATCH 31/60] Disable debug assertions for code coverage tests (#2184) * Build code coverage without debug assertions * codecov: remove unnecessary ignores * Don't install or run examples when codecov * Remove excl_br from ignored file * REVERTME: delete all but codecov * Allow inlining * ignore framework files for now * Improve comment * Clarify fomr the test name that different events are being run * Revert "REVERTME: delete all but codecov" This reverts commit 1cfbd35dbeee62714181ff1a0305ea9f4ac68b1e. * Delete unused ci scripts * Ignore coverage for unreachable lines * Use exclusions instead of special assert macro * REVERTME: delete all but codecov * POSSIBLY REVERTME: See if coverage changes with discarding * Revert "REVERTME: delete all but codecov" This reverts commit 73eab16b90cd485830c8c0b5c0a9e5b0dc6c73ae. --- .github/codecov.yml | 11 ++-- .github/workflows/build-docker.yml | 8 ++- .github/workflows/build-spack.yml | 3 +- CMakePresets.json | 5 +- app/celer-g4/CMakeLists.txt | 7 ++- app/celer-geo/Runner.cc | 1 - scripts/ci/gcovr.cfg | 1 + scripts/ci/host-system-info.cmake | 20 ------ scripts/ci/run-ci.sh | 68 --------------------- scripts/cmake-presets/ci-rocky-cuda.json | 9 +-- scripts/cmake-presets/ci-ubuntu-github.json | 14 +++-- src/corecel/Assert.hh | 15 +++-- 12 files changed, 47 insertions(+), 115 deletions(-) delete mode 100644 scripts/ci/host-system-info.cmake delete mode 100755 scripts/ci/run-ci.sh diff --git a/.github/codecov.yml b/.github/codecov.yml index efe84845db..2129699f2c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -17,7 +17,7 @@ coverage: # 'auto' will use the coverage from the base commit (pull request base or # parent commit) coverage to compare against. This is the default. target: auto - # The default threshold is 1%, which means any drop in coverage will cause + # The default threshold is 1%, which means any drop in coverage will cause the CI to fail threshold: 5% base: auto if_not_found: success @@ -39,9 +39,6 @@ comment: ignore: - "test" - "app" - - "doc/**/*" - - "docs/**/*" - - "*.md" - - "scripts/**/*" - - ".github/**/*" - - "src/larceler/LarCelerStandalone*" + - "example" + - "src/larceler/**/*" + - "src/ddceler/**/*" diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 4224fd3eb6..9d55bfd393 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -43,7 +43,7 @@ jobs: - buildtype: "debug" image: "ubuntu-rocm" # Debug device code breaks HIP optimizer include: - - buildtype: "reldeb" + - buildtype: "ndebug" geometry: "vecgeom" special: "codecov" geant: "11.3" @@ -132,6 +132,12 @@ jobs: - name: Install Celeritas id: install + if: >- + ${{ + !cancelled() + && steps.build.outcome == 'success' + && matrix.special != 'codecov' + }} working-directory: build run: | echo "::group::Install" diff --git a/.github/workflows/build-spack.yml b/.github/workflows/build-spack.yml index fef6a4e50b..583e0dd20b 100644 --- a/.github/workflows/build-spack.yml +++ b/.github/workflows/build-spack.yml @@ -79,7 +79,7 @@ jobs: geometry: "vecgeom" special: "tidy" geant: "11.2" - - build_type: "reldeb" + - build_type: "ndebug" geometry: "vecgeom" special: "codecov" geant: "11.3" @@ -358,6 +358,7 @@ jobs: !cancelled() && steps.build.outcome == 'success' && matrix.special != 'tidy' + && matrix.special != 'codecov' }} working-directory: build run: | diff --git a/CMakePresets.json b/CMakePresets.json index 6a2e5d4fbf..ede2da7689 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -146,8 +146,9 @@ "hidden": true, "description": "Enable coverage using GCC/LLVM", "cacheVariables": { - "CELERITAS_C_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, - "CELERITAS_CXX_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_C_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, + "CELERITAS_CXX_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage;-DCELERITAS_GCOV"}, "CELERITAS_LINK_FLAGS": {"type": "STRING", "value": "-fprofile-arcs"} } }, diff --git a/app/celer-g4/CMakeLists.txt b/app/celer-g4/CMakeLists.txt index 814dbfd36e..44ebcdbb17 100644 --- a/app/celer-g4/CMakeLists.txt +++ b/app/celer-g4/CMakeLists.txt @@ -57,9 +57,11 @@ endif() set(_driver "${CMAKE_CURRENT_SOURCE_DIR}/test-harness.py") set(_exe "$") set(_model "${CELER_APP_DATA_DIR}/simple-cms.gdml") +set(_test_ext "cms") if(NOT CELERITAS_DEBUG) set(_events "${CELER_APP_DATA_DIR}/ttbarsplit-12evt-1108prim.hepmc3") + set(_test_ext "${_label}-large") else() # Use fewer events for debug set(_events @@ -119,11 +121,12 @@ function(celer_app_test ext) list(APPEND _extra_env "CELER_DISABLE=1") endif() - add_test(NAME "app/celer-g4:${ext}" + set(_name "app/celer-g4:${_test_ext}:${ext}") + add_test(NAME "${_name}" COMMAND "${CELER_PYTHON}" "${_driver}" "${_exe}" "${_model}" "${_events}" ) - set_tests_properties("app/celer-g4:${ext}" PROPERTIES + set_tests_properties("${_name}" PROPERTIES ENVIRONMENT "${_env};${_extra_env}" REQUIRED_FILES "${_required_files}" LABELS "${_labels}" diff --git a/app/celer-geo/Runner.cc b/app/celer-geo/Runner.cc index f46c36acee..8058dc03e5 100644 --- a/app/celer-geo/Runner.cc +++ b/app/celer-geo/Runner.cc @@ -55,7 +55,6 @@ Runner::Runner(ModelSetup const& input) } else { - // GCOVR_EXCL_BR_SOURCE CELER_VALIDATE(std::ifstream{input_.geometry_file}.is_open(), << "input model filename '" << input_.geometry_file << "' does not exist"); diff --git a/scripts/ci/gcovr.cfg b/scripts/ci/gcovr.cfg index 38cc24cc06..b39a91262a 100644 --- a/scripts/ci/gcovr.cfg +++ b/scripts/ci/gcovr.cfg @@ -16,3 +16,4 @@ gcov-ignore-parse-errors = negative_hits.warn gcov-ignore-parse-errors = suspicious_hits.warn exclude-unreachable-branches = yes +exclude-lines-by-pattern = ^\s*CELER_((ASSERT_)?UNREACHABLE|NOT_CONFIGURED|(DEVICE_API|MPI)_CALL|EXPECT|ASSERT|ENSURE).* diff --git a/scripts/ci/host-system-info.cmake b/scripts/ci/host-system-info.cmake deleted file mode 100644 index 62f9d26c14..0000000000 --- a/scripts/ci/host-system-info.cmake +++ /dev/null @@ -1,20 +0,0 @@ -#------------------------------- -*- cmake -*- -------------------------------# -# Copyright Celeritas contributors: see top-level COPYRIGHT file for details -# SPDX-License-Identifier: (Apache-2.0 OR MIT) -#[=======================================================================[.rst: - -Run with `cmake -P 2>&1`, optionally passing -DKEY= - -See: -https://cmake.org/cmake/help/latest/command/cmake_host_system_information.html -for possible values. - -#]=======================================================================] - -if(NOT KEY) - set(KEY NUMBER_OF_LOGICAL_CORES) -endif() -cmake_host_system_information(RESULT result QUERY ${KEY}) -message(${result}) - -#-----------------------------------------------------------------------------# diff --git a/scripts/ci/run-ci.sh b/scripts/ci/run-ci.sh deleted file mode 100755 index 8421742cf9..0000000000 --- a/scripts/ci/run-ci.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh -e - -OS=$1 -CMAKE_PRESET=$2 - -set -x -test -n "${OS}" -test -n "${CMAKE_PRESET}" - -if [ -z "${CELER_SOURCE_DIR}" ]; then - CELER_SOURCE_DIR="$(dirname $0)"/../.. -fi -cd "${CELER_SOURCE_DIR}" -CELER_SOURCE_DIR=$(pwd) - -# Source environment script if necessary -_ENV_SCRIPT="scripts/env/ci-${OS}.sh" -if [ -f "${_ENV_SCRIPT}" ]; then - . "${_ENV_SCRIPT}" -fi - -# Fetch tags for version provenance -git fetch -f --tags - -# Clean older builds from jenkins *BEFORE* setting up presets -git clean -fxd -# Stale tmp files? -rm -rf /tmp/ompi.* - -# Link preset script -ln -fs scripts/cmake-presets/ci-${OS}.json CMakeUserPresets.json -# Configure -cmake --preset=${CMAKE_PRESET} -# Build and (optionally) install -cmake --build --preset=${CMAKE_PRESET} - -# Require regression-like tests to be enabled and pass -export CELER_TEST_STRICT=1 - -# Run tests -cd build -ctest -T Test \ - -j16 --timeout 180 \ - --no-compress-output --output-on-failure \ - --test-output-size-passed=65536 --test-output-size-failed=1048576 \ -# List XML files generated: jenkins will upload these later -find Testing -name '*.xml' - -# Install and test that the executables are there -cmake --install . -cd .. -test -x "${CELER_SOURCE_DIR}/install/bin/celer-sim" -test -x "${CELER_SOURCE_DIR}/install/bin/celer-g4" -"${CELER_SOURCE_DIR}/install/bin/celer-sim" --version - - -# Test examples against installed celeritas -export CMAKE_PRESET -export CELER_SOURCE_DIR -case "${CMAKE_PRESET}" in - *-vecgeom*) - # VecGeom is in use: ubuntu flags are too strict for it - export LDFLAGS=-Wl,--no-as-needed - ;; -esac - -# Test examples against installed celeritas -exec ${CELER_SOURCE_DIR}/scripts/ci/test-examples.sh diff --git a/scripts/cmake-presets/ci-rocky-cuda.json b/scripts/cmake-presets/ci-rocky-cuda.json index e265f53179..3b9c20d567 100644 --- a/scripts/cmake-presets/ci-rocky-cuda.json +++ b/scripts/cmake-presets/ci-rocky-cuda.json @@ -74,11 +74,12 @@ } }, { - "name": "reldeb-vecgeom-codecov", - "description": "Build with RelWithDebInfo, VecGeom and code coverage (no CUDA)", - "inherits": [".reldeb", ".vecgeom", ".gcov", "base"], + "name": "ndebug-vecgeom-codecov", + "description": "Build with code coverage with vecgeom (no CUDA, no assertions)", + "inherits": [".gcov", ".ndebug", ".vecgeom", "base"], "cacheVariables": { - "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CXX_FLAGS": null } } ], diff --git a/scripts/cmake-presets/ci-ubuntu-github.json b/scripts/cmake-presets/ci-ubuntu-github.json index 4c7ac8395b..4a831df8da 100644 --- a/scripts/cmake-presets/ci-ubuntu-github.json +++ b/scripts/cmake-presets/ci-ubuntu-github.json @@ -128,7 +128,10 @@ { "name": "ultralite-codecov", "inherits": [".nogtest", ".gcov", "type-ndebug", "tool-system"], - "displayName": "FINAL: ultralite code coverage with only app tests" + "displayName": "FINAL: ultralite code coverage with only app tests", + "cacheVariables": { + "CMAKE_CXX_FLAGS": null + } }, { "name": "reldeb-orange", @@ -162,9 +165,12 @@ "inherits": [".vecgeom", "type-reldeb", "tool-spack"] }, { - "name": "reldeb-vecgeom-codecov", + "name": "ndebug-vecgeom-codecov", "description": "FINAL: VecGeom and code coverage", - "inherits": [".vecgeom", ".gcov", "type-reldeb", "tool-spack"] + "inherits": [".vecgeom", ".gcov", "type-reldeb", "tool-spack"], + "cacheVariables": { + "CMAKE_CXX_FLAGS": null + } }, { "name": "reldeb-vgsurf", @@ -285,7 +291,7 @@ "name": ".app", "configurePreset": "base", "execution": { - "timeout": 30 + "timeout": 60 }, "filter": { "include": { diff --git a/src/corecel/Assert.hh b/src/corecel/Assert.hh index eb6ea74005..e98b1079b2 100644 --- a/src/corecel/Assert.hh +++ b/src/corecel/Assert.hh @@ -167,11 +167,16 @@ ::celeritas::unreachable(); \ } \ } while (0) -#define CELER_NOASSERT_(COND) \ - do \ - { \ - if (false && (COND)) {} \ - } while (0) +#ifndef CELERITAS_GCOV +# define CELER_NOASSERT_(COND) \ + do \ + { \ + if (false && (COND)) {} \ + } while (0) +#else +// Delete the code completely to avoid false posistives for coverage +# define CELER_NOASSERT_(COND) +#endif //! \endcond #define CELER_DEBUG_FAIL(MSG, WHICH) \ From c376486cc8a94ec98e2b462ffcb7acfe8360db13 Mon Sep 17 00:00:00 2001 From: Amanda Lund Date: Mon, 12 Jan 2026 19:06:34 -0600 Subject: [PATCH 32/60] Add optical problem input and setup (#2189) * Store aux registry in optical core params * Refactor input and setup to support optical-only celeritas simulation * Fix mismatched tags * clang-tidy --- src/accel/LocalOpticalGenOffload.cc | 12 +- src/accel/LocalTransporter.cc | 5 +- src/accel/LocalTransporter.hh | 4 +- src/accel/SetupOptions.cc | 56 +++- src/accel/SetupOptions.hh | 8 +- src/accel/SharedParams.cc | 133 ++------- src/accel/SharedParams.hh | 62 ++-- src/celeritas/global/CoreParams.cc | 24 +- src/celeritas/inp/Events.hh | 11 +- src/celeritas/inp/FrameworkInput.hh | 4 + src/celeritas/inp/Physics.hh | 4 +- src/celeritas/inp/Problem.hh | 26 ++ .../detail => celeritas/io}/OffloadWriter.hh | 7 +- src/celeritas/optical/CoreParams.cc | 29 ++ src/celeritas/optical/CoreParams.hh | 17 +- src/celeritas/optical/OpticalCollector.cc | 4 +- src/celeritas/optical/PhysicsParams.cc | 26 ++ src/celeritas/optical/PhysicsParams.hh | 11 + .../optical/gen/DirectGeneratorAction.cc | 6 +- .../optical/gen/DirectGeneratorAction.hh | 2 +- src/celeritas/optical/gen/GeneratorAction.cc | 6 +- src/celeritas/optical/gen/GeneratorAction.hh | 4 +- .../optical/gen/PrimaryGeneratorAction.cc | 8 +- .../optical/gen/PrimaryGeneratorAction.hh | 2 +- src/celeritas/setup/FrameworkInput.cc | 89 ++++-- src/celeritas/setup/FrameworkInput.hh | 5 +- src/celeritas/setup/Problem.cc | 279 +++++++++++------- src/celeritas/setup/Problem.hh | 25 +- src/corecel/io/OutputRegistry.cc | 31 ++ src/corecel/io/OutputRegistry.hh | 7 + test/accel/TrackingManagerIntegration.test.cc | 6 +- test/celeritas/GlobalTestBase.cc | 6 + test/celeritas/optical/Generator.test.cc | 10 +- .../optical/SurfacePhysicsIntegration.test.cc | 4 +- 34 files changed, 573 insertions(+), 360 deletions(-) rename src/{accel/detail => celeritas/io}/OffloadWriter.hh (93%) diff --git a/src/accel/LocalOpticalGenOffload.cc b/src/accel/LocalOpticalGenOffload.cc index ee6cc7ffc5..309908671c 100644 --- a/src/accel/LocalOpticalGenOffload.cc +++ b/src/accel/LocalOpticalGenOffload.cc @@ -44,16 +44,16 @@ LocalOpticalGenOffload::LocalOpticalGenOffload(SetupOptions const& options, << "invalid optical photon generation mechanism for local " "optical offload"); - // Check the thread ID and MT model - validate_geant_threading(params.Params()->max_streams()); - // Save a pointer to the optical transporter - transport_ = params.optical_transporter(); + transport_ = params.optical_problem_loaded().transporter; CELER_ASSERT(transport_); CELER_ASSERT(transport_->params()); auto const& optical_params = *transport_->params(); + // Check the thread ID and MT model + validate_geant_threading(optical_params.max_streams()); + // Save a pointer to the generator action auto const& gen_reg = *optical_params.gen_reg(); for (auto gen_id : range(GeneratorId(gen_reg.size()))) @@ -88,10 +88,10 @@ LocalOpticalGenOffload::LocalOpticalGenOffload(SetupOptions const& options, } // Allocate auxiliary data - if (params.Params()->aux_reg()) + if (optical_params.aux_reg()) { state_->aux() = std::make_shared( - *params.Params()->aux_reg(), memspace, stream_id, capacity.tracks); + *optical_params.aux_reg(), memspace, stream_id, capacity.tracks); } CELER_ENSURE(*this); diff --git a/src/accel/LocalTransporter.cc b/src/accel/LocalTransporter.cc index 7e6a6a40ae..8248fc4f49 100644 --- a/src/accel/LocalTransporter.cc +++ b/src/accel/LocalTransporter.cc @@ -43,6 +43,7 @@ #include "celeritas/inp/Control.hh" #include "celeritas/io/EventWriter.hh" #include "celeritas/io/JsonEventWriter.hh" +#include "celeritas/io/OffloadWriter.hh" #include "celeritas/io/RootEventWriter.hh" #include "celeritas/optical/CoreState.hh" #include "celeritas/optical/OpticalCollector.hh" @@ -52,8 +53,6 @@ #include "SetupOptions.hh" #include "SharedParams.hh" -#include "detail/OffloadWriter.hh" - namespace celeritas { namespace @@ -209,7 +208,7 @@ LocalTransporter::LocalTransporter(SetupOptions const& options, params.set_state(stream_id.get(), step_->sp_state()); // Save optical pointers if available, for diagnostics - optical_ = params.optical_collector(); + optical_ = params.problem_loaded().optical_collector; CELER_ENSURE(*this); } diff --git a/src/accel/LocalTransporter.hh b/src/accel/LocalTransporter.hh index ef3e299b28..68b9781b07 100644 --- a/src/accel/LocalTransporter.hh +++ b/src/accel/LocalTransporter.hh @@ -26,11 +26,11 @@ namespace celeritas namespace detail { class HitProcessor; -class OffloadWriter; } // namespace detail struct SetupOptions; class CoreStateInterface; +class OffloadWriter; class OpticalCollector; class ParticleParams; class SharedParams; @@ -100,7 +100,7 @@ class LocalTransporter final : public TrackOffloadInterface private: //// TYPES //// - using SPOffloadWriter = std::shared_ptr; + using SPOffloadWriter = std::shared_ptr; using BBox = BoundingBox; struct BufferAccum diff --git a/src/accel/SetupOptions.cc b/src/accel/SetupOptions.cc index 61c11f6748..dcadf3e1ce 100644 --- a/src/accel/SetupOptions.cc +++ b/src/accel/SetupOptions.cc @@ -126,9 +126,7 @@ void ProblemSetup::operator()(inp::Problem& p) const if (so.optical) { p.control.optical_capacity = so.optical->capacity; - p.physics.optical_generator = so.optical->generator; - p.tracking.optical_limits.steps = so.optical->max_steps; - p.tracking.optical_limits.step_iters = so.optical->max_step_iters; + p.tracking.optical_limits = so.optical->limits; } if (so.track_order != TrackOrder::size_) @@ -218,6 +216,45 @@ void ProblemSetup::operator()(inp::Problem& p) const p.diagnostics.add_user_actions = so.add_user_actions; } +//---------------------------------------------------------------------------// +/*! + * FrameworkInput adapter function for optical-only offload. + */ +struct OpticalProblemSetup +{ + SetupOptions const& so; + + void operator()(inp::OpticalProblem&) const; +}; + +//---------------------------------------------------------------------------// +/*! + * Set a Celeritas optical problem input definition. + */ +void OpticalProblemSetup::operator()(inp::OpticalProblem& p) const +{ + if (!so.geometry_file.empty()) + { + p.model.geometry = so.geometry_file; + } + + p.num_streams = [&so = this->so] { + if (so.get_num_streams) + { + return so.get_num_streams(); + } + return celeritas::get_geant_num_threads(); + }(); + + CELER_ASSERT(so.optical); + p.generator = so.optical->generator; + p.capacity = so.optical->capacity; + p.limits = so.optical->limits; + p.seed = CLHEP::HepRandom::getTheSeed(); + p.timers.action = so.action_times; + p.output_file = so.output_file; +} + //---------------------------------------------------------------------------// } // namespace @@ -296,7 +333,18 @@ inp::FrameworkInput to_inp(SetupOptions const& so) result.physics_import.data_selection.particles = selection; result.physics_import.data_selection.processes = selection; - result.adjust = ProblemSetup{so}; + if (!so.optical + || std::holds_alternative( + so.optical->generator)) + { + result.adjust = ProblemSetup{so}; + } + else + { + // Optical-only offload + result.adjust_optical = OpticalProblemSetup{so}; + } + return result; } diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 3dfcfb7f1d..63835c39f0 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -125,12 +125,8 @@ struct OpticalSetupOptions inp::OpticalStateCapacity capacity; //! Optical photon generation mechanism inp::OpticalGenerator generator; - //! Limit on number of steps per track before killing - size_type max_steps{inp::TrackingLimits::unlimited}; - //! Limit on number of optical step iterations before aborting - size_type max_step_iters{inp::TrackingLimits::unlimited}; - //! Optical photon track offload option - bool offload_tracks{false}; + //! Limits for the optical stepping loop + inp::OpticalTrackingLimits limits; }; //---------------------------------------------------------------------------// diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 23cfb8acee..1fafbeb87c 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -29,21 +29,16 @@ #include "corecel/Assert.hh" #include "corecel/cont/ArrayIO.hh" +#include "corecel/cont/VariantUtils.hh" #include "corecel/io/BuildOutput.hh" #include "corecel/io/Join.hh" #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" #include "corecel/io/ScopedTimeLog.hh" -#include "corecel/io/StringUtils.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" #include "corecel/sys/Device.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/EnvironmentIO.json.hh" -#include "corecel/sys/KernelRegistry.hh" -#include "corecel/sys/MemRegistry.hh" -#include "corecel/sys/MemRegistryIO.json.hh" #include "corecel/sys/ScopedMem.hh" #include "corecel/sys/ScopedProfiling.hh" #include "corecel/sys/ThreadId.hh" @@ -60,12 +55,10 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/inp/FrameworkInput.hh" #include "celeritas/inp/Scoring.hh" -#include "celeritas/io/EventWriter.hh" #include "celeritas/io/ImportData.hh" -#include "celeritas/io/JsonEventWriter.hh" -#include "celeritas/io/RootEventWriter.hh" #include "celeritas/mat/MaterialParams.hh" #include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/Transporter.hh" #include "celeritas/phys/CutoffParams.hh" #include "celeritas/phys/ParticleParams.hh" #include "celeritas/phys/PhysicsParams.hh" @@ -82,7 +75,6 @@ #include "TimeOutput.hh" #include "detail/IntegrationSingleton.hh" -#include "detail/OffloadWriter.hh" namespace celeritas { @@ -282,35 +274,24 @@ SharedParams::SharedParams(SetupOptions const& options) ? *options.offload_particles : default_offload_particles(); } - if (mode_ != Mode::enabled) { // Stop initializing but create output registry for diagnostics output_reg_ = std::make_shared(); - output_filename_ = options.output_file; + loaded_.output_file = options.output_file; // Create the timing output timer_ = std::make_shared(celeritas::get_geant_num_threads()); - if (!output_filename_.empty()) + if (!loaded_.output_file.empty()) { CELER_LOG(debug) << R"(Constructing output registry for no-offload run)"; // Celeritas core params didn't add system metadata: do it // ourselves to save system diagnostic information - output_reg_->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "memory", - celeritas::mem_registry())); - output_reg_->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "environ", - celeritas::environment())); - output_reg_->insert(std::make_shared()); + insert_system_diagnostics(*output_reg_); output_reg_->insert(timer_); } @@ -319,67 +300,40 @@ SharedParams::SharedParams(SetupOptions const& options) // Construct input and then build the problem setup auto framework_inp = to_inp(options); - auto loaded = setup::framework_input(framework_inp); - params_ = std::move(loaded.problem.core_params); - CELER_ASSERT(params_); - output_filename_ = loaded.problem.output_file; - - if (auto const& opt = options.optical) - { - if (std::holds_alternative(opt->generator)) - { - optical_transporter_ - = std::move(loaded.problem.optical_transporter); - CELER_ASSERT(optical_transporter_); - } - else if (std::holds_alternative(opt->generator)) - { - optical_collector_ = std::move(loaded.problem.optical_collector); - CELER_ASSERT(optical_collector_); - } - else if (std::holds_alternative( - opt->generator)) - { - optical_transporter_ - = std::move(loaded.problem.optical_transporter); - CELER_ASSERT(optical_transporter_); - } - else - { - CELER_VALIDATE(false, - << "invalid optical photon generation mechanism"); - } - } - - // Save action sequence - actions_ = std::move(loaded.problem.actions); - CELER_ASSERT(actions_); + loaded_ = setup::framework_input(framework_inp); // Load geant4 geometry adapter and save as "global" - CELER_ASSERT(loaded.geo); - geant_geo_ = std::move(loaded.geo); - celeritas::global_geant_geo(geant_geo_); - - // Save built attributes - output_reg_ = params_->output_reg(); - geant_sd_ = std::move(loaded.problem.geant_sd); - step_collector_ = std::move(loaded.problem.step_collector); - - // Translate supported particles - verify_offload( - offload_particles_, *params_->particle(), *params_->physics()); + CELER_ASSERT(loaded_.geo); + celeritas::global_geant_geo(loaded_.geo); // Create bounding box from navigator geometry - bbox_ = geant_geo_->get_clhep_bbox(); - - // Create streams - this->set_num_streams(params_->max_streams()); + bbox_ = loaded_.geo->get_clhep_bbox(); + + std::visit( + Overload{ + [&](setup::ProblemLoaded const& p) { + // Translate supported particles + verify_offload(offload_particles_, + *p.core_params->particle(), + *p.core_params->physics()); + + // Set streams and output registry from core params + output_reg_ = p.core_params->output_reg(); + this->set_num_streams(p.core_params->max_streams()); + }, + [&](setup::OpticalProblemLoaded const& p) { + // Set streams and output registry from optical params + output_reg_ = p.transporter->params()->output_reg(); + this->set_num_streams(p.transporter->params()->max_streams()); + }, + }, + loaded_.problem); // Add timing output - timer_ = std::make_shared(params_->max_streams()); + timer_ = std::make_shared(this->num_streams()); output_reg_->insert(timer_); - if (output_filename_ != "-") + if (loaded_.output_file != "-") { // Write output after params are constructed before anything can go // wrong @@ -391,29 +345,6 @@ SharedParams::SharedParams(SetupOptions const& options) << R"(Skipping 'startup' JSON output since writing to stdout)"; } - if (auto const& offload_file = loaded.problem.offload_file; - !offload_file.empty()) - { - std::unique_ptr writer; - if (ends_with(offload_file, ".jsonl")) - { - writer.reset( - new JsonEventWriter(offload_file, params_->particle())); - } - else if (ends_with(offload_file, ".root")) - { - writer.reset(new RootEventWriter( - std::make_shared(offload_file.c_str()), - params_->particle())); - } - else - { - writer.reset(new EventWriter(offload_file, params_->particle())); - } - offload_writer_ - = std::make_shared(std::move(writer)); - } - CELER_ENSURE(*this); } @@ -533,7 +464,7 @@ void SharedParams::set_num_streams(unsigned int num_streams) */ void SharedParams::try_output() const { - std::string filename = output_filename_; + std::string filename = loaded_.output_file; if (filename.empty()) { CELER_LOG(debug) << "Skipping output: SetupOptions::output_file is " diff --git a/src/accel/SharedParams.hh b/src/accel/SharedParams.hh index 357fd43583..7118fcdd9e 100644 --- a/src/accel/SharedParams.hh +++ b/src/accel/SharedParams.hh @@ -12,6 +12,7 @@ #include "corecel/Assert.hh" #include "geocel/BoundingBox.hh" +#include "celeritas/setup/FrameworkInput.hh" #include "Types.hh" @@ -21,11 +22,6 @@ class G4VPhysicalVolume; namespace celeritas { //---------------------------------------------------------------------------// -namespace detail -{ -class OffloadWriter; -} // namespace detail - namespace optical { class Transporter; @@ -36,6 +32,7 @@ class CoreParams; class CoreStateInterface; class GeantGeoParams; class GeantSd; +class OffloadWriter; class OpticalCollector; class OutputRegistry; class StepCollector; @@ -136,23 +133,20 @@ class SharedParams using SPActionSequence = std::shared_ptr; using SPGeantSd = std::shared_ptr; - using SPOffloadWriter = std::shared_ptr; + using SPOffloadWriter = std::shared_ptr; using SPOutputRegistry = std::shared_ptr; using SPTimeOutput = std::shared_ptr; using SPState = std::shared_ptr; - using SPOpticalCollector = std::shared_ptr; - using SPOpticalTransporter = std::shared_ptr; - using SPConstGeantGeoParams = std::shared_ptr; using BBox = BoundingBox; //! Initialization status and integration mode Mode mode() const { return mode_; } - // Optical properties (only if using optical physics) - inline SPOpticalCollector const& optical_collector() const; + // Access core Celeritas loaded problem data + inline setup::ProblemLoaded const& problem_loaded() const; - // Optical params (only if using optical physics) - inline SPOpticalTransporter const& optical_transporter() const; + // Access optical Celeritas loaded problem data + inline setup::OpticalProblemLoaded const& optical_problem_loaded() const; // Action sequence inline SPActionSequence const& actions() const; @@ -184,16 +178,8 @@ class SharedParams // Created during initialization Mode mode_{Mode::uninitialized}; - SPConstGeantGeoParams geant_geo_; - std::shared_ptr params_; - SPOpticalCollector optical_collector_; - SPOpticalTransporter optical_transporter_; - SPActionSequence actions_; - std::shared_ptr geant_sd_; - std::shared_ptr step_collector_; + setup::FrameworkLoaded loaded_; VecG4PD offload_particles_; - std::string output_filename_; - SPOffloadWriter offload_writer_; std::vector> states_; SPOutputRegistry output_reg_; SPTimeOutput timer_; @@ -223,8 +209,10 @@ void SharedParams::Initialize(SetupOptions const& options) auto SharedParams::Params() -> SPParams const& { CELER_EXPECT(mode_ == Mode::enabled); - CELER_ENSURE(params_); - return params_; + CELER_EXPECT(std::holds_alternative(loaded_.problem)); + auto const& p = std::get(loaded_.problem); + CELER_ENSURE(p.core_params); + return p.core_params; } //---------------------------------------------------------------------------// @@ -236,8 +224,8 @@ auto SharedParams::Params() -> SPParams const& auto SharedParams::Params() const -> SPConstParams { CELER_EXPECT(mode_ == Mode::enabled); - CELER_ENSURE(params_); - return params_; + CELER_ENSURE(this->problem_loaded().core_params); + return this->problem_loaded().core_params; } //---------------------------------------------------------------------------// @@ -252,22 +240,26 @@ auto SharedParams::OffloadParticles() const -> VecG4PD const& //---------------------------------------------------------------------------// /*! - * Optical transporter: null if Celeritas optical physics is disabled. + * Access Celeritas loaded core problem data. */ -auto SharedParams::optical_transporter() const -> SPOpticalTransporter const& +auto SharedParams::problem_loaded() const -> setup::ProblemLoaded const& { CELER_EXPECT(*this); - return optical_transporter_; + CELER_EXPECT(std::holds_alternative(loaded_.problem)); + return std::get(loaded_.problem); } //---------------------------------------------------------------------------// /*! - * Optical data: null if Celeritas optical physics is disabled. + * Access Celeritas loaded optical problem data. */ -auto SharedParams::optical_collector() const -> SPOpticalCollector const& +auto SharedParams::optical_problem_loaded() const + -> setup::OpticalProblemLoaded const& { CELER_EXPECT(*this); - return optical_collector_; + CELER_EXPECT( + std::holds_alternative(loaded_.problem)); + return std::get(loaded_.problem); } //---------------------------------------------------------------------------// @@ -279,7 +271,7 @@ auto SharedParams::optical_collector() const -> SPOpticalCollector const& auto SharedParams::hit_manager() const -> SPGeantSd const& { CELER_EXPECT(*this); - return geant_sd_; + return this->problem_loaded().geant_sd; } //---------------------------------------------------------------------------// @@ -289,7 +281,7 @@ auto SharedParams::hit_manager() const -> SPGeantSd const& auto SharedParams::actions() const -> SPActionSequence const& { CELER_EXPECT(*this); - return actions_; + return this->problem_loaded().actions; } //---------------------------------------------------------------------------// @@ -299,7 +291,7 @@ auto SharedParams::actions() const -> SPActionSequence const& auto SharedParams::offload_writer() const -> SPOffloadWriter const& { CELER_EXPECT(*this); - return offload_writer_; + return this->problem_loaded().offload_writer; } //---------------------------------------------------------------------------// diff --git a/src/celeritas/global/CoreParams.cc b/src/celeritas/global/CoreParams.cc index 19a91ee519..96615f3ac3 100644 --- a/src/celeritas/global/CoreParams.cc +++ b/src/celeritas/global/CoreParams.cc @@ -15,21 +15,12 @@ #include "corecel/Assert.hh" #include "corecel/data/AuxParamsRegistry.hh" // IWYU pragma: keep #include "corecel/data/Ref.hh" -#include "corecel/io/BuildOutput.hh" #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" // IWYU pragma: keep #include "corecel/random/params/RngParams.hh" // IWYU pragma: keep #include "corecel/sys/ActionRegistry.hh" // IWYU pragma: keep #include "corecel/sys/ActionRegistryOutput.hh" -#include "corecel/sys/Device.hh" -#include "corecel/sys/DeviceIO.json.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/EnvironmentIO.json.hh" -#include "corecel/sys/KernelRegistry.hh" -#include "corecel/sys/KernelRegistryIO.json.hh" -#include "corecel/sys/MemRegistry.hh" -#include "corecel/sys/MemRegistryIO.json.hh" #include "corecel/sys/MpiCommunicator.hh" #include "corecel/sys/ScopedMem.hh" #include "geocel/GeoParamsOutput.hh" @@ -332,18 +323,9 @@ CoreParams::CoreParams(Input input) : input_(std::move(input)) } // Save system diagnostic information - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "device", celeritas::device())); - input_.output_reg->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "kernels", - celeritas::kernel_registry())); - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "memory", celeritas::mem_registry())); - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "environ", celeritas::environment())); - input_.output_reg->insert(std::make_shared()); + insert_system_diagnostics(*input_.output_reg); + + // Save core sizes input_.output_reg->insert( OutputInterfaceAdapter::from_rvalue_ref( OutputInterface::Category::internal, diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index 7d391d35c2..fca6538de9 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -127,12 +127,21 @@ struct OpticalTrackOffload { }; +//---------------------------------------------------------------------------// +/*! + * Generate optical photons directly from optical track initializers. + */ +struct OpticalDirectGenerator +{ +}; + //---------------------------------------------------------------------------// //! Mechanism for generating optical photons using OpticalGenerator = std::variant; + OpticalTrackOffload, + OpticalDirectGenerator>; //---------------------------------------------------------------------------// /*! diff --git a/src/celeritas/inp/FrameworkInput.hh b/src/celeritas/inp/FrameworkInput.hh index 08cd75e69c..3cb42ee1ae 100644 --- a/src/celeritas/inp/FrameworkInput.hh +++ b/src/celeritas/inp/FrameworkInput.hh @@ -17,6 +17,7 @@ namespace celeritas namespace inp { struct Problem; +struct OpticalProblem; //---------------------------------------------------------------------------// /*! @@ -40,6 +41,9 @@ struct FrameworkInput //! User application/framework-defined adjustments std::function adjust; + + //! User application/framework-defined optical adjustments + std::function adjust_optical; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/inp/Physics.hh b/src/celeritas/inp/Physics.hh index d5efebe191..918efbb316 100644 --- a/src/celeritas/inp/Physics.hh +++ b/src/celeritas/inp/Physics.hh @@ -57,6 +57,8 @@ struct EmPhysics /*! * Optical physics processes, options, and surface definitions. * + * If scintillation or Cherenkov is enabled, optical photons will be generated. + * * \todo Move cherenkov/scintillation to a OpticalGenPhysics class. */ struct OpticalPhysics @@ -110,8 +112,6 @@ struct Physics //! Physics for optical photons OpticalPhysics optical; - //! Optical photon generation mechanism - OpticalGenerator optical_generator; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/inp/Problem.hh b/src/celeritas/inp/Problem.hh index 7243ce398d..387443d4b1 100644 --- a/src/celeritas/inp/Problem.hh +++ b/src/celeritas/inp/Problem.hh @@ -76,6 +76,32 @@ struct Problem Diagnostics diagnostics; }; +//---------------------------------------------------------------------------// +/*! + * Celeritas optical-only problem input definition. + */ +struct OpticalProblem +{ + //! Geometry, material, and region definitions + Model model; + //! Physics models and options + OpticalPhysics physics; + //! Optical photon generation mechanism + OpticalGenerator generator; + //! Hard cutoffs for counters + OpticalTrackingLimits limits; + //! Per-process state sizes for optical tracking loop + OpticalStateCapacity capacity; + //! Number of streams + size_type num_streams{}; + //! Random number generator seed + unsigned int seed{}; + //! Set up step or action timers + Timers timers; + //! Write Celeritas diagnostics to this file ("-" is stdout) + std::string output_file{"-"}; +}; + //---------------------------------------------------------------------------// } // namespace inp } // namespace celeritas diff --git a/src/accel/detail/OffloadWriter.hh b/src/celeritas/io/OffloadWriter.hh similarity index 93% rename from src/accel/detail/OffloadWriter.hh rename to src/celeritas/io/OffloadWriter.hh index 64d2c56a8a..2f60fe543f 100644 --- a/src/accel/detail/OffloadWriter.hh +++ b/src/celeritas/io/OffloadWriter.hh @@ -2,19 +2,17 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/detail/OffloadWriter.hh +//! \file celeritas/io/OffloadWriter.hh //---------------------------------------------------------------------------// #pragma once #include #include -#include "celeritas/io/EventIOInterface.hh" +#include "EventIOInterface.hh" namespace celeritas { -namespace detail -{ //---------------------------------------------------------------------------// /*! * Dump primaries to a shared file, one event per flush. @@ -64,5 +62,4 @@ void OffloadWriter::operator()(argument_type primaries) } //---------------------------------------------------------------------------// -} // namespace detail } // namespace celeritas diff --git a/src/celeritas/optical/CoreParams.cc b/src/celeritas/optical/CoreParams.cc index 3ea1f9bb36..1bf266e669 100644 --- a/src/celeritas/optical/CoreParams.cc +++ b/src/celeritas/optical/CoreParams.cc @@ -6,13 +6,18 @@ //---------------------------------------------------------------------------// #include "CoreParams.hh" +#include "corecel/data/AuxParamsRegistry.hh" #include "corecel/io/Logger.hh" +#include "corecel/io/OutputInterfaceAdapter.hh" +#include "corecel/io/OutputRegistry.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "corecel/sys/ActionRegistryOutput.hh" #include "corecel/sys/ScopedMem.hh" #include "geocel/SurfaceParams.hh" #include "celeritas/geo/CoreGeoParams.hh" #include "celeritas/mat/MaterialParams.hh" +#include "celeritas/optical/OpticalSizes.json.hh" #include "celeritas/phys/GeneratorRegistry.hh" #include "celeritas/track/SimParams.hh" @@ -132,6 +137,30 @@ CoreParams::CoreParams(Input&& input) : input_(std::move(input)) { detectors_ = std::make_shared(); } + if (!input_.aux_reg) + { + input_.aux_reg = std::make_shared(); + } + if (!input_.output_reg) + { + input_.output_reg = std::make_shared(); + insert_system_diagnostics(*input_.output_reg); + } + + // Save optical action diagnostic information + input_.output_reg->insert(std::make_shared( + input_.action_reg, "optical-actions")); + + // Add optical sizes + OpticalSizes sizes; + sizes.streams = this->max_streams(); + sizes.generators = input_.capacity.generators; + sizes.tracks = input_.capacity.tracks; + input_.output_reg->insert( + OutputInterfaceAdapter::from_rvalue_ref( + OutputInterface::Category::internal, + "optical-sizes", + std::move(sizes))); ScopedMem record_mem("optical::CoreParams.construct"); diff --git a/src/celeritas/optical/CoreParams.hh b/src/celeritas/optical/CoreParams.hh index 483b60655b..6c6e4a6090 100644 --- a/src/celeritas/optical/CoreParams.hh +++ b/src/celeritas/optical/CoreParams.hh @@ -13,7 +13,9 @@ #include "corecel/data/ObserverPtr.hh" #include "corecel/data/ParamsDataInterface.hh" #include "corecel/random/params/RngParamsFwd.hh" +#include "corecel/sys/Device.hh" #include "celeritas/geo/GeoFwd.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/user/SDParams.hh" #include "CoreTrackData.hh" @@ -22,8 +24,10 @@ namespace celeritas { //---------------------------------------------------------------------------// class ActionRegistry; +class AuxParamsRegistry; class CherenkovParams; class GeneratorRegistry; +class OutputRegistry; class ScintillationParams; class SurfaceParams; @@ -51,6 +55,8 @@ class CoreParams final : public ParamsDataInterface using SPConstSurface = std::shared_ptr; using SPConstSurfacePhysics = std::shared_ptr; using SPActionRegistry = std::shared_ptr; + using SPOutputRegistry = std::shared_ptr; + using SPAuxRegistry = std::shared_ptr; using SPGeneratorRegistry = std::shared_ptr; using SPConstDetectors = std::shared_ptr; using SPConstCherenkov = std::shared_ptr; @@ -76,16 +82,23 @@ class CoreParams final : public ParamsDataInterface SPConstScintillation scintillation; //!< Optional SPActionRegistry action_reg; + SPOutputRegistry output_reg; SPGeneratorRegistry gen_reg; + SPAuxRegistry aux_reg; //!< Optional, empty default //! Maximum number of simultaneous threads/tasks per process StreamId::size_type max_streams{1}; + //! Per-process state and buffer capacities + inp::OpticalStateCapacity capacity; + //! True if all params are assigned and valid explicit operator bool() const { return geometry && material && rng && sim && surface - && surface_physics && action_reg && gen_reg && max_streams; + && surface_physics && action_reg && gen_reg && max_streams + && capacity.generators > 0 && capacity.tracks > 0 + && capacity.primaries > 0; } }; @@ -115,6 +128,8 @@ class CoreParams final : public ParamsDataInterface return input_.surface_physics; } SPActionRegistry const& action_reg() const { return input_.action_reg; } + SPOutputRegistry const& output_reg() const { return input_.output_reg; } + SPAuxRegistry const& aux_reg() const { return input_.aux_reg; } SPGeneratorRegistry const& gen_reg() const { return input_.gen_reg; } SPConstDetectors const& detectors() const { return detectors_; } SPConstCherenkov const& cherenkov() const { return input_.cherenkov; } diff --git a/src/celeritas/optical/OpticalCollector.cc b/src/celeritas/optical/OpticalCollector.cc index fbf6cb43b1..73ba44ca07 100644 --- a/src/celeritas/optical/OpticalCollector.cc +++ b/src/celeritas/optical/OpticalCollector.cc @@ -59,8 +59,8 @@ OpticalCollector::OpticalCollector(CoreParams const& core, Input&& inp) = OffloadGatherAction::make_and_insert(core); // Create optical action to generate Cherenkov or scintillation photons - generate_ = optical::GeneratorAction::make_and_insert( - core, *inp.optical_params, inp.buffer_capacity); + generate_ = optical::GeneratorAction::make_and_insert(*inp.optical_params, + inp.buffer_capacity); if (inp.optical_params->cherenkov()) { diff --git a/src/celeritas/optical/PhysicsParams.cc b/src/celeritas/optical/PhysicsParams.cc index c879cc5bd9..aa890833ab 100644 --- a/src/celeritas/optical/PhysicsParams.cc +++ b/src/celeritas/optical/PhysicsParams.cc @@ -7,6 +7,8 @@ #include "PhysicsParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "celeritas/io/ImportData.hh" +#include "celeritas/optical/ModelImporter.hh" #include "MaterialParams.hh" #include "MfpBuilder.hh" @@ -17,6 +19,30 @@ namespace celeritas { namespace optical { +//---------------------------------------------------------------------------// +/*! + * Construct with imported data. + */ +std::shared_ptr +PhysicsParams::from_import(ImportData const& data, + SPConstCoreMaterials core_materials, + SPConstMaterials materials, + SPActionRegistry action_reg) +{ + Input input; + input.materials = materials; + input.action_registry = action_reg.get(); + ModelImporter importer{data, materials, core_materials}; + for (auto const& model : data.optical_models) + { + if (auto builder = importer(model.model_class)) + { + input.model_builders.push_back(*builder); + } + } + return std::make_shared(std::move(input)); +} + //---------------------------------------------------------------------------// /*! * Construct from imported and shared data. diff --git a/src/celeritas/optical/PhysicsParams.hh b/src/celeritas/optical/PhysicsParams.hh index bcc1292098..1d857c8913 100644 --- a/src/celeritas/optical/PhysicsParams.hh +++ b/src/celeritas/optical/PhysicsParams.hh @@ -16,6 +16,8 @@ namespace celeritas { class ActionRegistry; +struct ImportData; +class MaterialParams; namespace optical { @@ -27,7 +29,10 @@ class PhysicsParams final : public ParamsDataInterface public: //!@{ //! \name Type aliases + using SPActionRegistry = std::shared_ptr; using SPConstModel = std::shared_ptr; + using SPConstCoreMaterials + = std::shared_ptr; using SPConstMaterials = std::shared_ptr; using VecModels = std::vector; @@ -44,6 +49,12 @@ class PhysicsParams final : public ParamsDataInterface }; public: + // Construct with imported data, material params, and action registry + static std::shared_ptr from_import(ImportData const&, + SPConstCoreMaterials, + SPConstMaterials, + SPActionRegistry); + // Construct from models explicit PhysicsParams(Input input); diff --git a/src/celeritas/optical/gen/DirectGeneratorAction.cc b/src/celeritas/optical/gen/DirectGeneratorAction.cc index b6a174e0df..1137d0e70f 100644 --- a/src/celeritas/optical/gen/DirectGeneratorAction.cc +++ b/src/celeritas/optical/gen/DirectGeneratorAction.cc @@ -48,11 +48,11 @@ auto make_state(StreamId stream, size_type size) /*! * Construct and add to core params. */ -std::shared_ptr DirectGeneratorAction::make_and_insert( - ::celeritas::CoreParams const& core_params, CoreParams const& params) +std::shared_ptr +DirectGeneratorAction::make_and_insert(CoreParams const& params) { ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id()); diff --git a/src/celeritas/optical/gen/DirectGeneratorAction.hh b/src/celeritas/optical/gen/DirectGeneratorAction.hh index b7c7ba1157..683ce84e1e 100644 --- a/src/celeritas/optical/gen/DirectGeneratorAction.hh +++ b/src/celeritas/optical/gen/DirectGeneratorAction.hh @@ -43,7 +43,7 @@ class DirectGeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, CoreParams const&); + make_and_insert(CoreParams const&); // Construct with action ID and data IDs DirectGeneratorAction(ActionId, AuxId, GeneratorId); diff --git a/src/celeritas/optical/gen/GeneratorAction.cc b/src/celeritas/optical/gen/GeneratorAction.cc index 6e11eb67ee..1454fdc4d7 100644 --- a/src/celeritas/optical/gen/GeneratorAction.cc +++ b/src/celeritas/optical/gen/GeneratorAction.cc @@ -59,12 +59,10 @@ auto make_state(StreamId stream, size_type size) * Construct and add to core params. */ std::shared_ptr -GeneratorAction::make_and_insert(::celeritas::CoreParams const& core_params, - CoreParams const& params, - size_type capacity) +GeneratorAction::make_and_insert(CoreParams const& params, size_type capacity) { ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id(), capacity); diff --git a/src/celeritas/optical/gen/GeneratorAction.hh b/src/celeritas/optical/gen/GeneratorAction.hh index 4c1ef6ffbe..5ab30b8c35 100644 --- a/src/celeritas/optical/gen/GeneratorAction.hh +++ b/src/celeritas/optical/gen/GeneratorAction.hh @@ -48,9 +48,7 @@ class GeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, - CoreParams const&, - size_type capacity); + make_and_insert(CoreParams const&, size_type capacity); // Construct with action ID, data IDs, and optical properties GeneratorAction(ActionId, AuxId, GeneratorId, size_type capacity); diff --git a/src/celeritas/optical/gen/PrimaryGeneratorAction.cc b/src/celeritas/optical/gen/PrimaryGeneratorAction.cc index 8fceb1eb81..dafea685eb 100644 --- a/src/celeritas/optical/gen/PrimaryGeneratorAction.cc +++ b/src/celeritas/optical/gen/PrimaryGeneratorAction.cc @@ -33,14 +33,12 @@ namespace optical /*! * Construct and add to core params. */ -std::shared_ptr PrimaryGeneratorAction::make_and_insert( - ::celeritas::CoreParams const& core_params, - CoreParams const& params, - Input&& input) +std::shared_ptr +PrimaryGeneratorAction::make_and_insert(CoreParams const& params, Input&& input) { CELER_EXPECT(input); ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id(), std::move(input)); diff --git a/src/celeritas/optical/gen/PrimaryGeneratorAction.hh b/src/celeritas/optical/gen/PrimaryGeneratorAction.hh index aa9cc2a76a..bee7dde715 100644 --- a/src/celeritas/optical/gen/PrimaryGeneratorAction.hh +++ b/src/celeritas/optical/gen/PrimaryGeneratorAction.hh @@ -47,7 +47,7 @@ class PrimaryGeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, CoreParams const&, Input&&); + make_and_insert(CoreParams const&, Input&&); // Construct with IDs and distributions PrimaryGeneratorAction(ActionId, AuxId, GeneratorId, Input); diff --git a/src/celeritas/setup/FrameworkInput.cc b/src/celeritas/setup/FrameworkInput.cc index 8085a75eb7..858e3a6082 100644 --- a/src/celeritas/setup/FrameworkInput.cc +++ b/src/celeritas/setup/FrameworkInput.cc @@ -33,13 +33,22 @@ FrameworkLoaded framework_input(inp::FrameworkInput& fi) CELER_LOG(info) << "Activating Celeritas version " << version_string << " on " << (Device::num_devices() > 0 ? "GPU" : "CPU"); + CELER_VALIDATE(!(fi.adjust && fi.adjust_optical), + << "cannot setup both a problem and an optical-only " + "problem"); + + // TODO: How to determine which problem to setup without requiring adjust? + CELER_EXPECT(fi.adjust || fi.adjust_optical); + + FrameworkLoaded result; + // Set up system setup::system(fi.system); // Load Geant4 geometry wrapper, which saves it as global CELER_ASSERT(celeritas::global_geant_geo().expired()); - auto geo = GeantGeoParams::from_tracking_manager(); - CELER_ASSERT(geo); + result.geo = GeantGeoParams::from_tracking_manager(); + CELER_ASSERT(result.geo); // Load Geant4 data from user setup ImportData imported; @@ -48,45 +57,63 @@ FrameworkLoaded framework_input(inp::FrameworkInput& fi) // Load physics from external Geant4 data files setup::physics_from(inp::PhysicsFromGeantFiles{}, imported); - // Set up problem - inp::Problem problem; + if (fi.adjust_optical) + { + // Set up optical-only problem + inp::OpticalProblem problem; - // Copy optical physics from import data - // (TODO: will be replaced) - problem.physics.optical = imported.optical_physics; + problem.physics = imported.optical_physics; + problem.model = result.geo->make_model_input(); - // Load geometry, surfaces, regions from Geant4 world pointer - problem.model = geo->make_model_input(); + // Adjust problem + fi.adjust_optical(problem); - // Load physics - for (std::string const& process_name : fi.physics_import.ignore_processes) + // Save filename for diagnostic output + result.output_file = problem.output_file; + + // Set up core params + result.problem = setup::problem(problem, imported); + } + else { - ImportProcessClass ipc; - try - { - ipc = geant_name_to_import_process_class(process_name); - } - catch (RuntimeError const&) + // Set up problem + inp::Problem problem; + + // Copy optical physics from import data + // (TODO: will be replaced) + problem.physics.optical = imported.optical_physics; + + // Load geometry, surfaces, regions from Geant4 world pointer + problem.model = result.geo->make_model_input(); + + // Load physics + for (std::string const& process_name : + fi.physics_import.ignore_processes) { - CELER_LOG(error) << "User-ignored process '" << process_name - << "' is unknown to Celeritas"; - continue; + ImportProcessClass ipc; + try + { + ipc = geant_name_to_import_process_class(process_name); + } + catch (RuntimeError const&) + { + CELER_LOG(error) << "User-ignored process '" << process_name + << "' is unknown to Celeritas"; + continue; + } + problem.physics.em.user_processes.emplace( + ipc, WarnAndIgnoreProcess{ipc}); } - problem.physics.em.user_processes.emplace(ipc, - WarnAndIgnoreProcess{ipc}); - } - // Adjust problem - if (fi.adjust) - { + // Adjust problem fi.adjust(problem); - } - FrameworkLoaded result; - result.geo = std::move(geo); + // Save filename for diagnostic output + result.output_file = problem.diagnostics.output_file; - // Set up core params - result.problem = setup::problem(problem, imported); + // Set up core params + result.problem = setup::problem(problem, imported); + } return result; } diff --git a/src/celeritas/setup/FrameworkInput.hh b/src/celeritas/setup/FrameworkInput.hh index 9486b5e57e..65c9b58087 100644 --- a/src/celeritas/setup/FrameworkInput.hh +++ b/src/celeritas/setup/FrameworkInput.hh @@ -7,6 +7,7 @@ #pragma once #include +#include #include "Problem.hh" @@ -26,9 +27,11 @@ namespace setup struct FrameworkLoaded { //! Loaded problem - ProblemLoaded problem; + std::variant problem; //! Geant4 geometry wrapper std::shared_ptr geo; + //! Write diagnostic output + std::string output_file; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 887ce1de80..bd0113843d 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -18,6 +18,7 @@ #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" +#include "corecel/io/StringUtils.hh" #include "corecel/math/Algorithms.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" @@ -57,20 +58,24 @@ #include "celeritas/inp/Problem.hh" #include "celeritas/inp/Scoring.hh" #include "celeritas/inp/Tracking.hh" +#include "celeritas/io/EventWriter.hh" #include "celeritas/io/ImportData.hh" #include "celeritas/io/ImportProcess.hh" +#include "celeritas/io/JsonEventWriter.hh" +#include "celeritas/io/OffloadWriter.hh" #include "celeritas/io/RootCoreParamsOutput.hh" +#include "celeritas/io/RootEventWriter.hh" #include "celeritas/mat/MaterialParams.hh" #include "celeritas/optical/CoreParams.hh" #include "celeritas/optical/MaterialParams.hh" -#include "celeritas/optical/ModelImporter.hh" #include "celeritas/optical/OpticalCollector.hh" -#include "celeritas/optical/OpticalSizes.json.hh" #include "celeritas/optical/PhysicsParams.hh" #include "celeritas/optical/SimParams.hh" #include "celeritas/optical/Transporter.hh" #include "celeritas/optical/gen/CherenkovParams.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" #include "celeritas/optical/gen/GeneratorAction.hh" +#include "celeritas/optical/gen/PrimaryGeneratorAction.hh" #include "celeritas/optical/gen/ScintillationParams.hh" #include "celeritas/optical/surface/SurfacePhysicsParams.hh" #include "celeritas/phys/CutoffParams.hh" @@ -303,6 +308,8 @@ auto build_optical_params(inp::Problem const& p, "materials are present"); optical::CoreParams::Input params; + CELER_ASSERT(p.control.optical_capacity); + params.capacity = *p.control.optical_capacity; params.geometry = core.geometry(); params.material = optical::MaterialParams::from_import( imported, *core.geomaterial(), *core.material()); @@ -313,24 +320,13 @@ auto build_optical_params(inp::Problem const& p, params.surface = core.surface(); params.action_reg = std::make_shared(); params.gen_reg = std::make_shared(); + params.output_reg = core.output_reg(); + params.aux_reg = core.aux_reg(); params.max_streams = core.max_streams(); - { - // Construct optical physics models - optical::PhysicsParams::Input pp_inp; - pp_inp.materials = params.material; - pp_inp.action_registry = params.action_reg.get(); - optical::ModelImporter importer{ - imported, params.material, core.material()}; - for (auto const& model : imported.optical_models) - { - if (auto builder = importer(model.model_class)) - { - pp_inp.model_builders.push_back(*builder); - } - } - params.physics - = std::make_shared(std::move(pp_inp)); - } + + // Construct optical physics models + params.physics = optical::PhysicsParams::from_import( + imported, core.material(), params.material, params.action_reg); // Construct optical surface physics models CELER_ASSERT(p.physics.optical); @@ -364,9 +360,6 @@ auto build_optical_offload( CoreParams const& params, std::shared_ptr const& optical_params) { - CELER_EXPECT(std::holds_alternative( - p.physics.optical_generator)); - CELER_VALIDATE(optical_params->cherenkov() || optical_params->scintillation(), << "failed to construct optical offload procesess"); @@ -526,8 +519,6 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) //// DIAGNOSTICS //// - result.output_file = p.diagnostics.output_file; - // TODO: timers, counters, perfetto_file if (p.diagnostics.action) @@ -604,7 +595,28 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) // TODO: this is only implemented in accel::SharedParams, not in // celeritas core: should hook this into the to-be-updated // "primary" mechanism - result.offload_file = ef.offload; + if (!ef.offload.empty()) + { + std::unique_ptr writer; + if (ends_with(ef.offload, ".jsonl")) + { + writer.reset( + new JsonEventWriter(ef.offload, core_params->particle())); + } + else if (ends_with(ef.offload, ".root")) + { + writer.reset(new RootEventWriter( + std::make_shared(ef.offload.c_str()), + core_params->particle())); + } + else + { + writer.reset( + new EventWriter(ef.offload, core_params->particle())); + } + result.offload_writer + = std::make_shared(std::move(writer)); + } } //// STEP COLLECTORS //// @@ -654,11 +666,6 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) *core_params, std::move(step_interfaces)); } - // Whether to accumulate timing results for actions - bool action_times - = (!celeritas::device() - || (p.control.device_debug && p.control.device_debug->sync_stream)); - if (p.control.optical_capacity) { if (core_params->surface()->empty()) @@ -667,79 +674,12 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) "any geometry surface definitions: default " "physics will be used for all surfaces"; } - + // Construct the optical params from the core params auto optical_params = build_optical_params(p, *core_params, imported); - // Save optical diagnostic information - core_params->output_reg()->insert( - std::make_shared( - optical_params->action_reg(), "optical-actions")); - - auto const& capacity = *p.control.optical_capacity; - - // Add optical sizes - OpticalSizes sizes; - sizes.streams = core_params->max_streams(); - sizes.generators = capacity.generators; - sizes.tracks = capacity.tracks; - - core_params->output_reg()->insert( - OutputInterfaceAdapter::from_rvalue_ref( - OutputInterface::Category::internal, - "optical-sizes", - std::move(sizes))); - - std::visit( - Overload{ - [&](inp::OpticalEmGenerator) { - // Generate Cherenkov or scintillation optical - // photons from Celeritas tracks - result.optical_collector = build_optical_offload( - p, *core_params, optical_params); - }, - [&](inp::OpticalOffloadGenerator) { - // Generate Cherenkov or scintillation photons - optical::GeneratorAction::make_and_insert( - *core_params, *optical_params, capacity.generators); - - // Build the optical transporter \em after all optical - // actions have been added to the registry - optical::Transporter::Input inp; - inp.params = optical_params; - if (action_times) - { - // Create aux data to accumulate optical action times - inp.action_times = ActionTimes::make_and_insert( - optical_params->action_reg(), - core_params->aux_reg(), - "optical-action-times"); - } - result.optical_transporter - = std::make_shared( - std::move(inp)); - }, - [&](inp::OpticalTrackOffload) { - // Build optical track transporter - optical::Transporter::Input inp; - inp.params = optical_params; - if (action_times) - { - inp.action_times = ActionTimes::make_and_insert( - optical_params->action_reg(), - core_params->aux_reg(), - "optical-action-times"); - } - - result.optical_transporter - = std::make_shared( - std::move(inp)); - }, - [](inp::OpticalPrimaryGenerator) { - //! \todo Enable optical primary generator - CELER_NOT_IMPLEMENTED("optical primary generator"); - }, - }, - p.physics.optical_generator); + // Construct the optical offload and generation actions + result.optical_collector + = build_optical_offload(p, *core_params, optical_params); } else { @@ -754,7 +694,8 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) result.actions = [&] { ActionSequence::Options opt; auto const& action_reg = core_params->action_reg(); - if (action_times) + if (!celeritas::device() + || (p.control.device_debug && p.control.device_debug->sync_stream)) { // Create aux data to accumulate action times opt.action_times = ActionTimes::make_and_insert( @@ -771,6 +712,140 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) return result; } +//---------------------------------------------------------------------------// +/*! + * Create optical "core params" from a problem definition and import data. + * + * This constructs the optical params \em without additionally constructing the + * core params. + */ +OpticalProblemLoaded +problem(inp::OpticalProblem const& p, ImportData const& imported) +{ + CELER_LOG(status) << "Initializing problem"; + + ScopedMem record_mem("setup::problem"); + ScopedProfiling profile_this{"setup::problem"}; + + CELER_VALIDATE(!imported.optical_materials.empty(), + << "an optical tracking loop was requested but no optical " + "materials are present"); + + optical::CoreParams::Input pi; + + // Per-process state sizes + pi.capacity = p.capacity; + + // Create action manager and generator registry + pi.action_reg = std::make_shared(); + pi.gen_reg = std::make_shared(); + + // Load geometry and model + if (auto* filename = std::get_if(&p.model.geometry)) + { + CELER_VALIDATE(!filename->empty(), + << "empty filename in problem.model.geometry"); + } + auto loaded_model = setup::model(p.model); + pi.geometry = std::move(loaded_model.geometry); + pi.surface = std::move(loaded_model.surface); + CELER_VALIDATE(pi.surface && !pi.surface->disabled(), + << "surfaces are required for optical physics"); + if (pi.surface->empty()) + { + CELER_LOG(warning) << "Problem contains optical physics without any " + "geometry surface definitions: default physics " + "will be used for all surfaces"; + } + + // Create materials and geometry/material coupling + auto material = MaterialParams::from_import(imported); + auto geomaterial = GeoMaterialParams::from_import( + imported, pi.geometry, loaded_model.volume, material); + + // Create optical materials + pi.material = optical::MaterialParams::from_import( + imported, *geomaterial, *material); + + // Construct RNG params + pi.rng = std::make_shared(p.seed); + + // Construct simulation params + pi.sim = std::make_shared(p.limits); + + // Set up streams + CELER_VALIDATE(p.num_streams > 0, + << "p.num_streams must be manually set before setup"); + pi.max_streams = p.num_streams; + if (auto& device = celeritas::device()) + { + device.create_streams(pi.max_streams); + } + + // Construct optical bulk physics models + pi.physics = optical::PhysicsParams::from_import( + imported, material, pi.material, pi.action_reg); + + // Construct optical surface physics models + pi.surface_physics = std::make_shared( + pi.action_reg.get(), p.physics.surfaces); + + // Add photon generating processes + if (p.physics.cherenkov) + { + pi.cherenkov = std::make_shared(*pi.material); + } + if (p.physics.scintillation) + { + auto particle = ParticleParams::from_import(imported); + pi.scintillation = ScintillationParams::from_import(imported, particle); + CELER_ASSERT(pi.scintillation); + } + + //! \todo Get sensitive detectors + + CELER_ASSERT(pi); + auto params = std::make_shared(std::move(pi)); + + // Construct the optical generator + std::visit(Overload{ + [&](inp::OpticalEmGenerator) { + CELER_VALIDATE(false, + << "OpticalEmGenerator cannot be used " + "with only optical physics enabled"); + }, + [&](inp::OpticalOffloadGenerator) { + optical::GeneratorAction::make_and_insert( + *params, p.capacity.generators); + }, + [&](inp::OpticalPrimaryGenerator opg) { + optical::PrimaryGeneratorAction::make_and_insert( + *params, std::move(opg)); + }, + [&](inp::OpticalDirectGenerator) { + optical::DirectGeneratorAction::make_and_insert(*params); + }, + }, + p.generator); + + OpticalProblemLoaded result; + + // Build the optical transporter \em after all optical actions have been + // added to the registry + optical::Transporter::Input ti; + if (p.timers.action) + { + // Create aux data to accumulate optical action times + ti.action_times = ActionTimes::make_and_insert( + params->action_reg(), params->aux_reg(), "optical-action-times"); + } + ti.params = std::move(params); + result.transporter = std::make_shared(std::move(ti)); + + CELER_ENSURE(result.transporter); + return result; +} + //---------------------------------------------------------------------------// } // namespace setup } // namespace celeritas diff --git a/src/celeritas/setup/Problem.hh b/src/celeritas/setup/Problem.hh index 19066faff9..967b67e651 100644 --- a/src/celeritas/setup/Problem.hh +++ b/src/celeritas/setup/Problem.hh @@ -14,6 +14,7 @@ namespace celeritas //---------------------------------------------------------------------------// namespace inp { +struct OpticalProblem; struct Problem; } // namespace inp @@ -25,6 +26,7 @@ class Transporter; class ActionSequence; class CoreParams; class GeantSd; +class OffloadWriter; class OpticalCollector; class RootFileManager; class StepCollector; @@ -44,11 +46,6 @@ struct ProblemLoaded //! Step collector std::shared_ptr step_collector; - //! Optical-only offload management - std::shared_ptr optical_transporter; // optical - // primary w/ - // haydens - // class //! Combined EM and optical offload management std::shared_ptr optical_collector; //! Geant4 SD interface @@ -57,24 +54,30 @@ struct ProblemLoaded std::shared_ptr root_manager; //! Action sequence std::shared_ptr actions; - //!@} //!@{ //! \name Temporary: to be used downstream - //! \todo These should be refactored: should be built in Problem //! Write offloaded primaries - std::string offload_file; - //! Write diagnostic output - std::string output_file; - + std::shared_ptr offload_writer; //!@} }; +//---------------------------------------------------------------------------// +//! Result from loaded optical standalone input to be used in front-end apps +struct OpticalProblemLoaded +{ + //! Optical-only problem setup + std::shared_ptr transporter; +}; + //---------------------------------------------------------------------------// // Set up the problem ProblemLoaded problem(inp::Problem const& p, ImportData const& imported); +// Set up the optical-only problem +OpticalProblemLoaded +problem(inp::OpticalProblem const& p, ImportData const& imported); //---------------------------------------------------------------------------// } // namespace setup diff --git a/src/corecel/io/OutputRegistry.cc b/src/corecel/io/OutputRegistry.cc index 5ad7b9da9e..d64e94652e 100644 --- a/src/corecel/io/OutputRegistry.cc +++ b/src/corecel/io/OutputRegistry.cc @@ -16,11 +16,21 @@ #include "corecel/Assert.hh" #include "corecel/cont/Range.hh" +#include "corecel/io/BuildOutput.hh" #include "corecel/io/EnumStringMapper.hh" +#include "corecel/sys/Device.hh" +#include "corecel/sys/DeviceIO.json.hh" +#include "corecel/sys/Environment.hh" +#include "corecel/sys/EnvironmentIO.json.hh" +#include "corecel/sys/KernelRegistry.hh" +#include "corecel/sys/KernelRegistryIO.json.hh" +#include "corecel/sys/MemRegistry.hh" +#include "corecel/sys/MemRegistryIO.json.hh" #include "JsonPimpl.hh" #include "Logger.hh" // IWYU pragma: keep #include "OutputInterface.hh" +#include "OutputInterfaceAdapter.hh" namespace celeritas { @@ -106,5 +116,26 @@ bool OutputRegistry::empty() const [](auto const& m) { return m.empty(); }); } +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// +/*! + * Add interfaces for writing system diagnostics. + */ +void insert_system_diagnostics(OutputRegistry& output_reg) +{ + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "device", celeritas::device())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, + "kernels", + celeritas::kernel_registry())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "memory", celeritas::mem_registry())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "environ", celeritas::environment())); + output_reg.insert(std::make_shared()); +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/OutputRegistry.hh b/src/corecel/io/OutputRegistry.hh index ec4fe3efce..f1db913899 100644 --- a/src/corecel/io/OutputRegistry.hh +++ b/src/corecel/io/OutputRegistry.hh @@ -56,5 +56,12 @@ class OutputRegistry EnumArray> interfaces_; }; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +// Add an interfaces for writing system diagnostics +void insert_system_diagnostics(OutputRegistry&); + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/test/accel/TrackingManagerIntegration.test.cc b/test/accel/TrackingManagerIntegration.test.cc index 7b90f12238..4723e3d6bc 100644 --- a/test/accel/TrackingManagerIntegration.test.cc +++ b/test/accel/TrackingManagerIntegration.test.cc @@ -349,7 +349,8 @@ void LarSphereOptical::EndOfRunAction(G4Run const* run) EXPECT_EQ(is_running_events(), static_cast(local_transporter)); EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; - auto const& optical_collector = shared_params.optical_collector(); + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; if (local_transporter && optical_collector) { @@ -459,7 +460,8 @@ void OpNoviceOptical::EndOfRunAction(G4Run const* run) EXPECT_EQ(is_running_events(), static_cast(local_transporter)); EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; - auto const& optical_collector = shared_params.optical_collector(); + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; if (local_transporter && optical_collector) { diff --git a/test/celeritas/GlobalTestBase.cc b/test/celeritas/GlobalTestBase.cc index 6bb1609305..0fe9c84082 100644 --- a/test/celeritas/GlobalTestBase.cc +++ b/test/celeritas/GlobalTestBase.cc @@ -20,6 +20,7 @@ #include "corecel/io/OutputRegistry.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "corecel/sys/Device.hh" #include "geocel/GeantGeoParams.hh" #include "geocel/SurfaceParams.hh" #include "geocel/VolumeParams.hh" @@ -27,6 +28,7 @@ #include "celeritas/ext/ScopedRootErrorHandler.hh" #include "celeritas/geo/CoreGeoParams.hh" #include "celeritas/global/CoreParams.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/GeneratorRegistry.hh" #include "celeritas/track/ExtendFromPrimariesAction.hh" #include "celeritas/track/StatusChecker.hh" @@ -185,12 +187,16 @@ optical::CoreParams::Input GlobalTestBase::optical_params_input() inp.rng = this->rng(); inp.surface = this->core()->surface(); inp.action_reg = this->optical_action_reg(); + inp.output_reg = this->core()->output_reg(); inp.gen_reg = std::make_shared(); + inp.aux_reg = this->core()->aux_reg(); inp.physics = this->optical_physics(); inp.sim = this->optical_sim(); inp.surface_physics = this->optical_surface_physics(); inp.cherenkov = this->cherenkov(); inp.scintillation = this->scintillation(); + inp.capacity = inp::OpticalStateCapacity::from_default( + celeritas::Device::num_devices()); CELER_ENSURE(inp); return inp; diff --git a/test/celeritas/optical/Generator.test.cc b/test/celeritas/optical/Generator.test.cc index 05578a4876..ae74aad2fb 100644 --- a/test/celeritas/optical/Generator.test.cc +++ b/test/celeritas/optical/Generator.test.cc @@ -143,7 +143,7 @@ TEST_F(LArSphereGeneratorTest, primary_generator) inp.angle = inp::IsotropicDistribution{}; inp.shape = inp::PointDistribution{{0, 0, 0}}; auto generate = optical::PrimaryGeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), std::move(inp)); + *this->optical_params(), std::move(inp)); this->build_transporter(); this->build_state(4096); @@ -180,7 +180,7 @@ TEST_F(LArSphereGeneratorTest, TEST_IF_CELER_DEVICE(device_primary_generator)) inp.angle = inp::MonodirectionalDistribution{{1, 0, 0}}; inp.shape = inp::UniformBoxDistribution{{-10, -10, -10}, {10, 10, 10}}; auto generate = optical::PrimaryGeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), std::move(inp)); + *this->optical_params(), std::move(inp)); this->build_transporter(); this->build_state(16384); @@ -220,7 +220,7 @@ TEST_F(LArSphereGeneratorTest, direct_generator) 0, ImplVolumeId{0}}); auto generate = optical::DirectGeneratorAction::make_and_insert( - *this->core(), *this->optical_params()); + *this->optical_params()); this->build_transporter(); this->build_state(32); @@ -253,7 +253,7 @@ TEST_F(LArSphereGeneratorTest, generator) // Create optical action to generate Cherenkov and scintillation photons size_type capacity = 512; auto generate = optical::GeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), capacity); + *this->optical_params(), capacity); this->build_transporter(); this->build_state(4096); @@ -312,7 +312,7 @@ TEST_F(LArSphereGeneratorTest, TEST_IF_CELER_DEVICE(device_generator)) // Create optical action to generate Cherenkov and scintillation photons size_type capacity = 4096; auto generate = optical::GeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), capacity); + *this->optical_params(), capacity); this->build_transporter(); this->build_state(16384); diff --git a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsIntegration.test.cc index 86d2c43240..b6825b611e 100644 --- a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc +++ b/test/celeritas/optical/SurfacePhysicsIntegration.test.cc @@ -256,8 +256,8 @@ class SurfacePhysicsIntegrationTest : public GeantTestBase //! Run over a set of angles and collect the results SurfaceTestResults run(std::vector const& angles) { - auto generate = DirectGeneratorAction::make_and_insert( - *this->core(), *this->optical_params()); + auto generate + = DirectGeneratorAction::make_and_insert(*this->optical_params()); this->make_collector(); this->build_transporter(); From bb22b22f4e743feb3e549cdd1f641b0ce1e4c806 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 13 Jan 2026 08:00:24 -0500 Subject: [PATCH 33/60] Fix edge cases with optical setup and runtime (#2191) * Add high-level optical surface integration test * Allow optical materials to be unused by geometry volumes * Allow zero cross section for vacuum --- src/celeritas/optical/MaterialParams.cc | 43 ++- src/celeritas/optical/PhysicsStepUtils.hh | 8 +- test/accel/CMakeLists.txt | 14 +- test/accel/IntegrationTestBase.cc | 20 ++ test/accel/IntegrationTestBase.hh | 8 + test/accel/TrackingManagerIntegration.test.cc | 257 +++++++++++++----- 6 files changed, 245 insertions(+), 105 deletions(-) diff --git a/src/celeritas/optical/MaterialParams.cc b/src/celeritas/optical/MaterialParams.cc index 0b2ad11622..87f24a6908 100644 --- a/src/celeritas/optical/MaterialParams.cc +++ b/src/celeritas/optical/MaterialParams.cc @@ -52,43 +52,32 @@ MaterialParams::from_import(ImportData const& data, inp.properties.push_back(opt_mat.properties); } - // Construct volume-to-optical mapping - inp.volume_to_mat.reserve(geo_mat.num_volumes()); - bool has_opt_mat{false}; - for (auto impl_id : range(ImplVolumeId{geo_mat.num_volumes()})) + // Construct impl-volume-to-optical mapping + inp.volume_to_mat.assign(geo_mat.num_volumes(), OptMatId{}); + inp.optical_to_core.assign(inp.properties.size(), PhysMatId{}); + + std::unordered_set all_optmat; + for (auto iv_id : range(ImplVolumeId{geo_mat.num_volumes()})) { - OptMatId optmat; - if (PhysMatId matid = geo_mat.material_id(impl_id)) + if (PhysMatId matid = geo_mat.material_id(iv_id)) { auto mat_view = mat.get(matid); - optmat = mat_view.optical_material_id(); + OptMatId optmat = mat_view.optical_material_id(); if (optmat) { - has_opt_mat = true; + CELER_ASSERT(optmat < inp.optical_to_core.size()); + all_optmat.insert(optmat); + inp.optical_to_core[optmat.get()] = matid; + inp.volume_to_mat[iv_id.get()] = optmat; } } - inp.volume_to_mat.push_back(optmat); } - - CELER_VALIDATE(has_opt_mat, + CELER_VALIDATE(!all_optmat.empty(), << "no volumes have associated optical materials"); - CELER_ENSURE(inp.volume_to_mat.size() == geo_mat.num_volumes()); - - // Construct optical to core material mapping - inp.optical_to_core - = std::vector(inp.properties.size(), PhysMatId{}); - for (auto core_id : range(PhysMatId{mat.num_materials()})) - { - if (auto opt_mat_id = mat.get(core_id).optical_material_id()) - { - CELER_EXPECT(opt_mat_id < inp.optical_to_core.size()); - CELER_EXPECT(!inp.optical_to_core[opt_mat_id.get()]); - inp.optical_to_core[opt_mat_id.get()] = core_id; - } - } - CELER_ENSURE(std::all_of( - inp.optical_to_core.begin(), inp.optical_to_core.end(), Identity{})); + CELER_LOG(info) << "Constructed " << inp.properties.size() + << " optical materials with " << all_optmat.size() + << " present in the geometry"; return std::make_shared(std::move(inp)); } diff --git a/src/celeritas/optical/PhysicsStepUtils.hh b/src/celeritas/optical/PhysicsStepUtils.hh index 47807c6c84..732cb5fac3 100644 --- a/src/celeritas/optical/PhysicsStepUtils.hh +++ b/src/celeritas/optical/PhysicsStepUtils.hh @@ -33,13 +33,17 @@ inline CELER_FUNCTION StepLimit calc_physics_step_limit( total_xs += physics.calc_xs(model, particle.energy()); } physics.macro_xs(total_xs); - - CELER_ASSERT(physics.macro_xs() > 0); + CELER_ASSERT(physics.macro_xs() >= 0); StepLimit limit; limit.action = physics.discrete_action(); limit.step = physics.interaction_mfp() / total_xs; + if (CELER_UNLIKELY(total_xs == 0)) + { + limit.action = {}; + } + return limit; } diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index b98be49de4..d704f1e1ad 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -102,13 +102,16 @@ celeritas_add_integration_tests( RMTYPE serial mt ) +#-----------------------------------------------------------------------------# +# Disable MT Geant4 geometry for optical physics +#-----------------------------------------------------------------------------# + set(_optical_rm_type "serial") if(NOT (CELERITAS_CORE_GEO STREQUAL "Geant4")) list(APPEND _optical_rm_type "mt") endif() -# Test with optical physics, except that currently trying to use Geant4 -# navigation for the optical tracks wreaks havoc +# Test optical celeritas_add_integration_tests( TrackingManagerIntegration LarSphereOptical run OFFLOAD cpu gpu g4 @@ -129,4 +132,11 @@ celeritas_add_integration_tests( RMTYPE ${_optical_rm_type} ) +# Test optical surface physics +celeritas_add_integration_tests( + TrackingManagerIntegration OpticalSurfaces run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + #-----------------------------------------------------------------------------# diff --git a/test/accel/IntegrationTestBase.cc b/test/accel/IntegrationTestBase.cc index 7401545e22..c351553ca8 100644 --- a/test/accel/IntegrationTestBase.cc +++ b/test/accel/IntegrationTestBase.cc @@ -382,6 +382,26 @@ auto IntegrationTestBase::make_sens_det(std::string const&) -> UPSensDet return nullptr; } +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// +void enable_optical_physics(IntegrationTestBase::PhysicsInput& phys_inp) +{ + // Set default optical physics + auto& optical = phys_inp.optical; + optical = {}; + EXPECT_TRUE(optical); + EXPECT_TRUE(optical.cherenkov); + EXPECT_TRUE(optical.scintillation); + + // Disable WLS which isn't yet working (reemission) in Celeritas + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); +} + +//---------------------------------------------------------------------------// +// TEST PROBLEM MIXINS //---------------------------------------------------------------------------// /*! * Create physics list: default is EM only using make_physics_input. diff --git a/test/accel/IntegrationTestBase.hh b/test/accel/IntegrationTestBase.hh index 13876004af..877f52f78e 100644 --- a/test/accel/IntegrationTestBase.hh +++ b/test/accel/IntegrationTestBase.hh @@ -109,6 +109,14 @@ class IntegrationTestBase : public ::celeritas::test::Test //!@} }; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +void enable_optical_physics(IntegrationTestBase::PhysicsInput&); + +//---------------------------------------------------------------------------// +// TEST PROBLEM MIXINS //---------------------------------------------------------------------------// //! Generate LAr sphere geometry with 10 MeV electrons and functional hit call class LarSphereIntegrationMixin : virtual public IntegrationTestBase diff --git a/test/accel/TrackingManagerIntegration.test.cc b/test/accel/TrackingManagerIntegration.test.cc index 4723e3d6bc..61625fa910 100644 --- a/test/accel/TrackingManagerIntegration.test.cc +++ b/test/accel/TrackingManagerIntegration.test.cc @@ -14,11 +14,13 @@ #include #include +#include "corecel/cont/Array.hh" #include "corecel/io/Logger.hh" #include "geocel/GeantUtils.hh" #include "geocel/UnitUtils.hh" #include "celeritas/ext/GeantParticleView.hh" #include "celeritas/global/CoreState.hh" +#include "celeritas/inp/Events.hh" #include "celeritas/optical/CoreState.hh" #include "celeritas/optical/OpticalCollector.hh" #include "celeritas/phys/PDGNumber.hh" @@ -50,6 +52,39 @@ constexpr bool using_surface_vg = CELERITAS_VECGEOM_SURFACE && CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_VECGEOM; +/*! + * Count particle types. + */ +class CounterTrackingAction final : public G4UserTrackingAction +{ + public: + void PreUserTrackingAction(G4Track const* t) final + { + GeantParticleView particle{*t->GetParticleDefinition()}; + + if (particle.pdg() == pdg::electron()) + { + ++num_electrons_; + } + if (particle.pdg() == pdg::positron()) + { + ++num_positrons_; + } + else if (particle.is_optical_photon()) + { + ++num_photons_; + } + } + std::size_t num_photons() const { return num_photons_; } + std::size_t num_electrons() const { return num_electrons_; } + std::size_t num_positrons() const { return num_positrons_; } + + private: + std::size_t num_photons_{}; + std::size_t num_electrons_{}; + std::size_t num_positrons_{}; +}; + } // namespace //---------------------------------------------------------------------------// @@ -101,6 +136,8 @@ class TMITestBase : virtual public IntegrationTestBase std::function check_during_run_; }; +//---------------------------------------------------------------------------// +// LAR SPHERE //---------------------------------------------------------------------------// class LarSphere : public LarSphereIntegrationMixin, public TMITestBase { @@ -215,54 +252,36 @@ TEST_F(LarSphere, run_ui) // LAR SPHERE WITH OPTICAL //---------------------------------------------------------------------------// /*! - * Count particle types. - * - * \todo This is redundant with (but more "Geant4-like" than) - * \c GeantStepDiagnostic . + * Test the LarSphere, offloading both EM tracks *and* optical photons. */ -class TrackingAction : public G4UserTrackingAction +class LarSphereOptical : public LarSphere { public: - void PreUserTrackingAction(G4Track const* t) + PhysicsInput make_physics_input() const override { - GeantParticleView particle{*t->GetParticleDefinition()}; - - if (particle.pdg() == pdg::electron()) - { - ++num_electrons_; - } - if (particle.pdg() == pdg::positron()) - { - ++num_positrons_; - } - else if (particle.is_optical_photon()) - { - ++num_photons_; - } + auto result = LarSphere::make_physics_input(); + enable_optical_physics(result); + return result; } - std::size_t num_photons() const { return num_photons_; } - std::size_t num_electrons() const { return num_electrons_; } - std::size_t num_positrons() const { return num_positrons_; } - private: - std::size_t num_photons_{}; - std::size_t num_electrons_{}; - std::size_t num_positrons_{}; -}; + PrimaryInput make_primary_input() const override + { + auto result = LarSphereIntegrationMixin::make_primary_input(); + + result.shape = inp::PointDistribution{ + array_cast(from_cm({0.1, 0.1, 0}))}; + result.primaries_per_event = 1; + result.energy = inp::MonoenergeticDistribution{2}; // [MeV] + return result; + } -/*! - * Test the LarSphere, offloading both EM tracks *and* optical photons. - */ -class LarSphereOptical : public LarSphere -{ - public: - PhysicsInput make_physics_input() const override; - PrimaryInput make_primary_input() const override; SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; + UPTrackAction make_tracking_action() override { - auto result = std::make_unique(); + auto result = std::make_unique(); { // Store the raw pointer in the tracking_ vector using a static // mutex @@ -274,43 +293,9 @@ class LarSphereOptical : public LarSphere } private: - std::vector tracking_; + std::vector tracking_; }; -//---------------------------------------------------------------------------// -/*! - * Enable optical physics. - */ -auto LarSphereOptical::make_physics_input() const -> PhysicsInput -{ - auto result = LarSphereIntegrationMixin::make_physics_input(); - - // Set default optical physics - auto& optical = result.optical; - optical = {}; - EXPECT_TRUE(optical); - EXPECT_TRUE(optical.cherenkov); - EXPECT_TRUE(optical.scintillation); - - // Disable WLS which isn't yet working (reemission) in Celeritas - using WLSO = WavelengthShiftingOptions; - optical.wavelength_shifting = WLSO::deactivated(); - optical.wavelength_shifting2 = WLSO::deactivated(); - - return result; -} - -auto LarSphereOptical::make_primary_input() const -> PrimaryInput -{ - auto result = LarSphereIntegrationMixin::make_primary_input(); - - result.shape - = inp::PointDistribution{array_cast(from_cm({0.1, 0.1, 0}))}; - result.primaries_per_event = 1; - result.energy = inp::MonoenergeticDistribution{2}; // [MeV] - return result; -} - //---------------------------------------------------------------------------// /*! * Enable optical tracking. @@ -417,6 +402,9 @@ TEST_F(LarSphereOptical, run) rm.BeamOn(2); } +//---------------------------------------------------------------------------// +// OPNOVICE +//---------------------------------------------------------------------------// /*! * Test the Op-Novice example, offloading optical photons. */ @@ -426,7 +414,7 @@ class OpNoviceOptical : public OpNoviceIntegrationMixin, public TMITestBase void EndOfRunAction(G4Run const* run) override; UPTrackAction make_tracking_action() override { - auto result = std::make_unique(); + auto result = std::make_unique(); { // Store the raw pointer in the tracking_ vector using a static // mutex @@ -438,7 +426,7 @@ class OpNoviceOptical : public OpNoviceIntegrationMixin, public TMITestBase } private: - std::vector tracking_; + std::vector tracking_; }; //---------------------------------------------------------------------------// @@ -528,6 +516,127 @@ TEST_F(OpNoviceOptical, run) rm.BeamOn(10); } +//---------------------------------------------------------------------------// +// OPTICAL SURFACES +//---------------------------------------------------------------------------// +/*! + * Test the LarSphere, offloading both EM tracks *and* optical photons. + */ +class OpticalSurfaces : public TMITestBase +{ + public: + std::string_view gdml_basename() const final { return "optical-surfaces"; } + PhysicsInput make_physics_input() const override + { + auto result = TMITestBase::make_physics_input(); + enable_optical_physics(result); + return result; + } + + PrimaryInput make_primary_input() const override; + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; +}; + +//---------------------------------------------------------------------------// +/*! + * Fire positrons through the liquid argon toward the detectors. + */ +auto OpticalSurfaces::make_primary_input() const -> PrimaryInput +{ + PrimaryInput result; + result.pdg = {pdg::positron()}; + result.shape + = inp::PointDistribution{array_cast(from_cm({30, 0, 0}))}; + result.angle = inp::MonodirectionalDistribution{{-1, 0, 0}}; + result.energy = inp::MonoenergeticDistribution{10}; // [MeV] + result.primaries_per_event = 1; + result.num_events = 4; // Overridden with BeamOn + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking. + */ +auto OpticalSurfaces::make_setup_options() -> SetupOptions +{ + auto result = TMITestBase::make_setup_options(); + + result.sd.enabled = false; + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 32768; + opt.capacity.generators = 32768 * 8; + opt.capacity.primaries = opt.capacity.generators; + return opt; + }(); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Test that the optical tracking loop completed correctly. + * + * - Generator counters show whether any photons are queued but not run + * - Accumulated stats show whether the state has run some photons + */ +void OpticalSurfaces::EndOfRunAction(G4Run const* run) +{ + auto& integration = detail::IntegrationSingleton::instance(); + if (integration.mode() == OffloadMode::enabled) + { + auto& local_transporter = integration.local_transporter(); + auto const& shared_params = integration.shared_params(); + + // Check that local/shared data is available before end of run + EXPECT_EQ(is_running_events(), static_cast(local_transporter)); + EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; + + auto const& optical_collector = shared_params.optical_collector(); + EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; + if (local_transporter && optical_collector) + { + // Use diagnostic methods to check counters + auto const& accum_stats + = optical_collector->optical_state(local_transporter.GetState()) + .accum(); + CELER_LOG_LOCAL(info) + << "Ran " << accum_stats.steps << " over " + << accum_stats.step_iters << " step iterations from " + << accum_stats.flushes << " flushes"; + EXPECT_GT(accum_stats.steps, 0); + EXPECT_GT(accum_stats.step_iters, 0); + EXPECT_GT(accum_stats.flushes, 0); + + auto& aux_state = local_transporter.GetState().aux(); + auto counts = optical_collector->buffer_counts(aux_state); + EXPECT_EQ(0, counts.buffer_size); //!< Pending generators + EXPECT_EQ(0, counts.num_pending); //!< Photons pending generation + EXPECT_EQ(0, counts.num_generated); //!< Photons generated + } + } + + // Continue cleanup and other checks at end of run + TMITestBase::EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +/*! + * Check that the test runs. + */ +TEST_F(OpticalSurfaces, run) +{ + auto& rm = this->run_manager(); + TMI::Instance().SetOptions(this->make_setup_options()); + + CELER_LOG(status) << "Run initialization"; + rm.Initialize(); + CELER_LOG(status) << "Run two events"; + rm.BeamOn(2); +} + //---------------------------------------------------------------------------// // TESTEM3 //---------------------------------------------------------------------------// From 1278005f47e5d3a0ad312ee42845a803337e6c36 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 13 Jan 2026 09:58:43 -0500 Subject: [PATCH 34/60] Hotfix: build errors in 2191 (#2192) * Add high-level optical surface integration test * Allow optical materials to be unused by geometry volumes * Allow zero cross section for vacuum --- test/accel/TrackingManagerIntegration.test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/accel/TrackingManagerIntegration.test.cc b/test/accel/TrackingManagerIntegration.test.cc index 61625fa910..cca7ef5eb5 100644 --- a/test/accel/TrackingManagerIntegration.test.cc +++ b/test/accel/TrackingManagerIntegration.test.cc @@ -594,7 +594,8 @@ void OpticalSurfaces::EndOfRunAction(G4Run const* run) EXPECT_EQ(is_running_events(), static_cast(local_transporter)); EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; - auto const& optical_collector = shared_params.optical_collector(); + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; if (local_transporter && optical_collector) { From ab7bb1b1656f2891375e5c1ae9a8286572b607db Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 13 Jan 2026 10:34:06 -0500 Subject: [PATCH 35/60] Fix ddceler build error due to IWYU (#2188) * Fix IWYU failure * Use validatoin nomenclature standards * Minor cleanup * Fix overlap between absorber and detector layer --------- Co-authored-by: Sakib Rahman --- example/ddceler/input/Preshower.xml | 2 +- src/ddceler/CelerPhysics.cc | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/example/ddceler/input/Preshower.xml b/example/ddceler/input/Preshower.xml index 099f18c5eb..3ce753067e 100644 --- a/example/ddceler/input/Preshower.xml +++ b/example/ddceler/input/Preshower.xml @@ -68,7 +68,7 @@ - + diff --git a/src/ddceler/CelerPhysics.cc b/src/ddceler/CelerPhysics.cc index 0d75664d17..16536c18ff 100644 --- a/src/ddceler/CelerPhysics.cc +++ b/src/ddceler/CelerPhysics.cc @@ -12,6 +12,7 @@ #include #include +#include "corecel/io/Logger.hh" #include "celeritas/field/FieldDriverOptions.hh" #include "celeritas/inp/Field.hh" #include "accel/TrackingManagerIntegration.hh" @@ -71,11 +72,11 @@ SetupOptions CelerPhysics::make_options() // Validate configuration parameters CELER_VALIDATE(max_num_tracks_ > 0, - << "MaxNumTracks must be set to a positive value (got " - << max_num_tracks_ << ")"); + << "invalid MaxNumTracks=" << max_num_tracks_ + << "(should be positive)"); CELER_VALIDATE(init_capacity_ > 0, - << "InitCapacity must be set to a positive value (got " - << init_capacity_ << ")"); + << "invalid InitCapacity=" << init_capacity_ + << " (should be positive)"); opts.max_num_tracks = max_num_tracks_; opts.initializer_capacity = init_capacity_; @@ -88,7 +89,7 @@ SetupOptions CelerPhysics::make_options() // Get the field from DD4hep detector description and validate its type auto& detector = context()->detectorDescription(); - auto field = detector.field(); + auto&& field = detector.field(); auto* overlaid_obj = field.data(); // Validate field configuration: no electric components @@ -130,13 +131,8 @@ SetupOptions CelerPhysics::make_options() // Get field tracking parameters from DD4hep FieldSetup action // These parameters are set in the steering file (runner.field.*) - FieldDriverOptions driver_options; - - auto& kernel = context()->kernel(); - auto* config_phase = kernel.getPhase("configure"); - dd4hep::sim::Geant4Action* field_action = nullptr; - if (config_phase) + if (auto* config_phase = context()->kernel().getPhase("configure")) { // Find the MagFieldTrackingSetup action in the configure phase for (auto const& [action, callback] : config_phase->members()) @@ -149,6 +145,7 @@ SetupOptions CelerPhysics::make_options() } } + FieldDriverOptions driver_options; if (field_action) { driver_options = load_driver_options(field_action); From a0db16092d36e1441f413879c91c4a0355456e60 Mon Sep 17 00:00:00 2001 From: Greg Davidson Date: Wed, 14 Jan 2026 12:48:48 -0500 Subject: [PATCH 36/60] Add rng branching (#2155) * Add branching to RanluxppRngEngine * Add branching to Xorwow engine * Mark variables as maybe_unused * Enable ranluxpp and xorwow branching with branch state init * Move method comment down to definition --------- Co-authored-by: Seth R. Johnson --- src/corecel/random/data/RanluxppRngData.hh | 10 +++ src/corecel/random/data/XorwowRngData.hh | 4 + .../random/engine/RanluxppRngEngine.hh | 74 ++++++++++++++-- src/corecel/random/engine/XorwowRngEngine.hh | 86 +++++++++++++++---- test/corecel/random/RanluxppRngEngine.test.cc | 61 +++++++++++++ test/corecel/random/XorwowRngEngine.test.cc | 48 +++++++++++ 6 files changed, 258 insertions(+), 25 deletions(-) diff --git a/src/corecel/random/data/RanluxppRngData.hh b/src/corecel/random/data/RanluxppRngData.hh index 3ef6a01afc..00f3629173 100644 --- a/src/corecel/random/data/RanluxppRngData.hh +++ b/src/corecel/random/data/RanluxppRngData.hh @@ -70,6 +70,16 @@ struct RanluxppRngState int position; }; +//---------------------------------------------------------------------------// +/*! + * Initializes an RNG state. + */ +struct RanluxppRngStateInitializer +{ + //! Ranluxpp state number and carry bit + RanluxppNumber value; +}; + //---------------------------------------------------------------------------// /*! * Initializer object for the Ranluxpp engine diff --git a/src/corecel/random/data/XorwowRngData.hh b/src/corecel/random/data/XorwowRngData.hh index 9963f66678..969c982578 100644 --- a/src/corecel/random/data/XorwowRngData.hh +++ b/src/corecel/random/data/XorwowRngData.hh @@ -90,6 +90,10 @@ struct XorwowState XorwowUInt weylstate; //!< d }; +//---------------------------------------------------------------------------// +//! Initializes an RNG state for a branched RNG +using XorwowRngStateInitializer = XorwowState; + //---------------------------------------------------------------------------// /*! * XORWOW generator states for all threads. diff --git a/src/corecel/random/engine/RanluxppRngEngine.hh b/src/corecel/random/engine/RanluxppRngEngine.hh index fac883db78..671dea2539 100644 --- a/src/corecel/random/engine/RanluxppRngEngine.hh +++ b/src/corecel/random/engine/RanluxppRngEngine.hh @@ -61,6 +61,7 @@ class RanluxppRngEngine using Initializer_t = RanluxppInitializer; using ParamsRef = NativeCRef; using StateRef = NativeRef; + using RngStateInitializer_t = RanluxppRngStateInitializer; //@} public: @@ -83,21 +84,28 @@ class RanluxppRngEngine return celeritas::numeric_limits::max(); } - // Initialize state with the given seed. + // Initialize state with the given seed initializer inline CELER_FUNCTION RanluxppRngEngine& operator=(Initializer_t const& init); - // Generate a 32-bit random integer + // Initialize state with the given state initializer + inline CELER_FUNCTION RanluxppRngEngine& + operator=(RngStateInitializer_t const& state_init); + + // Generate a 32-bit random integer. inline CELER_FUNCTION result_type operator()(); // Advance the state \c count times. inline CELER_FUNCTION void discard(RanluxppUInt count); + // Initialize a state for a new spawned RNG. + inline CELER_FUNCTION RngStateInitializer_t branch(); + private: /// IMPLEMENTATION /// - // Produce the next block of random bits. - inline CELER_FUNCTION void advance(); + // Produce the next block of random bits for the given state. + inline CELER_FUNCTION void advance(RanluxppRngState& state); /// DATA /// static constexpr int offset_ = 48; @@ -153,6 +161,17 @@ RanluxppRngEngine::operator=(Initializer_t const& init) return *this; } +//---------------------------------------------------------------------------// +/*! + * Initialize state for the given state initializer. + */ +inline CELER_FUNCTION RanluxppRngEngine& +RanluxppRngEngine::operator=(RngStateInitializer_t const& state_init) +{ + (*state_).value = state_init.value; + return *this; +} + //---------------------------------------------------------------------------// /*! * Skip `n` random numbers without generating them. @@ -201,7 +220,7 @@ CELER_FUNCTION auto RanluxppRngEngine::operator()() -> result_type { if (state_->position + offset_ > params_.max_position) { - this->advance(); + this->advance(*state_); } // Extract the Nth word from the state @@ -222,16 +241,53 @@ CELER_FUNCTION auto RanluxppRngEngine::operator()() -> result_type return bits & 0xffffffffu; } +//---------------------------------------------------------------------------// +/*! + * Initialize a state for a new spawned RNG. + * + * \par Branching + * Branching is performed in two steps. First, the state of the new RNG + * (\f$x^{\prime}\f$) is initialized as + * \f[ + * x^{i,\prime}_j = x^i_j ^ x^{i+1}_j \, . + * \f] + * Second, to decorrelate the new RNG from this RNG, the new RNG is + * advanced forward to the next block + */ +CELER_FUNCTION RanluxppRngEngine::RngStateInitializer_t +RanluxppRngEngine::branch() +{ + // Create a new state + RanluxppRngState new_state = *state_; + + // Advance the RNG + this->advance(*state_); + + // XOR the new state with the updated and advanced state + for (auto i : celeritas::range(9)) + { + new_state.value.number[i] ^= (*state_).value.number[i]; + } + + // Advance the new rng to decorrelate it + this->advance(new_state); + + // Create and return the state initializer + RngStateInitializer_t initializer{new_state.value}; + + return initializer; +} + //---------------------------------------------------------------------------// /*! * Advance to the next state (block of random bits). */ -CELER_FUNCTION void RanluxppRngEngine::advance() +CELER_FUNCTION void RanluxppRngEngine::advance(RanluxppRngState& state) { - RanluxppArray9 lcg = celeritas::detail::to_lcg(state_->value); + RanluxppArray9 lcg = celeritas::detail::to_lcg(state.value); lcg = celeritas::detail::compute_mod_multiply(params_.advance_state, lcg); - state_->value = celeritas::detail::to_ranlux(lcg); - state_->position = 0; + state.value = celeritas::detail::to_ranlux(lcg); + state.position = 0; } //---------------------------------------------------------------------------// diff --git a/src/corecel/random/engine/XorwowRngEngine.hh b/src/corecel/random/engine/XorwowRngEngine.hh index 96084083f9..502e3b4971 100644 --- a/src/corecel/random/engine/XorwowRngEngine.hh +++ b/src/corecel/random/engine/XorwowRngEngine.hh @@ -62,6 +62,7 @@ class XorwowRngEngine using Initializer_t = XorwowRngInitializer; using ParamsRef = NativeCRef; using StateRef = NativeRef; + using RngStateInitializer_t = XorwowRngStateInitializer; //!@} public: @@ -75,15 +76,22 @@ class XorwowRngEngine StateRef const& state, TrackSlotId tid); - // Initialize state + // Initialize state with an RNG initializer inline CELER_FUNCTION XorwowRngEngine& operator=(Initializer_t const&); + // Initialize state with a state initializer + inline CELER_FUNCTION XorwowRngEngine& + operator=(RngStateInitializer_t const&); + // Generate a 32-bit pseudorandom number inline CELER_FUNCTION result_type operator()(); // Advance the state \c count times inline CELER_FUNCTION void discard(ull_int count); + // Initialize a state for a new spawned RNG + inline CELER_FUNCTION RngStateInitializer_t branch(); + private: /// TYPES /// @@ -98,9 +106,10 @@ class XorwowRngEngine //// HELPER FUNCTIONS //// inline CELER_FUNCTION void discard_subsequence(ull_int); - inline CELER_FUNCTION void next(); - inline CELER_FUNCTION void jump(ull_int, ArrayJumpPoly const&); - inline CELER_FUNCTION void jump(JumpPoly const&); + inline CELER_FUNCTION void next(XorwowState&); + inline CELER_FUNCTION void + jump(ull_int, ArrayJumpPoly const&, XorwowState&); + inline CELER_FUNCTION void jump(JumpPoly const&, XorwowState&); // Helper RNG for initializing the state struct SplitMix64 @@ -177,13 +186,25 @@ XorwowRngEngine::operator=(Initializer_t const& init) return *this; } +//---------------------------------------------------------------------------// +/*! + * Initialize the RNG engine with a state initializer. + */ +CELER_FUNCTION XorwowRngEngine& +XorwowRngEngine::operator=(RngStateInitializer_t const& state_init) +{ + state_->xorstate = state_init.xorstate; + state_->weylstate = state_init.weylstate; + return *this; +} + //---------------------------------------------------------------------------// /*! * Generate a 32-bit pseudorandom number using the 'xorwow' engine. */ CELER_FUNCTION auto XorwowRngEngine::operator()() -> result_type { - this->next(); + this->next(*state_); state_->weylstate += 362437u; return state_->weylstate + state_->xorstate[4]; } @@ -194,10 +215,41 @@ CELER_FUNCTION auto XorwowRngEngine::operator()() -> result_type */ CELER_FUNCTION void XorwowRngEngine::discard(ull_int count) { - this->jump(count, params_.jump); + this->jump(count, params_.jump, *state_); state_->weylstate += static_cast(count) * 362437u; } +//---------------------------------------------------------------------------// +/*! + * Generate a branched and (hopefully) decorreleated RNG + * + * \todo There are good reasons to believe that an RNG that is based on XOR + * operations may not be decorrelated when XORing the state. Work on an + * improved methodology may be warranted. + */ +CELER_FUNCTION XorwowRngEngine::RngStateInitializer_t XorwowRngEngine::branch() +{ + XorwowState new_state; + + // Copy the state into the new state + new_state.xorstate = state_->xorstate; + + // Advance this RNG 4 times to get new state + this->jump(4, params_.jump, *state_); + + // XOR the state with the new state + for (auto i : celeritas::range(new_state.xorstate.size())) + { + new_state.xorstate[i] ^= state_->xorstate[i]; + } + + // Advance the new state 4 times to (hopefully) decorrelate + this->jump(4, params_.jump, new_state); + + // Create and return the state initializer + return RngStateInitializer_t{new_state.xorstate, state_->weylstate}; +} + //---------------------------------------------------------------------------// /*! * Advance the state \c count subsequences (\c count * 2^67 times). @@ -207,7 +259,7 @@ CELER_FUNCTION void XorwowRngEngine::discard(ull_int count) */ CELER_FUNCTION void XorwowRngEngine::discard_subsequence(ull_int count) { - this->jump(count, params_.jump_subsequence); + this->jump(count, params_.jump_subsequence, *state_); } //---------------------------------------------------------------------------// @@ -216,9 +268,9 @@ CELER_FUNCTION void XorwowRngEngine::discard_subsequence(ull_int count) * * This does not update the Weyl sequence value. */ -CELER_FUNCTION void XorwowRngEngine::next() +CELER_FUNCTION void XorwowRngEngine::next(XorwowState& state) { - auto& s = state_->xorstate; + auto& s = state.xorstate; auto const t = (s[0] ^ (s[0] >> 2u)); s[0] = s[1]; @@ -235,8 +287,9 @@ CELER_FUNCTION void XorwowRngEngine::next() * This applies the jump polynomials until the given number of steps or * subsequences have been skipped. */ -CELER_FUNCTION void -XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) +CELER_FUNCTION void XorwowRngEngine::jump(ull_int count, + ArrayJumpPoly const& jump_poly_arr, + XorwowState& state) { // Maximum number of times to apply any jump polynomial. Since the jump // sizes are 4^i for i = [0, 32), the max is 3. @@ -251,7 +304,7 @@ XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) for (size_type i = 0; i < num_jump; ++i) { CELER_ASSERT(jump_idx < jump_poly_arr.size()); - this->jump(jump_poly_arr[jump_idx]); + this->jump(jump_poly_arr[jump_idx], state); } ++jump_idx; count >>= 2; @@ -286,7 +339,8 @@ XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) * addition is the same as subtraction and equivalent to bitwise exclusive or, * and multiplication is bitwise and. */ -CELER_FUNCTION void XorwowRngEngine::jump(JumpPoly const& jump_poly) +CELER_FUNCTION void +XorwowRngEngine::jump(JumpPoly const& jump_poly, XorwowState& state) { Array s = {0}; for (size_type i : range(params_.num_words())) @@ -297,13 +351,13 @@ CELER_FUNCTION void XorwowRngEngine::jump(JumpPoly const& jump_poly) { for (size_type k : range(params_.num_words())) { - s[k] ^= state_->xorstate[k]; + s[k] ^= state.xorstate[k]; } } - this->next(); + this->next(state); } } - state_->xorstate = s; + state.xorstate = s; } //---------------------------------------------------------------------------// diff --git a/test/corecel/random/RanluxppRngEngine.test.cc b/test/corecel/random/RanluxppRngEngine.test.cc index 45bf2a907b..28d2476e11 100644 --- a/test/corecel/random/RanluxppRngEngine.test.cc +++ b/test/corecel/random/RanluxppRngEngine.test.cc @@ -337,6 +337,67 @@ TEST_F(RanluxppRngEngineTest, jump) } } +TEST_F(RanluxppRngEngineTest, branch) +{ + unsigned int size = 4; + + // Initialize first RNG + HostStore states(params_->host_ref(), StreamId{0}, size); + RanluxppRngEngine rng(params_->host_ref(), states.ref(), TrackSlotId{0}); + rng = RanluxppInitializer{12345, 0, 0}; + + // Initialize second RNG + RanluxppRngEngine branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{1}); + branched_rng = rng.branch(); + + // Create a third RNG, and branch its state manually + auto& ref_rng_state = states.ref().state[TrackSlotId{2}]; + auto& ref_branched_rng_state = states.ref().state[TrackSlotId{3}]; + RanluxppRngEngine ref_branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{3}); + { + RanluxppRngEngine ref_rng( + params_->host_ref(), states.ref(), TrackSlotId{2}); + ref_rng = RanluxppInitializer{12345, 0, 0}; + ref_branched_rng = RanluxppInitializer{12345, 0, 0}; + } + + // Advance the reference RNG + { + RanluxppArray9 lcg = celeritas::detail::to_lcg(ref_rng_state.value); + lcg = celeritas::detail::compute_mod_multiply( + params_->host_ref().advance_state, lcg); + ref_rng_state.value = celeritas::detail::to_ranlux(lcg); + ref_rng_state.position = 0; + } + + // XOR with updated state + { + for (auto i : celeritas::range(9)) + { + ref_branched_rng_state.value.number[i] + ^= ref_rng_state.value.number[i]; + } + } + + // Advance the new RNG + { + RanluxppArray9 lcg + = celeritas::detail::to_lcg(ref_branched_rng_state.value); + lcg = celeritas::detail::compute_mod_multiply( + params_->host_ref().advance_state, lcg); + ref_branched_rng_state.value = celeritas::detail::to_ranlux(lcg); + ref_branched_rng_state.position = 0; + } + + // Draw 10 random numbers from the two branched RNGs and compare + for ([[maybe_unused]] auto i : celeritas::range(10)) + { + EXPECT_EQ(ref_branched_rng(), branched_rng()); + } +} + TEST_F(RanluxppRngEngineTest, TEST_IF_CELER_DEVICE(device)) { celeritas::device().create_streams(1); diff --git a/test/corecel/random/XorwowRngEngine.test.cc b/test/corecel/random/XorwowRngEngine.test.cc index d73b66f0f8..844274a52d 100644 --- a/test/corecel/random/XorwowRngEngine.test.cc +++ b/test/corecel/random/XorwowRngEngine.test.cc @@ -258,6 +258,54 @@ TEST_F(XorwowRngEngineTest, jump) } } +TEST_F(XorwowRngEngineTest, branch) +{ + unsigned int size = 4; + + HostStore states(params_->host_ref(), StreamId{0}, size); + XorwowRngEngine rng(params_->host_ref(), states.ref(), TrackSlotId{0}); + rng = XorwowRngInitializer{12345, 0, 0}; + + // Initialize second RNG + XorwowRngEngine branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{1}); + branched_rng = rng.branch(); + + // Create a third RNG, and branch its state manually + auto& ref_rng_state = states.ref().state[TrackSlotId{2}]; + auto& ref_branched_rng_state = states.ref().state[TrackSlotId{3}]; + XorwowRngEngine ref_branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{3}); + { + XorwowRngEngine ref_rng( + params_->host_ref(), states.ref(), TrackSlotId{2}); + ref_rng = XorwowRngInitializer{12345, 0, 0}; + ref_branched_rng = XorwowRngInitializer{12345, 0, 0}; + + // Advance the reference RNG + auto old_weyl = ref_rng_state.weylstate; + ref_rng.discard(4); + ref_rng_state.weylstate = old_weyl; + + // XOR the branched state with the updated ref state + for (auto i : celeritas::range(ref_branched_rng_state.xorstate.size())) + { + ref_branched_rng_state.xorstate[i] ^= ref_rng_state.xorstate[i]; + } + + // Advance the branched RNG + old_weyl = ref_branched_rng_state.weylstate; + ref_branched_rng.discard(4); + ref_branched_rng_state.weylstate = old_weyl; + } + + // Draw 10 random numbers from the two branched RNGs and compare + for ([[maybe_unused]] auto i : celeritas::range(10)) + { + EXPECT_EQ(ref_branched_rng(), branched_rng()); + } +} + TEST_F(XorwowRngEngineTest, TEST_IF_CELER_DEVICE(device)) { // Create and initialize states From 3dd8d4de3c6278ed68fe1624e716bc3b71626240 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Thu, 15 Jan 2026 11:47:27 -0500 Subject: [PATCH 37/60] Add checks for container validity to improve coverage (#2190) * Add always-on assertion to collection mirror * Add always-on test of optical data validity * Try putting exclusion markers on code lines * REVERTME: delete all but codecov * Try excluding branch? * Exclude DEBUG_FAIL and try an unreachable branch * Scrunch to one line and add comment * Revert "REVERTME: delete all but codecov" This reverts commit a9c2c4d647a77e1ebdcdb789b5838e88dc1ff7fe. --- scripts/ci/gcovr.cfg | 2 +- src/celeritas/ext/GeantImporter.cc | 21 ++++++++++++--------- src/corecel/data/CollectionMirror.hh | 13 ++++++++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/scripts/ci/gcovr.cfg b/scripts/ci/gcovr.cfg index b39a91262a..0522d49197 100644 --- a/scripts/ci/gcovr.cfg +++ b/scripts/ci/gcovr.cfg @@ -16,4 +16,4 @@ gcov-ignore-parse-errors = negative_hits.warn gcov-ignore-parse-errors = suspicious_hits.warn exclude-unreachable-branches = yes -exclude-lines-by-pattern = ^\s*CELER_((ASSERT_)?UNREACHABLE|NOT_CONFIGURED|(DEVICE_API|MPI)_CALL|EXPECT|ASSERT|ENSURE).* +exclude-lines-by-pattern = ^\s*CELER_((ASSERT_)?UNREACHABLE|NOT_CONFIGURED|(DEVICE_API|MPI)_CALL|EXPECT|ASSERT|ENSURE|DEBUG_FAIL).* diff --git a/src/celeritas/ext/GeantImporter.cc b/src/celeritas/ext/GeantImporter.cc index 4ab4ef64e9..5b876d6cfa 100644 --- a/src/celeritas/ext/GeantImporter.cc +++ b/src/celeritas/ext/GeantImporter.cc @@ -7,7 +7,6 @@ #include "GeantImporter.hh" #include -#include #include #include #include @@ -185,13 +184,14 @@ struct ProcessFilter //! Map particles defined in \c G4MaterialConstPropertyIndex . auto& optical_particles_map() { - static std::unordered_map const map - = {{"PROTON", pdg::proton()}, - {"DEUTERON", pdg::deuteron()}, - {"TRITON", pdg::triton()}, - {"ALPHA", pdg::alpha()}, - {"ION", pdg::ion()}, - {"ELECTRON", pdg::electron()}}; + static std::unordered_map const map = { + {"PROTON", pdg::proton()}, + {"DEUTERON", pdg::deuteron()}, + {"TRITON", pdg::triton()}, + {"ALPHA", pdg::alpha()}, + {"ION", pdg::ion()}, + {"ELECTRON", pdg::electron()}, + }; return map; } @@ -690,7 +690,10 @@ import_optical_materials(detail::GeoOpticalIdMap const& geo_to_opt) get_property( &optical.mie.backward_g, "MIEHG_BACKWARD", ImportUnits::unitless); - CELER_ASSERT(optical); + CELER_VALIDATE(optical, + << "failed to load valid optical material data for " + "OptMatId{" + << opt_mat_id.get() << "} = " << material->GetName()); } CELER_LOG(debug) << "Loaded " << result.size() << " optical materials"; diff --git a/src/corecel/data/CollectionMirror.hh b/src/corecel/data/CollectionMirror.hh index 68b7e28036..48ef91f9e7 100644 --- a/src/corecel/data/CollectionMirror.hh +++ b/src/corecel/data/CollectionMirror.hh @@ -95,10 +95,21 @@ template class P> CollectionMirror

::CollectionMirror(HostValue&& host) : host_(std::move(host)) { - CELER_EXPECT(host_); + if (CELER_UNLIKELY(!host_)) + { + CELER_DEBUG_FAIL("incomplete host data or bad copy", precondition); + } + host_ref_ = host_; + if (celeritas::device()) { + if constexpr (!CELER_USE_DEVICE) + { + // Mark unreachable for optimization and coverage + CELER_ASSERT_UNREACHABLE(); + } + // Copy data to device and save reference device_ = host_; device_ref_ = device_; From 052c5f67e3eebb02d7df8f36dade76c90e687410 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Fri, 16 Jan 2026 18:41:07 -0500 Subject: [PATCH 38/60] Remove polyprism (#2194) --- .../geometry/orange/construction.rst | 1 - src/orange/orangeinp/ObjectIO.json.cc | 15 --- src/orange/orangeinp/ObjectIO.json.hh | 2 - src/orange/orangeinp/PolySolid.cc | 112 ------------------ src/orange/orangeinp/PolySolid.hh | 42 ------- 5 files changed, 172 deletions(-) diff --git a/doc/implementation/geometry/orange/construction.rst b/doc/implementation/geometry/orange/construction.rst index 4e983f8f87..bc5fb181e3 100644 --- a/doc/implementation/geometry/orange/construction.rst +++ b/doc/implementation/geometry/orange/construction.rst @@ -94,7 +94,6 @@ be reused in multiple locations. :numref:`fig-orangeinp-types` summarizes these .. doxygenclass:: celeritas::orangeinp::PolySegments .. doxygenclass:: celeritas::orangeinp::PolyCone -.. doxygenclass:: celeritas::orangeinp::PolyPrism .. doxygenclass:: celeritas::orangeinp::RevolvedPolygon .. doxygenclass:: celeritas::orangeinp::StackedExtrudedPolygon diff --git a/src/orange/orangeinp/ObjectIO.json.cc b/src/orange/orangeinp/ObjectIO.json.cc index 98eb33788c..ba0fd11259 100644 --- a/src/orange/orangeinp/ObjectIO.json.cc +++ b/src/orange/orangeinp/ObjectIO.json.cc @@ -85,21 +85,6 @@ void to_json(nlohmann::json& j, PolyCone const& obj) } } -void to_json(nlohmann::json& j, PolyPrism const& obj) -{ - j = { - {"_type", "polyprism"}, - SIO_ATTR_PAIR(obj, label), - SIO_ATTR_PAIR(obj, segments), - SIO_ATTR_PAIR(obj, num_sides), - SIO_ATTR_PAIR(obj, orientation), - }; - if (auto azi = obj.enclosed_azi()) - { - j["enclosed_azi"] = azi; - } -} - void to_json(nlohmann::json& j, RevolvedPolygon const& obj) { j = { diff --git a/src/orange/orangeinp/ObjectIO.json.hh b/src/orange/orangeinp/ObjectIO.json.hh index 357ebfefed..3743828da4 100644 --- a/src/orange/orangeinp/ObjectIO.json.hh +++ b/src/orange/orangeinp/ObjectIO.json.hh @@ -25,7 +25,6 @@ template class JoinObjects; class NegatedObject; class PolyCone; -class PolyPrism; class RevolvedPolygon; class ShapeBase; class SolidBase; @@ -67,7 +66,6 @@ template void to_json(nlohmann::json& j, JoinObjects const&); void to_json(nlohmann::json& j, NegatedObject const&); void to_json(nlohmann::json& j, PolyCone const&); -void to_json(nlohmann::json& j, PolyPrism const&); void to_json(nlohmann::json& j, RevolvedPolygon const&); void to_json(nlohmann::json& j, ShapeBase const&); void to_json(nlohmann::json& j, SolidBase const&); diff --git a/src/orange/orangeinp/PolySolid.cc b/src/orange/orangeinp/PolySolid.cc index a397972fd2..250dd26af7 100644 --- a/src/orange/orangeinp/PolySolid.cc +++ b/src/orange/orangeinp/PolySolid.cc @@ -267,118 +267,6 @@ void PolyCone::output(JsonPimpl* j) const to_json_pimpl(j, *this); } -//---------------------------------------------------------------------------// -/*! - * Return a polyprism *or* a simplified version for only a single segment. - */ -auto PolyPrism::or_solid(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation) -> SPConstObject -{ - if (segments.size() > 1) - { - // Can't be simplified: make a polyprism - return std::make_shared(std::move(label), - std::move(segments), - std::move(enclosed), - num_sides, - orientation); - } - - auto const [zlo, zhi] = segments.z(0); - real_type const hh = (zhi - zlo) / 2; - - auto const [rlo, rhi] = segments.outer(0); - if (rlo != rhi) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - - Prism outer{num_sides, rlo, hh, orientation}; - std::optional inner; - if (segments.has_exclusion()) - { - auto const [rilo, rihi] = segments.inner(0); - if (rilo != rihi) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - - inner = Prism{num_sides, rilo, hh, orientation}; - } - - auto result = PrismSolid::or_shape(std::move(label), - std::move(outer), - std::move(inner), - std::move(enclosed)); - if (real_type dz = (zhi + zlo) / 2; dz != 0) - { - result = std::make_shared(std::move(result), - Translation{{0, 0, dz}}); - } - - return result; -} - -//---------------------------------------------------------------------------// -/*! - * Build with label, axial segments, optional restriction. - */ -PolyPrism::PolyPrism(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation) - : PolySolidBase{std::move(label), std::move(segments), std::move(enclosed)} - , num_sides_{num_sides} - , orientation_{orientation} -{ - CELER_VALIDATE(num_sides_ >= 3, - << "degenerate prism (num_sides = " << num_sides_ << ')'); - CELER_VALIDATE(orientation_ >= 0 && orientation_ < 1, - << "orientation is out of bounds [0, 1): " << orientation_); -} - -//---------------------------------------------------------------------------// -/*! - * Construct a volume from this shape. - */ -NodeId PolyPrism::build(VolumeBuilder& vb) const -{ - auto build_prism = [this](Real2 const& radii, real_type hh) { - if (radii[0] != radii[1]) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - return Prism{this->num_sides_, radii[0], hh, this->orientation_}; - }; - - // Construct union of all cone segments - NodeId result = construct_segments(*this, build_prism, vb); - - /*! - *\todo After adding short-circuit logic to evaluator, add "acceleration" - * structures here, e.g. "inside(inner cylinder) || [inside(outer cylinder) - * && (original union)]" - */ - - // Construct azimuthal truncation if applicable - result = construct_enclosed_angle(*this, vb, result); - - return result; -} - -//---------------------------------------------------------------------------// -/*! - * Write the shape to JSON. - */ -void PolyPrism::output(JsonPimpl* j) const -{ - to_json_pimpl(j, *this); -} - //---------------------------------------------------------------------------// } // namespace orangeinp } // namespace celeritas diff --git a/src/orange/orangeinp/PolySolid.hh b/src/orange/orangeinp/PolySolid.hh index ad77a09064..2a9e624b0a 100644 --- a/src/orange/orangeinp/PolySolid.hh +++ b/src/orange/orangeinp/PolySolid.hh @@ -172,48 +172,6 @@ class PolyCone final : public PolySolidBase void output(JsonPimpl*) const final; }; -//---------------------------------------------------------------------------// -/*! - * A series of stacked regular prisms or cone-y prisms. - * - * \todo This class is no longer used and is slated for removal. G4Polyhedra - * are now constructed using StackedExtrudedPolygon. - */ -class PolyPrism final : public PolySolidBase -{ - public: - // Return a polyprism *or* a simplified version for only a single segment - static SPConstObject or_solid(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation); - - // Build with label, axial segments, parameters, optional restriction - PolyPrism(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation); - - // Construct a volume from this object - NodeId build(VolumeBuilder&) const final; - - // Write the shape to JSON - void output(JsonPimpl*) const final; - - //// ACCESSORS //// - - //! Number of sides - int num_sides() const { return num_sides_; } - //! Rotation factor - real_type orientation() const { return orientation_; } - - private: - int num_sides_; - real_type orientation_; -}; - //---------------------------------------------------------------------------// } // namespace orangeinp } // namespace celeritas From d866e907b7954d718634f217d9cea3feac192a6c Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Sun, 18 Jan 2026 09:25:52 -0500 Subject: [PATCH 39/60] Simplify along-step propagation (#2187) * Remove unnused operator() * Skip along-step for at-rest tracks * Remove propagator specialization * Define field params constructor for UniformField * Combine field propagator factories * Un-generalize AlongStep for now * Uninline, fix cuda build, update presets * Remove unused tracks_can_loop * fixup! Remove unnused operator() --- scripts/cmake-presets/milan0.json | 4 +- scripts/cmake-presets/milan2.json | 2 + src/celeritas/alongstep/AlongStep.hh | 60 --------- .../AlongStepCartMapFieldMscAction.cc | 16 ++- .../AlongStepCartMapFieldMscAction.cu | 8 +- .../AlongStepCylMapFieldMscAction.cc | 16 ++- .../AlongStepCylMapFieldMscAction.cu | 14 +- .../alongstep/AlongStepGeneralLinearAction.cc | 4 +- .../alongstep/AlongStepNeutralAction.cc | 22 ++-- .../alongstep/AlongStepNeutralAction.cu | 13 +- .../alongstep/AlongStepRZMapFieldMscAction.cc | 16 ++- .../alongstep/AlongStepRZMapFieldMscAction.cu | 14 +- .../alongstep/AlongStepUniformMscAction.cc | 49 ++++--- .../alongstep/AlongStepUniformMscAction.cu | 11 +- .../alongstep/detail/AlongStepKernels.cu | 4 +- .../alongstep/detail/AlongStepNeutralImpl.hh | 42 ++++-- .../detail/CartMapFieldPropagatorFactory.hh | 43 ------ .../detail/CylMapFieldPropagatorFactory.hh | 43 ------ .../alongstep/detail/FieldFunctors.hh | 1 + .../alongstep/detail/FieldTrackPropagator.hh | 91 +++++++++++++ ...torFactory.hh => LinearTrackPropagator.hh} | 14 +- .../alongstep/detail/PropagationApplier.hh | 123 +++--------------- .../detail/RZMapFieldPropagatorFactory.hh | 43 ------ .../detail/UniformFieldPropagatorFactory.hh | 47 ------- src/celeritas/field/CartMapField.covfie.hh | 15 +-- src/celeritas/field/CylMapField.hh | 8 +- src/celeritas/field/FieldPropagator.hh | 17 --- src/celeritas/field/LinearPropagator.hh | 22 ---- src/celeritas/field/RZMapField.hh | 8 +- src/celeritas/field/UniformField.hh | 9 ++ .../field/detail/NotImplementedField.hh | 6 +- src/celeritas/global/TrackExecutor.hh | 2 + src/celeritas/phys/detail/PreStepExecutor.hh | 6 +- src/geocel/vg/detail/BVHNavigator.hh | 6 +- test/celeritas/field/Integrators.test.cc | 4 +- test/celeritas/field/LinearPropagator.test.cc | 2 +- 36 files changed, 305 insertions(+), 500 deletions(-) delete mode 100644 src/celeritas/alongstep/AlongStep.hh delete mode 100644 src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh delete mode 100644 src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh create mode 100644 src/celeritas/alongstep/detail/FieldTrackPropagator.hh rename src/celeritas/alongstep/detail/{LinearPropagatorFactory.hh => LinearTrackPropagator.hh} (65%) delete mode 100644 src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh delete mode 100644 src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh diff --git a/scripts/cmake-presets/milan0.json b/scripts/cmake-presets/milan0.json index a83a0b7d6b..f0afa467db 100644 --- a/scripts/cmake-presets/milan0.json +++ b/scripts/cmake-presets/milan0.json @@ -13,7 +13,9 @@ "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LArSoft": {"type": "BOOL", "value": "OFF"}, "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", "CMAKE_CXX_STANDARD": "20", "CMAKE_CXX_COMPILER": "/usr/bin/c++", diff --git a/scripts/cmake-presets/milan2.json b/scripts/cmake-presets/milan2.json index c2e2823076..14a52e64af 100644 --- a/scripts/cmake-presets/milan2.json +++ b/scripts/cmake-presets/milan2.json @@ -14,6 +14,8 @@ "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LArSoft": {"type": "BOOL", "value": "OFF"}, "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", "CMAKE_CXX_STANDARD": "20", "CMAKE_CXX_COMPILER": "/usr/bin/c++", diff --git a/src/celeritas/alongstep/AlongStep.hh b/src/celeritas/alongstep/AlongStep.hh deleted file mode 100644 index 184580205b..0000000000 --- a/src/celeritas/alongstep/AlongStep.hh +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/AlongStep.hh -//! \brief Along-step function and helper classes -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/global/CoreTrackView.hh" - -#include "detail/ElossApplier.hh" // IWYU pragma: associated -#include "detail/MscApplier.hh" // IWYU pragma: associated -#include "detail/MscStepLimitApplier.hh" // IWYU pragma: associated -#include "detail/PropagationApplier.hh" // IWYU pragma: associated -#include "detail/TimeUpdater.hh" // IWYU pragma: associated -#include "detail/TrackUpdater.hh" // IWYU pragma: associated - -namespace celeritas -{ -//---------------------------------------------------------------------------// -/*! - * Perform the along-step action using helper functions. - * - * \tparam MH MSC helper, e.g. \c detail::NoMsc - * \tparam MP Propagator factory, e.g. \c detail::LinearPropagatorFactory - * \tparam EH Energy loss helper, e.g. \c detail::NoELoss - */ -template -struct AlongStep -{ - inline CELER_FUNCTION void operator()(CoreTrackView& track); - - MH msc; - MP make_propagator; - EH eloss; -}; - -//---------------------------------------------------------------------------// -// DEDUCTION GUIDES -//---------------------------------------------------------------------------// -template -CELER_FUNCTION AlongStep(MH&&, MP&&, EH&&) -> AlongStep; - -//---------------------------------------------------------------------------// -// INLINE DEFINITIONS -//---------------------------------------------------------------------------// -template -CELER_FUNCTION void AlongStep::operator()(CoreTrackView& track) -{ - detail::MscStepLimitApplier{msc}(track); - detail::PropagationApplier{make_propagator}(track); - detail::MscApplier{msc}(track); - detail::TimeUpdater{}(track); - detail::ElossApplier{eloss}(track); - detail::TrackUpdater{}(track); -} - -//---------------------------------------------------------------------------// -} // namespace celeritas diff --git a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc index e9c7ef382b..598be0efac 100644 --- a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc @@ -6,26 +6,28 @@ //---------------------------------------------------------------------------// #include "AlongStepCartMapFieldMscAction.hh" -#include #include #include "corecel/Assert.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" // IWYU pragma: keep #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep +#include "celeritas/field/CartMapField.hh" // IWYU pragma: keep #include "celeritas/field/CartMapFieldInput.hh" -#include "celeritas/geo/GeoFwd.hh" #include "celeritas/global/ActionLauncher.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - -#include "detail/CartMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" namespace celeritas { @@ -97,7 +99,7 @@ void AlongStepCartMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{CartMapFieldPropagatorFactory{ + PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu index 446753bc09..85a08ce029 100644 --- a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu @@ -9,6 +9,7 @@ #include "corecel/sys/ScopedProfiling.hh" #include "celeritas/em/params/FluctuationParams.hh" #include "celeritas/em/params/UrbanMscParams.hh" +#include "celeritas/field/CartMapField.hh" #include "celeritas/field/CartMapFieldParams.hh" #include "celeritas/global/ActionLauncher.device.hh" #include "celeritas/global/CoreParams.hh" @@ -16,7 +17,7 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" -#include "detail/CartMapFieldPropagatorFactory.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/PropagationApplier.hh" namespace celeritas @@ -39,8 +40,9 @@ void AlongStepCartMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::CartMapFieldPropagatorFactory{ - field_->ref()}}); + detail::PropagationApplier{ + detail::FieldTrackPropagator{ + field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-cartmap"); launch_kernel(*this, params, state, execute_thread); diff --git a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc index 38b36965fc..65622dd4d1 100644 --- a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc @@ -19,13 +19,19 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - -#include "detail/CylMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/CylMapField.hh" namespace celeritas { @@ -97,7 +103,7 @@ void AlongStepCylMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{CylMapFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu index 69c566f3d0..03b7ebfbce 100644 --- a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu @@ -16,8 +16,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" -#include "detail/CylMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/FluctELoss.hh" +#include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" #include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/CylMapField.hh" namespace celeritas { @@ -39,7 +49,7 @@ void AlongStepCylMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::CylMapFieldPropagatorFactory{ + detail::PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-cylmap"); diff --git a/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc b/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc index 9ba47fd574..eb50a0f23e 100644 --- a/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc +++ b/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc @@ -23,7 +23,7 @@ #include "detail/ElossApplier.hh" #include "detail/FluctELoss.hh" // IWYU pragma: associated -#include "detail/LinearPropagatorFactory.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/MeanELoss.hh" // IWYU pragma: associated #include "detail/MscApplier.hh" #include "detail/MscStepLimitApplier.hh" @@ -95,7 +95,7 @@ void AlongStepGeneralLinearAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{LinearPropagatorFactory{}}(track); + PropagationApplier{LinearTrackPropagator{}}(track); if (this->has_msc()) { MscApplier{UrbanMsc{msc_->ref()}}(track); diff --git a/src/celeritas/alongstep/AlongStepNeutralAction.cc b/src/celeritas/alongstep/AlongStepNeutralAction.cc index 2b7191c680..843ca8e792 100644 --- a/src/celeritas/alongstep/AlongStepNeutralAction.cc +++ b/src/celeritas/alongstep/AlongStepNeutralAction.cc @@ -11,12 +11,10 @@ #include "celeritas/global/ActionLauncher.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" +#include "celeritas/global/CoreTrackView.hh" #include "celeritas/global/TrackExecutor.hh" -#include "AlongStep.hh" // IWYU pragma: associated - #include "detail/AlongStepNeutralImpl.hh" // IWYU pragma: associated -#include "detail/LinearPropagatorFactory.hh" // IWYU pragma: associated namespace celeritas { @@ -36,14 +34,16 @@ AlongStepNeutralAction::AlongStepNeutralAction(ActionId id) : id_(id) void AlongStepNeutralAction::step(CoreParams const& params, CoreStateHost& state) const { - auto execute = make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - AlongStep{detail::NoMsc{}, - detail::LinearPropagatorFactory{}, - detail::NoELoss{}}); - return launch_action(*this, params, state, execute); + using namespace ::celeritas::detail; + + return launch_action( + *this, + params, + state, + make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + AlongStepNeutralExecutor{})); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/AlongStepNeutralAction.cu b/src/celeritas/alongstep/AlongStepNeutralAction.cu index 47e542c860..3d544fba4c 100644 --- a/src/celeritas/alongstep/AlongStepNeutralAction.cu +++ b/src/celeritas/alongstep/AlongStepNeutralAction.cu @@ -12,7 +12,6 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepNeutralImpl.hh" -#include "detail/LinearPropagatorFactory.hh" namespace celeritas { @@ -23,13 +22,11 @@ namespace celeritas void AlongStepNeutralAction::step(CoreParams const& params, CoreStateDevice& state) const { - auto execute = make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - AlongStep{detail::NoMsc{}, - detail::LinearPropagatorFactory{}, - detail::NoELoss{}}); + auto execute + = make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + detail::AlongStepNeutralExecutor{}); static ActionLauncher const launch_kernel(*this); launch_kernel(*this, params, state, execute); } diff --git a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc index ed83e04c34..407f3fc995 100644 --- a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc @@ -10,6 +10,7 @@ #include #include "corecel/Assert.hh" +#include "celeritas/alongstep/detail/PropagationApplier.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" // IWYU pragma: keep #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep @@ -21,11 +22,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" -#include "detail/RZMapFieldPropagatorFactory.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/RZMapField.hh" namespace celeritas { @@ -97,7 +105,7 @@ void AlongStepRZMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{RZMapFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu index 4d545a5258..1565d1da7a 100644 --- a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu @@ -16,8 +16,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/FluctELoss.hh" +#include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" #include "detail/PropagationApplier.hh" -#include "detail/RZMapFieldPropagatorFactory.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/RZMapField.hh" namespace celeritas { @@ -39,7 +49,7 @@ void AlongStepRZMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::RZMapFieldPropagatorFactory{ + detail::PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-rzmap"); diff --git a/src/celeritas/alongstep/AlongStepUniformMscAction.cc b/src/celeritas/alongstep/AlongStepUniformMscAction.cc index da8d55ba2e..d53299f813 100644 --- a/src/celeritas/alongstep/AlongStepUniformMscAction.cc +++ b/src/celeritas/alongstep/AlongStepUniformMscAction.cc @@ -10,9 +10,6 @@ #include "corecel/Assert.hh" #include "corecel/Macros.hh" -#include "corecel/data/Ref.hh" -#include "corecel/io/Logger.hh" -#include "corecel/sys/Device.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep @@ -20,15 +17,21 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/track/TrackFunctors.hh" - -#include "AlongStep.hh" +#include "detail/ElossApplier.hh" #include "detail/FieldFunctors.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" -#include "detail/LinearPropagatorFactory.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/MeanELoss.hh" -#include "detail/UniformFieldPropagatorFactory.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/UniformField.hh" namespace celeritas { @@ -86,32 +89,19 @@ void AlongStepUniformMscAction::step(CoreParams const& params, CoreStateHost& state) const { using namespace ::celeritas::detail; - - auto launch_impl = [&](auto&& execute_track) { - return launch_action( - *this, - params, - state, - make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - std::forward(execute_track))); - }; - - launch_impl([&](CoreTrackView& track) { + auto execute_track = [&](CoreTrackView& track) { if (this->has_msc()) { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } if (IsInUniformField{field_->ref()}(track)) { - PropagationApplier{UniformFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); } else { - PropagationApplier{LinearPropagatorFactory{}}(track); + PropagationApplier{LinearTrackPropagator{}}(track); } if (this->has_msc()) { @@ -127,7 +117,16 @@ void AlongStepUniformMscAction::step(CoreParams const& params, ElossApplier{MeanELoss{}}(track); } TrackUpdater{}(track); - }); + }; + + return launch_action( + *this, + params, + state, + make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + execute_track)); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/AlongStepUniformMscAction.cu b/src/celeritas/alongstep/AlongStepUniformMscAction.cu index ba078dfd0c..b4a092de4c 100644 --- a/src/celeritas/alongstep/AlongStepUniformMscAction.cu +++ b/src/celeritas/alongstep/AlongStepUniformMscAction.cu @@ -20,9 +20,12 @@ #include "detail/AlongStepKernels.hh" #include "detail/FieldFunctors.hh" -#include "detail/LinearPropagatorFactory.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/PropagationApplier.hh" -#include "detail/UniformFieldPropagatorFactory.hh" + +// Field classes +#include "celeritas/field/UniformField.hh" namespace celeritas { @@ -46,7 +49,7 @@ void AlongStepUniformMscAction::step(CoreParams const& params, state.ptr(), detail::IsAlongStepUniformField{this->action_id(), field}, detail::PropagationApplier{ - detail::UniformFieldPropagatorFactory{field}}}; + detail::FieldTrackPropagator{field}}}; static ActionLauncher const launch_kernel( *this, "propagate"); launch_kernel(*this, params, state, execute_thread); @@ -59,7 +62,7 @@ void AlongStepUniformMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), detail::IsAlongStepLinear{this->action_id(), field}, - detail::PropagationApplier{detail::LinearPropagatorFactory{}}}; + detail::PropagationApplier{detail::LinearTrackPropagator{}}}; static ActionLauncher const launch_kernel( *this, "propagate-linear"); launch_kernel(*this, params, state, execute_thread); diff --git a/src/celeritas/alongstep/detail/AlongStepKernels.cu b/src/celeritas/alongstep/detail/AlongStepKernels.cu index 175945ba58..b74fd9ce75 100644 --- a/src/celeritas/alongstep/detail/AlongStepKernels.cu +++ b/src/celeritas/alongstep/detail/AlongStepKernels.cu @@ -17,7 +17,7 @@ #include "ElossApplier.hh" #include "FluctELoss.hh" -#include "LinearPropagatorFactory.hh" +#include "LinearTrackPropagator.hh" #include "MeanELoss.hh" #include "MscApplier.hh" #include "MscStepLimitApplier.hh" @@ -58,7 +58,7 @@ void launch_propagate(CoreStepActionInterface const& action, params.ptr(), state.ptr(), action.action_id(), - detail::PropagationApplier{detail::LinearPropagatorFactory{}}); + detail::PropagationApplier{detail::LinearTrackPropagator{}}); static ActionLauncher const launch_kernel( action, "propagate-linear"); launch_kernel(action, params, state, execute_thread); diff --git a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh index 9d2d2bfcb7..0314f7a006 100644 --- a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh +++ b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh @@ -6,18 +6,18 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/Assert.hh" -#include "corecel/Macros.hh" -#include "corecel/Types.hh" -#include "corecel/math/Quantity.hh" +#include "celeritas/global/CoreTrackView.hh" -#include "../AlongStep.hh" +#include "ElossApplier.hh" // IWYU pragma: associated +#include "LinearTrackPropagator.hh" // IWYU pragma: associated +#include "MscApplier.hh" // IWYU pragma: associated +#include "MscStepLimitApplier.hh" // IWYU pragma: associated +#include "PropagationApplier.hh" // IWYU pragma: associated +#include "TimeUpdater.hh" // IWYU pragma: associated +#include "TrackUpdater.hh" // IWYU pragma: associated namespace celeritas { -//---------------------------------------------------------------------------// -class CoreTrackView; - namespace detail { //---------------------------------------------------------------------------// @@ -54,6 +54,32 @@ struct NoELoss } }; +//---------------------------------------------------------------------------// +/*! + * Perform the along-step action using helper functions. + */ +struct AlongStepNeutralExecutor +{ + inline CELER_FUNCTION void operator()(CoreTrackView& track); + + NoMsc msc; + LinearTrackPropagator propagate_track; + NoELoss eloss; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +CELER_FUNCTION void AlongStepNeutralExecutor::operator()(CoreTrackView& track) +{ + MscStepLimitApplier{msc}(track); + PropagationApplier{propagate_track}(track); + MscApplier{msc}(track); + TimeUpdater{}(track); + ElossApplier{eloss}(track); + TrackUpdater{}(track); +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh deleted file mode 100644 index 792bf44db7..0000000000 --- a/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/CartMapField.hh" // IWYU pragma: associated -#include "celeritas/field/CartMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in a cartesian map magnetic field. - */ -struct CartMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - CartMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh deleted file mode 100644 index d83e78750b..0000000000 --- a/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/CylMapField.hh" // IWYU pragma: associated -#include "celeritas/field/CylMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in an Cyl map magnetic field. - */ -struct CylMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - CylMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/FieldFunctors.hh b/src/celeritas/alongstep/detail/FieldFunctors.hh index c12dfc7392..37ca3df993 100644 --- a/src/celeritas/alongstep/detail/FieldFunctors.hh +++ b/src/celeritas/alongstep/detail/FieldFunctors.hh @@ -10,6 +10,7 @@ #include "corecel/sys/ThreadId.hh" #include "celeritas/Types.hh" #include "celeritas/field/UniformFieldData.hh" +#include "celeritas/track/TrackFunctors.hh" namespace celeritas { diff --git a/src/celeritas/alongstep/detail/FieldTrackPropagator.hh b/src/celeritas/alongstep/detail/FieldTrackPropagator.hh new file mode 100644 index 0000000000..82e26aefb1 --- /dev/null +++ b/src/celeritas/alongstep/detail/FieldTrackPropagator.hh @@ -0,0 +1,91 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/alongstep/detail/FieldTrackPropagator.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "celeritas/field/DormandPrinceIntegrator.hh" +#include "celeritas/field/MakeMagFieldPropagator.hh" +#include "celeritas/global/CoreTrackView.hh" + +#if !CELER_DEVICE_COMPILE +# include "corecel/io/Logger.hh" +#endif + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Propagate a track in a mapped magnetic field. + * + * This class moves the track a single step based on the current sim step + * length. It updates the particle with looping behavior if necessary. + * + * \tparam Field The field type (e.g., RZMapField, CylMapField, CartMapField) + */ +template +struct FieldTrackPropagator +{ + //!@{ + //! \name Type aliases + using ParamsRef = typename Field::ParamsRef; + //!@} + + //! Create propagator, execute propagation, and return result + [[nodiscard]] CELER_FUNCTION Propagation + operator()(CoreTrackView& track) const; + + //// DATA //// + + ParamsRef field; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Create propagator, execute propagation, and return result. + */ +template +inline CELER_FUNCTION Propagation +FieldTrackPropagator::operator()(CoreTrackView& track) const +{ + auto sim = track.sim(); + auto propagator = make_mag_field_propagator( + Field{field}, field.options, track.particle(), track.geometry()); + + Propagation p = propagator(sim.step_length()); + + sim.update_looping(p.looping); + if (p.looping) + { + sim.step_length(p.distance); + sim.post_step_action([&track, &sim] { + auto particle = track.particle(); + if (particle.is_stable() + && sim.is_looping(particle.particle_id(), particle.energy())) + { +#if !CELER_DEVICE_COMPILE + CELER_LOG_LOCAL(debug) + << "Track (pid=" << particle.particle_id().get() + << ", E=" << particle.energy().value() << ' ' + << ParticleTrackView::Energy::unit_type::label() + << ") is looping after " << sim.num_looping_steps() + << " steps"; +#endif + return track.tracking_cut_action(); + } + return track.propagation_limit_action(); + }()); + } + return p; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/LinearPropagatorFactory.hh b/src/celeritas/alongstep/detail/LinearTrackPropagator.hh similarity index 65% rename from src/celeritas/alongstep/detail/LinearPropagatorFactory.hh rename to src/celeritas/alongstep/detail/LinearTrackPropagator.hh index 2a7baf763d..f438ecc565 100644 --- a/src/celeritas/alongstep/detail/LinearPropagatorFactory.hh +++ b/src/celeritas/alongstep/detail/LinearTrackPropagator.hh @@ -2,14 +2,12 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/LinearPropagatorFactory.hh +//! \file celeritas/alongstep/detail/LinearTrackPropagator.hh //---------------------------------------------------------------------------// #pragma once -#include "corecel/Macros.hh" -#include "celeritas/Types.hh" #include "celeritas/field/LinearPropagator.hh" -#include "celeritas/geo/GeoTrackView.hh" +#include "celeritas/global/CoreTrackView.hh" namespace celeritas { @@ -19,14 +17,12 @@ namespace detail /*! * Create a propagator for neutral particles or no fields. */ -struct LinearPropagatorFactory +struct LinearTrackPropagator { - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const + CELER_FUNCTION Propagation operator()(CoreTrackView const& track) const { - return LinearPropagator{track.geometry()}; + return LinearPropagator{track.geometry()}(track.sim().step_length()); } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return false; } }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/detail/PropagationApplier.hh b/src/celeritas/alongstep/detail/PropagationApplier.hh index c0bccb4a93..89024e7c04 100644 --- a/src/celeritas/alongstep/detail/PropagationApplier.hh +++ b/src/celeritas/alongstep/detail/PropagationApplier.hh @@ -6,10 +6,7 @@ //---------------------------------------------------------------------------// #pragma once -#include - -#include "corecel/math/Algorithms.hh" -#include "corecel/sys/KernelTraits.hh" +#include "corecel/Assert.hh" #include "celeritas/global/CoreTrackView.hh" #if !CELER_DEVICE_COMPILE @@ -21,100 +18,44 @@ namespace celeritas { namespace detail { -//---------------------------------------------------------------------------// -/*! - * Apply propagation over the step (implementation). - */ -template -struct PropagationApplierBaseImpl -{ - inline CELER_FUNCTION void operator()(CoreTrackView& track); - - MP make_propagator; -}; - //---------------------------------------------------------------------------// /*! * Apply propagation over the step. * - * \tparam MP Propagator factory + * \tparam TP Track propagator * - * MP should be a function-like object: - * \code Propagator(*)(CoreTrackView const&) \endcode - * - * This class is partially specialized with a second template argument to - * extract any launch bounds from the MP class. TODO: we could probably inherit - * from a helper class to pull in those constants (if available). + * TP should be a function-like object: + * \code Propagation (*)(CoreTrackView const&) \endcode */ -template -struct PropagationApplier : public PropagationApplierBaseImpl -{ - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } -}; - -template -struct PropagationApplier>> - : public PropagationApplierBaseImpl -{ - static constexpr int max_block_size = MP::max_block_size; - static constexpr int min_warps_per_eu = MP::min_warps_per_eu; - - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } -}; - -template -struct PropagationApplier>> - : public PropagationApplierBaseImpl +template +struct PropagationApplier { - static constexpr int max_block_size = MP::max_block_size; + inline CELER_FUNCTION void operator()(CoreTrackView& track); - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } + TP propagate; }; //---------------------------------------------------------------------------// // DEDUCTION GUIDES //---------------------------------------------------------------------------// -template -CELER_FUNCTION PropagationApplier(MP&&) -> PropagationApplier; +template +CELER_FUNCTION PropagationApplier(TP&&) -> PropagationApplier; //---------------------------------------------------------------------------// // INLINE DEFINITIONS //---------------------------------------------------------------------------// -template -CELER_FUNCTION void -PropagationApplierBaseImpl::operator()(CoreTrackView& track) +template +CELER_FUNCTION void PropagationApplier::operator()(CoreTrackView& track) { auto sim = track.sim(); - if (sim.step_length() == 0) - { - // Track is stopped: no movement or energy loss will happen - // (could be a stopped positron waiting for annihilation, or a - // particle waiting to decay?) - CELER_ASSERT(track.particle().is_stopped()); - CELER_ASSERT(sim.post_step_action() - == track.physics().scalars().discrete_action()); - CELER_ASSERT(track.physics().at_rest_process()); - return; - } + CELER_EXPECT(sim.step_length() > 0); - bool tracks_can_loop; Propagation p; { #if CELERITAS_DEBUG Real3 const orig_pos = track.geometry().pos(); #endif - auto propagate = make_propagator(track); - p = propagate(sim.step_length()); - tracks_can_loop = propagate.tracks_can_loop(); + p = this->propagate(track); CELER_ASSERT(p.distance > 0); #if CELERITAS_DEBUG if (CELER_UNLIKELY(track.geometry().pos() == orig_pos)) @@ -136,50 +77,18 @@ PropagationApplierBaseImpl::operator()(CoreTrackView& track) << " failed to change position"; # endif track.apply_errored(); - return; } #endif } - if (tracks_can_loop) - { - sim.update_looping(p.looping); - } - if (tracks_can_loop && p.looping) - { - // The track is looping, i.e. progressing little over many - // integration steps in the field propagator (likely a low energy - // particle in a low density material/strong magnetic field). - sim.step_length(p.distance); - - // Kill the track if it's stable and below the threshold energy or - // above the threshold number of steps allowed while looping. - sim.post_step_action([&track, &sim] { - auto particle = track.particle(); - if (particle.is_stable() - && sim.is_looping(particle.particle_id(), particle.energy())) - { -#if !CELER_DEVICE_COMPILE - CELER_LOG_LOCAL(debug) - << "Track (pid=" << particle.particle_id().get() - << ", E=" << particle.energy().value() << ' ' - << ParticleTrackView::Energy::unit_type::label() - << ") is looping after " << sim.num_looping_steps() - << " steps"; -#endif - return track.tracking_cut_action(); - } - return track.propagation_limit_action(); - }()); - } - else if (p.boundary) + if (p.boundary) { // Stopped at a geometry boundary: this is the new step action. CELER_ASSERT(p.distance <= sim.step_length()); sim.step_length(p.distance); sim.post_step_action(track.boundary_action()); } - else if (p.distance < sim.step_length()) + else if (!p.looping && p.distance < sim.step_length()) { // Some tracks may get stuck on a boundary and fail to move at // all in the field propagator, and will get bumped a small diff --git a/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh deleted file mode 100644 index 76f93058c7..0000000000 --- a/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/field/RZMapField.hh" // IWYU pragma: associated -#include "celeritas/field/RZMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in an RZ map magnetic field. - */ -struct RZMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - RZMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh deleted file mode 100644 index 40ea543d09..0000000000 --- a/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh +++ /dev/null @@ -1,47 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "corecel/Macros.hh" -#include "celeritas/field/DormandPrinceIntegrator.hh" // IWYU pragma: associated -#include "celeritas/field/MakeMagFieldPropagator.hh" // IWYU pragma: associated -#include "celeritas/field/UniformField.hh" // IWYU pragma: associated -#include "celeritas/field/UniformFieldData.hh" // IWYU pragma: associated - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in a uniform magnetic field. - */ -struct UniformFieldPropagatorFactory -{ -#if CELER_USE_DEVICE - inline static constexpr int max_block_size = 256; -#endif - - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - UniformField(field.field), - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/field/CartMapField.covfie.hh b/src/celeritas/field/CartMapField.covfie.hh index c3af1c3825..9649f3a77f 100644 --- a/src/celeritas/field/CartMapField.covfie.hh +++ b/src/celeritas/field/CartMapField.covfie.hh @@ -6,14 +6,12 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "corecel/Macros.hh" #include "corecel/Types.hh" #include "corecel/cont/Array.hh" -#include "corecel/cont/Range.hh" #include "celeritas/Types.hh" -#include "celeritas/field/CartMapFieldData.hh" + +#include "CartMapFieldData.hh" // IWYU pragma: keep #include "detail/CovfieFieldTraits.hh" @@ -30,19 +28,19 @@ class CartMapField //! \name Type aliases using real_type = float; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit CartMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit CartMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION inline Real3 operator()(Real3 const& pos) const; private: - using field_view_t = FieldParamsRef::view_t; + using field_view_t = ParamsRef::view_t; field_view_t const& field_; }; @@ -53,8 +51,7 @@ class CartMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -CartMapField::CartMapField(FieldParamsRef const& shared) - : field_{shared.get_view()} +CartMapField::CartMapField(ParamsRef const& shared) : field_{shared.get_view()} { } diff --git a/src/celeritas/field/CylMapField.hh b/src/celeritas/field/CylMapField.hh index eccbee69ce..e9491022d5 100644 --- a/src/celeritas/field/CylMapField.hh +++ b/src/celeritas/field/CylMapField.hh @@ -39,12 +39,12 @@ class CylMapField //! \name Type aliases using real_type = cylmap_real_type; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit CylMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit CylMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -52,7 +52,7 @@ class CylMapField private: // Shared constant field map - FieldParamsRef const& params_; + ParamsRef const& params_; NonuniformGrid const grid_r_; NonuniformGrid const grid_phi_; @@ -66,7 +66,7 @@ class CylMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -CylMapField::CylMapField(FieldParamsRef const& params) +CylMapField::CylMapField(ParamsRef const& params) : params_{params} , grid_r_{params_.grids.axes[CylAxis::r], params_.grids.storage} , grid_phi_{params_.grids.axes[CylAxis::phi], params_.grids.storage} diff --git a/src/celeritas/field/FieldPropagator.hh b/src/celeritas/field/FieldPropagator.hh index 0723344584..ac7a1abe3f 100644 --- a/src/celeritas/field/FieldPropagator.hh +++ b/src/celeritas/field/FieldPropagator.hh @@ -54,15 +54,9 @@ class FieldPropagator ParticleTrackView const& particle, GTV&& geo); - // Move track to next volume boundary. - inline CELER_FUNCTION result_type operator()(); - // Move track up to a user-provided distance, or to the next boundary inline CELER_FUNCTION result_type operator()(real_type dist); - //! Whether it's possible to have tracks that are looping - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - private: //! Limit on substeps inline CELER_FUNCTION short int max_substeps() const; @@ -109,17 +103,6 @@ CELER_FUNCTION FieldPropagator::FieldPropagator( state_.mom = value_as(particle.momentum()) * geo_.dir(); } -//---------------------------------------------------------------------------// -/*! - * Propagate a charged particle until it hits a boundary. - */ -template -CELER_FUNCTION auto FieldPropagator::operator()() - -> result_type -{ - return (*this)(numeric_limits::infinity()); -} - //---------------------------------------------------------------------------// /*! * Propagate a charged particle in a field. diff --git a/src/celeritas/field/LinearPropagator.hh b/src/celeritas/field/LinearPropagator.hh index 4285ad1b93..7463144570 100644 --- a/src/celeritas/field/LinearPropagator.hh +++ b/src/celeritas/field/LinearPropagator.hh @@ -31,15 +31,9 @@ class LinearPropagator { } - // Move track to next volume boundary. - inline CELER_FUNCTION result_type operator()(); - // Move track up to a user-provided distance, up to the next boundary inline CELER_FUNCTION result_type operator()(real_type dist); - //! Whether it's possible to have tracks that are looping - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return false; } - private: GTV geo_; }; @@ -50,22 +44,6 @@ class LinearPropagator template CELER_FUNCTION LinearPropagator(GTV&&) -> LinearPropagator; -//---------------------------------------------------------------------------// -/*! - * Move track to next volume boundary. - */ -template -CELER_FUNCTION auto LinearPropagator::operator()() -> result_type -{ - CELER_EXPECT(!geo_.is_outside()); - - result_type result = geo_.find_next_step(); - CELER_ASSERT(result.boundary); - geo_.move_to_boundary(); - - return result; -} - //---------------------------------------------------------------------------// /*! * Move track by a user-provided distance up to the next boundary. diff --git a/src/celeritas/field/RZMapField.hh b/src/celeritas/field/RZMapField.hh index e2563919d1..ce3dee6dec 100644 --- a/src/celeritas/field/RZMapField.hh +++ b/src/celeritas/field/RZMapField.hh @@ -30,12 +30,12 @@ class RZMapField //!@{ //! \name Type aliases using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit RZMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit RZMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -43,7 +43,7 @@ class RZMapField private: // Shared constant field map - FieldParamsRef const& params_; + ParamsRef const& params_; UniformGrid const grid_r_; UniformGrid const grid_z_; @@ -56,7 +56,7 @@ class RZMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -RZMapField::RZMapField(FieldParamsRef const& params) +RZMapField::RZMapField(ParamsRef const& params) : params_(params) , grid_r_(params_.grids.data_r) , grid_z_(params_.grids.data_z) diff --git a/src/celeritas/field/UniformField.hh b/src/celeritas/field/UniformField.hh index 11c22960eb..8863264d0d 100644 --- a/src/celeritas/field/UniformField.hh +++ b/src/celeritas/field/UniformField.hh @@ -8,6 +8,7 @@ #include "corecel/cont/Array.hh" #include "celeritas/Types.hh" +#include "celeritas/field/UniformFieldData.hh" namespace celeritas { @@ -21,9 +22,17 @@ namespace celeritas class UniformField { public: + using ParamsRef = NativeCRef; + //! Construct with a field vector explicit CELER_FUNCTION UniformField(Real3 const& value) : value_(value) {} + //! Construct with field params + explicit CELER_FUNCTION UniformField(ParamsRef const& params) + : value_(params.field) + { + } + //! Return the field at the given position CELER_FUNCTION Real3 const& operator()(Real3 const&) const { diff --git a/src/celeritas/field/detail/NotImplementedField.hh b/src/celeritas/field/detail/NotImplementedField.hh index 39e0af598f..4ed24ad881 100644 --- a/src/celeritas/field/detail/NotImplementedField.hh +++ b/src/celeritas/field/detail/NotImplementedField.hh @@ -27,12 +27,12 @@ class NotImplementedField //! \name Type aliases using real_type = float; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit NotImplementedField(FieldParamsRef const&); + inline CELER_FUNCTION explicit NotImplementedField(ParamsRef const&); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -40,7 +40,7 @@ class NotImplementedField }; CELER_FUNCTION -NotImplementedField::NotImplementedField(FieldParamsRef const&) +NotImplementedField::NotImplementedField(ParamsRef const&) { CELER_NOT_CONFIGURED("covfie"); } diff --git a/src/celeritas/global/TrackExecutor.hh b/src/celeritas/global/TrackExecutor.hh index 041558cb31..8ee00589a7 100644 --- a/src/celeritas/global/TrackExecutor.hh +++ b/src/celeritas/global/TrackExecutor.hh @@ -127,6 +127,8 @@ class ConditionalTrackExecutor return; } + // NOTE: "return value type" error means the executor function is + // incorrectly returning a value return execute_track_(track); } diff --git a/src/celeritas/phys/detail/PreStepExecutor.hh b/src/celeritas/phys/detail/PreStepExecutor.hh index d38d702a33..2443aae0e6 100644 --- a/src/celeritas/phys/detail/PreStepExecutor.hh +++ b/src/celeritas/phys/detail/PreStepExecutor.hh @@ -102,7 +102,11 @@ PreStepExecutor::operator()(celeritas::CoreTrackView const& track) // Initialize along-step action based on particle charge: // This should eventually be dependent on region, energy, etc. sim.along_step_action([&particle, &scalars = track.core_scalars()] { - if (particle.charge() == zero_quantity()) + if (particle.is_stopped()) + { + return ActionId{}; + } + else if (particle.charge() == zero_quantity()) { return scalars.along_step_neutral_action; } diff --git a/src/geocel/vg/detail/BVHNavigator.hh b/src/geocel/vg/detail/BVHNavigator.hh index f393896db2..065d9e2285 100644 --- a/src/geocel/vg/detail/BVHNavigator.hh +++ b/src/geocel/vg/detail/BVHNavigator.hh @@ -49,7 +49,11 @@ class BVHNavigator using NavState = detail::VgNavStateWrapper; #endif - static constexpr vg_real_type kBoundaryPush = 10 * vecgeom::kTolerance; +#ifdef VECGEOM_FLOAT_PRECISION + static constexpr vg_real_type kBoundaryPush = 10 * 1e-3f; +#else + static constexpr vg_real_type kBoundaryPush = 10 * 1e-9; +#endif //! Update path (which must be reset in advance) CELER_FUNCTION static void diff --git a/test/celeritas/field/Integrators.test.cc b/test/celeritas/field/Integrators.test.cc index 02366c43c7..03f1ad1f3f 100644 --- a/test/celeritas/field/Integrators.test.cc +++ b/test/celeritas/field/Integrators.test.cc @@ -156,7 +156,7 @@ TEST_F(IntegratorsTest, host_helix) TEST_F(IntegratorsTest, host_classical_rk4) { // Construct a uniform magnetic field - UniformField field({0, 0, param.field_value}); + UniformField field(Real3{0, 0, param.field_value}); // Test the classical 4th order Runge-Kutta integrate this->run_integration(field); @@ -166,7 +166,7 @@ TEST_F(IntegratorsTest, host_classical_rk4) TEST_F(IntegratorsTest, host_dormand_prince_547) { // Construct a uniform magnetic field - UniformField field({0, 0, param.field_value}); + UniformField field(Real3{0, 0, param.field_value}); // Test the Dormand-Prince 547(M) integrate this->run_integration(field); diff --git a/test/celeritas/field/LinearPropagator.test.cc b/test/celeritas/field/LinearPropagator.test.cc index 825401a152..f81a7311a8 100644 --- a/test/celeritas/field/LinearPropagator.test.cc +++ b/test/celeritas/field/LinearPropagator.test.cc @@ -109,7 +109,7 @@ TEST_F(LinearPropagatorTest, simple_cms) LinearPropagator propagate(geo); // Move to result boundary (infinite max distance) - Propagation result = propagate(); + Propagation result = propagate(numeric_limits::infinity()); EXPECT_SOFT_EQ(20, to_cm(result.distance)); EXPECT_TRUE(result.boundary); geo.cross_boundary(); From bebf203b74338b7debec481897e0409f40e5d014 Mon Sep 17 00:00:00 2001 From: Hayden Hollenbeck <127582780+hhollenb@users.noreply.github.com> Date: Sun, 18 Jan 2026 10:14:56 -0600 Subject: [PATCH 40/60] Add builtin surface roughness models (#1928) * Initial draft of surface normals * Disabled some tests not using ORANGE * Revert regressions * Polished and Smear roughness models * Saving progress after fixing some weird bugs * Refactor RoughnessModel * Switch roughness model to builtin surface model * Refined some of builtin roughness models * Add missing data file from cherry-pick * Add documentation * Added builtin surface model builder * Added surface model view tests * Cleanup surface physics view a bit * Pre PR clean up * Revert sincos change in GaussianRoughnessSampler * Added Seth's changes * Fix failing tests * Reverted sincos using global namespace * Added some of Seth's minor suggestions * Refactored builtin surface model input * Cache track subsurface traversal direction * Re-enabled celer-sim tests involving surface normals * IWYU * Fix comparison of containers of string_view * Remove anonymous namespace * Changed Gaussian roughness sampler to match Geant4 implementation * Added back in f_max for approximate rejection sampling in Gaussian roughness Sampler * Draft of roughness tests * Refactor out roughness and interaction integration tests * Fix optical core params in test base * Weird debug statement issue * IWYU * Add very large sigma alpha roughness calculator test * Add loop guard and comments * Remove loop guard and have roughness samplers copy normal to avoid free after use error * Revert superficial/accidental whitespace changes * Restore loop guard * Run but don't check reference configuration * Use turns, avoid using namespace * Skip tests that fail with vecgeom surface * fixup! Skip tests that fail with vecgeom surface --------- Co-authored-by: Hayden Hollenbeck Co-authored-by: Seth R Johnson --- src/celeritas/CMakeLists.txt | 4 +- .../surface/GaussianRoughnessSampler.hh | 14 +- .../optical/surface/SmearRoughnessSampler.hh | 2 +- src/celeritas/optical/surface/SurfaceModel.hh | 4 - .../optical/surface/SurfacePhysicsParams.cc | 9 +- .../surface/SurfacePhysicsTrackView.hh | 3 +- .../optical/surface/SurfacePhysicsUtils.hh | 21 +- .../detail/BuiltinSurfaceModelBuilder.hh | 9 +- .../surface/model/GaussianRoughnessData.hh | 58 ++ .../model/GaussianRoughnessExecutor.hh | 44 ++ .../surface/model/GaussianRoughnessModel.cc | 81 +++ .../surface/model/GaussianRoughnessModel.cu | 40 ++ .../surface/model/GaussianRoughnessModel.hh | 60 ++ .../surface/model/SmearRoughnessData.hh | 57 ++ .../surface/model/SmearRoughnessExecutor.hh | 47 ++ .../surface/model/SmearRoughnessModel.cc | 81 +++ .../surface/model/SmearRoughnessModel.cu | 40 ++ .../surface/model/SmearRoughnessModel.hh | 59 ++ .../model/SurfaceInteractionApplier.hh | 2 +- src/celeritas/phys/SurfaceModel.hh | 2 +- test/celeritas/CMakeLists.txt | 11 +- .../optical/RoughnessCalculator.test.cc | 13 +- test/celeritas/optical/SurfacePhysics.test.cc | 4 +- .../optical/SurfacePhysicsIntegration.test.cc | 558 ------------------ .../SurfacePhysicsIntegrationTestBase.cc | 116 ++++ .../SurfacePhysicsIntegrationTestBase.hh | 151 +++++ ...rfacePhysicsInteractionIntegration.test.cc | 356 +++++++++++ ...SurfacePhysicsRoughnessIntegration.test.cc | 209 +++++++ test/testdetail/TestMacrosImpl.hh | 31 +- 29 files changed, 1482 insertions(+), 604 deletions(-) create mode 100644 src/celeritas/optical/surface/model/GaussianRoughnessData.hh create mode 100644 src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh create mode 100644 src/celeritas/optical/surface/model/GaussianRoughnessModel.cc create mode 100644 src/celeritas/optical/surface/model/GaussianRoughnessModel.cu create mode 100644 src/celeritas/optical/surface/model/GaussianRoughnessModel.hh create mode 100644 src/celeritas/optical/surface/model/SmearRoughnessData.hh create mode 100644 src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh create mode 100644 src/celeritas/optical/surface/model/SmearRoughnessModel.cc create mode 100644 src/celeritas/optical/surface/model/SmearRoughnessModel.cu create mode 100644 src/celeritas/optical/surface/model/SmearRoughnessModel.hh delete mode 100644 test/celeritas/optical/SurfacePhysicsIntegration.test.cc create mode 100644 test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc create mode 100644 test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh create mode 100644 test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc create mode 100644 test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc diff --git a/src/celeritas/CMakeLists.txt b/src/celeritas/CMakeLists.txt index 8d7cc5269d..4a108c8774 100644 --- a/src/celeritas/CMakeLists.txt +++ b/src/celeritas/CMakeLists.txt @@ -394,8 +394,10 @@ celeritas_polysource(optical/gen/DirectGeneratorAction) celeritas_polysource(optical/gen/detail/GeneratorAlgorithms) celeritas_polysource(optical/gen/detail/OffloadAlgorithms) celeritas_polysource(optical/surface/BoundaryAction) -celeritas_polysource(optical/surface/model/PolishedRoughnessModel) celeritas_polysource(optical/surface/model/DielectricInteractionModel) +celeritas_polysource(optical/surface/model/GaussianRoughnessModel) +celeritas_polysource(optical/surface/model/PolishedRoughnessModel) +celeritas_polysource(optical/surface/model/SmearRoughnessModel) celeritas_polysource(optical/surface/model/TrivialInteractionModel) celeritas_polysource(phys/detail/DiscreteSelectAction) celeritas_polysource(phys/detail/PreStepAction) diff --git a/src/celeritas/optical/surface/GaussianRoughnessSampler.hh b/src/celeritas/optical/surface/GaussianRoughnessSampler.hh index 2277101888..64e76b8a42 100644 --- a/src/celeritas/optical/surface/GaussianRoughnessSampler.hh +++ b/src/celeritas/optical/surface/GaussianRoughnessSampler.hh @@ -9,10 +9,8 @@ #include #include "corecel/math/Algorithms.hh" -#include "corecel/math/ArrayUtils.hh" #include "corecel/random/distribution/NormalDistribution.hh" #include "corecel/random/distribution/RejectionSampler.hh" -#include "celeritas/Constants.hh" #include "celeritas/phys/InteractionUtils.hh" namespace celeritas @@ -59,9 +57,8 @@ class GaussianRoughnessSampler inline CELER_FUNCTION Real3 operator()(Engine& rng); private: - Real3 const& normal_; + Real3 normal_; NormalDistribution sample_alpha_; - real_type alpha_max_; real_type f_max_; }; @@ -76,8 +73,7 @@ GaussianRoughnessSampler::GaussianRoughnessSampler(Real3 const& normal, real_type sigma_alpha) : normal_(normal) , sample_alpha_(0, sigma_alpha) - , alpha_max_(fmin(real_type(constants::pi / 2), 4 * sigma_alpha)) - , f_max_(std::sin(alpha_max_)) + , f_max_(fmin(real_type{1}, 4 * sigma_alpha)) { CELER_EXPECT(sigma_alpha > 0); CELER_EXPECT(is_soft_unit_vector(normal_)); @@ -102,13 +98,13 @@ CELER_FUNCTION Real3 GaussianRoughnessSampler::operator()(Engine& rng) // Sample positive angle according to gaussian (chances of having a // nonpositive slope are generally vanishingly small) alpha = std::fabs(sample_alpha_(rng)); - } while (alpha >= alpha_max_); + } while (alpha >= real_type(constants::pi / 2)); sincos(alpha, &sin_alpha, &cos_alpha); // Transform to polar angle using rejection - } while (RejectionSampler{sin_alpha, f_max_}(rng)); + } while (sin_alpha < f_max_ && RejectionSampler{sin_alpha, f_max_}(rng)); - // Rotate normal by alpha and then sample azimuth rotation uniformly + // Rotate normal by alpha and then sample azimuthal rotation uniformly return ExitingDirectionSampler{cos_alpha, normal_}(rng); } diff --git a/src/celeritas/optical/surface/SmearRoughnessSampler.hh b/src/celeritas/optical/surface/SmearRoughnessSampler.hh index 61f85be6cd..f0679edd99 100644 --- a/src/celeritas/optical/surface/SmearRoughnessSampler.hh +++ b/src/celeritas/optical/surface/SmearRoughnessSampler.hh @@ -40,7 +40,7 @@ class SmearRoughnessSampler inline CELER_FUNCTION Real3 operator()(Engine& rng) const; private: - Real3 const& normal_; + Real3 normal_; real_type roughness_; PowerDistribution<> sample_r_{2}; }; diff --git a/src/celeritas/optical/surface/SurfaceModel.hh b/src/celeritas/optical/surface/SurfaceModel.hh index 5f937fdfdc..3b34cd80cf 100644 --- a/src/celeritas/optical/surface/SurfaceModel.hh +++ b/src/celeritas/optical/surface/SurfaceModel.hh @@ -6,10 +6,6 @@ //---------------------------------------------------------------------------// #pragma once -#include -#include - -#include "celeritas/optical/Types.hh" #include "celeritas/optical/action/ActionInterface.hh" #include "celeritas/phys/SurfaceModel.hh" diff --git a/src/celeritas/optical/surface/SurfacePhysicsParams.cc b/src/celeritas/optical/surface/SurfacePhysicsParams.cc index 9e6da2bba2..191a3300d6 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsParams.cc +++ b/src/celeritas/optical/surface/SurfacePhysicsParams.cc @@ -9,6 +9,8 @@ #include "corecel/data/CollectionBuilder.hh" #include "corecel/sys/ActionRegistry.hh" #include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/model/GaussianRoughnessModel.hh" +#include "celeritas/optical/surface/model/SmearRoughnessModel.hh" #include "celeritas/phys/SurfacePhysicsMapBuilder.hh" #include "model/DielectricInteractionModel.hh" @@ -140,8 +142,9 @@ auto SurfacePhysicsParams::build_models( case SurfacePhysicsOrder::roughness: build_model.build( input.roughness.polished); - build_model.build_fake("smear", input.roughness.smear); - build_model.build_fake("gaussian", input.roughness.gaussian); + build_model.build(input.roughness.smear); + build_model.build( + input.roughness.gaussian); break; case SurfacePhysicsOrder::reflectivity: build_model.build_fake("grid", input.reflectivity.grid); @@ -159,7 +162,7 @@ auto SurfacePhysicsParams::build_models( CELER_VALIDATE( build_model.num_surfaces() == num_phys_surfaces(input.materials), - << " same number of physics surfaces required for each " + << "same number of physics surfaces required for each " "surface physics step (" << num_phys_surfaces(input.materials) << " expected surfaces, " << build_model.num_surfaces() << " surfaces from " diff --git a/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh b/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh index 7838d9c571..d21c371f4d 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh +++ b/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh @@ -6,7 +6,7 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/math/ArrayUtils.hh" +#include "corecel/math/ArrayOperators.hh" #include "celeritas/optical/Types.hh" #include "celeritas/phys/SurfacePhysicsMapView.hh" @@ -237,6 +237,7 @@ CELER_FUNCTION void SurfacePhysicsTrackView::facet_normal(Real3 const& normal) { CELER_EXPECT(this->is_crossing_boundary()); CELER_EXPECT(is_soft_unit_vector(normal)); + CELER_EXPECT(dot_product(normal, this->global_normal()) >= 0); states_.facet_normal[track_id_] = normal; } diff --git a/src/celeritas/optical/surface/SurfacePhysicsUtils.hh b/src/celeritas/optical/surface/SurfacePhysicsUtils.hh index 8f8f66438f..f47d4356b7 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsUtils.hh +++ b/src/celeritas/optical/surface/SurfacePhysicsUtils.hh @@ -6,12 +6,15 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/data/Collection.hh" -#include "corecel/math/Algorithms.hh" -#include "corecel/math/ArrayOperators.hh" +#include "corecel/Macros.hh" #include "corecel/math/ArrayUtils.hh" #include "celeritas/optical/Types.hh" +#if !CELER_DEVICE_COMPILE +# include "corecel/io/Logger.hh" +# include "corecel/io/Repr.hh" +#endif + namespace celeritas { namespace optical @@ -96,8 +99,20 @@ class EnteringSurfaceNormalSampler CELER_FUNCTION Real3 operator()(Engine& rng) { Real3 local_normal; + int loop_guard{256}; do { + --loop_guard; +#if !CELER_DEVICE_COMPILE + CELER_VALIDATE( + loop_guard > 0, + << "failed to sample a microfacet direction into which " + << repr(dir_) << " is entering (last normal sampled: " + << repr(local_normal) << ")"); +#else + if (CELER_UNLIKELY(loop_guard == 0)) + return {0, 0, 0}; +#endif local_normal = sample_normal_(rng); } while (!is_entering_surface(local_normal, dir_)); return local_normal; diff --git a/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh b/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh index 0ea3b65950..773c4aa06e 100644 --- a/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh +++ b/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh @@ -6,19 +6,20 @@ //---------------------------------------------------------------------------// #pragma once +#include #include +#include +#include +#include "celeritas/Types.hh" #include "celeritas/optical/surface/SurfaceModel.hh" namespace celeritas { namespace optical { -//---------------------------------------------------------------------------// namespace detail { -namespace -{ //---------------------------------------------------------------------------// /*! * Fake model as a placeholder for surface models yet to be implemented. @@ -53,8 +54,6 @@ class FakeModel : public SurfaceModel VecSurfaceLayer layers_; }; -} // namespace - //---------------------------------------------------------------------------// /*! * Utility for building built-in surface models from input data. diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessData.hh b/src/celeritas/optical/surface/model/GaussianRoughnessData.hh new file mode 100644 index 0000000000..e0d2dd4196 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessData.hh @@ -0,0 +1,58 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/Collection.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Storage for Gaussian roughness model data. + */ +template +struct GaussianRoughnessData +{ + //!@{ + //! \name Type aliases + template + using SurfaceItems = Collection; + //!@} + + //// DATA ///// + + SurfaceItems sigma_alpha; + + //// METHODS //// + + //! True if assigned + explicit CELER_FUNCTION operator bool() const + { + return !sigma_alpha.empty(); + } + + //! Assign from another set of data + template + GaussianRoughnessData& + operator=(GaussianRoughnessData const& other) + { + CELER_EXPECT(other); + + sigma_alpha = other.sigma_alpha; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh b/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh new file mode 100644 index 0000000000..f177d0b900 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh @@ -0,0 +1,44 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/surface/GaussianRoughnessSampler.hh" + +#include "GaussianRoughnessData.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Sample and save a facet normal for the Gaussian roughness model. + */ +struct GaussianRoughnessExecutor +{ + NativeCRef data; + + //! Apply Gaussian roughness executor + CELER_FUNCTION void operator()(CoreTrackView& track) const + { + auto s_phys = track.surface_physics(); + auto sub_model_id = s_phys.interface(SurfacePhysicsOrder::roughness) + .internal_surface_id(); + CELER_ASSERT(sub_model_id < data.sigma_alpha.size()); + EnteringSurfaceNormalSampler sample_facet{ + track.geometry().dir(), + s_phys.global_normal(), + data.sigma_alpha[sub_model_id]}; + auto rng = track.rng(); + s_phys.facet_normal(sample_facet(rng)); + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc new file mode 100644 index 0000000000..03d8bca97f --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.cc +//---------------------------------------------------------------------------// +#include "GaussianRoughnessModel.hh" + +#include + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "GaussianRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Construct the model from an ID and a layer map. + */ +GaussianRoughnessModel::GaussianRoughnessModel( + SurfaceModelId id, std::map const& layer_map) + : SurfaceModel(id, "roughness-gaussian") +{ + surfaces_.reserve(layer_map.size()); + std::transform(layer_map.begin(), + layer_map.end(), + std::back_inserter(surfaces_), + [](auto const& layer) { return layer.first; }); + + HostVal data; + auto build_sigma_alpha = CollectionBuilder{&data.sigma_alpha}; + + for (auto const& [surface, gaussian] : layer_map) + { + CELER_ENSURE(gaussian); + build_sigma_alpha.push_back(gaussian.sigma_alpha); + } + + CELER_ENSURE(data); + CELER_ENSURE(data.sigma_alpha.size() == layer_map.size()); + + data_ = CollectionMirror{std::move(data)}; +} + +//---------------------------------------------------------------------------// +/*! + * Execute model with host data. + */ +void GaussianRoughnessModel::step(CoreParams const& params, + CoreStateHost& state) const +{ + launch_action(state, + make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + GaussianRoughnessExecutor{data_.host_ref()})); +} + +//---------------------------------------------------------------------------// +/*! + * Execute kernel with device data. + */ +#if !CELER_USE_DEVICE +void GaussianRoughnessModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu new file mode 100644 index 0000000000..a947ee39bc --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu @@ -0,0 +1,40 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.cu +//---------------------------------------------------------------------------// +#include "GaussianRoughnessModel.hh" + +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.device.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "GaussianRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Launch kernel with device data. + */ +void GaussianRoughnessModel::step(CoreParams const& params, + CoreStateDevice& state) const +{ + auto execute = make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + GaussianRoughnessExecutor{data_.device_ref()}); + + static ActionLauncher const launch_kernel(*this); + launch_kernel(state, execute); +} + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh b/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh new file mode 100644 index 0000000000..dd5e4ab055 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh @@ -0,0 +1,60 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionMirror.hh" +#include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +#include "GaussianRoughnessData.hh" + +namespace celeritas +{ +namespace inp +{ +struct GaussianRoughness; +} + +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Gaussian roughness surface model. + * + * Approximates the surface roughness of an optical surface with the UNIFIED + * Gaussian roughness model. + */ +class GaussianRoughnessModel : public SurfaceModel +{ + public: + //!@{ + //! \name Type aliases + using InputT = inp::GaussianRoughness; + //!@} + + public: + // Construct the model from an ID and layer map + GaussianRoughnessModel(SurfaceModelId, + std::map const&); + + //! Get the list of physical surfaces this model applies to + VecSurfaceLayer const& get_surfaces() const final { return surfaces_; } + + // Execute the model with host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Execute the model with device data + void step(CoreParams const&, CoreStateDevice&) const final; + + private: + VecSurfaceLayer surfaces_; + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessData.hh b/src/celeritas/optical/surface/model/SmearRoughnessData.hh new file mode 100644 index 0000000000..0b386737e5 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessData.hh @@ -0,0 +1,57 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/Collection.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Storage for uniform smear roughness model data. + */ +template +struct SmearRoughnessData +{ + //!@{ + //! \name Type aliases + template + using SurfaceItems = Collection; + //!@} + + //// DATA ///// + + SurfaceItems roughness; + + //// METHODS //// + + //! True if assigned + explicit CELER_FUNCTION operator bool() const + { + return !roughness.empty(); + } + + //! Assign from another set of data + template + SmearRoughnessData& operator=(SmearRoughnessData const& other) + { + CELER_EXPECT(other); + + roughness = other.roughness; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh b/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh new file mode 100644 index 0000000000..414be17dd9 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh @@ -0,0 +1,47 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/surface/SmearRoughnessSampler.hh" +#include "celeritas/optical/surface/SurfacePhysicsUtils.hh" + +#include "SmearRoughnessData.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Sample and save a facet normal for the smear roughness model. + * + * TODO: refactor into roughness applier and SmearNormalCalculator + */ +struct SmearRoughnessExecutor +{ + NativeCRef data; + + //! Apply smear roughness executor + CELER_FUNCTION void operator()(CoreTrackView& track) const + { + auto s_phys = track.surface_physics(); + auto sub_model_id = s_phys.interface(SurfacePhysicsOrder::roughness) + .internal_surface_id(); + CELER_ASSERT(sub_model_id < data.roughness.size()); + EnteringSurfaceNormalSampler sample_facet{ + track.geometry().dir(), + s_phys.global_normal(), + data.roughness[sub_model_id]}; + auto rng = track.rng(); + s_phys.facet_normal(sample_facet(rng)); + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.cc b/src/celeritas/optical/surface/model/SmearRoughnessModel.cc new file mode 100644 index 0000000000..017b653a56 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.cc @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.cc +//---------------------------------------------------------------------------// +#include "SmearRoughnessModel.hh" + +#include + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "SmearRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Construct the model from an ID and a layer map. + */ +SmearRoughnessModel::SmearRoughnessModel( + SurfaceModelId id, std::map const& layer_map) + : SurfaceModel(id, "roughness-smear") +{ + surfaces_.reserve(layer_map.size()); + std::transform(layer_map.begin(), + layer_map.end(), + std::back_inserter(surfaces_), + [](auto const& layer) { return layer.first; }); + + HostVal data; + auto build_roughness = CollectionBuilder{&data.roughness}; + + for (auto const& [surface, smear] : layer_map) + { + CELER_ENSURE(smear); + build_roughness.push_back(smear.roughness); + } + + CELER_ENSURE(data); + CELER_ENSURE(data.roughness.size() == layer_map.size()); + + data_ = CollectionMirror{std::move(data)}; +} + +//---------------------------------------------------------------------------// +/*! + * Execute model with host data. + */ +void SmearRoughnessModel::step(CoreParams const& params, + CoreStateHost& state) const +{ + launch_action(state, + make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + SmearRoughnessExecutor{data_.host_ref()})); +} + +//---------------------------------------------------------------------------// +/*! + * Execute kernel with device data. + */ +#if !CELER_USE_DEVICE +void SmearRoughnessModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.cu b/src/celeritas/optical/surface/model/SmearRoughnessModel.cu new file mode 100644 index 0000000000..a82143c6aa --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.cu @@ -0,0 +1,40 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.cu +//---------------------------------------------------------------------------// +#include "SmearRoughnessModel.hh" + +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.device.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "SmearRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Launch kernel with device data. + */ +void SmearRoughnessModel::step(CoreParams const& params, + CoreStateDevice& state) const +{ + auto execute = make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + SmearRoughnessExecutor{data_.device_ref()}); + + static ActionLauncher const launch_kernel(*this); + launch_kernel(state, execute); +} + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.hh b/src/celeritas/optical/surface/model/SmearRoughnessModel.hh new file mode 100644 index 0000000000..3019d9f306 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.hh @@ -0,0 +1,59 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionMirror.hh" +#include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +#include "SmearRoughnessData.hh" + +namespace celeritas +{ +namespace inp +{ +struct SmearRoughness; +} + +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Smear roughness model. + * + * Approximates the surface roughness of an optical surface with the GliSur3 + * uniform smear roughness model. + */ +class SmearRoughnessModel : public SurfaceModel +{ + public: + //!@{ + //! \name Type aliases + using InputT = inp::SmearRoughness; + //!@} + + public: + // Construct the model from an ID and layer map + SmearRoughnessModel(SurfaceModelId, std::map const&); + + //! Get the list of physical surfaces this model applies to + VecSurfaceLayer const& get_surfaces() const final { return surfaces_; } + + // Execute the model with host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Execute the model with device data + void step(CoreParams const&, CoreStateDevice&) const final; + + private: + VecSurfaceLayer surfaces_; + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh b/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh index c81ca30e63..4bccb8b477 100644 --- a/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh +++ b/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh @@ -18,7 +18,7 @@ namespace optical /*! * Wrap a surface interaction executor and apply it to a track. * - * The functor \c F must take a \c CoreTrackview and return a \c + * The functor \c F must take a \c CoreTrackView and return a \c * SurfaceInteraction. */ template diff --git a/src/celeritas/phys/SurfaceModel.hh b/src/celeritas/phys/SurfaceModel.hh index 46ee8951a3..dc2df4c957 100644 --- a/src/celeritas/phys/SurfaceModel.hh +++ b/src/celeritas/phys/SurfaceModel.hh @@ -54,7 +54,7 @@ class SurfaceModel std::string_view label() const { return label_; } protected: - // Construct with label and model ID + // Construct with static label and model ID SurfaceModel(SurfaceModelId, std::string_view); //!@{ diff --git a/test/celeritas/CMakeLists.txt b/test/celeritas/CMakeLists.txt index ad5cfc8113..496619516c 100644 --- a/test/celeritas/CMakeLists.txt +++ b/test/celeritas/CMakeLists.txt @@ -40,8 +40,10 @@ endif() if(CELERITAS_CORE_GEO STREQUAL "ORANGE") set(_core_geo_libs testcel_orange Celeritas::orange) elseif(CELERITAS_CORE_GEO STREQUAL "VecGeom") + set(_needs_orange DISABLE) set(_core_geo_libs testcel_geocel ${VecGeom_LIBRARIES}) elseif(CELERITAS_CORE_GEO STREQUAL "Geant4") + set(_needs_orange DISABLE) set(_core_geo_libs testcel_geocel ${Geant4_LIBRARIES}) endif() @@ -82,6 +84,7 @@ celeritas_add_test_library(testcel_celeritas optical/OpticalMockTestBase.cc optical/OpticalTestBase.cc optical/InteractorHostTestBase.cc + optical/SurfacePhysicsIntegrationTestBase.cc optical/ValidationUtils.cc phys/InteractionIO.cc phys/InteractorHostTestBase.cc @@ -292,6 +295,10 @@ else() set(_geo_mat_filter DISABLE) endif() +if(CELERITAS_VecGeom_SURFACE) + set(_fails_vgsurf DISABLE) +endif() + celeritas_add_test(geo/GeoMaterial.test.cc FILTER ${_geo_mat_filter} ${_optional_geant4_env}) @@ -401,7 +408,9 @@ celeritas_add_test(optical/ReflectionFormSampler.test.cc) celeritas_add_test(optical/Fresnel.test.cc) celeritas_add_test(optical/SurfacePhysicsUtils.test.cc) celeritas_add_test(optical/TrivialSurfaceInteractor.test.cc) -celeritas_add_test(optical/SurfacePhysicsIntegration.test.cc ${_needs_geant4}) +celeritas_add_test(optical/SurfacePhysicsInteractionIntegration.test.cc ${_needs_geant4} ${_fails_vgsurf}) +celeritas_add_test(optical/SurfacePhysicsRoughnessIntegration.test.cc + ${_needs_geant4}) #-----------------------------------------------------------------------------# # Mat diff --git a/test/celeritas/optical/RoughnessCalculator.test.cc b/test/celeritas/optical/RoughnessCalculator.test.cc index f4377bf7f7..b8f833da5c 100644 --- a/test/celeritas/optical/RoughnessCalculator.test.cc +++ b/test/celeritas/optical/RoughnessCalculator.test.cc @@ -137,7 +137,7 @@ TEST_F(RoughnessSamplerTest, gaussian) // A "very rough" crystal in the UNIFIED paper has sigma_alpha of 0.2053 // (note that the paper gives the value in degrees), having at most a // deflection angle cosine of ~0.76 (40 degrees) - for (real_type sigma_alpha : {0.01, 0.05, 0.1, 0.2053, 0.4}) + for (real_type sigma_alpha : {0.01, 0.05, 0.1, 0.2053, 0.4, 0.6}) { GaussianRoughnessSampler sample_normal{normal, sigma_alpha}; @@ -149,11 +149,12 @@ TEST_F(RoughnessSamplerTest, gaussian) } static SampledHistogram const expected[] = { - {{0, 0, 0, 0, 5}, 21.7884}, - {{0, 0, 0, 0, 5}, 21.9014}, - {{0, 0, 0, 0, 5}, 21.9502}, - {{0, 0, 0, 0.034, 4.966}, 20.192}, - {{0.0105, 0.051, 0.235, 0.971, 3.7325}, 15.1376}, + {{0, 0, 0, 0, 5}, 21.8074}, + {{0, 0, 0, 0, 5}, 22.0256}, + {{0, 0, 0, 0, 5}, 22.4858}, + {{0, 0, 0.0005, 0.037, 4.9625}, 22.3334}, + {{0.011, 0.051, 0.2305, 0.967, 3.7405}, 15.1088}, + {{0.174, 0.366, 0.7145, 1.298, 2.4475}, 11.5844}, }; if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) { diff --git a/test/celeritas/optical/SurfacePhysics.test.cc b/test/celeritas/optical/SurfacePhysics.test.cc index aafb0894f6..b5035fcd84 100644 --- a/test/celeritas/optical/SurfacePhysics.test.cc +++ b/test/celeritas/optical/SurfacePhysics.test.cc @@ -342,8 +342,8 @@ TEST_F(SurfacePhysicsTest, init_params) SurfaceOrderArray> expected_model_names; expected_model_names[SurfacePhysicsOrder::roughness] = { "roughness-polished", - "smear", - "gaussian", + "roughness-smear", + "roughness-gaussian", }; expected_model_names[SurfacePhysicsOrder::reflectivity] = { "grid", diff --git a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsIntegration.test.cc deleted file mode 100644 index b6825b611e..0000000000 --- a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc +++ /dev/null @@ -1,558 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/optical/SurfacePhysicsIntegration.test.cc -//---------------------------------------------------------------------------// -#include - -#include "corecel/Config.hh" - -#include "corecel/cont/ArrayIO.hh" -#include "corecel/data/AuxInterface.hh" -#include "corecel/data/AuxParamsRegistry.hh" -#include "corecel/data/AuxStateVec.hh" -#include "corecel/sys/ActionGroups.hh" -#include "corecel/sys/ActionRegistry.hh" -#include "corecel/sys/KernelLauncher.hh" -#include "geocel/UnitUtils.hh" -#include "celeritas/GeantTestBase.hh" -#include "celeritas/ext/GeantImporter.hh" -#include "celeritas/global/CoreParams.hh" -#include "celeritas/optical/CoreParams.hh" -#include "celeritas/optical/CoreState.hh" -#include "celeritas/optical/CoreTrackView.hh" -#include "celeritas/optical/TrackInitializer.hh" -#include "celeritas/optical/Transporter.hh" -#include "celeritas/optical/action/ActionLauncher.hh" -#include "celeritas/optical/gen/DirectGeneratorAction.hh" -#include "celeritas/optical/gen/OffloadData.hh" -#include "celeritas/optical/surface/SurfacePhysicsParams.hh" -#include "celeritas/phys/GeneratorRegistry.hh" -#include "celeritas/track/CoreStateCounters.hh" -#include "celeritas/track/TrackFunctors.hh" -#include "celeritas/track/Utils.hh" - -#include "celeritas_test.hh" - -namespace celeritas -{ -namespace optical -{ -namespace test -{ -/*! - * Reference results: - * - Double precision - * - Orange geometry (requires valid surface normals and relocation on - * boundary) - */ -constexpr bool reference_configuration - = ((CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) - && (CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_ORANGE) - && (CELERITAS_CORE_RNG == CELERITAS_CORE_RNG_XORWOW)); - -using namespace ::celeritas::test; -//---------------------------------------------------------------------------// -/*! - * Counters for photon status after a run at a single angle. - */ -struct CollectResults -{ - size_type num_absorbed{0}; - size_type num_failed{0}; - size_type num_reflected{0}; - size_type num_refracted{0}; - - //! Clear counters - void reset() - { - num_absorbed = 0; - num_failed = 0; - num_reflected = 0; - num_refracted = 0; - } - - //! Score track - void operator()(CoreTrackView const& track) - { - if (track.sim().status() == TrackStatus::alive) - { - auto vol = track.geometry().volume_instance_id(); - if (vol == VolumeInstanceId{1}) - { - num_reflected++; - return; - } - else if (vol == VolumeInstanceId{2}) - { - num_refracted++; - return; - } - } - else if (track.sim().status() == TrackStatus::killed) - { - num_absorbed++; - return; - } - - num_failed++; - } -}; - -//---------------------------------------------------------------------------// -/*! - * Hook into the stepping loop to score and kill optical tracks that finish - * doing a surface crossing. - */ -class CollectResultsAction final : public OpticalStepActionInterface, - public ConcreteAction -{ - public: - explicit CollectResultsAction(ActionId aid, CollectResults& results) - : ConcreteAction(aid, "collect-results", "collect test results") - , results_(results) - { - } - - void step(CoreParams const& params, CoreStateHost& state) const final - { - for (auto tid : range(TrackSlotId{state.size()})) - { - CoreTrackView track(params.host_ref(), state.ref(), tid); - auto sim = track.sim(); - if (this->is_post_boundary(track) - || this->is_absorbed_on_boundary(track)) - { - results_(track); - sim.status(TrackStatus::killed); - } - } - } - - void step(CoreParams const&, CoreStateDevice&) const final - { - CELER_NOT_IMPLEMENTED("not collecting on device"); - } - - StepActionOrder order() const final { return StepActionOrder::post; } - - private: - //! Whether the track finished a boundary crossing - inline bool is_post_boundary(CoreTrackView const& track) const - { - return AppliesValid{}(track) - && track.sim().post_step_action() - == track.surface_physics().scalars().post_boundary_action; - } - - //! Whether the track was absorbed during a boundary crossing - inline bool is_absorbed_on_boundary(CoreTrackView const& track) const - { - return track.sim().status() == TrackStatus::killed - && track.sim().post_step_action() - == track.surface_physics().scalars().surface_stepping_action; - } - - CollectResults& results_; -}; - -//---------------------------------------------------------------------------// -/*! - * Counter results for a series of runs at different angles. - */ -struct SurfaceTestResults -{ - std::vector num_absorbed; - std::vector num_reflected; - std::vector num_refracted; -}; - -//---------------------------------------------------------------------------// -// TEST CHASSIS -//---------------------------------------------------------------------------// - -class SurfacePhysicsIntegrationTest : public GeantTestBase -{ - public: - std::string_view gdml_basename() const override { return "optical-box"; } - - GeantPhysicsOptions build_geant_options() const override - { - auto result = GeantTestBase::build_geant_options(); - result.optical = {}; - CELER_ENSURE(result.optical); - return result; - } - - GeantImportDataSelection build_import_data_selection() const override - { - auto result = GeantTestBase::build_import_data_selection(); - result.processes |= GeantImportDataSelection::optical; - return result; - } - - std::vector select_optical_models() const override - { - return {IMC::absorption}; - } - - void SetUp() override {} - - virtual void setup_surface_models(inp::SurfacePhysics&) const = 0; - - SPConstOpticalSurfacePhysics build_optical_surface_physics() override - { - inp::SurfacePhysics input; - - this->setup_surface_models(input); - - // Default surface - - PhysSurfaceId phys_surface = [&] { - size_type num_surfaces = 0; - for (auto const& mats : input.materials) - { - num_surfaces += mats.size() + 1; - } - return PhysSurfaceId(num_surfaces); - }(); - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::absorb); - - return std::make_shared( - this->optical_action_reg().get(), input); - } - - void build_state(size_type num_tracks) - { - auto state = std::make_shared>( - *this->optical_params(), StreamId{0}, num_tracks); - state->aux() = std::make_shared( - *this->core()->aux_reg(), MemSpace::host, StreamId{0}, num_tracks); - state_ = state; - } - - void make_collector() - { - auto& reg = *this->optical_params()->action_reg(); - auto collector - = std::make_shared(reg.next_id(), collect_); - reg.insert(collector); - } - - void build_transporter() - { - Transporter::Input inp; - inp.params = this->optical_params(); - transport_ = std::make_shared(std::move(inp)); - } - - //! Run over a set of angles and collect the results - SurfaceTestResults run(std::vector const& angles) - { - auto generate - = DirectGeneratorAction::make_and_insert(*this->optical_params()); - - this->make_collector(); - this->build_transporter(); - this->build_state(128); - - SurfaceTestResults results; - for (auto deg_angle : angles) - { - collect_.reset(); - - auto angle = deg_angle * constants::pi / 180; - real_type sin_theta = std::sin(angle); - real_type cos_theta = std::cos(angle); - - std::vector inits( - 100, - TrackInitializer{units::MevEnergy{3e-6}, - from_cm(Real3{0, 49, 0}), - Real3{sin_theta, cos_theta, 0}, - Real3{0, 0, 1}, - 0, - ImplVolumeId{0}}); - - generate->insert(*state_, make_span(inits)); - - (*transport_)(*state_); - - EXPECT_EQ(0, collect_.num_failed); - results.num_absorbed.push_back(collect_.num_absorbed); - results.num_reflected.push_back(collect_.num_reflected); - results.num_refracted.push_back(collect_.num_refracted); - } - - return results; - } - - protected: - std::shared_ptr> state_; - std::shared_ptr aux_; - std::shared_ptr transport_; - CollectResults collect_; -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationBackscatterTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only back-scattering - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::backscatter); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationAbsorbTest : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only absorption - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::absorb); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationTransmitTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only transmission - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::transmit); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationFresnelTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Fresnel refraction / reflection - - input.interaction.dielectric.emplace( - phys_surface, - inp::DielectricInteraction::from_dielectric( - inp::ReflectionForm::from_spike())); - } -}; - -//---------------------------------------------------------------------------// -// TESTS -//---------------------------------------------------------------------------// -// Only back-scattering -TEST_F(SurfacePhysicsIntegrationBackscatterTest, backscatter) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_reflected = {100, 100, 100}; - expected.num_refracted = {0, 0, 0}; - expected.num_absorbed = {0, 0, 0}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Only absorption -TEST_F(SurfacePhysicsIntegrationAbsorbTest, absorb) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_refracted = {0, 0, 0}; - expected.num_reflected = {0, 0, 0}; - expected.num_absorbed = {100, 100, 100}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Only transmission -TEST_F(SurfacePhysicsIntegrationTransmitTest, transmit) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_refracted = {100, 100, 100}; - expected.num_reflected = {0, 0, 0}; - expected.num_absorbed = {0, 0, 0}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Fresnel reflection / refraction -TEST_F(SurfacePhysicsIntegrationFresnelTest, fresnel) -{ - if (reference_configuration) - { - std::vector angles{ - 0, - 10, - 20, - 30, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 60, - 70, - 80, - }; - - auto result = this->run(angles); - - static unsigned int const expected_num_absorbed[] = { - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - }; - static unsigned int const expected_num_reflected[] = { - 2u, - 0u, - 3u, - 4u, - 15u, - 11u, - 9u, - 17u, - 18u, - 34u, - 27u, - 42u, - 60u, - 100u, - 100u, - 100u, - 100u, - 100u, - }; - static unsigned int const expected_num_refracted[] = { - 98u, - 100u, - 97u, - 96u, - 85u, - 89u, - 91u, - 83u, - 82u, - 66u, - 73u, - 58u, - 40u, - 0u, - 0u, - 0u, - 0u, - 0u, - }; - - EXPECT_VEC_EQ(expected_num_reflected, result.num_reflected); - EXPECT_VEC_EQ(expected_num_refracted, result.num_refracted); - EXPECT_VEC_EQ(expected_num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -} // namespace test -} // namespace optical -} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc new file mode 100644 index 0000000000..0607e29ce2 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc @@ -0,0 +1,116 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsIntegrationTestBase.cc +//---------------------------------------------------------------------------// +#include "SurfacePhysicsIntegrationTestBase.hh" + +#include "geocel/UnitUtils.hh" +#include "celeritas/optical/TrackInitializer.hh" + +using ::celeritas::test::from_cm; + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +//---------------------------------------------------------------------------// +GeantPhysicsOptions +SurfacePhysicsIntegrationTestBase::build_geant_options() const +{ + auto result = GeantTestBase::build_geant_options(); + result.optical = {}; + CELER_ENSURE(result.optical); + return result; +} + +//---------------------------------------------------------------------------// +GeantImportDataSelection +SurfacePhysicsIntegrationTestBase::build_import_data_selection() const +{ + auto result = GeantTestBase::build_import_data_selection(); + result.processes |= GeantImportDataSelection::optical; + return result; +} + +//---------------------------------------------------------------------------// +auto SurfacePhysicsIntegrationTestBase::select_optical_models() const + -> std::vector +{ + return {IMC::absorption}; +} + +//---------------------------------------------------------------------------// +auto SurfacePhysicsIntegrationTestBase::build_optical_surface_physics() + -> SPConstOpticalSurfacePhysics +{ + inp::SurfacePhysics input; + + this->setup_surface_models(input); + + // Default surface + + PhysSurfaceId phys_surface = [&] { + size_type num_surfaces = 0; + for (auto const& mats : input.materials) + { + num_surfaces += mats.size() + 1; + } + return PhysSurfaceId(num_surfaces); + }(); + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, inp::FresnelReflection{}); + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::absorb); + + return std::make_shared( + this->optical_action_reg().get(), input); +} + +//---------------------------------------------------------------------------// +void SurfacePhysicsIntegrationTestBase::initialize_run() +{ + generate_ = DirectGeneratorAction::make_and_insert(*this->optical_params()); + + Transporter::Input inp; + inp.params = this->optical_params(); + transport_ = std::make_shared(std::move(inp)); + + size_type num_tracks = 128; + auto state = std::make_shared>( + *this->optical_params(), StreamId{0}, num_tracks); + state->aux() = std::make_shared( + *this->core()->aux_reg(), MemSpace::host, StreamId{0}, num_tracks); + state_ = state; +} + +//---------------------------------------------------------------------------// +void SurfacePhysicsIntegrationTestBase::run_step(RealTurn angle) +{ + real_type sin_theta; + real_type cos_theta; + sincos(angle, &sin_theta, &cos_theta); + + std::vector inits( + 100, + TrackInitializer{units::MevEnergy{3e-6}, + from_cm(Real3{0, 49, 0}), + Real3{sin_theta, cos_theta, 0}, // direction + Real3{0, 0, 1}, // polarization + 0, // time + ImplVolumeId{0}}); + + generate_->insert(*state_, make_span(inits)); + + (*transport_)(*state_); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh new file mode 100644 index 0000000000..44c83bddb9 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh @@ -0,0 +1,151 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsIntegrationTestBase.hh +//---------------------------------------------------------------------------// +#pragma once + +#include + +#include "corecel/Config.hh" + +#include "corecel/data/AuxStateVec.hh" +#include "corecel/math/Turn.hh" +#include "corecel/sys/ActionGroups.hh" +#include "corecel/sys/ActionRegistry.hh" +#include "celeritas/GeantTestBase.hh" +#include "celeritas/ext/GeantImporter.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/Transporter.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" +#include "celeritas/optical/surface/SurfacePhysicsParams.hh" +#include "celeritas/track/TrackFunctors.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +/*! + * Reference results: + * - Double precision + * - Orange geometry (requires valid surface normals and relocation on + * boundary) + */ +constexpr bool reference_configuration + = ((CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + && (CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_ORANGE) + && (CELERITAS_CORE_RNG == CELERITAS_CORE_RNG_XORWOW)); + +//---------------------------------------------------------------------------// +/*! + * Template class for capturing photons after a surface interaction and scoring + * them with the given functor. + */ +template +class CollectResultsAction final : public OpticalStepActionInterface, + public ConcreteAction +{ + public: + explicit CollectResultsAction(ActionId aid, Collector& results) + : ConcreteAction(aid, "collect-results", "collect test results") + , results_(results) + { + } + + void step(CoreParams const& params, CoreStateHost& state) const final + { + for (auto tid : range(TrackSlotId{state.size()})) + { + CoreTrackView track(params.host_ref(), state.ref(), tid); + auto sim = track.sim(); + if (this->is_post_boundary(track) + || this->is_absorbed_on_boundary(track)) + { + results_(track); + sim.status(TrackStatus::killed); + } + } + } + + void step(CoreParams const&, CoreStateDevice&) const final + { + CELER_NOT_IMPLEMENTED("not collecting on device"); + } + + StepActionOrder order() const final { return StepActionOrder::post; } + + private: + //! Whether the track finished a boundary crossing + inline bool is_post_boundary(CoreTrackView const& track) const + { + return AppliesValid{}(track) + && track.sim().post_step_action() + == track.surface_physics().scalars().post_boundary_action; + } + + //! Whether the track was absorbed during a boundary crossing + inline bool is_absorbed_on_boundary(CoreTrackView const& track) const + { + return track.sim().status() == TrackStatus::killed + && track.sim().post_step_action() + == track.surface_physics().scalars().surface_stepping_action; + } + + Collector& results_; +}; + +//---------------------------------------------------------------------------// +/*! + * A test base for running surface physics integration tests. + * + * Tests are run in the optical-box.gdml setup, where photons are initialized + * close to the top (positive-y) edge and are shot directly into it. The + * collect action is used to capture photons immediately after a surface + * interaction and log them in an appropriate functor. + */ +class SurfacePhysicsIntegrationTestBase + : public ::celeritas::test::GeantTestBase +{ + public: + std::string_view gdml_basename() const override { return "optical-box"; } + + GeantPhysicsOptions build_geant_options() const override; + GeantImportDataSelection build_import_data_selection() const override; + std::vector select_optical_models() const override; + SPConstOpticalSurfacePhysics build_optical_surface_physics() override; + + //! Initialize transporter and state for run + void initialize_run(); + + //! Run a single set of photons at the given angle + void run_step(RealTurn angle); + + //! Create a collector action for the given functor + template + void create_collector(C& collect) + { + auto& reg = *this->optical_params()->action_reg(); + auto collector = std::make_shared>( + reg.next_id(), collect); + reg.insert(collector); + } + + protected: + std::shared_ptr> state_; + std::shared_ptr aux_; + std::shared_ptr transport_; + std::shared_ptr generate_; + + virtual void setup_surface_models(inp::SurfacePhysics&) const = 0; +}; + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc new file mode 100644 index 0000000000..fea311635c --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc @@ -0,0 +1,356 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc +//---------------------------------------------------------------------------// + +#include "corecel/math/Turn.hh" + +#include "SurfacePhysicsIntegrationTestBase.hh" +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +constexpr Turn degree{real_type{1} / 360}; +//---------------------------------------------------------------------------// +/*! + * Counters for photon status after a run at a single angle. + */ +struct CollectResults +{ + size_type num_absorbed{0}; + size_type num_failed{0}; + size_type num_reflected{0}; + size_type num_refracted{0}; + + //! Clear counters + void reset() + { + num_absorbed = 0; + num_failed = 0; + num_reflected = 0; + num_refracted = 0; + } + + //! Score track + void operator()(CoreTrackView const& track) + { + if (track.sim().status() == TrackStatus::alive) + { + auto vol = track.geometry().volume_instance_id(); + if (vol == VolumeInstanceId{1}) + { + num_reflected++; + return; + } + else if (vol == VolumeInstanceId{2}) + { + num_refracted++; + return; + } + } + else if (track.sim().status() == TrackStatus::killed) + { + num_absorbed++; + return; + } + + num_failed++; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Counter results for a series of runs at different angles. + */ +struct SurfaceTestResults +{ + std::vector num_absorbed; + std::vector num_reflected; + std::vector num_refracted; +}; + +//---------------------------------------------------------------------------// +// TEST CHASSIS +//---------------------------------------------------------------------------// + +class SurfacePhysicsInteractionIntegrationTest + : public SurfacePhysicsIntegrationTestBase +{ + public: + SurfaceTestResults run(std::vector const& angles) + { + this->create_collector(collect_); + + this->initialize_run(); + + // Run over angles + SurfaceTestResults results; + for (auto angle : angles) + { + collect_.reset(); + + this->run_step(angle); + + EXPECT_EQ(0, collect_.num_failed); + results.num_absorbed.push_back(collect_.num_absorbed); + results.num_reflected.push_back(collect_.num_reflected); + results.num_refracted.push_back(collect_.num_refracted); + } + + return results; + } + + void reference_run(std::vector const& angles, + SurfaceTestResults const& expected) + { + auto result = this->run(angles); + if (reference_configuration) + { + EXPECT_EQ(expected.num_reflected, result.num_reflected); + EXPECT_EQ(expected.num_refracted, result.num_refracted); + EXPECT_EQ(expected.num_absorbed, result.num_absorbed); + } + } + + protected: + CollectResults collect_; +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationBackscatterTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only back-scattering + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::backscatter); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationAbsorbTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only absorption + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::absorb); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationTransmitTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only transmission + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::transmit); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationFresnelTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Fresnel refraction / reflection + + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_spike())); + } +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +// Only back-scattering +TEST_F(SurfacePhysicsIntegrationBackscatterTest, backscatter) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_reflected = {100, 100, 100}; + expected.num_refracted = {0, 0, 0}; + expected.num_absorbed = {0, 0, 0}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Only absorption +TEST_F(SurfacePhysicsIntegrationAbsorbTest, absorb) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_refracted = {0, 0, 0}; + expected.num_reflected = {0, 0, 0}; + expected.num_absorbed = {100, 100, 100}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Only transmission +TEST_F(SurfacePhysicsIntegrationTransmitTest, transmit) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_refracted = {100, 100, 100}; + expected.num_reflected = {0, 0, 0}; + expected.num_absorbed = {0, 0, 0}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Fresnel reflection / refraction +TEST_F(SurfacePhysicsIntegrationFresnelTest, fresnel) +{ + std::vector angles{ + RealTurn{0}, + 10 * degree, + 20 * degree, + 30 * degree, + 40 * degree, + 41 * degree, + 42 * degree, + 43 * degree, + 44 * degree, + 45 * degree, + 46 * degree, + 47 * degree, + 48 * degree, + 49 * degree, + 50 * degree, + 60 * degree, + 70 * degree, + 80 * degree, + }; + + SurfaceTestResults expected; + expected.num_absorbed = { + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + }; + expected.num_reflected = { + 2u, + 0u, + 3u, + 4u, + 15u, + 11u, + 9u, + 17u, + 18u, + 34u, + 27u, + 42u, + 60u, + 100u, + 100u, + 100u, + 100u, + 100u, + }; + expected.num_refracted = { + 98u, + 100u, + 97u, + 96u, + 85u, + 89u, + 91u, + 83u, + 82u, + 66u, + 73u, + 58u, + 40u, + 0u, + 0u, + 0u, + 0u, + 0u, + }; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc new file mode 100644 index 0000000000..6e1cffb2a4 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc @@ -0,0 +1,209 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc +//---------------------------------------------------------------------------// +#include "corecel/math/Turn.hh" +#include "corecel/random/Histogram.hh" + +#include "SurfacePhysicsIntegrationTestBase.hh" +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +using ::celeritas::test::Histogram; + +//---------------------------------------------------------------------------// +/*! + * Collect results based on the track's direction dot produced with respect to + * the surface normal. + * + * The surface normal is (0,1,0), so the dot product is just the y-component. + * This gives a distribution of reflected and refracted angles. + */ +struct CollectResults +{ + Histogram reflection_cosine{20, {-1, 1}}; + size_type num_failed{0}; + + //! Score track + void operator()(CoreTrackView const& track) + { + if (track.sim().status() == TrackStatus::alive) + { + reflection_cosine(track.geometry().dir()[1]); + return; + } + num_failed++; + } +}; + +//---------------------------------------------------------------------------// +// TEST CHASSIS +//---------------------------------------------------------------------------// +/*! + * Base class for testing surface roughness models. + * + * Sub-classes should use Fresnel reflection with the lobe mode to ensure the + * local facet normal is used for reflection. + */ +class SurfacePhysicsRoughnessIntegrationTest + : public SurfacePhysicsIntegrationTestBase +{ + public: + /*! + * Run for a certain number of iterations and compare to the expected + * distribution. + */ + void run(size_type loops, std::vector const& expected) + { + this->create_collector(collect_); + + this->initialize_run(); + + for ([[maybe_unused]] auto i : range(loops)) + { + this->run_step(RealTurn(0.0)); // along x = 0 + } + + if (reference_configuration) + { + EXPECT_EQ(0, collect_.num_failed); + EXPECT_VEC_EQ(expected, collect_.reflection_cosine.counts()); + } + } + + protected: + CollectResults collect_; +}; + +//---------------------------------------------------------------------------// +/*! + * Polished roughness model. + */ +class SurfacePhysicsIntegrationPolishedTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // polished roughness + + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + } +}; + +//---------------------------------------------------------------------------// +/*! + * Uniform smear roughness model. + */ +class SurfacePhysicsIntegrationSmearTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // smear roughness + + input.roughness.smear.emplace(phys_surface, inp::SmearRoughness{0.8}); + } +}; + +//---------------------------------------------------------------------------// +/*! + * Gaussian roughness model. + */ +class SurfacePhysicsIntegrationGaussianTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // Gaussian roughness + + input.roughness.gaussian.emplace(phys_surface, + inp::GaussianRoughness{0.6}); + } +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +// Only polished +TEST_F(SurfacePhysicsIntegrationPolishedTest, polished) +{ + std::vector expected{ + 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 985, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +// Only uniform smear +TEST_F(SurfacePhysicsIntegrationSmearTest, smear) +{ + std::vector expected{ + 4, 11, 6, 5, 7, 4, 3, 4, 7, 15, 0, 0, 0, 1, 0, 0, 0, 1, 34, 898, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +// Only Gaussian roughness +TEST_F(SurfacePhysicsIntegrationGaussianTest, gaussian) +{ + std::vector expected{ + 4, 17, 14, 23, 20, 27, 26, 21, 36, 33, + 22, 11, 21, 9, 11, 13, 13, 14, 57, 608, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/testdetail/TestMacrosImpl.hh b/test/testdetail/TestMacrosImpl.hh index 89200d33e6..7390ffd8a4 100644 --- a/test/testdetail/TestMacrosImpl.hh +++ b/test/testdetail/TestMacrosImpl.hh @@ -281,16 +281,25 @@ struct IsContainer : std::false_type { }; +template<> +struct IsContainer : std::false_type +{ +}; + template struct IsContainer> : std::true_type { }; -template +template struct IsContainer : std::true_type { }; +//! Whether a type is a container +template +inline constexpr bool IsContainer_v = IsContainer::value; + //---------------------------------------------------------------------------// /*! * Get the type of a container. @@ -310,6 +319,12 @@ struct ValueType template using ValueTypeT = typename ValueType::type; +//---------------------------------------------------------------------------// + +//! Whether a container *holds* another container +template +inline constexpr bool IsNestedContainer_v = IsContainer_v>; + //---------------------------------------------------------------------------// /*! * Recursively get the underlying scalar type of a container. @@ -321,7 +336,7 @@ struct ScalarValueType }; template -struct ScalarValueType::value>> +struct ScalarValueType>> { using type = typename ScalarValueType>::type; }; @@ -403,8 +418,8 @@ template char const* actual_expr, BinaryOp comp) { - if constexpr (IsContainer>::value - && IsContainer>::value) + if constexpr (IsNestedContainer_v + && IsNestedContainer_v) { // Handle nested containers recursively auto exp_size = std::distance(std::begin(expected), std::end(expected)); @@ -598,8 +613,8 @@ template ContainerE const& expected, ContainerA const& actual) { - if constexpr (IsContainer>::value - && IsContainer>::value) + if constexpr (IsNestedContainer_v + && IsNestedContainer_v) { // Handle nested containers recursively auto exp_size = std::distance(std::begin(expected), std::end(expected)); @@ -722,7 +737,7 @@ template * Compare two vectors of reference values using a tolerance. */ template -std::enable_if_t::value && IsContainer::value, +std::enable_if_t && IsContainer_v, ::testing::AssertionResult> IsRefEq(char const* expr1, char const* expr2, @@ -784,7 +799,7 @@ IsRefEq(char const* expr1, * Compare two vectors of reference values without a special tolerance. */ template -std::enable_if_t::value && IsContainer::value, +std::enable_if_t && IsContainer_v, ::testing::AssertionResult> IsRefEq(char const* expr1, char const* expr2, From 8b6aea13068958a62e54515eb6d7b906d837ff73 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 20 Jan 2026 08:00:22 -0500 Subject: [PATCH 41/60] Refactor logger handles (#2182) * Refactor logger handles * IWYU * Disable dd4hep and root on executor * Remove deprecation * Address feedback * Use to_ansi_color instead of deprecated verbiage --- app/celer-g4/celer-g4.cc | 10 +- .../geant4-interface/details.rst | 1 - scripts/cmake-presets/executor.json | 3 +- src/accel/Logger.cc | 6 +- src/accel/detail/IntegrationSingleton.cc | 8 +- src/accel/detail/LoggerImpl.cc | 2 +- src/corecel/io/LogHandlers.cc | 26 ++- src/corecel/io/LogHandlers.hh | 18 +++ src/corecel/io/Logger.cc | 153 +++--------------- src/corecel/io/Logger.hh | 31 ++-- src/corecel/io/LoggerTypes.cc | 37 ++++- src/corecel/io/LoggerTypes.hh | 28 ++-- src/corecel/io/detail/LoggerMessage.hh | 3 +- test/corecel/ScopedLogStorer.cc | 6 +- test/corecel/io/Logger.test.cc | 2 +- 15 files changed, 156 insertions(+), 178 deletions(-) diff --git a/app/celer-g4/celer-g4.cc b/app/celer-g4/celer-g4.cc index 5234fcf0e6..350ed1ca55 100644 --- a/app/celer-g4/celer-g4.cc +++ b/app/celer-g4/celer-g4.cc @@ -141,10 +141,12 @@ void run(std::string_view filename, std::shared_ptr params) celeritas::TracingSession tracing{setup.input().tracing_file}; // Set up loggers - world_logger() = Logger::from_handle_env(make_world_handler(), "CELER_LOG"); - self_logger() = Logger::from_handle_env( - make_self_handler(get_geant_num_threads(*run_manager)), - "CELER_LOG_LOCAL"); + world_logger() = Logger{make_world_handler(), + getenv_loglevel("CELER_LOG", LogLevel::status)}; + + self_logger() + = Logger{make_self_handler(get_geant_num_threads(*run_manager)), + getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; // Redirect Geant4 output and exceptions through Celeritas objects ScopedGeantLogger scoped_logger; diff --git a/doc/implementation/geant4-interface/details.rst b/doc/implementation/geant4-interface/details.rst index 1892021dbe..d4c824a1c8 100644 --- a/doc/implementation/geant4-interface/details.rst +++ b/doc/implementation/geant4-interface/details.rst @@ -15,7 +15,6 @@ Interface utilities .. celerstruct:: AlongStepFactoryInput -.. doxygenfunction:: celeritas::MakeMTLogger .. doxygenclass:: celeritas::ExceptionConverter .. doxygenclass:: celeritas::AlongStepFactoryInterface diff --git a/scripts/cmake-presets/executor.json b/scripts/cmake-presets/executor.json index 557b789c18..e0a6f4d541 100644 --- a/scripts/cmake-presets/executor.json +++ b/scripts/cmake-presets/executor.json @@ -21,12 +21,13 @@ "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_covfie": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_HIP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "OFF"}, diff --git a/src/accel/Logger.cc b/src/accel/Logger.cc index 603d766edb..f4c80a4a2f 100644 --- a/src/accel/Logger.cc +++ b/src/accel/Logger.cc @@ -92,7 +92,8 @@ Logger MakeMTWorldLogger(G4RunManager const& runman) handle = write_mt_world; } } - return Logger::from_handle_env(std::move(handle), "CELER_LOG"); + return Logger{std::move(handle), + getenv_loglevel("CELER_LOG", LogLevel::status)}; } //---------------------------------------------------------------------------// @@ -122,7 +123,8 @@ Logger MakeMTSelfLogger(G4RunManager const& runman) { handle = detail::MtSelfWriter{get_geant_num_threads(runman)}; } - return Logger::from_handle_env(std::move(handle), "CELER_LOG_LOCAL"); + return Logger{std::move(handle), + getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; } //---------------------------------------------------------------------------// diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index bdf3033294..1cebec02e9 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -431,11 +431,11 @@ void IntegrationSingleton::update_logger() } else { - if (auto* handle - = celeritas::world_logger().handle().target()) + if (celeritas::world_logger().handle().target()) { - // Update thread count - *handle = MtSelfWriter{get_geant_num_threads(*run_man)}; + // Update thread count by replacing log handle + celeritas::world_logger().handle( + MtSelfWriter{get_geant_num_threads(*run_man)}); } } } diff --git a/src/accel/detail/LoggerImpl.cc b/src/accel/detail/LoggerImpl.cc index 71dea46ab9..6287131b1f 100644 --- a/src/accel/detail/LoggerImpl.cc +++ b/src/accel/detail/LoggerImpl.cc @@ -69,7 +69,7 @@ std::ostream& operator<<(std::ostream& os, CleanedProvenance ssd) */ std::ostream& operator<<(std::ostream& os, ColorfulLogMessage clm) { - os << to_color_code(clm.lev) << to_cstring(clm.lev); + os << to_ansi_color(clm.lev) << to_cstring(clm.lev); if (!clm.prov.file.empty()) { os << color_code('x') << "@" diff --git a/src/corecel/io/LogHandlers.cc b/src/corecel/io/LogHandlers.cc index 202503a9aa..a6e9296143 100644 --- a/src/corecel/io/LogHandlers.cc +++ b/src/corecel/io/LogHandlers.cc @@ -10,6 +10,8 @@ #include #include +#include "corecel/sys/MpiCommunicator.hh" + #include "ColorUtils.hh" namespace celeritas @@ -33,7 +35,7 @@ void StreamLogHandler::operator()(LogProvenance prov, os_ << color_code(' ') << ": "; } - os_ << to_color_code(lev) << to_cstring(lev) << ": " << color_code(' '); + os_ << to_ansi_color(lev) << to_cstring(lev) << ": " << color_code(' '); os_ << std::move(msg) << std::endl; } @@ -54,5 +56,27 @@ void MutexedStreamLogHandler::operator()(LogProvenance prov, os_ << std::move(temp_os).str() << std::flush; } +//---------------------------------------------------------------------------// +/*! + * Construct with long-lived reference to a stream. + */ +LocalMpiHandler::LocalMpiHandler(std::ostream& os, MpiCommunicator const& comm) + : os_{os}, rank_(comm.rank()) +{ +} + +// Write with processor ID +void LocalMpiHandler::operator()(LogProvenance prov, + LogLevel lev, + std::string msg) const +{ + // Buffer to reduce I/O contention in MPI runner + std::ostringstream os; + os << color_code('W') << "rank " << rank_ << ": "; + StreamLogHandler{os}(prov, lev, msg); + + os_ << std::move(os).str() << std::flush; +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/LogHandlers.hh b/src/corecel/io/LogHandlers.hh index 13276399bc..defa8197df 100644 --- a/src/corecel/io/LogHandlers.hh +++ b/src/corecel/io/LogHandlers.hh @@ -13,6 +13,8 @@ namespace celeritas { +class MpiCommunicator; + //---------------------------------------------------------------------------// /*! * Simple log handler: write with colors to a long-lived ostream reference. @@ -47,5 +49,21 @@ class MutexedStreamLogHandler std::ostream& os_; }; +//---------------------------------------------------------------------------// +//! Log the local node number as well as the message +class LocalMpiHandler +{ + public: + // Construct with long-lived reference to a stream + LocalMpiHandler(std::ostream& os, MpiCommunicator const& comm); + + // Write with processor ID + void operator()(LogProvenance prov, LogLevel lev, std::string msg) const; + + private: + std::ostream& os_; + int rank_; +}; + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/Logger.cc b/src/corecel/io/Logger.cc index 77ad803e4e..e78469b38e 100644 --- a/src/corecel/io/Logger.cc +++ b/src/corecel/io/Logger.cc @@ -6,19 +6,10 @@ //---------------------------------------------------------------------------// #include "Logger.hh" -#include -#include #include -#include -#include #include "corecel/Assert.hh" -#include "corecel/cont/Range.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/MpiCommunicator.hh" -#include "corecel/sys/ScopedMpiInit.hh" -#include "ColorUtils.hh" #include "LogHandlers.hh" #include "LoggerTypes.hh" @@ -27,165 +18,73 @@ namespace celeritas namespace { //---------------------------------------------------------------------------// -// HELPER CLASSES -//---------------------------------------------------------------------------// -//! Log the local node number as well as the message -class LocalHandler -{ - public: - explicit LocalHandler(MpiCommunicator const& comm) : rank_(comm.rank()) {} - - void operator()(LogProvenance prov, LogLevel lev, std::string msg) - { - // Buffer to reduce I/O contention in MPI runner - std::ostringstream os; - os << color_code('W') << "rank " << rank_ << ": "; - StreamLogHandler{os}(prov, lev, msg); - - std::clog << std::move(os).str(); - } - - private: - int rank_; -}; - -//---------------------------------------------------------------------------// -} // namespace - -//---------------------------------------------------------------------------// -/*! - * Create a logger from a handle and level environment variable. - */ -Logger -Logger::from_handle_env(LogHandler&& handle, std::string const& level_env) +auto safe_getenv_loglevel(char const* env_var, LogLevel default_level) + -> LogLevel { - Logger result{std::move(handle)}; - try { - result.level(log_level_from_env(level_env)); + return getenv_loglevel(env_var, default_level); } catch (RuntimeError const& e) { - result(CELER_CODE_PROVENANCE, LogLevel::warning) << e.details().what; + std::clog << "Error during logger setup: " << e.what() << std::endl; + return default_level; } - - return result; } - //---------------------------------------------------------------------------// -/*! - * Construct with handler. - * - * A null handler will silence the logger. - */ -Logger::Logger(LogHandler&& handle) : handle_{std::move(handle)} {} +} // namespace -//---------------------------------------------------------------------------// -// FREE FUNCTIONS //---------------------------------------------------------------------------// /*! - * Get the log level from an environment variable. - * - * Default to \c Logger::default_level, which is 'info'. + * Construct a logger with handle. */ -LogLevel log_level_from_env(std::string const& level_env) +Logger::Logger(LogHandler&& handle) + : Logger(std::move(handle), default_level()) { - return log_level_from_env(level_env, Logger::default_level()); } //---------------------------------------------------------------------------// /*! - * Get the log level from an environment variable. + * Construct a logger with handle and level. */ -LogLevel log_level_from_env(std::string const& level_env, LogLevel default_lev) +Logger::Logger(LogHandler&& handle, LogLevel min_lev) + : handle_(std::move(handle)), min_level_(min_lev) { - // Search for the provided environment variable to set the default - // logging level using the `to_cstring` function in LoggerTypes. - std::string const& env_value = celeritas::getenv(level_env); - if (env_value.empty()) - { - return default_lev; - } - - auto levels = range(LogLevel::size_); - auto iter = std::find_if( - levels.begin(), levels.end(), [&env_value](LogLevel lev) { - return env_value == to_cstring(lev); - }); - CELER_VALIDATE(iter != levels.end(), - << "invalid log level '" << env_value - << "' in environment variable '" << level_env << "'"); - return *iter; + CELER_EXPECT(min_level_ != LogLevel::size_); } //---------------------------------------------------------------------------// -/*! - * Create a default logger using the world communicator. - * - * The result prints to stderr (buffered through \c std::clog ) only on one - * processor in the world communicator group. - * Since it's for messages that should be printed once across all processes, - * and usually during setup when no local threads are printing, it is not - * mutexed. - * This function can be useful when resetting a test harness. - */ -Logger make_default_world_logger() -{ - LogHandler handler; - if (celeritas::comm_world().rank() == 0) - { - handler = StreamLogHandler{std::clog}; - } - return Logger::from_handle_env(std::move(handler), "CELER_LOG"); -} - +// FREE FUNCTIONS //---------------------------------------------------------------------------// /*! - * Create a default logger using the local communicator. + * App-level logger: print only on "main" process. * - * If MPI is enabled, this will prepend the local process index to the message. - * Because multiple threads and processes can print log messages at the same - * time, this log output uses a mutex to synchronize output, and prints to - * buffered stderr through \c std::clog . - */ -Logger make_default_self_logger() -{ - LogHandler handler = LogHandler{MutexedStreamLogHandler{std::clog}}; - if (ScopedMpiInit::status() != ScopedMpiInit::Status::disabled) - { - handler = LocalHandler{celeritas::comm_world()}; - } - - return Logger::from_handle_env(std::move(handler), "CELER_LOG_LOCAL"); -} - -//---------------------------------------------------------------------------// -/*! - * Parallel-enabled logger: print only on "main" process. + * Setting the "CELER_LOG" environment variable to "debug", "info", + * "error", etc. will change the default log level. * - * Setting the "CELER_LOG" environment variable to "debug", "info", "error", - * etc. will change the default log level. + * \sa CELER_LOG . */ Logger& world_logger() { - // Use the null communicator if MPI isn't enabled, otherwise comm_world - static Logger logger = make_default_world_logger(); + static Logger logger{StreamLogHandler{std::clog}, + safe_getenv_loglevel("CELER_LOG", LogLevel::status)}; return logger; } //---------------------------------------------------------------------------// /*! - * Serial logger: print on *every* process that calls it. + * Serial logger: print on \em every process that calls it. * * Setting the "CELER_LOG_LOCAL" environment variable to "debug", "info", * "error", etc. will change the default log level. + * + * \sa CELER_LOG_LOCAL . */ Logger& self_logger() { - // Use the null communicator if MPI isn't enabled, otherwise comm_local. - // If only - static Logger logger = make_default_self_logger(); + static Logger logger{ + StreamLogHandler{std::clog}, + safe_getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; return logger; } diff --git a/src/corecel/io/Logger.hh b/src/corecel/io/Logger.hh index 621e1a9dcf..8bfa8708cf 100644 --- a/src/corecel/io/Logger.hh +++ b/src/corecel/io/Logger.hh @@ -6,10 +6,9 @@ //---------------------------------------------------------------------------// #pragma once -#include #include -#include "corecel/Config.hh" +#include "corecel/Macros.hh" #include "LoggerTypes.hh" @@ -52,8 +51,8 @@ * \def CELER_LOG_LOCAL * * Like \c CELER_LOG but for code paths that may only happen on a single - * process or thread. Use sparingly because this can be very verbose. This is - * typically used only for error messages coming from an a event or + * process or thread. Use sparingly because this can be very verbose. This + * should be used for error messages from an event or * track at runtime. */ #define CELER_LOG_LOCAL(LEVEL) \ @@ -89,7 +88,7 @@ class MpiCommunicator; * each process: rank 0 will have a handler that outputs to screen, and the * other ranks will have a "null" handler that suppresses all log output. * - * \todo For v1.0, replace the back-end with \c spdlog to reduce maintenance + * \todo Replace the back-end with \c spdlog to reduce maintenance * burden and improve flexibility. */ class Logger @@ -104,13 +103,16 @@ class Logger //! Get the default log level static constexpr LogLevel default_level() { return LogLevel::status; } - // Create a logger from a handle and level environment variable - static Logger from_handle_env(LogHandler&& handle, std::string const& key); + // Construct a null logger + Logger() = default; - // Construct from an output handle + // Construct a logger with handle and default level explicit Logger(LogHandler&& handle); - // Create a logger that flushes its contents when it destructs + // Construct a logger with handle and level + Logger(LogHandler&& handle, LogLevel min_lev); + + // Create a log message that flushes its contents when it destructs inline Message operator()(LogProvenance&& prov, LogLevel lev) const; //! Set the minimum logging verbosity @@ -122,8 +124,8 @@ class Logger //! Access the log handle LogHandler const& handle() const { return handle_; } - //! Access the log handle (mutable, try to avoid using?) - LogHandler& handle() { return handle_; } + //! Set the log handle (null disables logger) + void handle(LogHandler&& handle) { handle_ = std::move(handle); } private: LogHandler handle_; @@ -153,13 +155,6 @@ auto Logger::operator()(LogProvenance&& prov, LogLevel lev) const -> Message //---------------------------------------------------------------------------// // FREE FUNCTIONS //---------------------------------------------------------------------------// -// Get the log level from an environment variable -LogLevel log_level_from_env(std::string const&, LogLevel default_lev); -LogLevel log_level_from_env(std::string const&); - -// Create loggers with reasonable default behaviors. -Logger make_default_world_logger(); -Logger make_default_self_logger(); // Parallel logger (print only on "main" process) Logger& world_logger(); diff --git a/src/corecel/io/LoggerTypes.cc b/src/corecel/io/LoggerTypes.cc index 86a10af783..c6a41da0ae 100644 --- a/src/corecel/io/LoggerTypes.cc +++ b/src/corecel/io/LoggerTypes.cc @@ -6,6 +6,12 @@ //---------------------------------------------------------------------------// #include "LoggerTypes.hh" +#include +#include + +#include "corecel/cont/Range.hh" +#include "corecel/sys/Environment.hh" + #include "ColorUtils.hh" #include "EnumStringMapper.hh" @@ -33,7 +39,7 @@ char const* to_cstring(LogLevel lev) /*! * Get an ANSI color code appropriate to each log level. */ -char const* to_color_code(LogLevel lev) +char const* to_ansi_color(LogLevel lev) { // clang-format off char c = ' '; @@ -50,7 +56,34 @@ char const* to_color_code(LogLevel lev) }; // clang-format on - return color_code(c); + return ansi_color(c); +} + +//---------------------------------------------------------------------------// +/*! + * Get the log level from an environment variable. + * + * \throws RuntimeError if string is invalid + */ +LogLevel getenv_loglevel(std::string const& level_env, LogLevel default_lev) +{ + // Search for the provided environment variable to set the default + // logging level using the `to_cstring` function in LoggerTypes. + std::string const& env_value = celeritas::getenv(level_env); + if (env_value.empty()) + { + return default_lev; + } + + auto levels = range(LogLevel::size_); + auto iter = std::find_if( + levels.begin(), levels.end(), [&env_value](LogLevel lev) { + return env_value == to_cstring(lev); + }); + CELER_VALIDATE(iter != levels.end(), + << "invalid log level '" << env_value + << "' in environment variable '" << level_env << "'"); + return *iter; } //---------------------------------------------------------------------------// diff --git a/src/corecel/io/LoggerTypes.hh b/src/corecel/io/LoggerTypes.hh index e665241f6f..201351c45f 100644 --- a/src/corecel/io/LoggerTypes.hh +++ b/src/corecel/io/LoggerTypes.hh @@ -15,6 +15,8 @@ namespace celeritas { //---------------------------------------------------------------------------// +// TYPES +//---------------------------------------------------------------------------// /*! * Enumeration for how important a log message is. */ @@ -30,16 +32,7 @@ enum class LogLevel size_ //!< Sentinel value for looping over log levels }; -//---------------------------------------------------------------------------// -// Get the plain text equivalent of the log level above -char const* to_cstring(LogLevel); - -//---------------------------------------------------------------------------// -// Get an ANSI color code appropriate to each log level -char const* to_color_code(LogLevel); - -//---------------------------------------------------------------------------// -//! Stand-in for a more complex class for the "provenance" of data +//! Origin of a log message struct LogProvenance { std::string_view file; //!< Originating file @@ -49,5 +42,20 @@ struct LogProvenance //! Type for handling a log message using LogHandler = std::function; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +// Get the plain text equivalent of the log level above +char const* to_cstring(LogLevel); + +//---------------------------------------------------------------------------// +// Get an ANSI color code appropriate to each log level +char const* to_ansi_color(LogLevel); + +//---------------------------------------------------------------------------// +// Get a log level from an environment variable +LogLevel getenv_loglevel(std::string const&, LogLevel default_lev); + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/detail/LoggerMessage.hh b/src/corecel/io/detail/LoggerMessage.hh index f294a1f9a8..586e3c923c 100644 --- a/src/corecel/io/detail/LoggerMessage.hh +++ b/src/corecel/io/detail/LoggerMessage.hh @@ -11,8 +11,7 @@ #include #include "corecel/Macros.hh" - -#include "../LoggerTypes.hh" +#include "corecel/io/LoggerTypes.hh" namespace celeritas { diff --git a/test/corecel/ScopedLogStorer.cc b/test/corecel/ScopedLogStorer.cc index 316ecf7f0d..37d7a0b308 100644 --- a/test/corecel/ScopedLogStorer.cc +++ b/test/corecel/ScopedLogStorer.cc @@ -47,9 +47,7 @@ ScopedLogStorer::ScopedLogStorer(Logger* orig, LogLevel min_level) CELER_EXPECT(min_level != LogLevel::size_); // Create a new logger that calls our operator(), replace orig and store saved_logger_ = std::make_unique( - std::exchange(*logger_, Logger{std::ref(*this)})); - // Catch everything, keep only what we want - logger_->level(LogLevel::debug); + std::exchange(*logger_, Logger{std::ref(*this), LogLevel::debug})); } //---------------------------------------------------------------------------// @@ -80,7 +78,7 @@ void ScopedLogStorer::operator()(LogProvenance prov, std::string msg) { static LogLevel const debug_level - = log_level_from_env("CELER_LOG_SCOPED", LogLevel::warning); + = getenv_loglevel("CELER_LOG_SCOPED", LogLevel::warning); if (lev >= debug_level) { if (getenv_flag("CELER_LOG_SCOPED_VERBOSE", false).value) diff --git a/test/corecel/io/Logger.test.cc b/test/corecel/io/Logger.test.cc index e30a7a6474..15e420b0c2 100644 --- a/test/corecel/io/Logger.test.cc +++ b/test/corecel/io/Logger.test.cc @@ -169,7 +169,7 @@ TEST_F(LoggerTest, level_from_env) { auto set_level = [](std::string const& key, std::string const& val) { environment().insert({key, val}); - return log_level_from_env(key); + return getenv_loglevel(key, LogLevel::size_); }; EXPECT_EQ(LogLevel::debug, set_level("CELER_TEST_ENV_0", "debug")); From f7d06d6a3ee70fcbb2813df6637ed1b87f23dabc Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 20 Jan 2026 10:50:11 -0500 Subject: [PATCH 42/60] Improve messaging from multi-exception handler (#2196) * Improve messaging from multi-exception handler * Reference previous exception with a consistent label * Fix size type and add error handling * Change uncaught test to a death test --- src/corecel/sys/MultiExceptionHandler.cc | 51 +++++++++----- .../corecel/sys/MultiExceptionHandler.test.cc | 70 ++++++++++++------- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/corecel/sys/MultiExceptionHandler.cc b/src/corecel/sys/MultiExceptionHandler.cc index 04689d83e6..961fcf76d2 100644 --- a/src/corecel/sys/MultiExceptionHandler.cc +++ b/src/corecel/sys/MultiExceptionHandler.cc @@ -7,6 +7,7 @@ #include "MultiExceptionHandler.hh" #include +#include #include "corecel/Assert.hh" #include "corecel/Types.hh" @@ -74,6 +75,10 @@ class ExceptionLogger { public: using VecStr = std::vector; + using size_type = std::size_t; + + //! Initialize with total number of exceptions to log + explicit ExceptionLogger(size_type total_count) : size_(total_count) {} void operator()(VecStr const& msg_stack) { @@ -81,17 +86,18 @@ class ExceptionLogger if (msg_stack.front() == last_msg_) { - ++ignored_counter_; + ++num_ignored_; } else { - flush_suppressed(); + this->flush_suppressed(); last_msg_ = msg_stack.front(); - auto log_msg = CELER_LOG_LOCAL(critical); - log_msg << "Suppressed exception from parallel thread: " - << join(msg_stack.begin(), msg_stack.end(), "\n... from "); + CELER_LOG_LOCAL(critical) + << '[' << index_ + 1 << '/' << size_ << "]: " + << join(msg_stack.begin(), msg_stack.end(), "\n ...from "); } + ++index_; } // Flush any suppressed messages before destruction @@ -99,25 +105,34 @@ class ExceptionLogger { try { - flush_suppressed(); + this->flush_suppressed(); } - // NOLINTNEXTLINE(bugprone-empty-catch) - catch (...) + catch (std::exception const& e) { + std::clog + << R"(failed to print suppressed exceptions during ExceptionLogger teardown: )" + << e.what() << std::endl; } } private: std::string last_msg_; - size_type ignored_counter_{0}; + size_type index_{0}; + size_type size_{0}; + size_type num_ignored_{0}; void flush_suppressed() { - if (ignored_counter_ > 0) + if (num_ignored_ > 0) { - CELER_LOG_LOCAL(warning) - << "Suppressed " << ignored_counter_ << " similar exceptions"; - ignored_counter_ = 0; + // Count should be the + CELER_ASSERT(num_ignored_ < index_); + auto previous = index_ - num_ignored_; + CELER_LOG_LOCAL(critical) + << '[' << previous + 1 << "-" << index_ << '/' << size_ + << "]: identical root cause to exception [" << previous << '/' + << size_ << ']'; + num_ignored_ = 0; } } }; @@ -129,7 +144,7 @@ namespace detail { //---------------------------------------------------------------------------// /*! - * Throw the first exception and log all the rest. + * Log all exceptions and rethrow the first on the list. */ [[noreturn]] void log_and_rethrow_impl(MultiExceptionHandler&& exceptions) { @@ -137,9 +152,9 @@ namespace detail auto exc_vec = std::move(exceptions).release(); ExceptionStackUnwinder unwind_stack; - ExceptionLogger log_exception; + ExceptionLogger log_exception{exc_vec.size()}; - for (auto eptr_iter = exc_vec.begin() + 1; eptr_iter != exc_vec.end(); + for (auto eptr_iter = exc_vec.begin(); eptr_iter != exc_vec.end(); ++eptr_iter) { try @@ -182,8 +197,8 @@ namespace detail CELER_LOG_LOCAL(critical) << "(unknown exception)"; } } - CELER_LOG(critical) << "failed to clear exceptions from " - "MultiExceptionHandler"; + CELER_LOG(critical) + << R"(failed to clear exceptions from MultiExceptionHandler)"; std::terminate(); } diff --git a/test/corecel/sys/MultiExceptionHandler.test.cc b/test/corecel/sys/MultiExceptionHandler.test.cc index 3b354de225..2048b337ad 100644 --- a/test/corecel/sys/MultiExceptionHandler.test.cc +++ b/test/corecel/sys/MultiExceptionHandler.test.cc @@ -7,7 +7,9 @@ #include "corecel/sys/MultiExceptionHandler.hh" #include +#include +#include "corecel/Assert.hh" #include "corecel/ScopedLogStorer.hh" #include "corecel/io/Logger.hh" @@ -22,7 +24,15 @@ namespace test class MockContextException : public std::exception { public: - char const* what() const noexcept final { return "some context"; } + explicit MockContextException(std::string msg = "some context") + : msg_(std::move(msg)) + { + } + + char const* what() const noexcept final { return msg_.c_str(); } + + private: + std::string msg_; }; //---------------------------------------------------------------------------// @@ -35,6 +45,7 @@ class MultiExceptionHandlerTest : public ::celeritas::test::Test ScopedLogStorer scoped_log_; }; +// Test that a single error throws as expected TEST_F(MultiExceptionHandlerTest, single) { MultiExceptionHandler capture_exception; @@ -46,11 +57,13 @@ TEST_F(MultiExceptionHandlerTest, single) EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), RuntimeError); } +//! Test that four different messages all show up TEST_F(MultiExceptionHandlerTest, multi) { MultiExceptionHandler capture_exception; - CELER_TRY_HANDLE(CELER_RUNTIME_THROW("runtime", "first exception", ""), - capture_exception); + CELER_TRY_HANDLE( + throw RuntimeError({"runtime", "first exception", "", "test.cc", 0}), + capture_exception); for (auto i : range(3)) { DebugErrorDetails deets{ @@ -60,54 +73,61 @@ TEST_F(MultiExceptionHandlerTest, multi) EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), RuntimeError); static char const* const expected_log_messages[] = { - R"(Suppressed exception from parallel thread: test.cc:0: -celeritas: internal assertion failed: false)", - R"(Suppressed exception from parallel thread: test.cc:1: -celeritas: internal assertion failed: false)", - R"(Suppressed exception from parallel thread: test.cc:2: -celeritas: internal assertion failed: false)", + "[1/4]: runtime error: first exception\ntest.cc: failure", + "[2/4]: test.cc:0:\nceleritas: internal assertion failed: false", + "[3/4]: test.cc:1:\nceleritas: internal assertion failed: false", + "[4/4]: test.cc:2:\nceleritas: internal assertion failed: false", }; EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); static char const* const expected_log_levels[] - = {"critical", "critical", "critical"}; + = {"critical", "critical", "critical", "critical"}; EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); } +//! Test that similar messages are suppressed TEST_F(MultiExceptionHandlerTest, multi_nested) { MultiExceptionHandler capture_exception; CELER_TRY_HANDLE_CONTEXT( - CELER_RUNTIME_THROW("runtime", "first exception", ""), + throw RuntimeError({"runtime", "it just got real", "", "test.cc", 1}), capture_exception, MockContextException{}); - DebugErrorDetails deets{DebugErrorType::internal, "false", "test.cc", 2}; for (auto i = 0; i < 4; ++i) { - CELER_TRY_HANDLE_CONTEXT(throw DebugError(std::move(deets)), - capture_exception, - MockContextException{}); + CELER_TRY_HANDLE_CONTEXT( + throw DebugError({DebugErrorType::internal, "false", "test.cc", 2}), + capture_exception, + MockContextException{"context " + std::to_string(i)}); } EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), MockContextException); static char const* const expected_log_messages[] = { - R"(Suppressed exception from parallel thread: test.cc:2: + R"([1/5]: runtime error: it just got real +test.cc:1: failure + ...from some context)", + R"([2/5]: test.cc:2: celeritas: internal assertion failed: false -... from some context)", - "Suppressed 3 similar exceptions", + ...from context 0)", + "[3-5/5]: identical root cause to exception [2/5]", }; - EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); - static char const* const expected_log_levels[] = {"critical", "warning"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()) << scoped_log_; + static char const* const expected_log_levels[] + = {"critical", "critical", "critical"}; EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); } -// Failure case can't be tested as part of the rest of the suite -TEST_F(MultiExceptionHandlerTest, DISABLED_uncaught) +// Test that uncaught exceptions terminate the program +TEST_F(MultiExceptionHandlerTest, uncaught) { - MultiExceptionHandler catchme; - CELER_TRY_HANDLE(CELER_VALIDATE(false, << "derp"), catchme); - // Program will terminate when catchme leaves scope + EXPECT_DEATH( + { + MultiExceptionHandler catchme; + CELER_TRY_HANDLE(CELER_VALIDATE(false, << "derp"), catchme); + // Program will terminate when catchme leaves scope + }, + "failed to clear exceptions from MultiExceptionHandler"); } //---------------------------------------------------------------------------// From 3c486542d96dacb5380bb31a7dc420079874654e Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 20 Jan 2026 10:55:51 -0500 Subject: [PATCH 43/60] Add copilot agent instructions (#2195) * Claude copilot build instructions * Update instructions by hand * Move instructions to AGENTS.md and update * Update versions and split spack package * Update readme and recombine spack files * Update collection group doc --- AGENTS.md | 171 +++++++++++++++++++++++++++++++++ CMakeLists.txt | 5 +- README.md | 46 +++++---- doc/usage/installation.rst | 2 + scripts/spack.yaml | 106 ++++++++++++-------- src/corecel/data/Collection.hh | 17 ++-- 6 files changed, 278 insertions(+), 69 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..f688d1bf6b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,171 @@ +# Celeritas AI Agent Instructions +Celeritas is a GPU-accelerated HEP detector physics library for HL-LHC, integrating with Geant4. It's a C++17 codebase with CUDA/HIP device support. + +## Architecture Overview + +### Core Components (src/) +- **corecel/**: GPU abstractions, data structures, utilities (host-device compatible code) +- **geocel/**: Geometry cell interfaces (ORANGE, VecGeom, Geant4 adapters) +- **orange/**: ORANGE geometry engine (native Celeritas geometry) +- **celeritas/**: Core physics (EM processes, particles, materials, stepping loop) +- **accel/**: Geant4 integration layer (offload mechanisms, tracking managers) + +### Data Flow Pattern +Celeritas separates *shared* data from *state* data: +- **Params**: Immutable problem setup data (physics tables, geometry) - constructed once +- **States**: Mutable per-track data (particle states, RNG states) - one per concurrent event/track +- Data exists in **host** and **device** memory spaces with explicit ownership (`value`, `reference`, `const_reference`) + +To support GPU execution, it uses data-oriented design but with object-oriented interfaces (`View`s). + +## Build & Test Workflow + +### Quick Start +Use a standard CMake workflow: +```bash +cmake -B build -G Ninja +cd build +ninja +ctest +``` + +### Testing +- Unit tests in `test/` mirror `src/` structure +- Uses GoogleTest with base-class test harness and custom helper macros +- Unit test for class ``celeritas::A::Foo`` should be defined in namespace + ``celeritas::A::test`` + +## Code Conventions + +### File Extensions +- `.hh`: Host-device compatible C++ headers (use `CELER_FUNCTION` macros) +- `.cc`: Host-only C++ +- `.cu`: CUDA kernels and launch code (compiled by nvcc, but should be HIP-compatible via macros) +- `.device.hh/.device.cc`: Requires CUDA/HIP runtime but compilable by host compiler +- `.test.cc`: Unit tests + +Most development doesn't involve CUDA/HIP code: only kernel launches (device execution) should be in `.cu` files. + +### Host-Device Compatibility +Use these macros (from `corecel/Macros.hh`): +- `CELER_FUNCTION`: Mark functions callable from both host and device +- `CELER_FORCEINLINE_FUNCTION`: Force-inline version +- `CELER_CONSTEXPR_FUNCTION`: Compile-time + host/device + +Example: +```cpp +CELER_FUNCTION real_type calculate(Real3 const& pos) { /* ... */ } +``` + +### Naming Conventions +- Classes/structs: `CapWords` (e.g., `PhysicsTrackView`) +- Functions/variables: `snake_case` +- Private members: trailing underscore (`data_`) +- Functors: agent nouns (`ModelEvaluator`), instances are verbs (`evaluate_model`) +- OpaqueId types: `FooId` for `Foo` items, `Bar_` tag struct for abstract concepts + +### Data Structures +- Use `OpaqueId` for type-safe indices, not raw integers +- End structs with `operator bool()` for validity checks +- Prefer `Collection` over raw arrays for GPU-compatible storage +- Use `Span` for array views, `Array` for fixed-size arrays + +### Assertions & Error Handling +- `CELER_EXPECT`: Preconditions (function entry) +- `CELER_ASSERT`: Internal invariants +- `CELER_ENSURE`: Postconditions (function exit) +- `CELER_VALIDATE`: Always-on user input validation +- Debug assertions only active when `CELERITAS_DEBUG=ON` + +## Critical Patterns + +### Action/Executor/Interactor Paradigm +The stepping loop uses a three-layer pattern: +- **Action**: Implements `StepActionInterface`, defines when to run (`order()`), and launches kernels for host/device via `step()` methods +- **Executor**: Wraps the interactor and handles track-level logic (e.g., `make_action_track_executor` filters by `action_id`) +- **Interactor**: Pure physics functor that operates on a minimal physics information (e.g., `MaterialView`) and returns an `Interaction` + +Example flow: +```cpp +// In Model::step() [.cc or .cu] +auto execute = make_action_track_executor( + params.ptr(), state.ptr(), this->action_id(), + InteractionApplier{MyModelExecutor{this->host_ref()}}); +launch_action(*this, params, state, execute); // or ActionLauncher for device +``` + +See `src/celeritas/em/model/KleinNishinaModel.{cc,cu}` for a complete example. + +### Building Params Data with Inserters +Physics models build device-compatible data using "inserter" classes during construction. Inserters efficiently populate `Collection` objects with deduplication and proper memory layout: +```cpp +class XsGridInserter { +public: + GridId operator()(inp::XsGrid const& grid); // Returns ID for referencing +private: + DedupeCollectionBuilder reals_; // Deduplicates identical data + CollectionBuilder grids_; // Sequential insertion +}; +``` +See `src/celeritas/grid/XsGridInserter.hh` and `src/celeritas/em/model/detail/LivermoreXsInserter.hh`. + +### Collection Groups: Managing Host-Device Data +Collections manage deeply hierarchical GPU data via type-safe indices and ranges: +- **Collection**: Array-like container with ownership (`value`/`reference`/`const_reference`) and memory space (`host`/`device`) +- **ItemId**: Type-safe index into a `Collection` (actually `OpaqueId`) +- **ItemRange**: Slice of items [begin, end) for variable-length data +- **ItemMap**: Maps one ID type to another via offset arithmetic (not a hash map!) + +Example data structure: +```cpp +template +struct MyParamsData { + Collection materials; // Array of materials + Collection components; // Backend storage + Collection reals; // Backend reals + // Each Material has ItemRange referencing components + // Templated operator= enables copying across memory spaces + // Boolean operator validates initialization and copying +}; +``` + +Collections power the params/states architecture: build on host with `Ownership::value`, copy to device, then access via `const_reference` (params) or `reference` (states). See `src/corecel/data/Collection.hh` for details. + +### Test Requirements +Every class needs a unit test with cyclomatic complexity coverage. Detail classes (in `detail/` namespaces) are exempt but still recommended. + +## Integration Points + +### Geant4 Integration (accel/) +Users integrate via `SharedParams`, `TrackingManagerConstructor`, and run actions (`BeginOfRunAction`, `EndOfRunAction`). See `example/accel/` for templates. Use `celeritas_target_link_libraries()` instead of `target_link_libraries()` to handle VecGeom RDC linking. + +### Standalone Execution (app/) +EM-only execution is used for performance testing and verification via `celer-sim`. + +### Geometry +Supports ORANGE (native), VecGeom, and Geant4 geometries. GDML is the standard interchange format. Geometry loads through `inp::Model` (see `geocel/inp/Model.hh`). + +## Documentation + +- Doxygen comments go next to **definitions**, not declarations +- Document `operator()` behavior in class comment, not operator itself +- Use `\citep{author-keyword-year}` for references (maintained in Zotero at `doc/_static/zotero.bib`) +- Physics constants need units and paper citations + +## Development Tools + +- **pre-commit**: Auto-formats code (clang-format enforces 80-column limit, East const) +- **celeritas-gen.py** (`scripts/dev/`): Generate file skeletons with proper decorations +- **CMake presets**: System-specific configs in `scripts/cmake-presets/.json` + +## Common Pitfalls + +- Never copy-paste code: instead, refactor into reusable functors +- Failing to mark functions `CELER_FUNCTION` will cause `call to __host__ function from __device__` errors + +## External Dependencies + +Key dependencies (see `scripts/spack-packages.yaml` for versions): +- Geant4 +- GoogleTest (tests), CLI11 (apps), nlohmann_json (I/O) +- Optional: VecGeom, ROOT, HepMC3, DD4hep, MPI, OpenMP, Perfetto diff --git a/CMakeLists.txt b/CMakeLists.txt index 5191b4b019..101712bd4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -484,8 +484,9 @@ if(CELERITAS_USE_ROOT) ) endif() if(NOT ROOT_FOUND) - # ROOT requirement is due to missing CMake commands in old versions - find_package(ROOT 6.24 REQUIRED) + # ROOT requirement is due to missing CMake commands in < 6.24 + # and Python3 compatibility in 6.28 + find_package(ROOT 6.28 REQUIRED) endif() if(ROOT_CXX_STANDARD AND CMAKE_CXX_STANDARD AND NOT (ROOT_CXX_STANDARD STREQUAL CMAKE_CXX_STANDARD)) diff --git a/README.md b/README.md index b5c068e3a2..cd00321f1a 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,15 @@ requirements of the [HL-LHC upgrade][HLLHC]. # Documentation Most of the Celeritas documentation is readable through the codebase through a -combination of [static RST documentation][inline-docs] and Doxygen-markup -comments in the source code itself. The full [Celeritas user -documentation][user-docs] (including selected code documentation incorporated -by Breathe) and the [Celeritas code documentation][dev-docs] are mirrored on -our GitHub pages site. You can generate these yourself (if the necessary -prerequisites are installed) by -setting the `CELERITAS_BUILD_DOCS=ON` configuration option and running -`ninja doc` (user) or `ninja doxygen` (developer). - -[inline-docs]: doc/index.rst +combination of [static RST documentation][doc/index.rst] and Doxygen-markup +comments in the source code itself. +The full [Celeritas user documentation][user-docs] (including selected code +documentation incorporated by Breathe) and the [Celeritas code +documentation][dev-docs] are mirrored on our GitHub pages site. +You can generate these yourself (if the necessary prerequisites are installed) by +setting the `CELERITAS_BUILD_DOCS=ON` configuration option and running `ninja +doc` (user) or `ninja doxygen` (developer). + [user-docs]: https://celeritas-project.github.io/celeritas/user/index.html [dev-docs]: https://celeritas-project.github.io/celeritas/dev/index.html @@ -146,14 +145,14 @@ installed and want to do development on a CUDA system with Ampere-class graphics cards, execute the following steps from within the cloned Celeritas source directory: ```console -# Set up CUDA (optional) -$ spack external find cuda -# Install celeritas dependencies +# Create an environment from celeritas dependencies $ spack env create celeritas scripts/spack.yaml $ spack env activate celeritas -$ spack config add packages:all:variants:"cxxstd=17 +cuda cuda_arch=80" +# Set up CUDA/HIP (optional; example here is for Nvidia A100) +$ spack external find --not-buildable cuda +$ spack config add packages:all:prefer:"+cuda cuda_arch=80" +# Install dependencies $ spack install -# Set up # Configure, build, and test with a default development configure $ ./scripts/build.sh dev ``` @@ -169,9 +168,9 @@ $ make && ctest > [!NOTE] > It is **highly** recommended to use the `build.sh` script to set up your -> environment, even when not using Spack. The first time you run it, edit the -> `CMakeUserPresets.json` symlink it creates, and submit it in your next pull -> request. +> environment, even when not using Spack, for reproducibility and error +> checking. The first time you run it, edit the `CMakeUserPresets.json` +> symlink it creates, and submit it in your next pull request. Celeritas guarantees full compatibility and correctness only on the combinations of compilers and dependencies tested under continuous integration. @@ -188,7 +187,7 @@ See the configure output from the [GitHub runners][runners] for the full list of - C++ standard - C++17 and C++20 - Dependencies: - - Geant4 11.0.4 + - Geant4 10.5-11.4 - VecGeom 1.2.10 Partial compatibility and correctness is available for an extended range of @@ -214,9 +213,14 @@ Compatibility fixes that do not cause newer versions to fail are welcome. -See the [contribution guide][contributing-guidelines] for the contribution process, +See +[the contribution guide][contributing-guidelines] for the contribution process, [the development guidelines][development-guidelines] for further -details on coding in Celeritas, and [the administration guidelines][administration-guidelines] for community standards and roles. +details on coding in Celeritas, and +[the administration guidelines][administration-guidelines] for community +standards and roles. +The [AGENTS.md file][agents.md] contains instructions for AI tools but is also +a good quick-start guide for humans. [contributing-guidelines]: https://celeritas-project.github.io/celeritas/user/development/contributing.html [development-guidelines]: https://celeritas-project.github.io/celeritas/user/development/coding.html diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index f18edf4f45..8c614c5fde 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -54,6 +54,7 @@ internet if required but not available on the user's system. CLI11_, Runtime*, "Command line parsing" CUDA_, Runtime, "GPU computation" + DD4hep_, Runtime, "HEP detector framework integration" Geant4_, Runtime, "Physics data and user integration" G4EMLOW_, Runtime, "EM physics model data" G4VG_, Runtime, "Geant4-to-VecGeom translation" @@ -83,6 +84,7 @@ internet if required but not available on the user's system. .. _CLI11: https://cliutils.github.io/CLI11/book/ .. _CMake: https://cmake.org .. _CUDA: https://developer.nvidia.com/cuda-toolkit +.. _DD4hep: https://dd4hep.web.cern.ch .. _Doxygen: https://www.doxygen.nl .. _G4VG: https://github.com/celeritas-project/g4vg .. _G4EMLOW: https://geant4.web.cern.ch/support/download diff --git a/scripts/spack.yaml b/scripts/spack.yaml index f6dec3607b..8de7f30f65 100644 --- a/scripts/spack.yaml +++ b/scripts/spack.yaml @@ -1,48 +1,76 @@ spack: specs: - # Packages required for a *truly minimal* build - - cli11 - - cmake - - nlohmann-json - # ... plus packages required for *basic* realistic development - - "geant4@11" - - git - - "googletest@1.10:" - - "py-pre-commit ^py-identify@2.6:" - - "python@3.9:" - # ... plus *recommended* options - - "covfie@0.13:" - - "g4vg@1.0.3:" - - hepmc3 - - libpng - - ninja - - "root@6.28:" - - "vecgeom@1.2.10:1 +gdml" - # ... plus additional documentation tools - - doxygen - - git-lfs - - py-breathe - - py-furo - - py-sphinx - - py-sphinxcontrib-bibtex - # ... plus experimental options - - "dd4hep@1.18:" - - mpi + # Packages required for a *truly minimal* build + - cli11 + - cmake + - nlohmann-json + # ... plus packages required for *basic* realistic development + - geant4@11.3 + - git + - googletest + - py-pre-commit + - python + # ... plus *recommended* options + - covfie + - g4vg + - hepmc3 + - ninja + - vecgeom@1 + # Uncomment the following lines for additional supported optional packages +# - root +# # documentation +# - doxygen +# - py-breathe +# - py-furo +# - py-sphinx +# - py-sphinxcontrib-bibtex +# # experimental +# - dd4hep +# - mpi +# # R&D repositories +# - git-lfs view: true concretizer: unify: true + # These encode version and variant requirements for packages used by Celeritas. + # Update this file when modifying the top-level CMakeLists.txt. packages: + all: + prefer: + - "generator=ninja build_system=cmake" + - "build_type=Release" + - "cxxstd=20" + # Note: for CUDA support run this command in your environment: + # spack config add packages:all:prefer:"+cuda cuda_arch=" + cli11: + require: '@2.4:' + cmake: + require: '@3.18:' + nlohmann-json: + require: '@3.7.0:' + geant4: + require: '@10.5:' + googletest: + require: '@1.10:' + py-identify: # needed by py-pre-commit + require: '@2.6:' + python: + require: '@3.9:' + covfie: + require: '@0.13:' + g4vg: + require: '@1.0.3:' + vecgeom: + require: + - '@1.2.10:' + - +gdml root: - # Note: dd4hep requires +gsl+math - # ROOT here is built without GUI to reduce build time - variants: ~aqua ~davix ~examples ~opengl ~x ~tbb ~webgui +root7 + require: + - '@6.28:' + variants: + # Prefer building without GUI and with ROOT 7 support if available + - ~aqua ~davix ~examples ~opengl ~x ~tbb ~webgui +root7 dd4hep: - variants: ~utilityapps ~ddeve - all: require: - # Note: C++17 is the minimum required but we'll default to C++20 - - any_of: [cxxstd=20, '@:'] - - any_of: [build_system=cmake, '@:'] - - any_of: [build_type=Release, '@:'] - # Note: for CUDA support run this command in your environment: - # spack config add packages:all:variants:"+cuda cuda_arch=" + - '@1.18:' + - ~utilityapps ~ddeve diff --git a/src/corecel/data/Collection.hh b/src/corecel/data/Collection.hh index f16e733828..7fbde24ba5 100644 --- a/src/corecel/data/Collection.hh +++ b/src/corecel/data/Collection.hh @@ -20,11 +20,6 @@ namespace celeritas { -//---------------------------------------------------------------------------// -// Forward declare DedupeCollectionBuilder for use as a friend class -template -class DedupeCollectionBuilder; - //---------------------------------------------------------------------------// /*! * \page collections Collection: a data portability class @@ -69,8 +64,7 @@ class DedupeCollectionBuilder; * - Define an operator bool that is true if and only if the class data is * assigned and consistent * - Define a \em templated assignment operator on "other" Ownership and - MemSpace - * which assigns every member to the right-hand-side's member + * MemSpace which assigns every member to the right-hand-side's member * * Additionally, a \c StateData collection group must define * - A member function \c size() returning the number of entries (i.e. number @@ -109,6 +103,10 @@ class DedupeCollectionBuilder; * item. These often need to reference a variable-sized range of data and do so * by storing an \c ItemRange or \c ItemMap . These two types are offsets into * "backend" data stored by a collection group. + * + * Finally, note that the templated type aliases \c HostVal, \c HostCRef, + * \c DeviceRef, etc. are useful for functions that are specialized on + MemSpace. */ //! Opaque ID representing a single element of a container. @@ -234,6 +232,11 @@ class ItemMap Range range_; }; +//---------------------------------------------------------------------------// +// Forward declare DedupeCollectionBuilder for use as a friend class +template +class DedupeCollectionBuilder; + // Forward-declare collection builder, needed for GCC7 template class CollectionBuilder; From 45e241afaa038745893e6ca28c343bb8490322b3 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Tue, 20 Jan 2026 11:58:22 -0500 Subject: [PATCH 44/60] Demonstrate stuck particle in ATLAS (#1914) * Add test demonstrating failure * Disable test/document failure for VecGeom * Add additional volume level test and fix crash in level when vecgeom is outside * Update test for vecgeom surface 2.0.0-dev.6.10+master.f6a9cf598 * Fix soft equivalence when evaulating at origin * fixup! Fix soft equivalence when evaulating at origin * Adjust tracking tol on per-call basis * Fix single precision test and vecgeom 2 tests --- src/geocel/vg/VecgeomTrackView.hh | 1 + test/geocel/CMakeLists.txt | 1 + test/geocel/GenericGeoTestInterface.cc | 33 ++- test/geocel/GenericGeoTestInterface.hh | 7 + test/geocel/GeoTests.cc | 289 +++++++++++++++++++++++++ test/geocel/GeoTests.hh | 20 ++ test/geocel/data/atlas-hgtd.gdml | 169 +++++++++++++++ test/geocel/g4/GeantGeo.test.cc | 56 +++++ test/geocel/vg/Vecgeom.test.cc | 20 ++ test/orange/OrangeGeant.test.cc | 20 ++ 10 files changed, 610 insertions(+), 6 deletions(-) create mode 100644 test/geocel/data/atlas-hgtd.gdml diff --git a/src/geocel/vg/VecgeomTrackView.hh b/src/geocel/vg/VecgeomTrackView.hh index f1645e553e..682694767f 100644 --- a/src/geocel/vg/VecgeomTrackView.hh +++ b/src/geocel/vg/VecgeomTrackView.hh @@ -328,6 +328,7 @@ CELER_FUNCTION VolumeInstanceId VecgeomTrackView::volume_instance_id() const */ CELER_FUNCTION VolumeLevelId VecgeomTrackView::volume_level() const { + CELER_EXPECT(!this->is_outside()); auto result = id_cast(vgstate_.GetLevel()); CELER_ENSURE(result < params_.scalars.num_volume_levels); return result; diff --git a/test/geocel/CMakeLists.txt b/test/geocel/CMakeLists.txt index 03d2208aab..a06a425de9 100644 --- a/test/geocel/CMakeLists.txt +++ b/test/geocel/CMakeLists.txt @@ -81,6 +81,7 @@ if(CELERITAS_USE_VecGeom) "TwoBoxesVgdml" ) set(_g4vg_tests + "AtlasHgtd" "CmsEeBackDee" "Cmse" "FourLevels" diff --git a/test/geocel/GenericGeoTestInterface.cc b/test/geocel/GenericGeoTestInterface.cc index 623e57a523..438f8ac37e 100644 --- a/test/geocel/GenericGeoTestInterface.cc +++ b/test/geocel/GenericGeoTestInterface.cc @@ -8,6 +8,7 @@ #include +#include "corecel/Types.hh" #include "corecel/io/Logger.hh" #include "corecel/math/ArrayOperators.hh" #include "corecel/math/ArrayUtils.hh" @@ -26,6 +27,7 @@ namespace celeritas { namespace test { + //---------------------------------------------------------------------------// /*! * Track until exiting the geometry. @@ -35,6 +37,7 @@ namespace test */ auto GenericGeoTestInterface::track(Real3 const& pos, Real3 const& dir, + TrackingTol const& tol, int remaining_steps) -> TrackingResult { TrackingResult result; @@ -86,7 +89,6 @@ auto GenericGeoTestInterface::track(Real3 const& pos, // Convert from Celeritas native unit system to unit test's internal system auto from_native_length = [scale = unit_length.value](auto&& v) { return v / scale; }; - auto const tol = this->tracking_tol(); while (!geo.is_outside()) { @@ -143,8 +145,15 @@ auto GenericGeoTestInterface::track(Real3 const& pos, result.volumes.back() += "/" + this->volume_name(geo); } GGTI_EXPECT_NO_THROW(next = geo.find_next_step()); - EXPECT_SOFT_NEAR(next.distance, half_distance, tol.distance) - << "reinitialized distance mismatch at index " + real_type length_scale = half_distance; + for (auto x : geo.pos()) + { + length_scale = max(length_scale, std::fabs(x)); + } + EXPECT_NEAR( + next.distance, half_distance, tol.distance * length_scale) + << "reinitialized distance mismatch (length scale=" + << length_scale << ") at index " << result.volumes.size() - 1 << ": " << geo; } } @@ -174,6 +183,14 @@ auto GenericGeoTestInterface::track(Real3 const& pos, return result; } +// Track until exiting the geometry (default test tol) +auto GenericGeoTestInterface::track(Real3 const& pos_cm, + Real3 const& dir, + int max_steps) -> TrackingResult +{ + return this->track(pos_cm, dir, this->tracking_tol(), max_steps); +} + //---------------------------------------------------------------------------// /*! * Get the volume instance stack at a position. @@ -181,20 +198,24 @@ auto GenericGeoTestInterface::track(Real3 const& pos, auto GenericGeoTestInterface::volume_stack(Real3 const& pos) -> VolumeStackResult { + VolumeStackResult result; + CheckedGeoTrackView geo{this->make_geo_track_view_interface()}; - geo = this->make_initializer(pos, Real3{0, 0, 1}); + GGTI_EXPECT_NO_THROW(geo = this->make_initializer(pos, Real3{0, 0, 1})); auto vlev = geo.volume_level(); if (!vlev) { - return {}; + return result; } std::vector inst_ids(vlev.get() + 1); geo.volume_instance_id(make_span(inst_ids)); - return VolumeStackResult::from_span( + result = VolumeStackResult::from_span( this->get_test_volumes()->volume_instance_labels(), make_span(inst_ids)); + + return result; } //---------------------------------------------------------------------------// diff --git a/test/geocel/GenericGeoTestInterface.hh b/test/geocel/GenericGeoTestInterface.hh index 6e47a3eb0c..7587d4ab4a 100644 --- a/test/geocel/GenericGeoTestInterface.hh +++ b/test/geocel/GenericGeoTestInterface.hh @@ -43,6 +43,7 @@ class GenericGeoTestInterface : public LazyGeantGeoManager //!@{ //! \name Type aliases using TrackingResult = GenericGeoTrackingResult; + using TrackingTol = GenericGeoTrackingTolerance; using VolumeStackResult = GenericGeoVolumeStackResult; using GeoTrackView = GeoTrackInterface; using UPGeoTrack = std::unique_ptr; @@ -52,6 +53,12 @@ class GenericGeoTestInterface : public LazyGeantGeoManager //// TESTS //// // Track until exiting the geometry + TrackingResult track(Real3 const& pos_cm, + Real3 const& dir, + TrackingTol const& tol, + int max_steps); + + // Track until exiting the geometry (default test tol) TrackingResult track(Real3 const& pos_cm, Real3 const& dir, int max_steps = 50); diff --git a/test/geocel/GeoTests.cc b/test/geocel/GeoTests.cc index 6499e67f94..1f5c6bae4c 100644 --- a/test/geocel/GeoTests.cc +++ b/test/geocel/GeoTests.cc @@ -12,6 +12,7 @@ #include "corecel/cont/Range.hh" #include "corecel/io/Logger.hh" #include "corecel/math/ArrayOperators.hh" +#include "corecel/math/ArrayUtils.hh" #include "corecel/math/Turn.hh" #include "corecel/sys/Version.hh" #include "geocel/BoundingBox.hh" @@ -89,6 +90,294 @@ void delete_orange_safety(GenericGeoTestInterface const& interface, //---------------------------------------------------------------------------// } // namespace +//---------------------------------------------------------------------------// +// ATLAS HGTD +//---------------------------------------------------------------------------// +void AtlasHgtdGeoTest::test_trace() const +{ + auto tol = test_->tracking_tol(); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + // Tracking errors at most distances: 5e-6 ~ 2e-4 + tol.distance = 5e-4; + } + { + SCOPED_TRACE("+z"); + auto result = test_->track({12.5, 0, -2600}, {0, 0, 1}, tol, 50); + GenericGeoTrackingResult ref; + ref.volumes = { + "Atlas", "HGTD", "SPlate", "HGTD", "SPlate", "HGTD", "SPlate", + "HGTD", "SPlate", "HGTD", "ITK", "HGTD", "SPlate", "HGTD", + "SPlate", "HGTD", "SPlate", "HGTD", "SPlate", "HGTD", "Atlas", + }; + ref.volume_instances = { + "Atlas_PV", "HGTD", "SPlate_7@1", "HGTD", "SPlate_6@1", "HGTD", + "SPlate_5@1", "HGTD", "SPlate_4@1", "HGTD", "ITK", "HGTD", + "SPlate_4@0", "HGTD", "SPlate_5@0", "HGTD", "SPlate_6@0", "HGTD", + "SPlate_7@0", "HGTD", "Atlas_PV", + }; + ref.distances = { + 2245.5, 6.75, 0.1, 0.6, 0.1, 1.7, 0.1, 0.6, 0.1, 2.45, 684, + 2.45, 0.1, 0.6, 0.1, 1.7, 0.1, 0.6, 0.1, 6.75, 2250.1, + }; + ref.halfway_safeties = { + 771.8918204645, + 1.5, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 1.225, + 9.62, + 1.225, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 1.5, + 769.72940862358, + }; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + if (test_->geometry_type() == "VecGeom" && CELERITAS_VECGEOM_SURFACE) + { + // World safety differs + ref.halfway_safeties[0] = 725.849243164062; + ref.halfway_safeties[20] = 723.549255371094; + } + + EXPECT_REF_NEAR(ref, result, tol); + } + { + SCOPED_TRACE("+z near trouble"); + auto result = test_->track({24, 18, 300}, {0, 0, 1}, tol, 50); + GenericGeoTrackingResult ref; + ref.volumes = { + "ITK", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "Atlas", + }; + ref.volume_instances = { + "ITK", + "HGTD", + "SPlate_4@0", + "HGTD", + "SPlate_5@0", + "HGTD", + "SPlate_6@0", + "HGTD", + "SPlate_7@0", + "HGTD", + "Atlas_PV", + }; + ref.distances = { + 42, // enter HGTD at z=342 + 2.45, // enter HGTDSupportPlate at z=344.45 + 0.1, // leave into HGTD at z=344.55 + 0.6, + 0.1, + 1.7, + 0.1, + 0.6, + 0.1, + 6.75, + 2250.1, + }; + ref.halfway_safeties = { + 21, + 1.225, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 3.375, + 763.93626206641, + }; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + if (test_->geometry_type() == "VecGeom" && CELERITAS_VECGEOM_SURFACE) + { + // World safety differs + ref.halfway_safeties[10] = 723.549255371094; + } + EXPECT_REF_NEAR(ref, result, tol); + } + + { + // See https://github.com/celeritas-project/celeritas/issues/1902 + // in HGTD::HGTDSupportPlate, on boundary, taking small step + SCOPED_TRACE("tangent at far away point"); + + auto tol = test_->tracking_tol(); + tol.distance = 1e-7; + + Real3 pos{24.097769534015998, 17.956803215217408, 344.45}; + Real3 dir{ + 0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}; + axpy(real_type{-1}, dir, &pos); + + if (test_->geometry_type() == "VecGeom" && !CELERITAS_VECGEOM_SURFACE) + { + GTEST_SKIP() << "VecGeom fails the tangent trace"; + } + else if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + // Initialization is exactly on a surface with single precision + auto up_view = test_->make_geo_track_view_interface(); + *up_view = test_->make_initializer(pos, dir); + EXPECT_TRUE(up_view->failed()); + GTEST_SKIP() << "ORANGE single precision starts on a boundary"; + } + + auto result = test_->track(pos, dir, tol, /* max steps = */ 10); + + GenericGeoTrackingResult ref; + ref.volumes = {"SPlate", "HGTD", "ITK", "Atlas"}; + ref.volume_instances = {"SPlate_4@0", "HGTD", "ITK", "Atlas_PV"}; + ref.distances = { + 1.0000000238587, 81.021892625066, 4.8164185705351, 1305.6446868933}; + ref.halfway_safeties = { + 4.6451791604341e-07, + 2.4499623638801, + 2.3998244128449, + 652.50340341824, + }; + ref.dot_normal + = {9.2903580992121e-07, 0.99644211932289, 0.99673389979137}; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + EXPECT_REF_NEAR(ref, result, tol); + } +} + +//---------------------------------------------------------------------------// +void AtlasHgtdGeoTest::test_volume_stack() const +{ + if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + GTEST_SKIP() << "Track starts on a boundary with single precision"; + } + + std::vector all_stacks; + + Real3 const dir{ + 0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}; + + for (real_type dx : {-1.0, -1e-3, -1e-5, 1e-5, 1e-3, 1.0}) + { + // Near z=344.45 is inside HGTDSupportPlate + Real3 pos{24.097769534015998, 17.956803215217408, 344.45}; + axpy(dx, dir, &pos); + + auto result = test_->volume_stack(pos); + all_stacks.emplace_back(to_string(join(result.volume_instances.begin(), + result.volume_instances.end(), + ","))); + } + + // NOTE: Geant4 returns SPlate for dx=1e-6, whereas VecGeom returns HGTD + std::vector expected_all_stacks = { + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD", + "Atlas_PV,ITK,HGTD", + "Atlas_PV,ITK,HGTD", + }; + if (test_->geometry_type() == "Geant4") + { + // Geant4 navigation (probably "skin" checks) overpredicts the + // volume extents + expected_all_stacks[3] = expected_all_stacks.front(); + } + if (test_->geometry_type() == "VecGeom" && vecgeom_version >= Version{2}) + { + // VecGeom surface overpredicts even more + expected_all_stacks[3] = expected_all_stacks.front(); + expected_all_stacks[4] = expected_all_stacks.front(); + if (CELERITAS_VECGEOM_SURFACE) + { + expected_all_stacks[5] = expected_all_stacks.front(); + } + } + + EXPECT_VEC_EQ(expected_all_stacks, all_stacks); +} + +void AtlasHgtdGeoTest::test_detailed_tracking() const +{ + if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + GTEST_SKIP() << "Track starts on a boundary with single precision"; + } + + { + // See https://github.com/celeritas-project/celeritas/issues/1902 + SCOPED_TRACE("almost tangent at large Z"); + auto geo = make_geo_track_view( + *test_, + {23.51934584635, 17.141066715148, 344.45000092904}, + {0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}); + ASSERT_FALSE(geo.is_outside()); + EXPECT_EQ("SPlate", test_->volume_name(geo)); + EXPECT_FALSE(geo.is_on_boundary()); + + // Find next boundary + auto next = geo.find_next_step(from_cm(2.0)); + EXPECT_SOFT_NEAR(1.0, to_cm(next.distance), 1e-5); + EXPECT_TRUE(next.boundary); + geo.move_to_boundary(); + EXPECT_SOFT_EQ(344.45, to_cm(geo.pos()[2])); + EXPECT_EQ("SPlate", test_->volume_name(geo)); + EXPECT_TRUE(geo.is_on_boundary()); + geo.cross_boundary(); + if (test_->geometry_type() == "VecGeom" && vecgeom_version < Version{2}) + { + // VecGeom fails to cross the boundary! the internal bump along the + // path of travel doesn't change the Z coordinate, so it assumes + // the updated point is still inside the original volume. + EXPECT_EQ("SPlate", test_->volume_name(geo)); + return; + } + EXPECT_EQ("HGTD", test_->volume_name(geo)); + EXPECT_TRUE(geo.is_on_boundary()); + + // Suppose safety distance results in small step limit + next = geo.find_next_step(from_cm(5e-9)); + EXPECT_SOFT_EQ(5e-9, to_cm(next.distance)); + geo.move_internal(from_cm(5e-9)); + EXPECT_FALSE(geo.is_on_boundary()); + EXPECT_EQ("HGTD", test_->volume_name(geo)); + + // Should be able to continue stepping as normal + next = geo.find_next_step(from_cm(1e-6)); + EXPECT_SOFT_EQ(1e-6, to_cm(next.distance)); + EXPECT_FALSE(next.boundary); + EXPECT_FALSE(geo.is_on_boundary()); + EXPECT_EQ("HGTD", test_->volume_name(geo)); + } +} + //---------------------------------------------------------------------------// // CMS EE //! Test geometry accessors diff --git a/test/geocel/GeoTests.hh b/test/geocel/GeoTests.hh index 99141f5dff..8b5ee75528 100644 --- a/test/geocel/GeoTests.hh +++ b/test/geocel/GeoTests.hh @@ -24,6 +24,26 @@ constexpr bool using_surface_vg = CELERITAS_VECGEOM_SURFACE; constexpr bool using_solids_vg = CELERITAS_VECGEOM_VERSION && !CELERITAS_VECGEOM_SURFACE; +//---------------------------------------------------------------------------// +/*! + * Test the ATLAS HGTD (translated distant pancakes). + */ +class AtlasHgtdGeoTest +{ + public: + static std::string_view gdml_basename() { return "atlas-hgtd"; } + + //! Construct with a reference to the GoogleTest + AtlasHgtdGeoTest(GenericGeoTestInterface* geo_test) : test_{geo_test} {} + + void test_trace() const; + void test_volume_stack() const; + void test_detailed_tracking() const; + + private: + GenericGeoTestInterface* test_; +}; + //---------------------------------------------------------------------------// /*! * Test the CMS EE (reflecting) geometry. diff --git a/test/geocel/data/atlas-hgtd.gdml b/test/geocel/data/atlas-hgtd.gdml new file mode 100644 index 0000000000..221dd77ee4 --- /dev/null +++ b/test/geocel/data/atlas-hgtd.gdml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/geocel/g4/GeantGeo.test.cc b/test/geocel/g4/GeantGeo.test.cc index 864cc334a1..e405e2f909 100644 --- a/test/geocel/g4/GeantGeo.test.cc +++ b/test/geocel/g4/GeantGeo.test.cc @@ -106,6 +106,62 @@ class GeantGeoTest : public GeantGeoTestBase } }; +//---------------------------------------------------------------------------// +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, model) +{ + auto result = this->summarize_model(); + GenericGeoModelInp ref; + ref.volume.labels = {"SPlate", "HGTD", "ITK", "Atlas"}; + ref.volume.materials = {0, 1, 1, 1}; + ref.volume.daughters = {{}, {3, 4, 5, 6, 7, 8, 9, 10}, {2}, {1}}; + ref.volume_instance.labels = { + "Atlas_PV", + "ITK", + "HGTD", + "SPlate_4@0", + "SPlate_5@0", + "SPlate_6@0", + "SPlate_7@0", + "SPlate_4@1", + "SPlate_5@1", + "SPlate_6@1", + "SPlate_7@1", + }; + ref.volume_instance.volumes = { + 3, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }; + ref.world = "Atlas"; + EXPECT_REF_EQ(ref, result); +} + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + //---------------------------------------------------------------------------// using CmseTest = GenericGeoParameterizedTest; diff --git a/test/geocel/vg/Vecgeom.test.cc b/test/geocel/vg/Vecgeom.test.cc index 7efa23db61..885291fcc2 100644 --- a/test/geocel/vg/Vecgeom.test.cc +++ b/test/geocel/vg/Vecgeom.test.cc @@ -119,6 +119,26 @@ using GeantVecgeomTest = VecgeomTestBase; //---------------------------------------------------------------------------// +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + +//---------------------------------------------------------------------------// + using CmsEeBackDeeTest = GenericGeoParameterizedTest; diff --git a/test/orange/OrangeGeant.test.cc b/test/orange/OrangeGeant.test.cc index 7bbbc8dd78..9f059b8cd6 100644 --- a/test/orange/OrangeGeant.test.cc +++ b/test/orange/OrangeGeant.test.cc @@ -59,6 +59,26 @@ class GeantOrangeTest : public OrangeTestBase } }; +//---------------------------------------------------------------------------// + +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + //---------------------------------------------------------------------------// using FourLevelsTest = GenericGeoParameterizedTest; From 1a961a96cdd571a885c6ba06fc573015fb0599b2 Mon Sep 17 00:00:00 2001 From: Stefano Tognini Date: Tue, 20 Jan 2026 14:03:07 -0500 Subject: [PATCH 45/60] Add muon-catalyzed fusion process/model skeletons (#2174) * Add initial muon CDF data * Add process/model skeletons * Draft host/device data * Add model implementation; draft host/device data initialization * Add mucf executor + interactors + helper classes skeletons * Make sure it builds * Expand documentation * Fixup * Address most comments: - Move types to new mucf/Types.hh - Move MucfParticles::from_params to model.cc as a free function - Move all static data to MucfPhysics.cc - Refactor/Remove types in DTMixMucfData - Remove CELER_EXPECT(mu_minus) during MucfProcess construction * Move component id getter function into DTMixMucfData * Assign all current PDG numbers * Address comments: - Fix import data - Refactor the material calculator to behave as a material inserter - Interactors: define number of secondaries as EnumArrays - Rename internal material id to MucfMatId - Remove find MucfMatId function from DTMixMucfData and make it a lambda in the executor * Minor improvements * Update documentation * Fixup * Improve documentation * Fix `RootInterfaceLinkDef` * Address comments: - Fix integral_xs bool - Fwd declare material and particle params - Remove trailing includes * Fix missing CELER_FUNCTION * Fix device data * Fix tidy warnings * Use double-precision for input to fix root errors, and un-inline from_default * Add operator bool * fixup! Use double-precision for input to fix root errors, and un-inline from_default * fixup! Fix tidy warnings * fixup! Fix tidy warnings --------- Co-authored-by: Seth R Johnson --- doc/CMakeLists.txt | 6 + doc/implementation/mucf-physics.rst | 41 ++- src/celeritas/CMakeLists.txt | 3 + src/celeritas/Types.hh | 19 -- src/celeritas/ext/RootInterfaceLinkDef.h | 8 +- src/celeritas/inp/MucfPhysics.cc | 123 ++++++++- src/celeritas/inp/MucfPhysics.hh | 51 +++- src/celeritas/mucf/Types.hh | 71 +++++ src/celeritas/mucf/data/DTMixMucfData.hh | 113 ++++++++ .../mucf/executor/DTMixMucfExecutor.hh | 148 +++++++++++ .../mucf/executor/detail/DDChannelSelector.hh | 72 ++++++ .../mucf/executor/detail/DTChannelSelector.hh | 72 ++++++ .../detail/DTMixMuonicAtomSelector.hh | 61 +++++ .../detail/DTMixMuonicMoleculeSelector.hh | 67 +++++ .../executor/detail/MuonicAtomSpinSelector.hh | 64 +++++ .../detail/MuonicMoleculeSpinSelector.hh | 64 +++++ .../mucf/executor/detail/TTChannelSelector.hh | 73 ++++++ .../mucf/interactor/DDMucfInteractor.hh | 128 +++++++++ .../mucf/interactor/DTMucfInteractor.hh | 123 +++++++++ .../mucf/interactor/TTMucfInteractor.hh | 124 +++++++++ src/celeritas/mucf/model/DTMixMucfModel.cc | 179 +++++++++++++ src/celeritas/mucf/model/DTMixMucfModel.cu | 33 +++ src/celeritas/mucf/model/DTMixMucfModel.hh | 74 ++++++ .../mucf/model/detail/MucfMaterialInserter.cc | 243 ++++++++++++++++++ .../mucf/model/detail/MucfMaterialInserter.hh | 77 ++++++ src/celeritas/mucf/process/MucfProcess.cc | 67 +++++ src/celeritas/mucf/process/MucfProcess.hh | 61 +++++ src/celeritas/phys/PDGNumber.hh | 4 + .../data/four-steel-slabs.root-dump.json | 19 ++ test/celeritas/ext/GeantImporter.test.cc | 20 +- 30 files changed, 2163 insertions(+), 45 deletions(-) create mode 100644 src/celeritas/mucf/Types.hh create mode 100644 src/celeritas/mucf/data/DTMixMucfData.hh create mode 100644 src/celeritas/mucf/executor/DTMixMucfExecutor.hh create mode 100644 src/celeritas/mucf/executor/detail/DDChannelSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/DTChannelSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh create mode 100644 src/celeritas/mucf/executor/detail/TTChannelSelector.hh create mode 100644 src/celeritas/mucf/interactor/DDMucfInteractor.hh create mode 100644 src/celeritas/mucf/interactor/DTMucfInteractor.hh create mode 100644 src/celeritas/mucf/interactor/TTMucfInteractor.hh create mode 100644 src/celeritas/mucf/model/DTMixMucfModel.cc create mode 100644 src/celeritas/mucf/model/DTMixMucfModel.cu create mode 100644 src/celeritas/mucf/model/DTMixMucfModel.hh create mode 100644 src/celeritas/mucf/model/detail/MucfMaterialInserter.cc create mode 100644 src/celeritas/mucf/model/detail/MucfMaterialInserter.hh create mode 100644 src/celeritas/mucf/process/MucfProcess.cc create mode 100644 src/celeritas/mucf/process/MucfProcess.hh diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index eb2d012e88..ad297309b8 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -263,6 +263,12 @@ file(GLOB _DOXYGEN_SOURCE "${PROJECT_SOURCE_DIR}/src/celeritas/em/msc/detail/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/em/process/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/em/xs/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/data/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/executor/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/interactor/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/model/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/model/detail/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/process/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/interactor/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/interactor/detail/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/model/*.hh" diff --git a/doc/implementation/mucf-physics.rst b/doc/implementation/mucf-physics.rst index ec6d81f84e..75e13de3bf 100644 --- a/doc/implementation/mucf-physics.rst +++ b/doc/implementation/mucf-physics.rst @@ -72,8 +72,8 @@ sticking factor and the fusion cycle time are the main conditions that define how many fusion cycles a muon can undergo. The fusion cycle time depends on the d-t mixture, its temperature, and on the final spin of the molecule. Only muonic molecules where the total spin :math:`F = I_N \pm 1/2` is on, or has a -projection onto the total angular momentum J = 1 are reactive. The spin states -of the three possible muonic molecules are summarized in table +projection onto the total angular momentum :math:`J = 1` are reactive. The spin +states of the three possible muonic molecules are summarized in table :numref:`muon_spin_states`. @@ -108,9 +108,36 @@ enabling the ``mucf_physics`` option in Geant4 integration ------------------ -For integration interfaces, if ``mucf_physics`` option in -:cpp:class:`celeritas::ext::GeantPhysicsOptions` is enabled, the muon-catalyzed -fusion data is constructed when the ``G4MuonMinusAtomicCapture`` process is -registered. +For integration interfaces, enabling the ``mucf_physics`` option in +:cpp:class:`celeritas::ext::GeantPhysicsOptions` will check if the +``G4MuonMinusAtomicCapture`` process is registered in the Geant4's Physics List. +If the process is present, the :cpp:class:`celeritas::inp::MucfPhysics` will be +populated, and the :cpp:class:`celeritas::MucfProcess` will be initialized. -.. todo:: Add process/model/executor details +Code implementation +=================== + +The :cpp:class:`celeritas::MucfProcess` process has only the +:cpp:class:`celeritas::DTMixMucfModel` attached to it, responsible for +deuterium-tritium mixtures. It can simulate materials from near absolute zero to +1500 kelvin. It is an *at rest* model that encompasses the full cycle---atom +formation, molecule formation, and fusion. + +.. doxygenclass:: celeritas::MucfProcess +.. doxygenclass:: celeritas::DTMixMucfModel + +Most of the data is material-dependent, being calculated and cached during model +construction. All of the cached quantities are calculated and added to +host/device data via :cpp:class:`celeritas::detail::MucfMaterialInserter`. + +.. doxygenclass:: celeritas::detail::MucfMaterialInserter + +The main cycle is managed by the model's +:cpp:class:`celeritas::DTMixMucfExecutor`, with the Interactors reserved for +sampling final states of the outgoing secondaries. + +.. note:: Only reactive channels are implemented. + +.. doxygenclass:: celeritas::DDMucfInteractor +.. doxygenclass:: celeritas::DTMucfInteractor +.. doxygenclass:: celeritas::TTMucfInteractor diff --git a/src/celeritas/CMakeLists.txt b/src/celeritas/CMakeLists.txt index 4a108c8774..b0c122e52d 100644 --- a/src/celeritas/CMakeLists.txt +++ b/src/celeritas/CMakeLists.txt @@ -100,6 +100,8 @@ list(APPEND SOURCES mat/MaterialParams.cc mat/MaterialParamsOutput.cc mat/detail/Utils.cc + mucf/model/detail/MucfMaterialInserter.cc + mucf/process/MucfProcess.cc neutron/model/CascadeOptions.cc neutron/model/CascadeOptionsIO.json.cc neutron/process/NeutronElasticProcess.cc @@ -375,6 +377,7 @@ celeritas_polysource(em/model/CoulombScatteringModel) celeritas_polysource(geo/detail/BoundaryAction) celeritas_polysource(global/detail/KillActive) celeritas_polysource(global/detail/TrackSlotUtils) +celeritas_polysource(mucf/model/DTMixMucfModel) celeritas_polysource(neutron/model/ChipsNeutronElasticModel) celeritas_polysource(neutron/model/NeutronInelasticModel) celeritas_polysource(optical/model/AbsorptionModel) diff --git a/src/celeritas/Types.hh b/src/celeritas/Types.hh index c2bc81764a..c2cd0e9e63 100644 --- a/src/celeritas/Types.hh +++ b/src/celeritas/Types.hh @@ -227,25 +227,6 @@ enum class CylAxis size_ }; -//---------------------------------------------------------------------------// -//! Muon-catalyzed fusion atoms -enum class MucfMuonicAtom -{ - deuterium, - tritium, - size_ -}; - -//---------------------------------------------------------------------------// -//! Muon-catalyzed fusion molecules -enum class MucfMuonicMolecule -{ - deuterium_deuterium, - deuterium_tritium, - tritium_tritium, - size_ -}; - //---------------------------------------------------------------------------// // HELPER STRUCTS //---------------------------------------------------------------------------// diff --git a/src/celeritas/ext/RootInterfaceLinkDef.h b/src/celeritas/ext/RootInterfaceLinkDef.h index 3e1ac8f498..0425bc3110 100644 --- a/src/celeritas/ext/RootInterfaceLinkDef.h +++ b/src/celeritas/ext/RootInterfaceLinkDef.h @@ -51,10 +51,11 @@ #pragma link C++ class celeritas::inp::Grid+; #pragma link C++ class celeritas::inp::GridReflection+; #pragma link C++ class celeritas::inp::Interpolation+; -#pragma link C++ class celeritas::inp::MucfPhysics+; -#pragma link C++ class celeritas::inp::MucfCycleRate+; #pragma link C++ class celeritas::inp::MucfAtomTransferRate+; #pragma link C++ class celeritas::inp::MucfAtomSpinFlipRate+; +#pragma link C++ class celeritas::inp::MucfCycleRate+; +#pragma link C++ class celeritas::inp::MucfPhysics+; +#pragma link C++ class celeritas::inp::MucfScalars+; #pragma link C++ class celeritas::inp::MuPairProductionEnergyTransferTable+; #pragma link C++ class celeritas::inp::NoRoughness+; #pragma link C++ class celeritas::inp::OpticalPhysics+; @@ -82,6 +83,9 @@ // Quantities #pragma link C++ class celeritas::Quantity+; #pragma link C++ class celeritas::Quantity+; +#pragma link C++ class celeritas::Quantity+; +#pragma link C++ class celeritas::Quantity+; + // Event data used by Geant4/Celeritas offloading applications #pragma link C++ class celeritas::EventData+; diff --git a/src/celeritas/inp/MucfPhysics.cc b/src/celeritas/inp/MucfPhysics.cc index 9724a6e0b0..9a298eaa7b 100644 --- a/src/celeritas/inp/MucfPhysics.cc +++ b/src/celeritas/inp/MucfPhysics.cc @@ -10,6 +10,117 @@ namespace celeritas { namespace inp { +namespace +{ +//---------------------------------------------------------------------------// +/*! + * Muon energy CDF data for muon-catalyzed fusion. + */ +Grid mucf_muon_energy_cdf() +{ + Grid cdf; + cdf.interpolation.type = InterpolationType::cubic_spline; + + // Cumulative distribution data [unitless] + cdf.x = { + 0, + 0.04169381107491854, + 0.08664495114006499, + 0.14332247557003264, + 0.20456026058631915, + 0.2723127035830618, + 0.34136807817589576, + 0.41563517915309456, + 0.48990228013029324, + 0.5667752442996744, + 0.6306188925081434, + 0.6866449511400652, + 0.7309446254071662, + 0.7778501628664496, + 0.8104234527687297, + 0.8403908794788275, + 0.8618892508143323, + 0.8814332247557004, + 0.8970684039087949, + 0.903583061889251, + 1.0, + }; + + // Energy [keV] + cdf.y = { + 0, + 0.48850540675768084, + 0.8390389347819425, + 1.2521213482687141, + 1.7153033196164724, + 2.253638712180777, + 2.854653691809707, + 3.606073540073316, + 4.470346052913727, + 5.560291219507215, + 6.700556502915258, + 7.953772477101693, + 9.194596305637525, + 10.849180562221111, + 12.353474314071864, + 14.045888515617822, + 15.650634617544647, + 17.38079707555165, + 19.111008546659452, + 19.976130619913615, + 80.0, + }; + + CELER_ENSURE(cdf); + return cdf; +} + +//---------------------------------------------------------------------------// +/*! + * Cycle rate data for muon-catalyzed fusion. + */ +std::vector mucf_cycle_rates() +{ + //! \todo Implement + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Spin-flip data for muon-catalyzed fusion. + */ +std::vector mucf_atom_spin_flip_rates() +{ + //! \todo Implement + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Atom transfer data for muon-catalyzed fusion. + */ +std::vector mucf_atom_transfer_rates() +{ + //! \todo Implement + return {}; +} +//---------------------------------------------------------------------------// +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Initialize with hardcoded values. + */ +MucfScalars MucfScalars::from_default() +{ + MucfScalars result; + result.protium = AmuMass{1.007825031898}; + result.deuterium = AmuMass{2.014101777844}; + result.tritium = AmuMass{3.016049281320}; + result.liquid_hydrogen_density = InvCcDensity{4.25e22}; + return result; +} + //---------------------------------------------------------------------------// /*! * Construct hardcoded muon-catalyzed fusion physics data. @@ -21,16 +132,14 @@ namespace inp MucfPhysics MucfPhysics::from_default() { MucfPhysics result; - - //! \todo Initialize hardcoded CDF data - //! \todo Initialize hardcoded cycle rate data - //! \todo Initialize hardcoded atom transfer data - //! \todo Initialize hardcoded spin flip data + result.scalars = MucfScalars::from_default(); + result.muon_energy_cdf = mucf_muon_energy_cdf(); + result.cycle_rates = mucf_cycle_rates(); + result.atom_transfer = mucf_atom_transfer_rates(); + result.atom_spin_flip = mucf_atom_spin_flip_rates(); // Temporary test dummy data to verify correct import { - result.muon_energy_cdf = Grid::from_constant(1.0); - MucfCycleRate dt_cycle; dt_cycle.molecule = MucfMuonicMolecule::deuterium_tritium; diff --git a/src/celeritas/inp/MucfPhysics.hh b/src/celeritas/inp/MucfPhysics.hh index 88c67a90e4..401e58b525 100644 --- a/src/celeritas/inp/MucfPhysics.hh +++ b/src/celeritas/inp/MucfPhysics.hh @@ -10,12 +10,43 @@ #include #include "corecel/inp/Grid.hh" -#include "celeritas/Types.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/UnitTypes.hh" +#include "celeritas/mucf/Types.hh" namespace celeritas { namespace inp { +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion scalars. + * + * Default values are the same used by Acceleron. + */ +struct MucfScalars +{ + using AmuMass = Quantity; + using InvCcDensity = Quantity; + + // Atomic masses + AmuMass protium; //!< Protium atomic mass [AMU] + AmuMass deuterium; //!< Deuterium atomic mass [AMU] + AmuMass tritium; //!< Tritium atomic mass [AMU] + InvCcDensity liquid_hydrogen_density; //!< LHD unit [1/cm^3] + + //! Whether scalars have been defined + explicit operator bool() const + { + return protium > zero_quantity() && deuterium > zero_quantity() + && tritium > zero_quantity() + && liquid_hydrogen_density > zero_quantity(); + } + + // Initialize with hardcoded values + static MucfScalars from_default(); +}; + //---------------------------------------------------------------------------// /*! * Muon-catalyzed fusion mean cycle rate data. @@ -68,6 +99,9 @@ struct MucfCycleRate struct MucfAtomTransferRate { //! \todo Implement + + //! True if data is assigned \todo Implement + explicit operator bool() const { return true; } }; //---------------------------------------------------------------------------// @@ -91,6 +125,9 @@ struct MucfAtomTransferRate struct MucfAtomSpinFlipRate { //! \todo Implement + + //! True if data is assigned \todo Implement + explicit operator bool() const { return true; } }; //---------------------------------------------------------------------------// @@ -98,7 +135,8 @@ struct MucfAtomSpinFlipRate * Muon-catalyzed fusion physics options and data import. * * Minimum requirements for muon-catalyzed fusion: - * - Muon energy CDF data, required for sampling the outgoing muCF muon, and + * - Muon energy CDF data, required for sampling the outgoing muCF muon, + * and * - Mean cycle rate data for dd, dt, and tt muonic molecules. * * Muonic atom transfer and muonic atom spin flip are secondary effects and not @@ -109,15 +147,16 @@ struct MucfPhysics template using Vec = std::vector; - Grid muon_energy_cdf; //!< CDF for outgoing muCF muon + MucfScalars scalars; + Grid muon_energy_cdf; //!< CDF for sampling the outgoing muCF muon Vec cycle_rates; //!< Mean cycle rates for muonic molecules - Vec atom_transfer; //!< Muon atom transfer rates - Vec atom_spin_flip; //!< Muon atom spin flip rates + Vec atom_transfer; //!< Muonic atom transfer rates + Vec atom_spin_flip; //!< Muonic atom spin flip rates //! Whether muon-catalyzed fusion physics is enabled explicit operator bool() const { - return muon_energy_cdf && !cycle_rates.empty(); + return scalars && muon_energy_cdf && !cycle_rates.empty(); } //! Construct hardcoded muon-catalyzed fusion physics data diff --git a/src/celeritas/mucf/Types.hh b/src/celeritas/mucf/Types.hh new file mode 100644 index 0000000000..0a7c410dda --- /dev/null +++ b/src/celeritas/mucf/Types.hh @@ -0,0 +1,71 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/Types.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/OpaqueId.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +// ENUMERATIONS +//---------------------------------------------------------------------------// +/*! + * Muonic atom selection from material data. This is *not* intended to be used + * by the transport loop. + */ +enum class MucfMuonicAtom +{ + deuterium, + tritium, + size_ +}; + +//---------------------------------------------------------------------------// +/*! + * Muonic molecule selection from material data. This is *not* intended to be + * used by the transport loop. + */ +enum class MucfMuonicMolecule +{ + deuterium_deuterium, + deuterium_tritium, + tritium_tritium, + size_ +}; + +//---------------------------------------------------------------------------// +/*! + * Enum for safely accessing hydrogen isoprotologues. + * + * Hydrogen isoprotologue molecules are: + * - Homonuclear: \f$ ^2H \f$, \f$ ^2d \f$, and \f$ ^2t \f$ + * - Heteronuclear: hd, ht, and dt. + * + * \note Muon-catalyzed fusion data is only applicable to a material with + * concentrations in thermodynamic equilibrium. This equilibrium is calculated + * at model construction from the material temperature and its h, d, and t + * fractions. + */ +enum class MucfIsoprotologueMolecule +{ + protium_protium, + protium_deuterium, + protium_tritium, + deuterium_tritium, + tritium_tritium, + size_ +}; + +//---------------------------------------------------------------------------// +// TYPE ALIASES +//---------------------------------------------------------------------------// + +//! Opaque index of a muCF material component +using MuCfMatId = OpaqueId; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/data/DTMixMucfData.hh b/src/celeritas/mucf/data/DTMixMucfData.hh new file mode 100644 index 0000000000..63affe3867 --- /dev/null +++ b/src/celeritas/mucf/data/DTMixMucfData.hh @@ -0,0 +1,113 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/data/DTMixMucfData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include + +#include "corecel/Macros.hh" +#include "corecel/cont/EnumArray.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "corecel/io/Join.hh" +#include "celeritas/Types.hh" +#include "celeritas/mucf/Types.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * ParticleIds used by the \c DTMixMucfModel . + */ +struct MucfParticleIds +{ + //! Primary + ParticleId mu_minus; + + //!@{ + //! Elementary particles and nuclei + ParticleId neutron; + ParticleId proton; + ParticleId alpha; + ParticleId he3; + //!@} + + //!@{ + //! Muonic atoms + ParticleId muonic_hydrogen; + ParticleId muonic_deuteron; + ParticleId muonic_triton; + ParticleId muonic_alpha; + ParticleId muonic_he3; + //!@} + + //! Check whether all particles are assigned + CELER_FUNCTION explicit operator bool() const + { + return mu_minus && neutron && proton && alpha && he3 && muonic_hydrogen + && muonic_alpha && muonic_triton && muonic_he3; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Data for for the \c DTMixMucfModel . + */ +template +struct DTMixMucfData +{ + template + using Items = Collection; + template + using MaterialItems = Collection; + using GridRecord = NonuniformGridRecord; + using CycleTimesArray = EnumArray>; + + //! Particle IDs + MucfParticleIds particles; + + //! Muon CDF energy grid for sampling outgoing muCF muons + GridRecord muon_energy_cdf; //! \todo Verify energy unit + Items reals; + + //!@{ + //! Material-dependent data calculated at model construction + //! \c PhysMatId indexed by \c MuCfMatId + MaterialItems mucfmatid_to_matid; + //! Cycle times per material: [mat_comp_id][muonic_molecule][spin_index] + MaterialItems cycle_times; //!< In [s] + //! \todo Add mean atom spin flip times + //! \todo Add mean atom transfer times + //!@} + + //! Check whether the data are assigned + explicit CELER_FUNCTION operator bool() const + { + return particles && muon_energy_cdf && !mucfmatid_to_matid.empty() + && !cycle_times.empty() + && (mucfmatid_to_matid.size() == cycle_times.size()); + } + + //! Assign from another set of data + template + DTMixMucfData& operator=(DTMixMucfData const& other) + { + CELER_EXPECT(other); + + //! \todo Finish implementation + this->particles = other.particles; + this->reals = other.reals; + this->muon_energy_cdf = other.muon_energy_cdf; + this->mucfmatid_to_matid = other.mucfmatid_to_matid; + this->cycle_times = other.cycle_times; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/DTMixMucfExecutor.hh b/src/celeritas/mucf/executor/DTMixMucfExecutor.hh new file mode 100644 index 0000000000..030d2c2bf9 --- /dev/null +++ b/src/celeritas/mucf/executor/DTMixMucfExecutor.hh @@ -0,0 +1,148 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/DTMixMucfExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/global/CoreTrackView.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/mucf/interactor/DDMucfInteractor.hh" +#include "celeritas/mucf/interactor/DTMucfInteractor.hh" +#include "celeritas/mucf/interactor/TTMucfInteractor.hh" + +#include "detail/DDChannelSelector.hh" +#include "detail/DTChannelSelector.hh" +#include "detail/DTMixMuonicAtomSelector.hh" +#include "detail/DTMixMuonicMoleculeSelector.hh" +#include "detail/MuonicAtomSpinSelector.hh" +#include "detail/MuonicMoleculeSpinSelector.hh" +#include "detail/TTChannelSelector.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +struct DTMixMucfExecutor +{ + inline CELER_FUNCTION Interaction + operator()(celeritas::CoreTrackView const& track); + + NativeCRef data; +}; + +//---------------------------------------------------------------------------// +/*! + * Execute muon-catalyzed fusion for muonic dd, dt, or tt molecules. + */ +CELER_FUNCTION Interaction +DTMixMucfExecutor::operator()(celeritas::CoreTrackView const& track) +{ + auto phys_step_view = track.physics_step(); + auto elcomp_id = phys_step_view.element(); + CELER_ASSERT(elcomp_id); + + auto element = track.material().material_record().element_record(elcomp_id); + CELER_ASSERT(element.atomic_number() == AtomicNumber{1}); // Must be H + + auto rng = track.rng(); + + // Muon decay may compete against other "actions" in this executor + real_type const decay_len{}; //! \todo Set muon decay interaction length + + // Form d or t muonic atom + detail::DTMixMuonicAtomSelector form_atom; + auto muonic_atom = form_atom(rng); + + // Select atom spin via a helper class + detail::MuonicAtomSpinSelector select_atom_spin(muonic_atom); + + // { + // Competing at-rest processes which add to the total track time + //! \todo Muonic atom transfer + //! \todo Muonic atom spin flip + // } + + // Form dd, dt, or tt muonic molecule + detail::DTMixMuonicMoleculeSelector form_muonic_molecule; + auto muonic_molecule = form_muonic_molecule(rng); + + // Select molecule spin + detail::MuonicMoleculeSpinSelector select_molecule_spin(muonic_molecule); + auto const molecule_spin = select_molecule_spin(rng); + + // Find muCF material ID from PhysMatId + // Make this a View if ever used beyond this executor + auto find = [&](PhysMatId matid) -> MuCfMatId { + CELER_EXPECT(matid); + for (auto i : range(data.mucfmatid_to_matid.size())) + { + if (auto const comp_id = MuCfMatId{i}; + data.mucfmatid_to_matid[comp_id] == matid) + { + return comp_id; + } + } + // MuCF material ID not found + return MuCfMatId{}; + }; + + // Load cycle time for the selected molecule + auto const mucf_matid = find(track.material().material_id()); + CELER_ASSERT(mucf_matid); + auto const cycle_time + = data.cycle_times[mucf_matid][muonic_molecule][molecule_spin]; + CELER_ASSERT(cycle_time > 0); + + // Check if muon decays before fusion happens + real_type const mucf_len = cycle_time * track.sim().step_length(); + if (decay_len < mucf_len) + { + // Muon decays and halts the interaction + //! \todo Update track time and return muon decay interactor + } + + //! \todo Correct track time update? Or should be done in Interactors? + track.sim().add_time(cycle_time); + + // Fuse molecule and generate secondaries + //! \todo Maybe move the channel selectors into the interactors + auto allocate_secondaries = phys_step_view.make_secondary_allocator(); + Interaction result; + switch (muonic_molecule) + { + case MucfMuonicMolecule::deuterium_deuterium: { + // Return DD interaction + DDMucfInteractor interact( + data, detail::DDChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + case MucfMuonicMolecule::deuterium_tritium: { + // Return DT interaction + DTMucfInteractor interact( + data, detail::DTChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + case MucfMuonicMolecule::tritium_tritium: { + // Return TT interaction + TTMucfInteractor interact( + data, detail::TTChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + default: + CELER_ASSERT_UNREACHABLE(); + } + + //! \todo Muon stripping: strip muon from muonic atom secondaries + // May be added as a separate discrete process in the stepping loop + + return result; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DDChannelSelector.hh b/src/celeritas/mucf/executor/detail/DDChannelSelector.hh new file mode 100644 index 0000000000..05394c8019 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DDChannelSelector.hh @@ -0,0 +1,72 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DDChannelSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/DDMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic dd molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class DDChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = DDMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DDChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION DDChannelSelector::DDChannelSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf dd fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the dd muCF interactor. + * + * \sa celeritas::DDMucfInteractor + */ +template +CELER_FUNCTION DDChannelSelector::Channel DDChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTChannelSelector.hh b/src/celeritas/mucf/executor/detail/DTChannelSelector.hh new file mode 100644 index 0000000000..47e097cdfd --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTChannelSelector.hh @@ -0,0 +1,72 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTChannelSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/DTMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic dt molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class DTChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = DTMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION DTChannelSelector::DTChannelSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf dt fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the dt muCF interactor. + * + * \sa celeritas::DTMucfInteractor + */ +template +CELER_FUNCTION DTChannelSelector::Channel DTChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh b/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh new file mode 100644 index 0000000000..f193109e0c --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/cont/EnumArray.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select a muonic atom given the material information. + */ +class DTMixMuonicAtomSelector +{ + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTMixMuonicAtomSelector(/* args */); + + // Select muonic atom + template + inline CELER_FUNCTION MucfMuonicAtom operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + + * \todo Update documentation + */ +CELER_FUNCTION DTMixMuonicAtomSelector::DTMixMuonicAtomSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf muonic atom selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected muonic atom. + */ +template +CELER_FUNCTION MucfMuonicAtom DTMixMuonicAtomSelector::operator()(Engine&) +{ + MucfMuonicAtom result{MucfMuonicAtom::size_}; + + //! \todo Implement + + CELER_ENSURE(result < MucfMuonicAtom::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh b/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh new file mode 100644 index 0000000000..cc4c7bd30b --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh @@ -0,0 +1,67 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select a muonic molecule by calculating the interaction lengths of the + * possible molecule formations. + * + * This is the equivalent of Geant4's + * \c G4VRestProcess::AtRestGetPhysicalInteractionLength + */ +class DTMixMuonicMoleculeSelector +{ + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTMixMuonicMoleculeSelector(/* args */); + + // Select muonic molecule + template + inline CELER_FUNCTION MucfMuonicMolecule operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION +DTMixMuonicMoleculeSelector::DTMixMuonicMoleculeSelector(/* args */) +{ + //! \todo Implement + CELER_NOT_IMPLEMENTED("Mucf muonic molecule selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected muonic molecule. + */ +template +CELER_FUNCTION MucfMuonicMolecule +DTMixMuonicMoleculeSelector::operator()(Engine&) +{ + MucfMuonicMolecule result{MucfMuonicMolecule::size_}; + + //! \todo Implement + + CELER_ENSURE(result < MucfMuonicMolecule::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh b/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh new file mode 100644 index 0000000000..6d22817345 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh @@ -0,0 +1,64 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select muonic atom spin, in units of \f$ \frac{\hbar}{2} \f$. + * + * Applicable to \f$ (p)_\mu \f$, \f$ (d)_\mu \f$, and \f$ (t)_\mu \f$. + */ +class MuonicAtomSpinSelector +{ + public: + // Construct with muonic atom type + inline CELER_FUNCTION MuonicAtomSpinSelector(MucfMuonicAtom atom); + + // Sample and return a spin value in units of hbar / 2 + template + inline CELER_FUNCTION size_type operator()(Engine&); + + private: + MucfMuonicAtom atom_; + //! \todo Add constant atom spin sampling rejection fractions +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with muonic atom. + */ +CELER_FUNCTION +MuonicAtomSpinSelector::MuonicAtomSpinSelector(MucfMuonicAtom atom) + : atom_(atom) +{ + CELER_EXPECT(atom_ < MucfMuonicAtom::size_); + CELER_NOT_IMPLEMENTED("Mucf muonic atom spin selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected spin, in units of \f$ \hbar / 2 \f$. + */ +template +CELER_FUNCTION size_type MuonicAtomSpinSelector::operator()(Engine&) +{ + //! \todo switch on atom_ + CELER_ASSERT_UNREACHABLE(); +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh b/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh new file mode 100644 index 0000000000..67ae8d4e29 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh @@ -0,0 +1,64 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select muonic molecule spin, in units of \f$ \frac{\hbar}{2} \f$. + * + * Applicable to \f$ (dd)_\mu \f$, * \f$ (dt)_\mu \f$ , and \f$ (tt)_\mu \f$. + */ +class MuonicMoleculeSpinSelector +{ + public: + // Construct with muonic molecule type + inline CELER_FUNCTION + MuonicMoleculeSpinSelector(MucfMuonicMolecule molecule); + + // Sample and return a spin value in units of hbar / 2 + template + inline CELER_FUNCTION size_type operator()(Engine&); + + private: + MucfMuonicMolecule molecule_; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with muonic molecule. + */ +CELER_FUNCTION MuonicMoleculeSpinSelector::MuonicMoleculeSpinSelector( + MucfMuonicMolecule molecule) + : molecule_(molecule) +{ + CELER_EXPECT(molecule_ < MucfMuonicMolecule::size_); + CELER_NOT_IMPLEMENTED("Mucf muonic molecule spin selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected spin, in units of \f$ \hbar / 2 \f$. + */ +template +CELER_FUNCTION size_type MuonicMoleculeSpinSelector::operator()(Engine&) +{ + //! \todo switch on molecule_ + CELER_ASSERT_UNREACHABLE(); +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/TTChannelSelector.hh b/src/celeritas/mucf/executor/detail/TTChannelSelector.hh new file mode 100644 index 0000000000..1f7c0b40ea --- /dev/null +++ b/src/celeritas/mucf/executor/detail/TTChannelSelector.hh @@ -0,0 +1,73 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/TTChannelSelectionHelper.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/TTMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic tt molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class TTChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = TTMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION TTChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION TTChannelSelector::TTChannelSelector(/* args */) +{ + //! \todo Implement + CELER_NOT_IMPLEMENTED("Mucf tt fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the tt muCF interactor. + * + * \sa celeritas::TTMucfInteractor + */ +template +CELER_FUNCTION TTChannelSelector::Channel TTChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/DDMucfInteractor.hh b/src/celeritas/mucf/interactor/DDMucfInteractor.hh new file mode 100644 index 0000000000..b75fa71c02 --- /dev/null +++ b/src/celeritas/mucf/interactor/DDMucfInteractor.hh @@ -0,0 +1,128 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/DDMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (dd)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ ^3\text{He} + \mu + n \f$ + * - \f$ (^3\text{He})_\mu + n \f$ + * - \f$ ^3\text{H} + \mu + p \f$ + */ +class DDMucfInteractor +{ + public: + //! \todo Implement muonichydrogen3_proton (\f$ (^3\text{H})_\mu + p \f$) + enum class Channel + { + helium3_muon_neutron, //!< \f$ ^3\text{He} + \mu + n \f$ + muonichelium3_neutron, //!< \f$ (^3\text{He})_\mu + n \f$ + hydrogen3_muon_proton, //!< \f$ ^3\text{H} + \mu + p \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + DDMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 3, // helium3_muon_neutron + 2, // muonichelium3_neutron + 3 // hydrogen3_muon_proton + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +DDMucfInteractor::DDMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a dd muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction DDMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +DDMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/DTMucfInteractor.hh b/src/celeritas/mucf/interactor/DTMucfInteractor.hh new file mode 100644 index 0000000000..568bf3ba0e --- /dev/null +++ b/src/celeritas/mucf/interactor/DTMucfInteractor.hh @@ -0,0 +1,123 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/DTMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (dt)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ \alpha + \mu + n \f$ + * - \f$ (\alpha)_\mu + n \f$ + */ +class DTMucfInteractor +{ + public: + enum class Channel + { + alpha_muon_neutron, //!< \f$ \alpha + \mu + n \f$ + muonicalpha_neutron, //!< \f$ (\alpha)_\mu + n \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + DTMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 3, // alpha_muon_neutron + 2 // muonicalpha_neutron + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +DTMucfInteractor::DTMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel_ < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a dt muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction DTMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +DTMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/TTMucfInteractor.hh b/src/celeritas/mucf/interactor/TTMucfInteractor.hh new file mode 100644 index 0000000000..d0239fc561 --- /dev/null +++ b/src/celeritas/mucf/interactor/TTMucfInteractor.hh @@ -0,0 +1,124 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/TTMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (tt)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ \alpha + \mu + n + n \f$ + * - \f$ (\alpha)_\mu + n + n \f$ + */ +class TTMucfInteractor +{ + public: + enum class Channel + { + alpha_muon_neutron_neutron, //!< \f$ \alpha + \mu + n + n \f$ + muonicalpha_neutron_neutron, //!< \f$ (\alpha)_\mu + n + n \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + TTMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 4, // alpha_muon_neutron_neutron + 3 // muonicalpha_neutron_neutron + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +TTMucfInteractor::TTMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel_ < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a tt muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction TTMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +TTMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.cc b/src/celeritas/mucf/model/DTMixMucfModel.cc new file mode 100644 index 0000000000..d4cee2333a --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.cc @@ -0,0 +1,179 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.cc +//---------------------------------------------------------------------------// +#include "DTMixMucfModel.hh" + +#include + +#include "corecel/OpaqueIdIO.hh" +#include "corecel/inp/Grid.hh" +#include "celeritas/global/ActionLauncher.hh" +#include "celeritas/global/TrackExecutor.hh" +#include "celeritas/grid/NonuniformGridBuilder.hh" +#include "celeritas/inp/MucfPhysics.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/mucf/executor/DTMixMucfExecutor.hh" // IWYU pragma: associated +#include "celeritas/phys/InteractionApplier.hh" // IWYU pragma: associated +#include "celeritas/phys/PDGNumber.hh" +#include "celeritas/phys/ParticleParams.hh" + +#include "detail/MucfMaterialInserter.hh" + +namespace celeritas +{ +namespace +{ +//---------------------------------------------------------------------------// +/*! + * Assign particle IDs from \c ParticleParams . + */ +static MucfParticleIds from_params(ParticleParams const& particles) +{ + using PairStrPdg = std::pair; + std::vector missing; + MucfParticleIds result; + +#define MP_ADD(MEMBER) \ + result.MEMBER = particles.find(pdg::MEMBER()); \ + if (!result.MEMBER) \ + { \ + missing.push_back({#MEMBER, pdg::MEMBER()}); \ + } + + MP_ADD(mu_minus); + MP_ADD(neutron); + MP_ADD(proton); + MP_ADD(alpha); + MP_ADD(he3); + MP_ADD(muonic_deuteron); + MP_ADD(muonic_triton); + + //! \todo Decide whether to implement these PDGs in PDGNumber.hh +#if 0 + MP_ADD(muonic_hydrogen); + MP_ADD(muonic_alpha); + MP_ADD(muonic_he3); +#endif + + CELER_VALIDATE(missing.empty(), + << "missing particles required for muon-catalyzed fusion: " + << join_stream(missing.begin(), + missing.end(), + ", ", + [](std::ostream& os, PairStrPdg const& p) { + os << p.first << " (PDG " + << p.second.unchecked_get() << ')'; + })); + return result; + +#undef MP_ADD +} +//---------------------------------------------------------------------------// +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Construct from model ID and other necessary data. + * + * Most of the muon-catalyzed fusion data is static throughout the simulation, + * as it is only material-dependent (DT mixture and temperature). Therefore, + * most grids can be host-only and used to calculate final values, which are + * then cached and copied to device. The exception to this is the muon energy + * CDF grid, needed to sample the final state of the outgoing muon after a muCF + * interaction. + * + * \todo Correctly update \c ImportProcessClass and \c ImportModelClass . These + * operate under the assumption that there is a one-to-one equivalente between + * Geant4 and Celeritas. But for muCF, everything is done via one + * process/model/executor in Celeritas, whereas in Geant4 atom formation, spin + * flip, atom transfer, etc., are are all separate processes. + */ +DTMixMucfModel::DTMixMucfModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials) + : StaticConcreteAction( + id, + "dt-mucf", + R"(interact by muon forming and fusing a dd, dt, or tt muonic molecule)") +{ + CELER_EXPECT(id); + + // Initialize muCF physics input data + inp::MucfPhysics inp_data = inp::MucfPhysics::from_default(); + CELER_EXPECT(inp_data); + + HostVal host_data; + host_data.particles = from_params(particles); + + // Copy muon energy CDF data using NonuniformGridBuilder + NonuniformGridBuilder build_grid_record{&host_data.reals}; + host_data.muon_energy_cdf = build_grid_record(inp_data.muon_energy_cdf); + + // Calculate and cache quantities for all materials with dt mixtures + detail::MucfMaterialInserter insert(&host_data); + for (auto const& matid : range(materials.num_materials())) + { + auto const& mat_view = materials.get(PhysMatId{matid}); + if (insert(mat_view)) + { + CELER_LOG(debug) << "Added material ID " << mat_view.material_id() + << " as a muCF d-t mixture"; + } + } + + // Copy to device + data_ = CollectionMirror{std::move(host_data)}; + CELER_ENSURE(this->data_); +} + +//---------------------------------------------------------------------------// +/*! + * Particle types and energy ranges that this model applies to. + */ +auto DTMixMucfModel::applicability() const -> SetApplicability +{ + Applicability applic; + applic.particle = this->host_ref().particles.mu_minus; + // At-rest model + applic.lower = zero_quantity(); + applic.upper = zero_quantity(); + + return {applic}; +} + +//---------------------------------------------------------------------------// +/*! + * At-rest model does not require microscopic cross sections. + */ +auto DTMixMucfModel::micro_xs(Applicability) const -> XsTable +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Interact with host data. + */ +void DTMixMucfModel::step(CoreParams const& params, CoreStateHost& state) const +{ + auto execute = make_action_track_executor( + params.ptr(), + state.ptr(), + this->action_id(), + InteractionApplier{DTMixMucfExecutor{this->host_ref()}}); + return launch_action(*this, params, state, execute); +} + +//---------------------------------------------------------------------------// +#if !CELER_USE_DEVICE +void DTMixMucfModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.cu b/src/celeritas/mucf/model/DTMixMucfModel.cu new file mode 100644 index 0000000000..4ac694e935 --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.cu @@ -0,0 +1,33 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.cu +//---------------------------------------------------------------------------// +#include "DTMixMucfModel.hh" + +#include "celeritas/global/ActionLauncher.device.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" +#include "celeritas/global/TrackExecutor.hh" +#include "celeritas/mucf/executor/DTMixMucfExecutor.hh" +#include "celeritas/phys/InteractionApplier.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Interact with device data. + */ +void DTMixMucfModel::step(CoreParams const& params, CoreStateDevice& state) const +{ + auto execute = make_action_track_executor( + params.ptr(), + state.ptr(), + this->action_id(), + InteractionApplier{DTMixMucfExecutor{this->device_ref()}}); + static ActionLauncher const launch_kernel(*this); + launch_kernel(*this, params, state, execute); +} +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.hh b/src/celeritas/mucf/model/DTMixMucfModel.hh new file mode 100644 index 0000000000..893d6c2e5e --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.hh @@ -0,0 +1,74 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Model.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion model for dd, dt, and tt molecules. + * + * In this model the executor performs the full muon-catalyzed fusion workflow. + * It forms a muonic d or t atom, samples which muonic molecule will be + * produced, selects the channel, and calls the appropriate interactor. + * + * The full set of "actions" is as follows, and in this ordering: + * - Define muon decay time to compete with the rest of the execution + * - Form muonic atom and select its spin + * - May execute atom spin flip or atom transfer + * - Form muonic molecule and select its spin + * - Calculate mean cycle time (time it takes from atom formation to fusion) + * - Confirm if fusion happens or the if the muon should decay + * - Call appropriate Interactor: Muon decay, or one of the muCF interactors + * + * \note This is an at-rest model. + */ +class DTMixMucfModel final : public Model, public StaticConcreteAction +{ + public: + //!@{ + //! \name Type aliases + using HostRef = HostCRef; + using DeviceRef = DeviceCRef; + //!@} + + // Construct from model ID and other necessary data + DTMixMucfModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials); + + // Particle types and energy ranges that this model applies to + SetApplicability applicability() const final; + + // Get the microscopic cross sections for the given particle and material + XsTable micro_xs(Applicability) const final; + + // Apply the interaction kernel on host + void step(CoreParams const&, CoreStateHost&) const final; + + // Apply the interaction kernel on device + void step(CoreParams const&, CoreStateDevice&) const final; + + //!@{ + //! Access model data + HostRef const& host_ref() const { return data_.host_ref(); } + DeviceRef const& device_ref() const { return data_.device_ref(); } + //!@} + + private: + // Host/device storage and reference + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc b/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc new file mode 100644 index 0000000000..f859297ab9 --- /dev/null +++ b/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc @@ -0,0 +1,243 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/detail/MucfMaterialInserter.cc +//---------------------------------------------------------------------------// +#include "MucfMaterialInserter.hh" + +#include "corecel/Assert.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Construct with \c DTMixMucfModel model data. + */ +MucfMaterialInserter::MucfMaterialInserter(HostVal* host_data) + : mucfmatid_to_matid_(&host_data->mucfmatid_to_matid) + , cycle_times_(&host_data->cycle_times) +{ + CELER_EXPECT(host_data); +} + +//---------------------------------------------------------------------------// +/*! + * Insert material information if applicable. + * + * Calculates and caches material-dependent properties needed by the + * \c DTMixMucfModel . If the material does not contain deuterium and/or + * tritium the operator will return false. + */ +bool MucfMaterialInserter::operator()(MaterialView const& material) +{ + for (auto elcompid : range(material.num_elements())) + { + auto const& element_view + = material.element_record(ElementComponentId{elcompid}); + if (element_view.atomic_number() != AtomicNumber{1}) + { + // Skip non-hydrogen elements + continue; + } + + // Found hydrogen; Check for d and t isotopes + IsotopeChecker has_isotope{false, false}; + for (auto el_comp : range(element_view.num_isotopes())) + { + auto iso_view + = element_view.isotope_record(IsotopeComponentId{el_comp}); + auto mass = iso_view.atomic_mass_number(); + if (mass == AtomicMassNumber{1}) + { + // Skip protium + continue; + } + + auto const atom = this->from_mass_number(mass); + if (atom < MucfMuonicAtom::size_) + { + // Mark d or t isotope as found + has_isotope[atom] = true; + } + } + + if (!has_isotope[MucfMuonicAtom::deuterium] + && !has_isotope[MucfMuonicAtom::tritium]) + { + // No deuterium or tritium found; skip material + return false; + } + + // Temporary data needed to calculate model data, such as cycle times + lhd_densities_ = this->calc_lhd_densities(element_view); + equilibrium_densities_ = this->calc_equilibrium_densities(element_view); + + // Calculate and insert muCF material data into model data + mucfmatid_to_matid_.push_back(material.material_id()); + cycle_times_.push_back( + this->calc_cycle_times(element_view, has_isotope)); + //! \todo Store mean atom spin flip and transfer times + } + return true; +} + +//---------------------------------------------------------------------------// +/*! + * Return \c MucfMuonicAtom from a given atomic mass number. + */ +MucfMuonicAtom MucfMaterialInserter::from_mass_number(AtomicMassNumber mass) +{ + if (mass == AtomicMassNumber{2}) + { + return MucfMuonicAtom::deuterium; + } + if (mass == AtomicMassNumber{3}) + { + return MucfMuonicAtom::tritium; + } + return MucfMuonicAtom::size_; +} + +//---------------------------------------------------------------------------// +/*! + * Convert dt mixture densities to units of liquid hydrogen density. + * + * Used during cycle time calculations. + */ +MucfMaterialInserter::LhdArray +MucfMaterialInserter::calc_lhd_densities(ElementView const&) +{ + LhdArray result; + + //! \todo Implement + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dt mixture densities after reaching thermodynamical + * equilibrium. + * + * Used during cycle time calculations. + */ +MucfMaterialInserter::EquilibriumArray +MucfMaterialInserter::calc_equilibrium_densities(ElementView const&) +{ + EquilibriumArray result; + + //! \todo Implement + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate fusion mean cycle times. + * + * This is designed to work with the user's material definition being either: + * - Single element, multiple isotopes (H element, with H, d, and t isotopes); + * or + * - Multiple elements, single isotope each (separate H, d, and t elements). + */ +MucfMaterialInserter::CycleTimesArray +MucfMaterialInserter::calc_cycle_times(ElementView const& element, + IsotopeChecker const& has_isotope) +{ + CycleTimesArray result; + for (auto el_comp : range(element.num_isotopes())) + { + auto iso_view = element.isotope_record(IsotopeComponentId{el_comp}); + + // Select possible muonic atom based on the isotope/element mass number + auto atom = this->from_mass_number(iso_view.atomic_mass_number()); + switch (atom) + { + // Calculate cycle times for dd molecules + case MucfMuonicAtom::deuterium: { + result[MucfMuonicMolecule::deuterium_deuterium] + = this->calc_dd_cycle(element); + if (has_isotope[MucfMuonicAtom::tritium]) + { + // Calculate cycle times for dt molecules + result[MucfMuonicMolecule::deuterium_tritium] + = this->calc_dt_cycle(element); + } + break; + } + // Calculate cycle times for tt molecules + case MucfMuonicAtom::tritium: { + result[MucfMuonicMolecule::tritium_tritium] + = this->calc_tt_cycle(element); + break; + } + default: + CELER_ASSERT_UNREACHABLE(); + } + } + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dd muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for dd molecules come from F = 0 and F = 1 spin states. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_dd_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Reactive states are F = 0 and F = 1 + CELER_ENSURE(result[0] >= 0 && result[1] >= 0); + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dt muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for dt molecules come from F = 1/2 and F = 3/2 spin states. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_dt_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Reactive states are F = 1/2 and F = 3/2 + CELER_ENSURE(result[0] >= 0 && result[1] >= 0); + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate tt muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for tt molecules come only from the F = 1/2 spin state. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_tt_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Only F = 1/2 is reactive + CELER_ENSURE(result[0] >= 0 && result[1] == 0); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh b/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh new file mode 100644 index 0000000000..ad7c307b9b --- /dev/null +++ b/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh @@ -0,0 +1,77 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/detail/MucfMaterialInserter.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/Types.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Helper class to calculate and insert muCF material-dependent data into + * \c DTMixMucfData . + */ +class MucfMaterialInserter +{ + public: + // Construct with muCF host data + explicit MucfMaterialInserter(HostVal* host_data); + + // Insert material if it is a valid d-t mixture + bool operator()(MaterialView const& material); + + private: + //// DATA //// + + using MoleculeCycles = Array; + using CycleTimesArray = EnumArray; + using LhdArray = EnumArray; + using EquilibriumArray = EnumArray; + using AtomicMassNumber = AtomicNumber; + using MucfIsotope = MucfMuonicAtom; + using IsotopeChecker = EnumArray; + + // DTMixMucfModel host data references populated by operator() + CollectionBuilder mucfmatid_to_matid_; + CollectionBuilder cycle_times_; + // Temporary quantities needed for calculating the model data + LhdArray lhd_densities_; + EquilibriumArray equilibrium_densities_; + + //// HELPER FUNCTIONS //// + + // Return muonic atom from given atomic mass number + MucfMuonicAtom from_mass_number(AtomicMassNumber mass); + + // Calculate dt mixture densities in units of liquid hydrogen density + LhdArray calc_lhd_densities(ElementView const&); + + // Calculate thermal equilibrium densities + EquilibriumArray calc_equilibrium_densities(ElementView const&); + + // Calculate mean fusion cycle times for all reactive muonic molecules + CycleTimesArray calc_cycle_times(ElementView const& element, + IsotopeChecker const& has_isotope); + + // Calculate mean fusion cycle times for dd muonic molecules + Array calc_dd_cycle(ElementView const&); + + // Calculate mean fusion cycle times for dt muonic molecules + Array calc_dt_cycle(ElementView const&); + + // Calculate mean fusion cycle times for tt muonic molecules + Array calc_tt_cycle(ElementView const&); +}; + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/process/MucfProcess.cc b/src/celeritas/mucf/process/MucfProcess.cc new file mode 100644 index 0000000000..cb2947e0a6 --- /dev/null +++ b/src/celeritas/mucf/process/MucfProcess.cc @@ -0,0 +1,67 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/process/MucfProcess.cc +//---------------------------------------------------------------------------// +#include "MucfProcess.hh" + +#include + +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/mucf/model/DTMixMucfModel.hh" +#include "celeritas/phys/Model.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from host data. + */ +MucfProcess::MucfProcess(SPConstParticles particles, SPConstMaterials materials) + : particles_(particles), materials_(materials) +{ + //! \todo Fix ImportProcessClass + CELER_EXPECT(particles_); + CELER_EXPECT(materials_); +} + +//---------------------------------------------------------------------------// +/*! + * Construct the models associated with this process. + */ +auto MucfProcess::build_models(ActionIdIter start_id) const -> VecModel +{ + return {std::make_shared( + *start_id++, *particles_, *materials_)}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the interaction cross sections for the given energy range. + */ +auto MucfProcess::macro_xs(Applicability) const -> XsGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the energy loss for the given energy range. + */ +auto MucfProcess::energy_loss(Applicability) const -> EnergyLossGrid +{ + return {}; +} +//---------------------------------------------------------------------------// +/*! + * Name of the process. + */ +std::string_view MucfProcess::label() const +{ + return "Muon-catalyzed fusion"; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/process/MucfProcess.hh b/src/celeritas/mucf/process/MucfProcess.hh new file mode 100644 index 0000000000..23949c2acc --- /dev/null +++ b/src/celeritas/mucf/process/MucfProcess.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/process/MucfProcess.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/Types.hh" +#include "celeritas/phys/Process.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of muonic dd, dt, or tt molecules. + * + * \note This is an at-rest process. + */ +class MucfProcess final : public Process +{ + public: + //!@{ + //! \name Type aliases + using SPConstParticles = std::shared_ptr; + using SPConstMaterials = std::shared_ptr; + //!@} + + public: + // Construct from shared and imported data + explicit MucfProcess(SPConstParticles particles, + SPConstMaterials materials); + + // Construct the models associated with this process + VecModel build_models(ActionIdIter start_id) const final; + + // Get the interaction cross sections for the given energy range + XsGrid macro_xs(Applicability) const final; + + // Get the energy loss for the given energy range + EnergyLossGrid energy_loss(Applicability) const final; + + //! Whether the integral method can be used to sample interaction length + bool supports_integral_xs() const final { return false; } + + //! Whether the process applies when the particle is stopped + bool applies_at_rest() const final { return true; } + + // Name of the process + std::string_view label() const final; + + private: + SPConstParticles particles_; + SPConstMaterials materials_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/phys/PDGNumber.hh b/src/celeritas/phys/PDGNumber.hh index 44755468c1..15baf0edbf 100644 --- a/src/celeritas/phys/PDGNumber.hh +++ b/src/celeritas/phys/PDGNumber.hh @@ -146,6 +146,10 @@ CELER_DEFINE_PDGNUMBER(anti_he3, -1000020030) CELER_DEFINE_PDGNUMBER(alpha, 1000020040) CELER_DEFINE_PDGNUMBER(anti_alpha, -1000020040) +// Muonic nuclei +CELER_DEFINE_PDGNUMBER(muonic_deuteron, 2000010020) +CELER_DEFINE_PDGNUMBER(muonic_triton, 2000010030) + //!@} #undef CELER_DEFINE_PDGNUMBER diff --git a/test/celeritas/data/four-steel-slabs.root-dump.json b/test/celeritas/data/four-steel-slabs.root-dump.json index 22948401fa..cba069f76c 100644 --- a/test/celeritas/data/four-steel-slabs.root-dump.json +++ b/test/celeritas/data/four-steel-slabs.root-dump.json @@ -569,6 +569,25 @@ }, "mucf_physics" : { "_typename" : "celeritas::inp::MucfPhysics", + "scalars" : { + "_typename" : "celeritas::inp::MucfScalars", + "protium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "deuterium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "tritium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "liquid_hydrogen_density" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + } + }, "muon_energy_cdf" : { "_typename" : "celeritas::inp::Grid", "x" : [], diff --git a/test/celeritas/ext/GeantImporter.test.cc b/test/celeritas/ext/GeantImporter.test.cc index 68022e0a32..a42578192a 100644 --- a/test/celeritas/ext/GeantImporter.test.cc +++ b/test/celeritas/ext/GeantImporter.test.cc @@ -2132,15 +2132,27 @@ TEST_F(OpticalSurfaces, surfaces) } //---------------------------------------------------------------------------// -TEST_F(MucfBox, run) +TEST_F(MucfBox, static_data) { auto const& mucf = this->imported_data().mucf_physics; EXPECT_TRUE(mucf); - static double const expected_muon_energy_cdf_y[] = {1, 1}; - EXPECT_EQ(2, mucf.muon_energy_cdf.x.size()); - EXPECT_VEC_EQ(expected_muon_energy_cdf_y, mucf.muon_energy_cdf.y); + auto average = [](inp::Grid::VecDbl const& data) -> double { + double sum{0}; + for (auto y : data) + { + sum += y; + } + return sum / data.size(); + }; + + static size_t const expected_muon_energy_cdf_size = 21; + EXPECT_EQ(expected_muon_energy_cdf_size, mucf.muon_energy_cdf.x.size()); + EXPECT_EQ(expected_muon_energy_cdf_size, mucf.muon_energy_cdf.y.size()); + EXPECT_SOFT_EQ(0.55157437567861023, average(mucf.muon_energy_cdf.x)); + EXPECT_SOFT_EQ(11.250286274435437, average(mucf.muon_energy_cdf.y)); + // Dummy data auto const& cycle_f0 = mucf.cycle_rates[0]; static double const expected_cycle_rate_f0_y[] = {2, 2}; EXPECT_TRUE(cycle_f0); From 6da545d9418148235fc9949bc9eb141d4c7cbfe3 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Wed, 21 Jan 2026 07:39:08 -0500 Subject: [PATCH 46/60] Add documentation and debug statements about Geant4 logger redirection (#2193) * Write additional debug information when suppressing logger redirect * Add Geant4 documentation * Fix doc errors --- doc/CMakeLists.txt | 15 ++-- doc/implementation/corecel/fundamentals.rst | 33 ++++++-- .../geant4-interface/details.rst | 42 +++++----- .../geant4-interface/high-level.rst | 12 +-- .../geant4-interface/low-level.rst | 26 +++++-- doc/usage/execution/environment.rst | 16 +++- src/accel/detail/IntegrationSingleton.cc | 1 + src/celeritas/g4/SupportedOpticalPhysics.hh | 2 +- src/corecel/Assert.hh | 17 ++++- src/corecel/Macros.hh | 23 ++++-- src/corecel/sys/MultiExceptionHandler.hh | 7 +- src/geocel/ScopedGeantExceptionHandler.hh | 10 +-- src/geocel/ScopedGeantLogger.cc | 76 ++++++++++++------- src/geocel/ScopedGeantLogger.hh | 32 +++++--- 14 files changed, 212 insertions(+), 100 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index ad297309b8..a6a4572cdd 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -107,7 +107,7 @@ doxygen_add_docs(doxygen ) #-----------------------------------------------------------------------------# -# USER MANUAL (Sphinx/Breathe) +# USER MANUAL #-----------------------------------------------------------------------------# if(CELERITAS_PYTHONPATH_RERUN) @@ -184,7 +184,8 @@ function(celeritas_build_latex input output) endfunction() #-----------------------------------------------------------------------------# -# USER DOXYGEN +# USER MANUAL: Doxygen setup + function(_set_jsonbool varname) if(ARGC GREATER 1 AND ARGV1) set(${varname} true PARENT_SCOPE) @@ -279,6 +280,7 @@ file(GLOB _DOXYGEN_SOURCE "${PROJECT_SOURCE_DIR}/src/celeritas/optical/surface/model/*.hh" "${PROJECT_SOURCE_DIR}/test/*.hh" ) + # IMPORTANT: the target name and USE_STAMP_FILE generate a dependency used by # the downstream sphinx docs when Breathe is available doxygen_add_docs(doxygen_xml @@ -289,7 +291,7 @@ doxygen_add_docs(doxygen_xml ) #-----------------------------------------------------------------------------# -# GRAPHICS PROCESSING +# USER MANUAL: graphics generation (GraphViz) # Note that *all* dot files must be present at the same time to prevent # errors when doing separate HTML/PDF builds: the doctrees directory caches the @@ -335,7 +337,7 @@ foreach(_inp IN LISTS _dot_input) endforeach() #-----------------------------------------------------------------------------# -# RST PROCESSING +# USER MANUAL: Sphinx RST pre-processing if(Sphinx_FOUND) file(GLOB_RECURSE _sphinx_deps "${CMAKE_CURRENT_SOURCE_DIR}/*.rst") @@ -354,7 +356,8 @@ celeritas_build_sphinx(dummy "${_doctree_env}" "${_sphinx_deps}") add_custom_target(doc_preprocess DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_doctree_env}") #-----------------------------------------------------------------------------# -# HTML +# USER MANUAL: Sphinx HTML generation + set(_doc_html "html/index.html") celeritas_build_sphinx(html "${_doc_html}" "${_doctree_env}") add_custom_target(doc DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_doc_html}") @@ -365,7 +368,7 @@ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html/" ) #-----------------------------------------------------------------------------# -# PDF +# USER MANUAL: Sphinx PDF generation set(_doc_tex "latex/Celeritas.tex") celeritas_build_sphinx(latex "${_doc_tex}" "${_doctree_env}") diff --git a/doc/implementation/corecel/fundamentals.rst b/doc/implementation/corecel/fundamentals.rst index 8fc7e6d2be..5bc8af8c29 100644 --- a/doc/implementation/corecel/fundamentals.rst +++ b/doc/implementation/corecel/fundamentals.rst @@ -24,7 +24,7 @@ CMake configuration options are set. The assertion macros ``CELER_EXPECT``, ``CELER_ASSERT``, and ``CELER_ENSURE`` correspond to "precondition contract", "internal assertion", and "postcondition -contract". +contract". They all throw :cpp:class:`celeritas::DebugError`. .. doxygendefine:: CELER_EXPECT .. doxygendefine:: CELER_ASSERT @@ -36,24 +36,45 @@ behavior at runtime to allow compiler optimizations: .. doxygendefine:: CELER_ASSERT_UNREACHABLE .. doxygendefine:: CELER_ASSUME -Finally, a few runtime macros will always throw helpful errors based on -incorrect configuration or input values. +Finally, a few runtime macros will always throw helpful +:cpp:class:`celeritas::RuntimeError` errors based on incorrect configuration or +input values. .. doxygendefine:: CELER_VALIDATE .. doxygendefine:: CELER_NOT_CONFIGURED .. doxygendefine:: CELER_NOT_IMPLEMENTED +These macros all throw subclasses of standard exceptions. See the developer +documentation for more details. + +.. doxygenclass:: celeritas::DebugError +.. doxygenclass:: celeritas::RuntimeError + + Utility macros ^^^^^^^^^^^^^^ The :file:`corecel/Macros.hh` file defines language and compiler abstraction macro definitions. -.. doxygendefine:: CELER_TRY_HANDLE -.. doxygendefine:: CELER_TRY_HANDLE_CONTEXT - .. doxygendefine:: CELER_DEFAULT_COPY_MOVE .. doxygendefine:: CELER_DELETE_COPY_MOVE .. doxygendefine:: CELER_DEFAULT_MOVE_DELETE_COPY .. doxygendefine:: CELER_DISCARD + +.. _exception_handling: + +Exception handling +^^^^^^^^^^^^^^^^^^ + +During multithreaded execution or when called by a Geant4 simulation, it is +important not to throw C++ exceptions lest they be uncaught and result in an +immediate program abort. Celeritas provides macros and classes for managing +exceptions and providing detailed diagnostics. + +.. doxygendefine:: CELER_TRY_HANDLE +.. doxygendefine:: CELER_TRY_HANDLE_CONTEXT +.. doxygenclass:: celeritas::RichContextException +.. doxygenclass:: celeritas::MultiExceptionHandler +.. doxygenfunction:: celeritas::log_and_rethrow diff --git a/doc/implementation/geant4-interface/details.rst b/doc/implementation/geant4-interface/details.rst index d4c824a1c8..43f6860601 100644 --- a/doc/implementation/geant4-interface/details.rst +++ b/doc/implementation/geant4-interface/details.rst @@ -11,7 +11,7 @@ Geant4. .. doxygenclass:: celeritas::LocalTransporter Interface utilities -------------------- +^^^^^^^^^^^^^^^^^^^ .. celerstruct:: AlongStepFactoryInput @@ -21,47 +21,51 @@ Interface utilities .. _api_accel_adapters: Classes usable by Geant4 ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ These utilities are based on Celeritas data structures and capabilities but are written to be usable both by the ``celer-g4`` app and potential other users. Fields -^^^^^^ +"""""" .. doxygenclass:: celeritas::RZMapMagneticField .. doxygenclass:: celeritas::CylMapMagneticField .. doxygenfunction:: celeritas::MakeCylMapFieldInput Primary generators -^^^^^^^^^^^^^^^^^^ +"""""""""""""""""" .. doxygenclass:: celeritas::HepMC3PrimaryGenerator .. doxygenclass:: celeritas::PGPrimaryGeneratorAction -.. _api_geant4_physics_options: +Physics lists +""""""""""""" -Physics constructors -^^^^^^^^^^^^^^^^^^^^ +Two physics constructors build exclusively processes supported by Celeritas for +Geant4: -A Geant4 physics constructor :cpp:class:`celeritas::SupportedEmStandardPhysics` allows -very fine-grained selection of the EM physics processes supported by Celeritas. -The input options incorporate process and model selection as well as default EM -parameters to send to Geant4. - -.. celerstruct:: GeantPhysicsOptions .. doxygenclass:: celeritas::SupportedEmStandardPhysics +.. doxygenclass:: celeritas::SupportedOpticalPhysics -Physics lists -^^^^^^^^^^^^^ - -Two physics lists (one using Geant4 hadronics, the other using pure Celeritas) -allow setup of EM physics using only processes supported by Celeritas. +Two "modular" physics lists (one using Geant4 hadronics, the other using pure +Celeritas) are stand-ins for physics factories suitable for sending to +``G4RunManager::SetUserInitialization``. .. doxygenclass:: celeritas::EmPhysicsList .. doxygenclass:: celeritas::FtfpBertPhysicsList +.. _api_geant4_physics_options: + +Physics setup +""""""""""""" + +The input options incorporate process and model selection as well as default EM +parameters to send to Geant4. + +.. celerstruct:: GeantPhysicsOptions + Sensitive detectors -^^^^^^^^^^^^^^^^^^^ +""""""""""""""""""" .. doxygenclass:: celeritas::GeantSimpleCalo diff --git a/doc/implementation/geant4-interface/high-level.rst b/doc/implementation/geant4-interface/high-level.rst index 164dd1d6b0..8056839926 100644 --- a/doc/implementation/geant4-interface/high-level.rst +++ b/doc/implementation/geant4-interface/high-level.rst @@ -4,17 +4,19 @@ .. _api_accel_high_level: High level interfaces -===================== +--------------------- The high-level integration classes are the easiest way to add Celeritas to a Geant4 application. Under the hood, it contains a singleton class instance that sets up the UI commands (see :cpp:class:`celeritas::SetupOptionsMessenger`), -MPI (if configured), and Celeritas logging. +MPI (if configured), and Celeritas logging (redirecting "world" logging with +:cpp:func:`celeritas::MakeMTWorldLogger` and "self" logging with +:cpp:func:`celeritas::MakeMTSelfLogger`) . .. doxygenclass:: celeritas::IntegrationBase Tracking manager ----------------- +^^^^^^^^^^^^^^^^ Using Celeritas to "offload" all electrons, photons, and gammas from Geant4 can be done using the new-ish Geant4 interface :cpp:class:`G4VTrackingManager` @@ -31,7 +33,7 @@ See :ref:`example_template` for a template of adding to a user application. :members: Fast simulation ---------------- +^^^^^^^^^^^^^^^ It is currently *not* recommended to offload tracks on a per-region basis, since tracks exiting that region remain in Celeritas and on GPU. @@ -41,7 +43,7 @@ tracks exiting that region remain in Celeritas and on GPU. :members: User action ------------ +^^^^^^^^^^^ For compatibility with older versions of Geant4, you may use the following class to integrate Celeritas by manually intercepting tracks with a diff --git a/doc/implementation/geant4-interface/low-level.rst b/doc/implementation/geant4-interface/low-level.rst index 1ed44c380e..6106a78f8e 100644 --- a/doc/implementation/geant4-interface/low-level.rst +++ b/doc/implementation/geant4-interface/low-level.rst @@ -1,13 +1,13 @@ .. Copyright Celeritas contributors: see top-level COPYRIGHT file for details .. SPDX-License-Identifier: CC-BY-4.0 -Low-level Celeritas integration -=============================== +Low-level integration utilities +------------------------------- -This subsection contains details of importing Geant4 data into Celeritas. +This subsection contains details of integrating Geant4 with Celeritas. -Geant4 geometry utilities -^^^^^^^^^^^^^^^^^^^^^^^^^ +Geometry utilities +^^^^^^^^^^^^^^^^^^ These utility classes are used to set up the Geant4 global geometry state. @@ -16,11 +16,23 @@ These utility classes are used to set up the Geant4 global geometry state. .. doxygenfunction:: celeritas::save_gdml .. doxygenfunction:: celeritas::find_geant_volumes -Geant4 physics interfaces -^^^^^^^^^^^^^^^^^^^^^^^^^ +Physics interfaces +^^^^^^^^^^^^^^^^^^ This will be replaced by other utilities in conjunction with the :ref:`problem input `. .. doxygenclass:: celeritas::GeantImporter .. doxygenclass:: celeritas::GeantSetup + + +.. _g4_utils: + +Utility interfaces +^^^^^^^^^^^^^^^^^^ + +.. doxygenfunction:: celeritas::MakeMTSelfLogger +.. doxygenfunction:: celeritas::MakeMTWorldLogger + +.. doxygenclass:: celeritas::ScopedGeantLogger +.. doxygenclass:: celeritas::ScopedGeantExceptionHandler diff --git a/doc/usage/execution/environment.rst b/doc/usage/execution/environment.rst index 05268304b4..3141a40d90 100644 --- a/doc/usage/execution/environment.rst +++ b/doc/usage/execution/environment.rst @@ -100,10 +100,18 @@ Celeritas or its apps: Logging ------- -The Celeritas library writes informational messages to ``stderr``. The given -levels can be used with the ``CELER_LOG`` and ``CELER_LOG_LOCAL`` environment -variables to suppress or increase the output. The default is to print -diagnostic messages and higher. +The Celeritas library has an internal logger that, by default, writes +informational messages to ``stderr`` through the ``std::clog`` (buffered +standard error) handle. +The "world" logger generally prints messages during setup or at circumstances +shared by all threads/tasks/processes, and the "self" logger is for messages +related to a particular thread. By default the self logger is mutexed for +thread safety, and log messages are assembled internally before printing to +reduce I/O contention. + +The levels below can be used with the ``CELER_LOG`` and ``CELER_LOG_LOCAL`` +environment variables to suppress or increase the output. +The default is to print diagnostic messages and higher. .. table:: Logging levels in increasing severity. diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 1cebec02e9..371f1cd5fa 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -428,6 +428,7 @@ void IntegrationSingleton::update_logger() celeritas::world_logger() = celeritas::MakeMTWorldLogger(*run_man); celeritas::self_logger() = celeritas::MakeMTSelfLogger(*run_man); have_created_logger_ = true; + CELER_LOG(debug) << "Celeritas logging redirected through Geant4"; } else { diff --git a/src/celeritas/g4/SupportedOpticalPhysics.hh b/src/celeritas/g4/SupportedOpticalPhysics.hh index befc992924..ae874d9bc5 100644 --- a/src/celeritas/g4/SupportedOpticalPhysics.hh +++ b/src/celeritas/g4/SupportedOpticalPhysics.hh @@ -14,7 +14,7 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * Construct Celeritas-supported optical physics. + * Construct Celeritas-supported optical physics processes. */ class SupportedOpticalPhysics : public G4VPhysicsConstructor { diff --git a/src/corecel/Assert.hh b/src/corecel/Assert.hh index e98b1079b2..097c1360e4 100644 --- a/src/corecel/Assert.hh +++ b/src/corecel/Assert.hh @@ -394,7 +394,13 @@ struct JsonPimpl; //---------------------------------------------------------------------------// /*! - * Error thrown by Celeritas assertions. + * Error thrown by Celeritas assertions and debug failures. + * + * This class contains additional user-accessible context for the source of the + * error, the failing condition, etc., allowing detailed exception output or + * forwarding the exception details through other interfaces such as + * G4Exception. Since a \c DebugError indicates a \em programming bug, it + * inherits from \c std::logic_error. */ class DebugError : public std::logic_error { @@ -416,6 +422,11 @@ class DebugError : public std::logic_error //---------------------------------------------------------------------------// /*! * Error thrown by working code from unexpected runtime conditions. + * + * The runtime error contains additional user-accessible context for the source + * of the error, the failing condition, etc. Since it indicates an error with + * user input or other system conditions, it inherits from \c + * std::runtime_error. */ class RuntimeError : public std::runtime_error { @@ -445,10 +456,10 @@ class RuntimeError : public std::runtime_error /*! * Base class for writing arbitrary exception context to JSON. * - * This can be overridden in higher-level parts of the code for specific needs + * This can be subclassed in higher-level parts of the code for specific needs * (e.g., writing thread, event, and track contexts in Celeritas solver * kernels). Note that in order for derived classes to work with - * `std::throw_with_nested`, they *MUST NOT* be `final`. + * \c std::throw_with_nested, they MUST NOT be \c final. */ class RichContextException : public std::exception { diff --git a/src/corecel/Macros.hh b/src/corecel/Macros.hh index 2b02814979..b4db222418 100644 --- a/src/corecel/Macros.hh +++ b/src/corecel/Macros.hh @@ -237,10 +237,6 @@ * * "Try" to execute the statement, and "handle" *all* thrown errors by calling * the given function-like error handler with a \c std::exception_ptr object. - * - * \note A file that uses this macro must include the \c \ header - * (but since the \c HANDLE_EXCEPTION needs to take an exception pointer, it's - * got to be included anyway). */ #define CELER_TRY_HANDLE(STATEMENT, HANDLE_EXCEPTION) \ do \ @@ -261,8 +257,23 @@ * Try the given statement, and if it fails, chain it into the given exception. * * The given \c CONTEXT_EXCEPTION must be an expression that yields an rvalue - * to a \c std::exception subclass that isn't \c final . The resulting chained - * exception will be passed into \c HANDLE_EXCEPTION for processing. + * to a \c std::exception subclass that is not marked \c final . The resulting + * chained exception will be passed into \c HANDLE_EXCEPTION for processing. + * + * This example shows how to safely propagate exceptions out of an OpenMP + * parallel block using celeritas::MultiExceptionHandler : + * \code + MultiExceptionHandler capture_exception; + #pragma omp parallel for + for (size_type i = 0; i < num_threads; ++i) + { + CELER_TRY_HANDLE_CONTEXT( + execute_thread(ThreadId{i}), + capture_exception, + KernelContextException(ThreadId{i})); + } + log_and_rethrow(std::move(capture_exception)); + * \endcode */ #define CELER_TRY_HANDLE_CONTEXT( \ STATEMENT, HANDLE_EXCEPTION, CONTEXT_EXCEPTION) \ diff --git a/src/corecel/sys/MultiExceptionHandler.hh b/src/corecel/sys/MultiExceptionHandler.hh index e3534da7a0..9e5572ab1b 100644 --- a/src/corecel/sys/MultiExceptionHandler.hh +++ b/src/corecel/sys/MultiExceptionHandler.hh @@ -18,7 +18,7 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * Temporarily store exception pointers. + * Temporarily store exception pointers to allow propagation across threads. * * This is useful for storing multiple exceptions in unrelated loops (where one * exception shouldn't affect the program flow outside of the scope), @@ -34,6 +34,11 @@ namespace celeritas log_and_rethrow(std::move(capture_exception)); * \endcode * + * If an exception is caught and the stored exception pointers are not cleared + * via \c release or \c log_and_rethrow , then at the end of the exception + * handler's lifetime, the stored exceptions will be printed and the program + * will be terminated. + * * \note This class implements an OpenMP \c critical mutex, not a \c std * mutex. If using this class in a \c std::thread context, wrap the call * operator in a lambda with a \c std::scoped_lock . We could refactor as a diff --git a/src/geocel/ScopedGeantExceptionHandler.hh b/src/geocel/ScopedGeantExceptionHandler.hh index 2ad8bece07..3807113f45 100644 --- a/src/geocel/ScopedGeantExceptionHandler.hh +++ b/src/geocel/ScopedGeantExceptionHandler.hh @@ -23,15 +23,15 @@ namespace celeritas /*! * Convert Geant4 exceptions to RuntimeError during this class lifetime. * - * Note that creating a \c G4RunManagerKernel resets the exception - * handler, so errors thrown during setup \em cannot be caught by Celeritas, - * and this class can only be used after creating the \c G4RunManager. - * * Because the underlying Geant4 error handler is thread-local, this class must * be scoped to inside each worker thread. Additionally, since throwing from a * worker thread terminates the program, an error handler \em must be specified * if used in a worker thread: you should probably use a \c - * celeritas::MultiExceptionHandler . + * celeritas::MultiExceptionHandler if used inside a worker thread. + * + * \note Creating a \c G4RunManagerKernel resets the exception + * handler, so errors thrown during setup \em cannot be caught by Celeritas, + * and this class can only be used after creating the \c G4RunManager. */ class ScopedGeantExceptionHandler { diff --git a/src/geocel/ScopedGeantLogger.cc b/src/geocel/ScopedGeantLogger.cc index 19fbd5e792..9bf73422c4 100644 --- a/src/geocel/ScopedGeantLogger.cc +++ b/src/geocel/ScopedGeantLogger.cc @@ -104,22 +104,43 @@ G4int log_impl(celeritas::Logger& log, G4String const& str, LogLevel level) return 0; } +//---------------------------------------------------------------------------// +//! Thread-local flags for ownership/usability of the Geant4 logger +enum class SGLState +{ + inactive, + active, + disabled +}; + +SGLState& sgl_state() +{ + static G4ThreadLocal SGLState result{SGLState::inactive}; + return result; +} + +//---------------------------------------------------------------------------// +} // namespace + //---------------------------------------------------------------------------// /*! * Send Geant4 log messages to Celeritas' world logger. */ -class GeantLoggerAdapter final : public G4coutDestination +class ScopedGeantLogger::StreamDestination final : public G4coutDestination { public: // Assign to Geant handlers on construction - explicit GeantLoggerAdapter(Logger&); - CELER_DELETE_COPY_MOVE(GeantLoggerAdapter); - ~GeantLoggerAdapter() final; + explicit StreamDestination(Logger&); + CELER_DELETE_COPY_MOVE(StreamDestination); + ~StreamDestination() final; // Handle error messages G4int ReceiveG4cout(G4String const& str) final; G4int ReceiveG4cerr(G4String const& str) final; + // Access logger from ScopedGeantLogger cleanup + Logger& logger() { return celer_log_; } + private: Logger& celer_log_; #if CELER_G4SSBUF @@ -134,7 +155,7 @@ class GeantLoggerAdapter final : public G4coutDestination * * Note that all these buffers, and the UI pointers, are thread-local. */ -GeantLoggerAdapter::GeantLoggerAdapter(Logger& celer_log) +ScopedGeantLogger::StreamDestination::StreamDestination(Logger& celer_log) : celer_log_{celer_log} { // Make sure UI pointer has been instantiated, since its constructor @@ -163,7 +184,7 @@ GeantLoggerAdapter::GeantLoggerAdapter(Logger& celer_log) /*! * Restore iostream buffers on destruction. */ -GeantLoggerAdapter::~GeantLoggerAdapter() +ScopedGeantLogger::StreamDestination::~StreamDestination() { #if CELER_G4SSBUF G4coutbuf.SetDestination(saved_cout_); @@ -177,7 +198,7 @@ GeantLoggerAdapter::~GeantLoggerAdapter() /*! * Process stdout message. */ -G4int GeantLoggerAdapter::ReceiveG4cout(G4String const& str) +G4int ScopedGeantLogger::StreamDestination::ReceiveG4cout(G4String const& str) { return log_impl(celer_log_, str, LogLevel::diagnostic); } @@ -186,29 +207,11 @@ G4int GeantLoggerAdapter::ReceiveG4cout(G4String const& str) /*! * Process stderr message. */ -G4int GeantLoggerAdapter::ReceiveG4cerr(G4String const& str) +G4int ScopedGeantLogger::StreamDestination::ReceiveG4cerr(G4String const& str) { return log_impl(celer_log_, str, LogLevel::info); } -//---------------------------------------------------------------------------// -//! Thread-local flags for ownership/usability of the Geant4 logger -enum class SGLState -{ - inactive, - active, - disabled -}; - -SGLState& sgl_state() -{ - static G4ThreadLocal SGLState result{SGLState::inactive}; - return result; -} - -//---------------------------------------------------------------------------// -} // namespace - //---------------------------------------------------------------------------// //! Enable and disable to avoid recursion with accel/Logger //!@{ @@ -236,8 +239,20 @@ ScopedGeantLogger::ScopedGeantLogger(Logger& celer_log) auto& state = sgl_state(); if (state == SGLState::inactive) { + celer_log(CELER_CODE_PROVENANCE, LogLevel::debug) + << "Activating ScopedGeantLogger"; + state = SGLState::active; - logger_ = std::make_unique(celer_log); + logger_ = std::make_unique(celer_log); + } + else + { + celer_log(CELER_CODE_PROVENANCE, LogLevel::debug) + << R"(Ignoring ScopedGeantLogger initialization because )" + << (state == SGLState::active + ? R"(Geant4 is already being redirected on this thread)" + : state == SGLState::disabled ? R"(log redirection is disabled)" + : ""); } } @@ -261,7 +276,12 @@ ScopedGeantLogger::~ScopedGeantLogger() { state = SGLState::inactive; } - logger_.reset(); + if (logger_) + { + logger_->logger()(CELER_CODE_PROVENANCE, LogLevel::debug) + << "Resetting ScopedGeantLogger"; + logger_.reset(); + } } //---------------------------------------------------------------------------// diff --git a/src/geocel/ScopedGeantLogger.hh b/src/geocel/ScopedGeantLogger.hh index 1aad05be86..8a96652e9c 100644 --- a/src/geocel/ScopedGeantLogger.hh +++ b/src/geocel/ScopedGeantLogger.hh @@ -12,18 +12,28 @@ #include "corecel/Macros.hh" -class G4coutDestination; - namespace celeritas { class Logger; //---------------------------------------------------------------------------// /*! - * Install a Geant output destination during this class's lifetime. + * Redirect Geant4 logging through Celeritas' logger. + * + * This parses messages sent to \c G4cout and \c G4cerr from Geant4. Based on + * the message (whether it starts with warning, error, '!!!', '***') it tries + * to use the appropriate logging level and source context. * * Since the Geant4 output streams are thread-local, this class is as well. - * Multiple geant loggers can be nested, and only the outermost on a given - * thread will "own" the log destination. + * Multiple geant loggers can be nested in scope, but only the outermost on a + * given thread will "own" the log destination. + * + * - When instantiated during setup, this should be constructed with + * \c celeritas::world_logger to avoid printing duplicate messages per + * thread/process. + * - When instantiated during runtime, it should take the + * \c celeritas::self_logger so that only warning/error messages are printed + * for event/track-specific details. + * */ class ScopedGeantLogger { @@ -45,20 +55,24 @@ class ScopedGeantLogger CELER_DELETE_COPY_MOVE(ScopedGeantLogger); private: -#if CELERITAS_USE_GEANT4 - std::unique_ptr logger_; -#endif + class StreamDestination; + + std::unique_ptr logger_; }; #if !CELERITAS_USE_GEANT4 -// Do nothing if Geant4 is disabled (source file will not be compiled) +// Do nothing if Geant4 is disabled inline bool ScopedGeantLogger::enabled() { return false; } +inline void ScopedGeantLogger::enabled(bool) {} inline ScopedGeantLogger::ScopedGeantLogger(Logger&) {} inline ScopedGeantLogger::ScopedGeantLogger() {} inline ScopedGeantLogger::~ScopedGeantLogger() {} +class ScopedGeantLogger::StreamDestination +{ +}; #endif //---------------------------------------------------------------------------// From 596873907c0a18463172247893db969952bf044c Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Wed, 21 Jan 2026 08:22:53 -0500 Subject: [PATCH 47/60] Simplify errors when initialization fails (#2198) * Use intended run manger types for optical physics and add docs to cmake * Add error checking test to tracking manager integration * Check for setup failure to avoid cascading failures * Merge BeginOfRun implementations * Update error messages for single-thread case * Tweak test setup * Combine begin/end into integration singleton * Change setup verification name and eliminate empty check * Improve nomenclature and docs * Fix doc * Rename and document test variable --- app/celer-g4/ActionInitialization.cc | 2 +- app/celer-g4/DetectorConstruction.cc | 4 +- app/celer-g4/GeantDiagnostics.cc | 2 +- app/celer-g4/GeantDiagnostics.hh | 4 +- app/celer-g4/LogHandlers.cc | 4 +- app/celer-g4/RunAction.cc | 2 +- cmake/CeleritasG4Tests.cmake | 6 +- doc/CMakeLists.txt | 4 + .../geant4-interface/high-level.rst | 1 + example/offload-template/src/EventAction.cc | 2 +- example/offload-template/src/EventAction.hh | 3 +- example/offload-template/src/RunAction.cc | 2 +- src/accel/ExceptionConverter.cc | 4 +- src/accel/ExceptionConverter.hh | 10 +- src/accel/FastSimulationIntegration.cc | 49 +--- src/accel/FastSimulationIntegration.hh | 15 +- src/accel/IntegrationBase.cc | 63 +++-- src/accel/IntegrationBase.hh | 18 +- src/accel/Logger.cc | 4 +- src/accel/SetupOptions.hh | 3 +- src/accel/SharedParams.cc | 2 +- src/accel/SharedParams.hh | 14 +- src/accel/TimeOutput.cc | 4 +- src/accel/TrackingManager.cc | 2 +- src/accel/TrackingManager.hh | 6 +- src/accel/TrackingManagerIntegration.cc | 53 ++-- src/accel/TrackingManagerIntegration.hh | 16 +- src/accel/UserActionIntegration.cc | 30 +-- src/accel/UserActionIntegration.hh | 18 +- src/accel/detail/IntegrationSingleton.cc | 240 ++++++++++-------- src/accel/detail/IntegrationSingleton.hh | 33 +-- src/accel/detail/LoggerImpl.cc | 2 +- src/celeritas/ext/GeantSd.cc | 2 +- src/celeritas/g4/DetectorConstruction.cc | 2 +- test/accel/CMakeLists.txt | 36 ++- test/accel/IntegrationTestBase.cc | 26 +- test/accel/IntegrationTestBase.hh | 3 + test/accel/TrackingManagerIntegration.test.cc | 79 +++++- 38 files changed, 419 insertions(+), 351 deletions(-) diff --git a/app/celer-g4/ActionInitialization.cc b/app/celer-g4/ActionInitialization.cc index ddec13b33f..77cc23953b 100644 --- a/app/celer-g4/ActionInitialization.cc +++ b/app/celer-g4/ActionInitialization.cc @@ -58,7 +58,7 @@ ActionInitialization::ActionInitialization(SPParams params) //---------------------------------------------------------------------------// /*! - * Construct actions on the master thread. + * Construct actions on the manager thread. * * Since our \c RunAction::EndOfRunAction only calls \c SharedParams::Finalize * on the master thread, we need a special case for MT mode. diff --git a/app/celer-g4/DetectorConstruction.cc b/app/celer-g4/DetectorConstruction.cc index 713255b680..3486974671 100644 --- a/app/celer-g4/DetectorConstruction.cc +++ b/app/celer-g4/DetectorConstruction.cc @@ -68,8 +68,8 @@ DetectorConstruction::DetectorConstruction(SPParams params) /*! * Load geometry and sensitive detector volumes. * - * This should only be called once from the master thread, toward the very - * beginning of the program. + * This will be called only once, from the manager/single thread, during + * Geant4 initialization. */ G4VPhysicalVolume* DetectorConstruction::Construct() { diff --git a/app/celer-g4/GeantDiagnostics.cc b/app/celer-g4/GeantDiagnostics.cc index 321b9201be..2c5c07ee76 100644 --- a/app/celer-g4/GeantDiagnostics.cc +++ b/app/celer-g4/GeantDiagnostics.cc @@ -53,7 +53,7 @@ auto GeantDiagnostics::queued_output() -> VecOutputInterface& //---------------------------------------------------------------------------// /*! - * Construct from shared Celeritas params on the master thread. + * Construct from shared Celeritas params on the main thread. */ GeantDiagnostics::GeantDiagnostics(SharedParams const& params) { diff --git a/app/celer-g4/GeantDiagnostics.hh b/app/celer-g4/GeantDiagnostics.hh index b631636d8a..dfd1286a11 100644 --- a/app/celer-g4/GeantDiagnostics.hh +++ b/app/celer-g4/GeantDiagnostics.hh @@ -29,8 +29,8 @@ class DetectorConstruction; /*! * Diagnostics for Geant4 (i.e., for tracks not offloaded to Celeritas). * - * A single instance of this class should be created by the master thread and - * shared across all threads. + * A single instance of this class should be created by the main thread and + * shared among worker threads. */ class GeantDiagnostics { diff --git a/app/celer-g4/LogHandlers.cc b/app/celer-g4/LogHandlers.cc index a4c19c25e8..12c0b0784e 100644 --- a/app/celer-g4/LogHandlers.cc +++ b/app/celer-g4/LogHandlers.cc @@ -47,7 +47,7 @@ void handle_serial(LogProvenance prov, LogLevel lev, std::string msg) } //---------------------------------------------------------------------------// -//! Tag a singular output with worker/master: should usually be master +//! Tag a singular output with worker/main: should usually be main void handle_mt_world(LogProvenance prov, LogLevel lev, std::string msg) { if (G4Threading::G4GetThreadId() > 0) @@ -143,7 +143,7 @@ LogHandler make_world_handler() return SelfLogHandler{get_geant_num_threads()}; } - // Only master and the first worker write + // Only the main thread (and a single worker if MT) write return handle_mt_world; } diff --git a/app/celer-g4/RunAction.cc b/app/celer-g4/RunAction.cc index c1866fdb8b..c6c09aa9b6 100644 --- a/app/celer-g4/RunAction.cc +++ b/app/celer-g4/RunAction.cc @@ -66,7 +66,7 @@ void RunAction::BeginOfRunAction(G4Run const* run) if (init_shared_) { - // This worker (or master thread) is responsible for initializing + // This is the main thread responsible for initializing // celeritas: initialize shared data and setup GPU on all threads CELER_TRY_HANDLE(params_->Initialize(*options_), call_g4exception); CELER_ASSERT(*params_); diff --git a/cmake/CeleritasG4Tests.cmake b/cmake/CeleritasG4Tests.cmake index 1b9134b4f0..9ca85deb9c 100644 --- a/cmake/CeleritasG4Tests.cmake +++ b/cmake/CeleritasG4Tests.cmake @@ -20,14 +20,16 @@ run manager. celeritas_g4_add_tests( - [PREFIX string] # before target in test name - [SUFFIX string] # after target in test name + [NAME string] # test name (default: target) [ARGS arg [...]] # passed to command line [LABELS label [...]] # tagged in CTestFile + [RMTYPE [serial] [mt] [task]] # run manager + [OFFLOAD [cpu] [gpu] [g4] [ko]] # offload [DISABLE] # display but don't run [WILL_FAIL] # expect the test to exit with a failure ) + Note that DISABLE silently overrides WILL_FAIL. #]=======================================================================] diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index a6a4572cdd..d797729cce 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -137,6 +137,10 @@ else() mark_as_advanced(CELERITAS_SPHINX_ARGS) celeritas_get_pyenv(CELER_PYTHONENV) + # Build sphinx docs: + # - type: html, pdf, dummy + # - output: relative path of main output file (index.html, Celeritas.tex) + # - depends: list of file dependencies function(celeritas_build_sphinx type output depends) if(NOT type STREQUAL "dummy") set(_comment "Building Sphinx ${type} documentation: doc/${output}") diff --git a/doc/implementation/geant4-interface/high-level.rst b/doc/implementation/geant4-interface/high-level.rst index 8056839926..572b4a5f13 100644 --- a/doc/implementation/geant4-interface/high-level.rst +++ b/doc/implementation/geant4-interface/high-level.rst @@ -14,6 +14,7 @@ MPI (if configured), and Celeritas logging (redirecting "world" logging with :cpp:func:`celeritas::MakeMTSelfLogger`) . .. doxygenclass:: celeritas::IntegrationBase + :members: Tracking manager ^^^^^^^^^^^^^^^^ diff --git a/example/offload-template/src/EventAction.cc b/example/offload-template/src/EventAction.cc index 090d135388..5ac4b33edb 100644 --- a/example/offload-template/src/EventAction.cc +++ b/example/offload-template/src/EventAction.cc @@ -34,7 +34,7 @@ EventAction::SPStepDiagnostic& step_diagnostic() //---------------------------------------------------------------------------// /*! - * From MakeCelerOptions during setup on master, set the step diagnostic. + * From MakeCelerOptions during initialization, set the step diagnostic. * * This should only be called once from the main thread during BeginRun via * MakeCelerOptions. diff --git a/example/offload-template/src/EventAction.hh b/example/offload-template/src/EventAction.hh index c6e5a031fa..cc8f0a31ec 100644 --- a/example/offload-template/src/EventAction.hh +++ b/example/offload-template/src/EventAction.hh @@ -31,7 +31,8 @@ class EventAction final : public G4UserEventAction //!@} public: - // From MakeCelerOptions during setup on master, set the step diagnostic + // From MakeCelerOptions during setup on main thread, set the step + // diagnostic static void SetStepDiagnostic(SPStepDiagnostic&&); // During problem destruction, clear the diagnostic static void ClearStepDiagnostic(); diff --git a/example/offload-template/src/RunAction.cc b/example/offload-template/src/RunAction.cc index f7b059a377..4521699af2 100644 --- a/example/offload-template/src/RunAction.cc +++ b/example/offload-template/src/RunAction.cc @@ -20,7 +20,7 @@ RunAction::RunAction() : G4UserRunAction() {} //---------------------------------------------------------------------------// /*! - * Initialize master and worker threads in Celeritas. + * Initialize main and worker threads in Celeritas. */ void RunAction::BeginOfRunAction(G4Run const* run) { diff --git a/src/accel/ExceptionConverter.cc b/src/accel/ExceptionConverter.cc index 04626e39a2..216eea1253 100644 --- a/src/accel/ExceptionConverter.cc +++ b/src/accel/ExceptionConverter.cc @@ -88,7 +88,7 @@ void log_state(Logger::Message& msg, /*! * Capture the current exception and convert it to a G4Exception call. */ -void ExceptionConverter::operator()(std::exception_ptr eptr) const +void ExceptionConverter::operator()(std::exception_ptr eptr) { try { @@ -151,6 +151,7 @@ void ExceptionConverter::operator()(std::exception_ptr eptr) const return std::move(os).str(); }(); + forwarded_ = true; G4Exception(where.c_str(), err_code_, FatalException, what.c_str()); } catch (DebugError const& e) @@ -160,6 +161,7 @@ void ExceptionConverter::operator()(std::exception_ptr eptr) const where << detail::CleanedProvenance{e.details().file, e.details().line}; std::ostringstream what; what << to_cstring(e.details().which) << ": " << e.details().condition; + forwarded_ = true; G4Exception( where.str().c_str(), err_code_, FatalException, what.str().c_str()); } diff --git a/src/accel/ExceptionConverter.hh b/src/accel/ExceptionConverter.hh index ac48ec95d0..24ade1131f 100644 --- a/src/accel/ExceptionConverter.hh +++ b/src/accel/ExceptionConverter.hh @@ -26,6 +26,10 @@ class SharedParams; // Transport any tracks left in the buffer celeritas::ExceptionConverter call_g4exception{"celer.event.flush"}; CELER_TRY_HANDLE(transport_->Flush(), call_g4exception); + + // Debug error checking + CELER_ENSURE(transport_.GetBufferSize() == 0 + || call_g4exception.forwarded()); } * \endcode */ @@ -39,11 +43,15 @@ class ExceptionConverter inline explicit ExceptionConverter(char const* err_code); // Capture the current exception and convert it to a G4Exception call - void operator()(std::exception_ptr p) const; + void operator()(std::exception_ptr p); + + //! Whether an exception was passed to G4Exception + bool forwarded() const { return forwarded_; } private: char const* err_code_; SharedParams const* params_{nullptr}; + bool forwarded_{false}; void convert_device_exceptions(std::exception_ptr p) const; }; diff --git a/src/accel/FastSimulationIntegration.cc b/src/accel/FastSimulationIntegration.cc index f4b95e5b29..c14005e9d5 100644 --- a/src/accel/FastSimulationIntegration.cc +++ b/src/accel/FastSimulationIntegration.cc @@ -8,28 +8,12 @@ #include -#include "corecel/sys/Stopwatch.hh" +#include "corecel/io/Logger.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" - -#include "detail/IntegrationSingleton.hh" namespace celeritas { -namespace -{ -//---------------------------------------------------------------------------// -/*! - * Check fast simulation is set up. - * - * \todo This is currently a null op, but we should loop through the regions to - * ensure that at least one of them is a celeritas::FastSimulationModel. - */ -void verify_fast_sim() {} - -//---------------------------------------------------------------------------// -} // namespace //---------------------------------------------------------------------------// /*! @@ -43,35 +27,12 @@ FastSimulationIntegration& FastSimulationIntegration::Instance() //---------------------------------------------------------------------------// /*! - * Start the run, initializing Celeritas options. + * Verify fast simulation setup. */ -void FastSimulationIntegration::BeginOfRunAction(G4Run const*) +void FastSimulationIntegration::verify_local_setup() { - Stopwatch get_setup_time; - - auto& singleton = detail::IntegrationSingleton::instance(); - - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - bool enable_offload = singleton.initialize_local_transporter(); - - if (enable_offload) - { - // Set tracking manager on workers when Celeritas is not fully disabled - CELER_LOG(debug) << "Verifying fast simulation"; - - CELER_TRY_HANDLE(verify_fast_sim(), - ExceptionConverter{"celer.init.verify"}); - } - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } + // TODO: Loop through regions to ensure at least one has a + // celeritas::FastSimulationModel } //---------------------------------------------------------------------------// diff --git a/src/accel/FastSimulationIntegration.hh b/src/accel/FastSimulationIntegration.hh index 5f9921177c..ea6c06b13d 100644 --- a/src/accel/FastSimulationIntegration.hh +++ b/src/accel/FastSimulationIntegration.hh @@ -18,16 +18,13 @@ namespace celeritas * application. To use this class in your Geant4 application to offload tracks * to Celeritas: * - * - Add the \c FastSimulationModel to regions of interest. * - Use \c SetOptions to set up options before \c G4RunManager::Initialize: * usually in \c main for simple applications. + * - In your \c G4VUserDetectorConstruction::ConstructSDandField, called during + * initialization, attach the \c FastSimulationModel to regions of interest * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * - * The method names correspond to methods in Geant4 User Actions and \em must - * be called from all threads, both worker and master. + * See further documentation in \c celeritas::IntegrationBase. */ class FastSimulationIntegration final : public IntegrationBase { @@ -35,12 +32,12 @@ class FastSimulationIntegration final : public IntegrationBase // Access the public-facing integration singleton static FastSimulationIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - private: // Tracking manager can only be created privately FastSimulationIntegration(); + + // Verify fast simulation setup + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/IntegrationBase.cc b/src/accel/IntegrationBase.cc index 9a62b6e27d..6f52a0b764 100644 --- a/src/accel/IntegrationBase.cc +++ b/src/accel/IntegrationBase.cc @@ -11,7 +11,6 @@ #include "corecel/Assert.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" #include "detail/IntegrationSingleton.hh" @@ -20,54 +19,68 @@ using celeritas::detail::IntegrationSingleton; namespace celeritas { //---------------------------------------------------------------------------// +//!@{ +//! \name User integration points + /*! * Set options before starting the run. * - * This captures the input to indicate that options cannot be modified after - * this point. + * This captures the input to indicate that options cannot be modified by the + * framework after this point. */ void IntegrationBase::SetOptions(SetupOptions&& opts) { IntegrationSingleton::instance().setup_options(std::move(opts)); } -//---------------------------------------------------------------------------// /*! - * Access whether Celeritas is set up, enabled, or uninitialized. + * Start the run. * - * This is only legal to call after \c SetOptions. + * This handles shared/local setup and calls verify_setup if offload is + * enabled. */ -OffloadMode IntegrationBase::GetMode() const +void IntegrationBase::BeginOfRunAction(G4Run const*) { - return IntegrationSingleton::instance().mode(); + auto& singleton = IntegrationSingleton::instance(); + + // Initialize shared params and local transporter + bool enable_offload = singleton.initialize_offload(); + + if (enable_offload) + { + // Allow derived classes to perform their specific verification + CELER_TRY_HANDLE(this->verify_local_setup(), + ExceptionConverter{"celer.init.verify"}); + } } -//---------------------------------------------------------------------------// /*! * End the run. */ void IntegrationBase::EndOfRunAction(G4Run const*) { - CELER_LOG(status) << "Finalizing Celeritas"; - auto& singleton = IntegrationSingleton::instance(); - // Record the run time - auto time = singleton.stop_timer(); + singleton.finalize_offload(); +} - // Remove local transporter - singleton.finalize_local_transporter(); +//!@} +//---------------------------------------------------------------------------// +//!@{ +//! \name Low-level Celeritas accessors - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordTotalTime(time); - singleton.finalize_shared_params(); - } +/*! + * Access whether Celeritas is set up, enabled, or uninitialized. + * + * This is only legal to call after \c SetOptions. + */ +OffloadMode IntegrationBase::GetMode() const +{ + return IntegrationSingleton::instance().mode(); } -//---------------------------------------------------------------------------// /*! - * Access Celeritas shared params. + * Access \em global Celeritas shared params during a run, if not disabled. */ CoreParams const& IntegrationBase::GetParams() { @@ -84,12 +97,11 @@ CoreParams const& IntegrationBase::GetParams() return *singleton.shared_params().Params(); } -//---------------------------------------------------------------------------// /*! - * Access THREAD-LOCAL Celeritas core state data for user diagnostics. + * Access \em thread-local Celeritas core state data for user diagnostics. * * - This can \em only be called when Celeritas is enabled (not kill-offload, - * not disabled) + * not disabled). * - This cannot be called from the main thread of an MT application. */ CoreStateInterface& IntegrationBase::GetState() @@ -109,6 +121,7 @@ CoreStateInterface& IntegrationBase::GetState() return singleton.local_transporter().GetState(); } +//!@} //---------------------------------------------------------------------------// /*! diff --git a/src/accel/IntegrationBase.hh b/src/accel/IntegrationBase.hh index 946b30d053..d85b8ba292 100644 --- a/src/accel/IntegrationBase.hh +++ b/src/accel/IntegrationBase.hh @@ -27,9 +27,6 @@ class CoreParams; * The \c GetParams and \c GetState methods may only be used during a run with * Celeritas offloading enabled. * - * \note It cannot be accessed before the Geant4 run manager is created (this - * requirement may be relaxed in the future). - * * \sa celeritas::UserActionIntegration * \sa celeritas::TrackingManagerIntegration * @@ -39,12 +36,11 @@ class CoreParams; class IntegrationBase { public: + //// USER INTEGRATION POINTS //// + // Set options before starting the run void SetOptions(SetupOptions&& opts); - // Access Celeritas offload mode type after options are set - OffloadMode GetMode() const; - // REMOVE in v0.7 [[deprecated]] void BuildForMaster() {} @@ -52,12 +48,15 @@ class IntegrationBase [[deprecated]] void Build() {} // Start the run - virtual void BeginOfRunAction(G4Run const* run) = 0; + void BeginOfRunAction(G4Run const* run); // End the run void EndOfRunAction(G4Run const* run); - //// ACCESSORS //// + //// LOW-LEVEL ACCESSORS //// + + // Access Celeritas offload mode type after options are set + OffloadMode GetMode() const; // Access Celeritas shared params CoreParams const& GetParams(); @@ -69,6 +68,9 @@ class IntegrationBase IntegrationBase(); ~IntegrationBase() = default; CELER_DEFAULT_COPY_MOVE(IntegrationBase); + + //! Verify setup after initialization (called if thread is doing offload) + virtual void verify_local_setup() = 0; }; //---------------------------------------------------------------------------// diff --git a/src/accel/Logger.cc b/src/accel/Logger.cc index f4c80a4a2f..b5d3020f2c 100644 --- a/src/accel/Logger.cc +++ b/src/accel/Logger.cc @@ -34,7 +34,7 @@ void write_serial(LogProvenance prov, LogLevel lev, std::string msg) } //---------------------------------------------------------------------------// -//! Tag a singular output with worker/master: should usually be master +//! Tag a singular output with worker/main: should usually be main void write_mt_world(LogProvenance prov, LogLevel lev, std::string msg) { if (G4Threading::G4GetThreadId() > 0) @@ -88,7 +88,7 @@ Logger MakeMTWorldLogger(G4RunManager const& runman) } else { - // Only master and the first worker write + // Only main (and the first worker in MT mode) write handle = write_mt_world; } } diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 63835c39f0..a3d57cb293 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -64,7 +64,8 @@ struct AlongStepFactoryInput; * are never set. * * The \c force_volumes option can be used for unusual cases (i.e., when using - * a custom run manager) that do not define SDs on the "master" thread. + * a custom run manager) that do not define the same SDs on the main thread + * as on worker threads. * Similarly, the \c skip_volumes option allows optimized GPU-defined SDs to be * used in place of a Geant4 callback. For both options, the \c * FindVolumes helper function can be used to determine LV pointers from diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 1fafbeb87c..6f35698f7b 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -253,7 +253,7 @@ bool SharedParams::KillOffloadTracks() * * This is a separate step from construction because it has to happen at the * beginning of the run, not when user classes are created. It should be called - * from the "master" thread (for MT mode) or from the main thread (for Serial), + * from the main thread ("master" when MT), * and it must complete before any worker thread tries to access the shared * data. */ diff --git a/src/accel/SharedParams.hh b/src/accel/SharedParams.hh index 7118fcdd9e..be4d472d29 100644 --- a/src/accel/SharedParams.hh +++ b/src/accel/SharedParams.hh @@ -47,14 +47,14 @@ struct SetupOptions; * The \c CeleritasDisabled accessor queries the \c CELER_DISABLE environment * variable as a global option for disabling Celeritas offloading. * - * This should be instantiated on the master thread during problem setup, + * This should be instantiated on the main thread during problem setup, * preferably as a shared pointer. The shared pointer should be * passed to a thread-local \c LocalTransporter instance. At the beginning of * the run, after Geant4 has initialized physics data, the \c Initialize method - * must be called first on the "master" thread to populate the Celeritas data + * must be called on the main thread to populate the Celeritas data * structures (geometry, physics). \c InitializeWorker must subsequently be - * invoked on all worker threads to set up thread-local data (specifically, - * CUDA device initialization). + * invoked on all worker threads to set up thread-local data (including + * CUDA device initialization and objects that require Geant4 allocators). * * Some low-level objects, such as the output diagnostics and Geant4 geometry * wrapper, can be created independently of Celeritas being enabled. @@ -99,16 +99,16 @@ class SharedParams // Construct in an uninitialized state SharedParams() = default; - // Construct Celeritas using Geant4 data on the master thread. + // Construct Celeritas using Geant4 data on the main thread. explicit SharedParams(SetupOptions const& options); - // Initialize shared data on the "master" thread + // Initialize shared data on the main thread inline void Initialize(SetupOptions const& options); // On worker threads, set up data with thread storage duration void InitializeWorker(SetupOptions const& options); - // Write (shared) diagnostic output and clear shared data on master + // Write (shared) diagnostic output and clear shared data on main thread void Finalize(); //!@} diff --git a/src/accel/TimeOutput.cc b/src/accel/TimeOutput.cc index b8bfaa9fc3..07a145473b 100644 --- a/src/accel/TimeOutput.cc +++ b/src/accel/TimeOutput.cc @@ -77,7 +77,7 @@ void TimeOutput::RecordEventTime(double time) /*! * Record the time for setting up Celeritas. * - * This should be called once by the master thread. + * This should be called once by the main thread. */ void TimeOutput::RecordSetupTime(double time) { @@ -88,7 +88,7 @@ void TimeOutput::RecordSetupTime(double time) /*! * Record the total time spent in transport and hit I/O (excluding setup). * - * This should be called once by the master thread. + * This should be called once by the main thread. */ void TimeOutput::RecordTotalTime(double time) { diff --git a/src/accel/TrackingManager.cc b/src/accel/TrackingManager.cc index 20d7a352ca..6ca3125ac7 100644 --- a/src/accel/TrackingManager.cc +++ b/src/accel/TrackingManager.cc @@ -124,7 +124,7 @@ void TrackingManager::PreparePhysicsTable(G4ParticleDefinition const& part) /*! * Offload the incoming track to Celeritas. * - * This will \em not be called in the master thread of an MT run. + * This will \em not be called in the "master" thread of an MT run. */ void TrackingManager::HandOverOneTrack(G4Track* track) { diff --git a/src/accel/TrackingManager.hh b/src/accel/TrackingManager.hh index e659c3b158..8f7162bccf 100644 --- a/src/accel/TrackingManager.hh +++ b/src/accel/TrackingManager.hh @@ -30,9 +30,9 @@ class TrackOffloadInterface; * This thread-local manager points to a corresponding thread-local * transporter. * - * Because physics initialization also happens on the master MT thread, where - * no events are processed, a custom tracking manager \em also exists for that - * thread. In that case, the local transporter should be null. + * The \c TrackingManagerConstructor class creates an instance of this class + * for every worker thread (or the main thread when using a serial run + * manager.) * * \note As of Geant4 11.3, instances of this class (one per thread) will never * be deleted. diff --git a/src/accel/TrackingManagerIntegration.cc b/src/accel/TrackingManagerIntegration.cc index f5b3fa65ba..8d0a66a8e0 100644 --- a/src/accel/TrackingManagerIntegration.cc +++ b/src/accel/TrackingManagerIntegration.cc @@ -6,7 +6,6 @@ //---------------------------------------------------------------------------// #include "TrackingManagerIntegration.hh" -#include #include #include #include @@ -19,12 +18,10 @@ #endif #include "corecel/io/Join.hh" -#include "corecel/sys/Stopwatch.hh" #include "corecel/sys/TypeDemangler.hh" #include "geocel/GeantUtils.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" #include "TrackingManagerConstructor.hh" #include "detail/IntegrationSingleton.hh" @@ -151,50 +148,30 @@ TrackingManagerIntegration& TrackingManagerIntegration::Instance() //---------------------------------------------------------------------------// /*! - * Start the run, initializing Celeritas options. + * Verify tracking manager setup. */ -void TrackingManagerIntegration::BeginOfRunAction(G4Run const*) +void TrackingManagerIntegration::verify_local_setup() { CELER_VALIDATE(G4VERSION_NUMBER >= 1100, << "the current version of Geant4 (" << G4VERSION_NUMBER << ") is too old to support the tracking manager offload " "interface (11.0 or higher is required)"); - Stopwatch get_setup_time; - auto& singleton = detail::IntegrationSingleton::instance(); - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - bool enable_offload = singleton.initialize_local_transporter(); - - if (enable_offload) - { - // Set particle offloading based on user options - auto const& user_offload = singleton.setup_options().offload_particles; - auto const& offload_particles - = user_offload ? *user_offload - : SharedParams::default_offload_particles(); - - // Set tracking manager on workers when Celeritas is not fully disabled - CELER_LOG(debug) << "Verifying tracking manager"; - CELER_TRY_HANDLE( - verify_tracking_managers( - make_span(singleton.shared_params().OffloadParticles()), - make_span(offload_particles), - singleton.shared_params(), - singleton.local_offload()), - ExceptionConverter{"celer.init.verify"}); - } - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } + // Set particle offloading based on user options + auto const& user_offload = singleton.setup_options().offload_particles; + auto const& offload_particles + = user_offload ? *user_offload + : SharedParams::default_offload_particles(); + + // Set tracking manager on workers when Celeritas is not fully disabled + CELER_LOG(debug) << "Verifying tracking managers"; + verify_tracking_managers( + make_span(singleton.shared_params().OffloadParticles()), + make_span(offload_particles), + singleton.shared_params(), + singleton.local_offload()); } //---------------------------------------------------------------------------// diff --git a/src/accel/TrackingManagerIntegration.hh b/src/accel/TrackingManagerIntegration.hh index 8a4bde3700..e2aade6265 100644 --- a/src/accel/TrackingManagerIntegration.hh +++ b/src/accel/TrackingManagerIntegration.hh @@ -6,8 +6,6 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "IntegrationBase.hh" namespace celeritas @@ -26,13 +24,7 @@ namespace celeritas * usually in \c main for simple applications. * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * - * The method names correspond to methods in Geant4 User Actions and \em must - * be called from all threads, both worker and master. - * - * \todo Provide default minimal action initialization classes for user + * See further documentation in \c celeritas::IntegrationBase. */ class TrackingManagerIntegration final : public IntegrationBase { @@ -40,12 +32,12 @@ class TrackingManagerIntegration final : public IntegrationBase // Access the public-facing integration singleton static TrackingManagerIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - private: // Tracking manager can only be created privately TrackingManagerIntegration(); + + // Verify tracking manager setup + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index e83cd7fd93..7d94803614 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -29,30 +29,6 @@ UserActionIntegration& UserActionIntegration::Instance() return uai; } -//---------------------------------------------------------------------------// -/*! - * Start the run. - */ -void UserActionIntegration::BeginOfRunAction(G4Run const*) -{ - Stopwatch get_setup_time; - - auto& singleton = detail::IntegrationSingleton::instance(); - - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - singleton.initialize_local_transporter(); - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } -} - //---------------------------------------------------------------------------// /*! * Send Celeritas the event ID. @@ -127,5 +103,11 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) */ UserActionIntegration::UserActionIntegration() = default; +//---------------------------------------------------------------------------// +/*! + * No verification is performed by the user action. + */ +void UserActionIntegration::verify_local_setup() {} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/UserActionIntegration.hh b/src/accel/UserActionIntegration.hh index 16ba4a9261..f1aa7b985f 100644 --- a/src/accel/UserActionIntegration.hh +++ b/src/accel/UserActionIntegration.hh @@ -29,20 +29,18 @@ struct SetupOptions; * * - Use \c SetOptions to set Celeritas configuration before calling \c * G4RunManager::BeamOn - * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction + * - Call \c BeginOfRunAction and \c EndOfRunAction (in \c IntegrationBase) + * from \c UserRunAction * - Call \c BeginOfEvent and \c EndOfEvent from \c UserEventAction * - Call \c PreUserTrackingAction from your \c UserTrackingAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * * The method names correspond to methods in Geant4 User Actions and \em must * be called from all threads, both worker and master. * - * \note Prefer to use \c celeritas::TrackingManagerIntegration instead of this - * class, unless you need support for Geant4 earlier than 11.1. + * See further documentation in \c celeritas::IntegrationBase. * - * \todo Provide default minimal action initialization classes for user? + * \note Prefer to use \c celeritas::TrackingManagerIntegration instead of this + * class, unless you need support for Geant4 earlier than 11.0. */ class UserActionIntegration final : public IntegrationBase { @@ -50,9 +48,6 @@ class UserActionIntegration final : public IntegrationBase // Access the singleton static UserActionIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - // Send Celeritas the event ID void BeginOfEventAction(G4Event const* event); @@ -67,6 +62,9 @@ class UserActionIntegration final : public IntegrationBase UserActionIntegration(); Stopwatch get_event_time_; + + // Verify setup after initialization (called if offload is enabled) + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 371f1cd5fa..10f7ad0d3a 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -211,70 +211,41 @@ OffloadMode IntegrationSingleton::mode() const //---------------------------------------------------------------------------// /*! - * Construct shared params on master (or single) thread. + * Initialize shared params and thread-local transporter. * - * \todo The query for CeleritasDisabled initializes the environment before - * we've had a chance to load the user setup options. Make sure we can update - * the environment *first* when refactoring the setup. + * This handles both global (master thread) and local (worker thread) + * initialization. In MT mode, the master thread initializes shared params, + * and worker threads initialize their local state. * - * \note In Geant4 threading, \em only MT mode on non-master thread has - * \c G4Threading::IsWorkerThread()==true. For MT mode, the master thread - * does not track any particles. For single-thread mode, the master thread - * \em does do work. + * \return Whether Celeritas offloading is enabled */ -void IntegrationSingleton::initialize_shared_params() +bool IntegrationSingleton::initialize_offload() { - ExceptionConverter call_g4exception{"celer.init.global"}; - if (G4Threading::IsMasterThread()) { - CELER_LOG(debug) << "Initializing shared params"; - CELER_TRY_HANDLE( - { - CELER_VALIDATE( - options_, - << R"(SetOptions or UI entries were not completely set before BeginRun)"); - CELER_VALIDATE( - !params_, - << R"(BeginOfRunAction cannot be called more than once)"); - - // Update logger in case run manager has changed number of - // threads, or user called initialization after run manager - this->update_logger(); - - params_.Initialize(options_); - }, - call_g4exception); + failed_setup_ = false; + + ExceptionConverter call_g4exception{"celer.init.global"}; + CELER_TRY_HANDLE(this->initialize_master_impl(), call_g4exception); + failed_setup_ = call_g4exception.forwarded(); + + // Start the run timer + get_time_ = {}; } - else + else if (!failed_setup_) { - CELER_LOG(debug) << "Initializing worker"; - CELER_TRY_HANDLE( - { - CELER_ASSERT(G4Threading::IsMultithreadedApplication()); - CELER_VALIDATE( - params_, - << R"(BeginOfRunAction was not called on master thread)"); - params_.InitializeWorker(options_); - }, - call_g4exception); + CELER_TRY_HANDLE(this->initialize_worker_impl(), + ExceptionConverter{"celer.init.worker"}); } + CELER_ASSERT(params_ || failed_setup_); - CELER_ENSURE(params_); -} - -//---------------------------------------------------------------------------// -/*! - * Construct thread-local transporter. - * - * Note that this uses the thread-local static data. It *must not* be called - * from the master thread in a multithreaded run. - * - * \return Whether Celeritas offloading is enabled - */ -bool IntegrationSingleton::initialize_local_transporter() -{ - CELER_EXPECT(params_); + // Now initialize local transporter + if (CELER_UNLIKELY(failed_setup_)) + { + CELER_LOG_LOCAL(debug) + << R"(Skipping local initialization due to failure)"; + return false; + } if (params_.mode() == OffloadMode::disabled) { @@ -286,13 +257,10 @@ bool IntegrationSingleton::initialize_local_transporter() if (G4Threading::IsMultithreadedApplication() && G4Threading::IsMasterThread()) { - // Cannot construct local transporter on master MT thread + // Do not construct local transporter on master MT thread return false; } - CELER_ASSERT(!G4Threading::IsMultithreadedApplication() - || G4Threading::IsWorkerThread()); - if (params_.mode() == OffloadMode::kill_offload) { // When "kill offload", we still need to intercept tracks @@ -301,70 +269,40 @@ bool IntegrationSingleton::initialize_local_transporter() return true; } - CELER_LOG(debug) << "Constructing local state"; - - CELER_TRY_HANDLE( - { - auto& lt = this->local_offload(); - CELER_VALIDATE(!lt, - << "local thread " - << G4Threading::G4GetThreadId() + 1 - << " cannot be initialized more than once"); - lt.Initialize(options_, params_); - }, - ExceptionConverter("celer.init.local")); + CELER_TRY_HANDLE(this->initialize_local_impl(), + ExceptionConverter("celer.init.local")); return true; } //---------------------------------------------------------------------------// /*! - * Destroy local transporter. + * Finalize thread-local transporter and (if main thread) shared params. */ -void IntegrationSingleton::finalize_local_transporter() +void IntegrationSingleton::finalize_offload() { - CELER_EXPECT(params_); - - if (params_.mode() != OffloadMode::enabled) + if (CELER_UNLIKELY(failed_setup_)) { return; } + CELER_EXPECT(params_); - if (G4Threading::IsMultithreadedApplication() - && G4Threading::IsMasterThread()) + // Finalize local transporter + if (params_.mode() == OffloadMode::enabled) { - // Cannot destroy local transporter on master MT thread - return; - } - - CELER_LOG(debug) << "Destroying local state"; - - CELER_TRY_HANDLE( + if (!G4Threading::IsMultithreadedApplication() + || !G4Threading::IsMasterThread()) { - auto& lt = this->local_offload(); - CELER_VALIDATE(lt, - << "local thread " - << G4Threading::G4GetThreadId() + 1 - << " cannot be finalized more than once"); - params_.timer()->RecordActionTime(lt.GetActionTime()); - lt.Finalize(); - }, - ExceptionConverter("celer.finalize.local")); -} + CELER_TRY_HANDLE(this->finalize_local_impl(), + ExceptionConverter("celer.finalize.local")); + } + } -//---------------------------------------------------------------------------// -/*! - * Destroy params. - */ -void IntegrationSingleton::finalize_shared_params() -{ - CELER_LOG(status) << "Finalizing Celeritas"; - CELER_TRY_HANDLE( - { - CELER_VALIDATE(params_, - << "params cannot be finalized more than once"); - params_.Finalize(); - }, - ExceptionConverter("celer.finalize.global")); + // Finalize shared params on main thread + if (G4Threading::IsMasterThread()) + { + CELER_TRY_HANDLE(this->finalize_shared_impl(), + ExceptionConverter("celer.finalize.global")); + } } //---------------------------------------------------------------------------// @@ -442,6 +380,92 @@ void IntegrationSingleton::update_logger() } } +//---------------------------------------------------------------------------// +/*! + * Initialize shared params implementation. + */ +void IntegrationSingleton::initialize_master_impl() +{ + CELER_EXPECT(G4Threading::IsMasterThread()); + + CELER_LOG(debug) << "Initializing shared params"; + CELER_VALIDATE( + options_, + << R"(SetOptions or UI entries were not completely set before BeginRun)"); + CELER_VALIDATE(!params_, + << R"(BeginOfRunAction cannot be called more than once)"); + + Stopwatch get_setup_time; + + // Update logger in case run manager has changed number of + // threads, or user called initialization after run manager + this->update_logger(); + + // Perform initialization + params_.Initialize(options_); + + // Record the setup time after initialization + params_.timer()->RecordSetupTime(get_setup_time()); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize worker thread implementation. + */ +void IntegrationSingleton::initialize_worker_impl() +{ + CELER_EXPECT(G4Threading::IsMultithreadedApplication()); + + CELER_LOG(debug) << "Initializing worker"; + CELER_VALIDATE(params_, + << R"(BeginOfRunAction was not called on master thread)"); + params_.InitializeWorker(options_); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize local transporter implementation. + */ +void IntegrationSingleton::initialize_local_impl() +{ + CELER_EXPECT(!G4Threading::IsMultithreadedApplication() + || G4Threading::IsWorkerThread()); + + CELER_LOG(debug) << "Constructing local state"; + auto& lt = this->local_offload(); + CELER_VALIDATE(!lt, + << "local thread " << G4Threading::G4GetThreadId() + 1 + << " cannot be initialized more than once"); + lt.Initialize(options_, params_); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize local transporter implementation. + */ +void IntegrationSingleton::finalize_local_impl() +{ + CELER_LOG(debug) << "Destroying local state"; + auto& lt = this->local_offload(); + CELER_VALIDATE(lt, + << "local thread " << G4Threading::G4GetThreadId() + 1 + << " cannot be finalized more than once"); + params_.timer()->RecordActionTime(lt.GetActionTime()); + lt.Finalize(); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize shared params implementation. + */ +void IntegrationSingleton::finalize_shared_impl() +{ + CELER_LOG(status) << "Finalizing Celeritas"; + CELER_VALIDATE(params_, << "params cannot be finalized more than once"); + params_.timer()->RecordTotalTime(get_time_()); + params_.Finalize(); +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index e17eff7f64..e8d89617b9 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -85,23 +85,11 @@ class IntegrationSingleton //// HELPERS //// - // Construct shared params on master (or single) thread - void initialize_shared_params(); + // Initialize shared params and thread-local transporter + bool initialize_offload(); - // Construct thread-local transporter - bool initialize_local_transporter(); - - // Destroy local transporter - void finalize_local_transporter(); - - // Destroy params - void finalize_shared_params(); - - // Start the transport timer [s] - void start_timer() { get_time_ = {}; } - - // Stop the timer and return the elapsed time [s] - real_type stop_timer() { return get_time_(); } + // Destroy local transporter and shared params + void finalize_offload(); private: //// TYPES //// @@ -116,6 +104,7 @@ class IntegrationSingleton std::unique_ptr messenger_; Stopwatch get_time_; bool have_created_logger_{false}; + bool failed_setup_{false}; //// PRIVATE MEMBER FUNCTIONS //// @@ -133,6 +122,18 @@ class IntegrationSingleton // Set up or update logging if the run manager is enabled void update_logger(); + + // Initialize shared params implementation + void initialize_master_impl(); + // Initialize worker thread implementation + void initialize_worker_impl(); + // Initialize local transporter implementation + void initialize_local_impl(); + + // Finalize local transporter implementation + void finalize_local_impl(); + // Finalize shared params implementation + void finalize_shared_impl(); }; //---------------------------------------------------------------------------// diff --git a/src/accel/detail/LoggerImpl.cc b/src/accel/detail/LoggerImpl.cc index 6287131b1f..761c574be6 100644 --- a/src/accel/detail/LoggerImpl.cc +++ b/src/accel/detail/LoggerImpl.cc @@ -116,7 +116,7 @@ void MtSelfWriter::operator()(LogProvenance prov, } else { - // Logging "local" message from the master thread! + // Logging "local" message from the master thread cerr << color_code('W') << "[M!] "; } cerr << ColorfulLogMessage{prov, lev, msg} << std::endl; diff --git a/src/celeritas/ext/GeantSd.cc b/src/celeritas/ext/GeantSd.cc index b7ff792102..047e573896 100644 --- a/src/celeritas/ext/GeantSd.cc +++ b/src/celeritas/ext/GeantSd.cc @@ -196,7 +196,7 @@ void GeantSd::setup_volumes(inp::GeantSd const& setup) { if (G4VSensitiveDetector* sd = lv->GetSensitiveDetector()) { - // Sensitive detector is attached to the master thread + // Sensitive detector is attached to the main thread insert_volume(lv, sd); } } diff --git a/src/celeritas/g4/DetectorConstruction.cc b/src/celeritas/g4/DetectorConstruction.cc index 78f79a2dde..8928f6c9c4 100644 --- a/src/celeritas/g4/DetectorConstruction.cc +++ b/src/celeritas/g4/DetectorConstruction.cc @@ -37,7 +37,7 @@ DetectorConstruction::DetectorConstruction(std::string const& filename, /*! * Load geometry and sensitive detector volumes. * - * This should only be called once from the master thread, toward the very + * This should only be called once from the main thread, toward the very * beginning of the program. */ G4VPhysicalVolume* DetectorConstruction::Construct() diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index d704f1e1ad..ff62bfa58a 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -10,6 +10,7 @@ endif() include(CeleritasG4Tests) #-----------------------------------------------------------------------------# +# Library celeritas_get_g4libs(_g4_libs global track tracking run event intercoms digits_hits geometry tasking ) @@ -30,6 +31,14 @@ celeritas_setup_tests(SERIAL testcel_accel testcel_celeritas testcel_core Celeritas::accel ) +#-----------------------------------------------------------------------------# +# Variables + +set(_optical_rm_type "serial") +if(NOT (CELERITAS_CORE_GEO STREQUAL "Geant4")) + list(APPEND _optical_rm_type "mt") +endif() + #-----------------------------------------------------------------------------# # TESTS #-----------------------------------------------------------------------------# @@ -81,18 +90,23 @@ endfunction() foreach(_basename IN ITEMS UserActionIntegration TrackingManagerIntegration) # Add every combination of test for LArSphere.run - celeritas_add_integration_tests(${_basename} LarSphere run) + celeritas_add_integration_tests( + ${_basename} LarSphere run + ) # Do TestEm3 only with celeritas cpu/gpu and serial run manager - celeritas_add_integration_tests(${_basename} TestEm3 run + celeritas_add_integration_tests( + ${_basename} TestEm3 run OFFLOAD cpu gpu RMTYPE serial ) - # Test OpNovice + + # Test OpNovice with optical-friendly run manager types celeritas_add_integration_tests( ${_basename} OpNoviceOptical run OFFLOAD cpu gpu g4 - RMTYPE ${_optical_rm_type}) + RMTYPE ${_optical_rm_type} + ) endforeach() # Test that the UI integration works (and ignores if Celeritas is disabled) @@ -102,14 +116,12 @@ celeritas_add_integration_tests( RMTYPE serial mt ) -#-----------------------------------------------------------------------------# -# Disable MT Geant4 geometry for optical physics -#-----------------------------------------------------------------------------# - -set(_optical_rm_type "serial") -if(NOT (CELERITAS_CORE_GEO STREQUAL "Geant4")) - list(APPEND _optical_rm_type "mt") -endif() +# Test that the UI integration fails if options aren't set +celeritas_add_integration_tests( + TrackingManagerIntegration LarSphere no_set_options + OFFLOAD cpu + RMTYPE serial mt +) # Test optical celeritas_add_integration_tests( diff --git a/test/accel/IntegrationTestBase.cc b/test/accel/IntegrationTestBase.cc index c351553ca8..cca0ae1a9c 100644 --- a/test/accel/IntegrationTestBase.cc +++ b/test/accel/IntegrationTestBase.cc @@ -7,7 +7,6 @@ #include "IntegrationTestBase.hh" #include -#include #include #include #include @@ -29,6 +28,7 @@ #include "corecel/Config.hh" +#include "corecel/io/ColorUtils.hh" #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" #include "corecel/math/ArrayUtils.hh" @@ -38,7 +38,6 @@ #include "corecel/sys/TypeDemangler.hh" #include "geocel/GeantUtils.hh" #include "geocel/ScopedGeantExceptionHandler.hh" -#include "geocel/ScopedGeantLogger.hh" #include "geocel/UnitUtils.hh" #include "celeritas/Quantities.hh" #include "celeritas/Units.hh" @@ -108,7 +107,6 @@ class RunAction final : public G4UserRunAction } } - // TODO: push exception onto a vector so we can do validation testing void handle_exception(std::exception_ptr ep) { try @@ -121,15 +119,14 @@ class RunAction final : public G4UserRunAction if (cstring_equal(d.which, "Geant4")) { // GeantExceptionHandler wrapped this error - FAIL() << "GeantExceptionHandler caught runtime error (" - << thread_label() << ',' << d.condition << "): from " - << d.file << ": " << d.what; + test_->caught_g4_runtime_error(e); } else { // Some other error - FAIL() << "Caught runtime error from " << thread_description() - << ": " << e.what(); + FAIL() << ansi_color('r') << "Caught runtime error from " + << thread_description() << ansi_color(' ') << ": " + << e.what(); } } catch (std::exception const& e) @@ -382,6 +379,19 @@ auto IntegrationTestBase::make_sens_det(std::string const&) -> UPSensDet return nullptr; } +//---------------------------------------------------------------------------// +/*! + * Fail when GeantExceptionHandler catches a celeritas RuntimeError. + */ +void IntegrationTestBase::caught_g4_runtime_error(RuntimeError const& e) +{ + // GeantExceptionHandler wrapped this error + auto const& d = e.details(); + FAIL() << ansi_color('R') << "GeantExceptionHandler caught runtime error (" + << thread_label() << ',' << d.condition << ")" << ansi_color(' ') + << ": from " << d.file << ": " << d.what; +} + //---------------------------------------------------------------------------// // FREE FUNCTIONS //---------------------------------------------------------------------------// diff --git a/test/accel/IntegrationTestBase.hh b/test/accel/IntegrationTestBase.hh index 877f52f78e..3adcfcb706 100644 --- a/test/accel/IntegrationTestBase.hh +++ b/test/accel/IntegrationTestBase.hh @@ -100,6 +100,9 @@ class IntegrationTestBase : public ::celeritas::test::Test // Create THREAD-LOCAL sensitive detectors for an SD name in the GDML file virtual UPSensDet make_sens_det(std::string const& sd_name); + // Fail when GeantExceptionHandler catches a celeritas RuntimeError + virtual void caught_g4_runtime_error(RuntimeError const& e); + //!@{ //! \name Dispatch from user run/event actions virtual void BeginOfRunAction(G4Run const* run) = 0; diff --git a/test/accel/TrackingManagerIntegration.test.cc b/test/accel/TrackingManagerIntegration.test.cc index cca7ef5eb5..c5504d43af 100644 --- a/test/accel/TrackingManagerIntegration.test.cc +++ b/test/accel/TrackingManagerIntegration.test.cc @@ -8,12 +8,16 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include "corecel/StringSimplifier.hh" #include "corecel/cont/Array.hh" #include "corecel/io/Logger.hh" #include "geocel/GeantUtils.hh" @@ -31,9 +35,11 @@ #include "accel/detail/IntegrationSingleton.hh" #include "IntegrationTestBase.hh" +#include "TestMacros.hh" #include "celeritas_test.hh" using TMI = celeritas::TrackingManagerIntegration; +using namespace std::string_view_literals; namespace celeritas { @@ -137,10 +143,11 @@ class TMITestBase : virtual public IntegrationTestBase }; //---------------------------------------------------------------------------// -// LAR SPHERE +// LAR SPHERE (EM only) //---------------------------------------------------------------------------// class LarSphere : public LarSphereIntegrationMixin, public TMITestBase { + public: void BeginOfEventAction(G4Event const* event) override { if (event->GetEventID() == 1) @@ -170,6 +177,47 @@ class LarSphere : public LarSphereIntegrationMixin, public TMITestBase EXPECT_DOUBLE_EQ((event_id == 1 ? 10.0 : 1.0), step->GetTrack()->GetWeight()); } + + //! Check wrapped RuntimeError caught by GeantExceptionHandler + void caught_g4_runtime_error(RuntimeError const& e) override + { + if (!check_runtime_errors_) + { + // Let the base class manage and fail on the caught error + return TMITestBase::caught_g4_runtime_error(e); + } + CELER_EXPECT(std::string_view(e.details().which) == "Geant4"sv); + + static std::recursive_mutex exc_mutex; + std::lock_guard scoped_lock{exc_mutex}; + + static std::regex extract_error{R"(runtime error:\s*(.+?)(?:\n|$))"}; + std::smatch match; + std::string what = e.what(); + if (std::regex_search(what, match, extract_error)) + { + CELER_ASSERT(match.size() > 1); + exceptions_.push_back(match[1].str()); + } + else + { + exceptions_.push_back(std::move(what)); + } + } + + void TearDown() override + { + if (!exceptions_.empty()) + { + FAIL() << exceptions_.size() + << " runtime errors were caught but not checked"; + } + } + + //! Append caught exceptions in this local test rather than failing + bool check_runtime_errors_{false}; + //! Exceptions that were caught by this test suite's error handler + std::vector exceptions_; }; /*! @@ -248,6 +296,35 @@ TEST_F(LarSphere, run_ui) EXPECT_EQ(get_geant_num_threads(rm), check_count.load()); } +/*! + * Check that omitting the SetOptions call causes the expected errors. + */ +TEST_F(LarSphere, no_set_options) +{ + auto& rm = this->run_manager(); + TMI::Instance(); + check_runtime_errors_ = true; + + CELER_LOG(status) << "Run initialization"; + rm.Initialize(); + EXPECT_EQ(0, exceptions_.size()); + CELER_LOG(status) << "Run two events"; + rm.BeamOn(2); + + std::vector expected_exceptions = { + "SetOptions or UI entries were not completely set before BeginRun", + }; + if (!G4Threading::IsMultithreadedApplication()) + { + // Geant4 still starts the first local event if an error happens during + // BeginOfRun + expected_exceptions.push_back( + R"(Celeritas was not initialized properly (maybe BeginOfRunAction was not called?))"); + } + EXPECT_VEC_EQ(expected_exceptions, exceptions_); + exceptions_.clear(); +} + //---------------------------------------------------------------------------// // LAR SPHERE WITH OPTICAL //---------------------------------------------------------------------------// From be6f7c11f9fc1906d5e7c310904c2618d720f736 Mon Sep 17 00:00:00 2001 From: "Seth R. Johnson" Date: Wed, 21 Jan 2026 12:56:23 -0500 Subject: [PATCH 48/60] Tweak optical problem setup (#2199) --- src/celeritas/optical/CoreParams.cc | 12 +- src/celeritas/optical/CoreParams.hh | 32 ++--- src/celeritas/optical/CoreTrackData.hh | 4 +- src/celeritas/setup/Problem.cc | 182 +++++++++++++------------ 4 files changed, 121 insertions(+), 109 deletions(-) diff --git a/src/celeritas/optical/CoreParams.cc b/src/celeritas/optical/CoreParams.cc index 1bf266e669..c9554cc8de 100644 --- a/src/celeritas/optical/CoreParams.cc +++ b/src/celeritas/optical/CoreParams.cc @@ -20,6 +20,7 @@ #include "celeritas/optical/OpticalSizes.json.hh" #include "celeritas/phys/GeneratorRegistry.hh" #include "celeritas/track/SimParams.hh" +#include "celeritas/user/SDParams.hh" #include "CoreState.hh" #include "MaterialParams.hh" @@ -55,10 +56,10 @@ build_params_refs(CoreParams::Input const& p, CoreScalars const& scalars) ref.geometry = get_ref(*p.geometry); ref.material = get_ref(*p.material); ref.physics = get_ref(*p.physics); - ref.surface = get_ref(*p.surface); - ref.surface_physics = get_ref(*p.surface_physics); ref.rng = get_ref(*p.rng); ref.sim = get_ref(*p.sim); + ref.surface = get_ref(*p.surface); + ref.surface_physics = get_ref(*p.surface_physics); // TODO: Get detectors ref if (p.cherenkov) { @@ -131,11 +132,10 @@ CoreParams::CoreParams(Input&& input) : input_(std::move(input)) CELER_EXPECT(input_); - // TODO: provide detectors in input, passing from core params - detectors_ = input_.detectors; - if (!detectors_) + // TODO: require and validate detectors + if (!input_.detectors) { - detectors_ = std::make_shared(); + input_.detectors = std::make_shared(); } if (!input_.aux_reg) { diff --git a/src/celeritas/optical/CoreParams.hh b/src/celeritas/optical/CoreParams.hh index 6c6e4a6090..0c28fdf8e1 100644 --- a/src/celeritas/optical/CoreParams.hh +++ b/src/celeritas/optical/CoreParams.hh @@ -6,17 +6,13 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "corecel/Assert.hh" #include "corecel/data/DeviceVector.hh" #include "corecel/data/ObserverPtr.hh" #include "corecel/data/ParamsDataInterface.hh" #include "corecel/random/params/RngParamsFwd.hh" -#include "corecel/sys/Device.hh" #include "celeritas/geo/GeoFwd.hh" #include "celeritas/inp/Control.hh" -#include "celeritas/user/SDParams.hh" #include "CoreTrackData.hh" @@ -30,6 +26,7 @@ class GeneratorRegistry; class OutputRegistry; class ScintillationParams; class SurfaceParams; +class SDParams; namespace optical { @@ -47,6 +44,11 @@ class CoreParams final : public ParamsDataInterface public: //!@{ //! \name Type aliases + using SPActionRegistry = std::shared_ptr; + using SPOutputRegistry = std::shared_ptr; + using SPGeneratorRegistry = std::shared_ptr; + using SPAuxRegistry = std::shared_ptr; + using SPConstCoreGeo = std::shared_ptr; using SPConstMaterial = std::shared_ptr; using SPConstPhysics = std::shared_ptr; @@ -54,11 +56,8 @@ class CoreParams final : public ParamsDataInterface using SPConstSim = std::shared_ptr; using SPConstSurface = std::shared_ptr; using SPConstSurfacePhysics = std::shared_ptr; - using SPActionRegistry = std::shared_ptr; - using SPOutputRegistry = std::shared_ptr; - using SPAuxRegistry = std::shared_ptr; - using SPGeneratorRegistry = std::shared_ptr; using SPConstDetectors = std::shared_ptr; + using SPConstCherenkov = std::shared_ptr; using SPConstScintillation = std::shared_ptr; @@ -70,6 +69,13 @@ class CoreParams final : public ParamsDataInterface struct Input { + // Registries + SPActionRegistry action_reg; + SPOutputRegistry output_reg; + SPGeneratorRegistry gen_reg; + SPAuxRegistry aux_reg; //!< Optional, empty default + + // Problem definition and state SPConstCoreGeo geometry; SPConstMaterial material; SPConstPhysics physics; @@ -78,14 +84,10 @@ class CoreParams final : public ParamsDataInterface SPConstSurface surface; SPConstSurfacePhysics surface_physics; SPConstDetectors detectors; + SPConstCherenkov cherenkov; //!< Optional SPConstScintillation scintillation; //!< Optional - SPActionRegistry action_reg; - SPOutputRegistry output_reg; - SPGeneratorRegistry gen_reg; - SPAuxRegistry aux_reg; //!< Optional, empty default - //! Maximum number of simultaneous threads/tasks per process StreamId::size_type max_streams{1}; @@ -131,7 +133,7 @@ class CoreParams final : public ParamsDataInterface SPOutputRegistry const& output_reg() const { return input_.output_reg; } SPAuxRegistry const& aux_reg() const { return input_.aux_reg; } SPGeneratorRegistry const& gen_reg() const { return input_.gen_reg; } - SPConstDetectors const& detectors() const { return detectors_; } + SPConstDetectors const& detectors() const { return input_.detectors; } SPConstCherenkov const& cherenkov() const { return input_.cherenkov; } SPConstScintillation const& scintillation() const { @@ -156,8 +158,6 @@ class CoreParams final : public ParamsDataInterface // Copy of DeviceRef in device memory DeviceVector device_ref_vec_; - - SPConstDetectors detectors_; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/optical/CoreTrackData.hh b/src/celeritas/optical/CoreTrackData.hh index 91851ca09a..2b804a3442 100644 --- a/src/celeritas/optical/CoreTrackData.hh +++ b/src/celeritas/optical/CoreTrackData.hh @@ -19,7 +19,6 @@ #include "PhysicsData.hh" #include "SimData.hh" #include "TrackInitData.hh" -#include "Types.hh" #include "gen/CherenkovData.hh" #include "gen/ScintillationData.hh" #include "surface/SurfacePhysicsData.hh" @@ -80,9 +79,9 @@ struct CoreParamsData sim = other.sim; surface = other.surface; surface_physics = other.surface_physics; - scalars = other.scalars; cherenkov = other.cherenkov; scintillation = other.scintillation; + scalars = other.scalars; return *this; } }; @@ -98,7 +97,6 @@ struct CoreStateData using Items = StateCollection; GeoStateData geometry; - // TODO: should we cache the material ID? ParticleStateData particle; PhysicsStateData physics; RngStateData rng; diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index bd0113843d..cedc6406ef 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include "Problem.hh" +#include #include #include #include @@ -297,7 +298,9 @@ auto build_along_step(inp::Field const& var_field, //---------------------------------------------------------------------------// /*! - * Construct optical parameters. + * Construct optical parameters from EM parameters. + * + * \todo build unique RNG streams for optical loop */ auto build_optical_params(inp::Problem const& p, CoreParams const& core, @@ -306,49 +309,107 @@ auto build_optical_params(inp::Problem const& p, CELER_VALIDATE(!imported.optical_materials.empty(), << "an optical tracking loop was requested but no optical " "materials are present"); + CELER_EXPECT(p.physics.optical); + CELER_EXPECT(p.control.optical_capacity); - optical::CoreParams::Input params; - CELER_ASSERT(p.control.optical_capacity); - params.capacity = *p.control.optical_capacity; - params.geometry = core.geometry(); - params.material = optical::MaterialParams::from_import( - imported, *core.geomaterial(), *core.material()); - // TODO: unique RNG streams for optical loop - params.rng = core.rng(); - params.sim - = std::make_shared(p.tracking.optical_limits); - params.surface = core.surface(); - params.action_reg = std::make_shared(); - params.gen_reg = std::make_shared(); - params.output_reg = core.output_reg(); - params.aux_reg = core.aux_reg(); - params.max_streams = core.max_streams(); + optical::CoreParams::Input pi; - // Construct optical physics models - params.physics = optical::PhysicsParams::from_import( - imported, core.material(), params.material, params.action_reg); + // Registries + pi.action_reg = std::make_shared(); + pi.output_reg = core.output_reg(); + pi.gen_reg = std::make_shared(); + pi.aux_reg = core.aux_reg(); - // Construct optical surface physics models - CELER_ASSERT(p.physics.optical); - params.surface_physics = std::make_shared( - params.action_reg.get(), p.physics.optical.surfaces); + // Geometry, physics, core + pi.geometry = core.geometry(); + pi.material = optical::MaterialParams::from_import( + imported, *core.geomaterial(), *core.material()); + pi.physics = optical::PhysicsParams::from_import( + imported, core.material(), pi.material, pi.action_reg); + pi.rng = core.rng(); + pi.sim = std::make_shared(p.tracking.optical_limits); + pi.surface = core.surface(); + pi.surface_physics = std::make_shared( + pi.action_reg.get(), p.physics.optical.surfaces); + // TODO: copy detectors from core - // Add photon generating processes + // Photon generating processes if (p.physics.optical.cherenkov) { - params.cherenkov = std::make_shared(*params.material); + pi.cherenkov = std::make_shared(*pi.material); } if (p.physics.optical.scintillation) { - params.scintillation + pi.scintillation = ScintillationParams::from_import(imported, core.particle()); } - //! \todo Get sensitive detectors + // Streams and capacities + pi.max_streams = core.max_streams(); + pi.capacity = *p.control.optical_capacity; + + CELER_ENSURE(pi); + return std::make_shared(std::move(pi)); +} + +//---------------------------------------------------------------------------// +/*! + * Construct optical parameters from optical problem definition. + */ +auto build_optical_params(inp::OpticalProblem const& p, + ModelLoaded&& loaded_model, + ImportData const& imported) +{ + CELER_VALIDATE(!imported.optical_materials.empty(), + << "an optical tracking loop was requested but no optical " + "materials are present"); + + // Create materials and geometry/material coupling + auto material = MaterialParams::from_import(imported); + auto geomaterial = GeoMaterialParams::from_import( + imported, loaded_model.geometry, loaded_model.volume, material); + + optical::CoreParams::Input pi; + + // Registries + pi.action_reg = std::make_shared(); + pi.output_reg = nullptr; + pi.gen_reg = std::make_shared(); + pi.aux_reg = nullptr; // TODO: require instead of building in CP + + // Geometry, materials, physics + pi.geometry = std::move(loaded_model.geometry); + pi.material = optical::MaterialParams::from_import( + imported, *geomaterial, *material); + pi.physics = optical::PhysicsParams::from_import( + imported, material, pi.material, pi.action_reg); + pi.rng = std::make_shared(p.seed); + pi.sim = std::make_shared(p.limits); + pi.surface = std::move(loaded_model.surface); + pi.surface_physics = std::make_shared( + pi.action_reg.get(), p.physics.surfaces); + // TODO: save loaded detectors + + // Streams and capacities + pi.max_streams = p.num_streams; + pi.capacity = p.capacity; + + // Photon generating processes are needed to offload via Geant4 optical + if (p.physics.cherenkov) + { + pi.cherenkov = std::make_shared(*pi.material); + } + if (p.physics.scintillation) + { + auto particle = ParticleParams::from_import(imported); + pi.scintillation = ScintillationParams::from_import(imported, particle); + CELER_ASSERT(pi.scintillation); + } - CELER_ENSURE(params); + std::move(loaded_model) = {}; - return std::make_shared(std::move(params)); + CELER_ENSURE(pi); + return std::make_shared(std::move(pi)); } //---------------------------------------------------------------------------// @@ -453,6 +514,7 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) } params.surface = std::make_shared(); } + // TODO: save detectors to params } // Load materials @@ -731,15 +793,6 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) << "an optical tracking loop was requested but no optical " "materials are present"); - optical::CoreParams::Input pi; - - // Per-process state sizes - pi.capacity = p.capacity; - - // Create action manager and generator registry - pi.action_reg = std::make_shared(); - pi.gen_reg = std::make_shared(); - // Load geometry and model if (auto* filename = std::get_if(&p.model.geometry)) { @@ -747,65 +800,26 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) << "empty filename in problem.model.geometry"); } auto loaded_model = setup::model(p.model); - pi.geometry = std::move(loaded_model.geometry); - pi.surface = std::move(loaded_model.surface); - CELER_VALIDATE(pi.surface && !pi.surface->disabled(), + CELER_VALIDATE(loaded_model.surface && !loaded_model.surface->disabled(), << "surfaces are required for optical physics"); - if (pi.surface->empty()) + if (loaded_model.surface->empty()) { CELER_LOG(warning) << "Problem contains optical physics without any " "geometry surface definitions: default physics " "will be used for all surfaces"; } - // Create materials and geometry/material coupling - auto material = MaterialParams::from_import(imported); - auto geomaterial = GeoMaterialParams::from_import( - imported, pi.geometry, loaded_model.volume, material); - - // Create optical materials - pi.material = optical::MaterialParams::from_import( - imported, *geomaterial, *material); - - // Construct RNG params - pi.rng = std::make_shared(p.seed); - - // Construct simulation params - pi.sim = std::make_shared(p.limits); - // Set up streams CELER_VALIDATE(p.num_streams > 0, << "p.num_streams must be manually set before setup"); - pi.max_streams = p.num_streams; if (auto& device = celeritas::device()) { - device.create_streams(pi.max_streams); - } - - // Construct optical bulk physics models - pi.physics = optical::PhysicsParams::from_import( - imported, material, pi.material, pi.action_reg); - - // Construct optical surface physics models - pi.surface_physics = std::make_shared( - pi.action_reg.get(), p.physics.surfaces); - - // Add photon generating processes - if (p.physics.cherenkov) - { - pi.cherenkov = std::make_shared(*pi.material); - } - if (p.physics.scintillation) - { - auto particle = ParticleParams::from_import(imported); - pi.scintillation = ScintillationParams::from_import(imported, particle); - CELER_ASSERT(pi.scintillation); + device.create_streams(p.num_streams); } - //! \todo Get sensitive detectors - - CELER_ASSERT(pi); - auto params = std::make_shared(std::move(pi)); + // Build optical params + auto params = build_optical_params(p, std::move(loaded_model), imported); + CELER_ASSERT(params); // Construct the optical generator std::visit(Overload{ From d0d4c7acd7468fec203a50086be66e2c1f2e4f15 Mon Sep 17 00:00:00 2001 From: Loren Schwiebert Date: Wed, 21 Jan 2026 17:22:17 -0500 Subject: [PATCH 49/60] Move global CoreStateCounters to the GPU (#2177) --- .../offload-template/src/StepDiagnostic.cc | 2 +- .../offload-template/src/StepDiagnostic.cu | 2 +- src/celeritas/global/CoreState.cc | 57 ++++++++++++++-- src/celeritas/global/CoreState.hh | 24 ++++--- src/celeritas/global/Stepper.cc | 15 ++-- .../optical/detail/OpticalLaunchAction.cc | 4 +- src/celeritas/track/CoreStateCounters.hh | 12 +++- .../track/ExtendFromPrimariesAction.cc | 17 ++--- .../track/ExtendFromPrimariesAction.cu | 6 +- .../track/ExtendFromSecondariesAction.cc | 10 ++- .../track/ExtendFromSecondariesAction.cu | 4 +- src/celeritas/track/InitializeTracksAction.cc | 10 ++- src/celeritas/track/InitializeTracksAction.cu | 6 +- src/celeritas/track/TrackInitData.hh | 18 ++++- .../track/detail/InitTracksExecutor.hh | 12 ++-- .../track/detail/ProcessPrimariesExecutor.hh | 6 +- .../detail/ProcessSecondariesExecutor.hh | 15 ++-- .../track/detail/TrackInitAlgorithms.cc | 24 ++++--- .../track/detail/TrackInitAlgorithms.cu | 68 ++++++++++--------- .../track/detail/TrackInitAlgorithms.hh | 19 ++---- src/corecel/data/Filler.cu | 3 + test/celeritas/global/Stepper.test.cc | 4 +- test/celeritas/track/TrackInit.test.cc | 36 +++++----- 23 files changed, 223 insertions(+), 151 deletions(-) diff --git a/example/offload-template/src/StepDiagnostic.cc b/example/offload-template/src/StepDiagnostic.cc index d266fe49a3..838ad473f7 100644 --- a/example/offload-template/src/StepDiagnostic.cc +++ b/example/offload-template/src/StepDiagnostic.cc @@ -149,7 +149,7 @@ void StepDiagnostic::step(CoreParams const& params, CoreStateHost& state) const auto& step_state = state.aux_data(aux_id_); // Accumulate counters - this->accum_counters(state.counters(), step_state.host_data); + this->accum_counters(state.sync_get_counters(), step_state.host_data); // Create a functor that gathers data from a single track slot auto execute = make_active_track_executor( diff --git a/example/offload-template/src/StepDiagnostic.cu b/example/offload-template/src/StepDiagnostic.cu index f8dccefdbb..a8e30d1768 100644 --- a/example/offload-template/src/StepDiagnostic.cu +++ b/example/offload-template/src/StepDiagnostic.cu @@ -28,7 +28,7 @@ void StepDiagnostic::step(CoreParams const& params, CoreStateDevice& state) cons auto& step_state = state.aux_data(aux_id_); // Accumulate counters - this->accum_counters(state.counters(), step_state.host_data); + this->accum_counters(state.sync_get_counters(), step_state.host_data); // Create a functor that gathers data from a single track slot auto execute = make_active_track_executor( diff --git a/src/celeritas/global/CoreState.cc b/src/celeritas/global/CoreState.cc index f83378725b..4322ea9a62 100644 --- a/src/celeritas/global/CoreState.cc +++ b/src/celeritas/global/CoreState.cc @@ -52,7 +52,9 @@ CoreState::CoreState(CoreParams const& params, states_ = CollectionStateStore( params.host_ref(), stream_id, num_track_slots); - counters_.num_vacancies = num_track_slots; + auto counters = CoreStateCounters{}; + counters.num_vacancies = num_track_slots; + this->sync_put_counters(counters); if constexpr (M == MemSpace::device) { @@ -114,7 +116,7 @@ CoreState::~CoreState() template void CoreState::warming_up(bool new_state) { - CELER_EXPECT(!new_state || counters_.num_active == 0); + CELER_EXPECT(!new_state || this->sync_get_counters().num_active == 0); warming_up_ = new_state; } @@ -133,19 +135,64 @@ Range CoreState::get_action_range(ActionId action_id) const return {thread_offsets[action_id], thread_offsets[action_id + 1]}; } +//---------------------------------------------------------------------------// +/*! + * Copy the core state counters from the device to the host. For host-only + * code, the counters reside on the host, so this just returns a + * CoreStateCounters object. Note that it does not return a reference, so + * sync_put_counters() must be used if any counters change. + */ +template +CoreStateCounters CoreState::sync_get_counters() const +{ + auto* counters + = static_cast(this->ref().init.counters.data()); + CELER_ASSERT(counters); + if constexpr (M == MemSpace::device) + { + auto result + = ItemCopier{this->stream_id()}(counters); + device().stream(this->stream_id()).sync(); + return result; + } + return *counters; +} + +//---------------------------------------------------------------------------// +/*! + * Copy the core state counters from the host to the device. For host-only + * code, this function copies a CoreStateCounter object into the CoreState + * object, which is needed when any of the counters change, because + * sync_get_counters() doesn't return a reference. + */ +template +void CoreState::sync_put_counters(CoreStateCounters const& host_counters) +{ + auto* counters + = static_cast(this->ref().init.counters.data()); + CELER_ASSERT(counters); + Copier copy{{counters, 1}, this->stream_id()}; + copy(MemSpace::host, {&host_counters, 1}); + if constexpr (M == MemSpace::device) + { + device().stream(this->stream_id()).sync(); + } +} + //---------------------------------------------------------------------------// /*! * Reset the state data. * * This clears the state counters and initializes the necessary state data so - * the state can be reused for a new event. This should only be necessary if + * the state can be reused for a new event. This should be necessary only if * the previous event aborted early. */ template void CoreState::reset() { - counters_ = CoreStateCounters{}; - counters_.num_vacancies = this->size(); + auto counters = CoreStateCounters{}; + counters.num_vacancies = this->size(); + sync_put_counters(counters); // Reset all the track slots to inactive fill(TrackStatus::inactive, &this->ref().sim.status); diff --git a/src/celeritas/global/CoreState.hh b/src/celeritas/global/CoreState.hh index f0c1d5ee80..5c18aba534 100644 --- a/src/celeritas/global/CoreState.hh +++ b/src/celeritas/global/CoreState.hh @@ -48,9 +48,14 @@ class CoreStateInterface //! Number of track slots virtual size_type size() const = 0; - //! Access track initialization counters - virtual CoreStateCounters const& counters() const = 0; - + //! Synchronize and copy track initialization counters from device to host + //! For host-only code, this replaces the old counters() function + [[nodiscard]] virtual CoreStateCounters sync_get_counters() const = 0; + + //! Synchronize and copy track initialization counters from host to device + //! For host-only code, this replaces the old counters() function + //! since we return a CoreStateCounters object instead of a reference + virtual void sync_put_counters(CoreStateCounters const&) = 0; //! Access auxiliary state data virtual AuxStateVec const& aux() const = 0; @@ -130,11 +135,13 @@ class CoreState final : public CoreStateInterface //// COUNTERS //// - //! Track initialization counters - CoreStateCounters& counters() { return counters_; } + //! Synchronize and copy track initialization counters from device to host + [[nodiscard]] CoreStateCounters sync_get_counters() const final; - //! Track initialization counters - CoreStateCounters const& counters() const final { return counters_; } + //! Synchronize and copy track initialization counters from host to device + //! For host-only code, this copies the local CoreStateCounters back to the + //! class, since sync_get_counters() doesn't return a reference + void sync_put_counters(CoreStateCounters const&) final; //// AUXILIARY DATA //// @@ -178,9 +185,6 @@ class CoreState final : public CoreStateInterface // Native pointer to ref data Ptr ptr_; - // Counters for track initialization and activity - CoreStateCounters counters_; - // User-added data associated with params SPAuxStateVec aux_state_; diff --git a/src/celeritas/global/Stepper.cc b/src/celeritas/global/Stepper.cc index fc991afeb5..97215967de 100644 --- a/src/celeritas/global/Stepper.cc +++ b/src/celeritas/global/Stepper.cc @@ -107,14 +107,14 @@ Stepper::~Stepper() = default; template void Stepper::warm_up() { - CELER_VALIDATE(state_->counters().num_active == 0, + CELER_VALIDATE(state_->sync_get_counters().num_active == 0, << "cannot warm up when state has active tracks"); ScopedProfiling profile_this{"warmup"}; state_->warming_up(true); ScopeExit on_exit_{[this] { state_->warming_up(false); }}; actions_->step(*params_, *state_); - CELER_ENSURE(state_->counters().num_active == 0); + CELER_ENSURE(state_->sync_get_counters().num_active == 0); } //---------------------------------------------------------------------------// @@ -128,9 +128,11 @@ template auto Stepper::operator()() -> result_type { ScopedProfiling profile_this{"step"}; - auto& counters = state_->counters(); + auto counters = state_->sync_get_counters(); counters.num_generated = 0; + state_->sync_put_counters(counters); actions_->step(*params_, *state_); + counters = state_->sync_get_counters(); // Get the number of track initializers and active tracks result_type result; @@ -164,7 +166,9 @@ auto Stepper::operator()(SpanConstPrimary primaries) -> result_type << "event number " << max_id->event_id.unchecked_get() << " exceeds max_events=" << params_->init()->max_events()); - state_->counters().num_pending = primaries.size(); + auto counters = state_->sync_get_counters(); + counters.num_pending = primaries.size(); + state_->sync_put_counters(counters); primaries_action_->insert(*params_, *state_, primaries); return (*this)(); @@ -180,7 +184,8 @@ auto Stepper::operator()(SpanConstPrimary primaries) -> result_type template void Stepper::kill_active() { - CELER_LOG_LOCAL(error) << "Killing " << state_->counters().num_active + CELER_LOG_LOCAL(error) << "Killing " + << state_->sync_get_counters().num_active << " active tracks"; detail::kill_active(*params_, *state_); } diff --git a/src/celeritas/optical/detail/OpticalLaunchAction.cc b/src/celeritas/optical/detail/OpticalLaunchAction.cc index c080a805f8..1dcc73b86f 100644 --- a/src/celeritas/optical/detail/OpticalLaunchAction.cc +++ b/src/celeritas/optical/detail/OpticalLaunchAction.cc @@ -134,8 +134,8 @@ void OpticalLaunchAction::execute_impl(CoreParams const&, auto& state = get>(core_state.aux(), this->aux_id()); CELER_ASSERT(state.size() > 0); - auto const& core_counters = core_state.counters(); - auto& counters = state.counters(); + auto const core_counters = core_state.sync_get_counters(); + auto const& counters = state.counters(); if ((counters.num_pending < data_.auto_flush && (core_counters.num_alive > 0 || core_counters.num_initializers > 0)) diff --git a/src/celeritas/track/CoreStateCounters.hh b/src/celeritas/track/CoreStateCounters.hh index 53c716435e..144a8af65a 100644 --- a/src/celeritas/track/CoreStateCounters.hh +++ b/src/celeritas/track/CoreStateCounters.hh @@ -14,9 +14,10 @@ namespace celeritas /*! * Counters for within-step track initialization and activity. * - * These counters are updated *by value on the host at every step* so they - * should not be stored in TrackInitStateData because then the device-memory - * copy will not be synchronized. + * When running device code, these counters are now updated on the device + * throughout the step, so they are stored in TrackInitStateData. They need to + * be synchronized between the host and device before and after the step to + * maintain consistency. * * For all user \c StepActionOrder (TODO: this may change if we add a * "user_end"), all but the secondaries/alive @@ -49,6 +50,11 @@ struct CoreStateCounters size_type num_secondaries{0}; //!< Number of secondaries produced size_type num_alive{0}; //!< Number of alive tracks at end //!@} + + //!@{ + //! \name Set by CUDA CUB when partitioning the tracks, unused by celeritas + size_type num_neutral{0}; //!< Number of neutral tracks + //!@} }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/track/ExtendFromPrimariesAction.cc b/src/celeritas/track/ExtendFromPrimariesAction.cc index 027a71a60f..305fc5d9b5 100644 --- a/src/celeritas/track/ExtendFromPrimariesAction.cc +++ b/src/celeritas/track/ExtendFromPrimariesAction.cc @@ -103,7 +103,7 @@ void ExtendFromPrimariesAction::insert(CoreParams const& params, CoreStateInterface& state, Span host_primaries) const { - size_type num_initializers = state.counters().num_initializers; + size_type num_initializers = state.sync_get_counters().num_initializers; size_type init_capacity = params.init()->capacity(); CELER_VALIDATE(host_primaries.size() + num_initializers <= init_capacity, @@ -184,15 +184,18 @@ void ExtendFromPrimariesAction::step_impl(CoreParams const& params, CoreState& state) const { auto& primaries = get>(state.aux(), aux_id_); + auto counters = state.sync_get_counters(); // Create track initializers from primaries - state.counters().num_initializers += primaries.count; + counters.num_initializers += primaries.count; + state.sync_put_counters(counters); this->process_primaries(params, state, primaries); // Mark that the primaries have been processed - state.counters().num_generated += primaries.count; - state.counters().num_pending = 0; + counters.num_generated += primaries.count; + counters.num_pending = 0; primaries.count = 0; + state.sync_put_counters(counters); } //---------------------------------------------------------------------------// @@ -205,10 +208,8 @@ void ExtendFromPrimariesAction::process_primaries( PrimaryStateData const& pstate) const { auto primaries = pstate.primaries(); - detail::ProcessPrimariesExecutor execute{params.ptr(), - state.ptr(), - state.counters(), - primaries}; + detail::ProcessPrimariesExecutor execute{ + params.ptr(), state.ptr(), primaries}; return launch_action(*this, primaries.size(), params, state, execute); } diff --git a/src/celeritas/track/ExtendFromPrimariesAction.cu b/src/celeritas/track/ExtendFromPrimariesAction.cu index 4d37f4be24..3e1ea93f50 100644 --- a/src/celeritas/track/ExtendFromPrimariesAction.cu +++ b/src/celeritas/track/ExtendFromPrimariesAction.cu @@ -24,11 +24,9 @@ void ExtendFromPrimariesAction::process_primaries( PrimaryStateData const& pstate) const { auto primaries = pstate.primaries(); + auto counters = state.sync_get_counters(); detail::ProcessPrimariesExecutor execute_thread{ - params.ptr(), - state.ptr(), - state.counters(), - primaries}; + params.ptr(), state.ptr(), primaries}; static ActionLauncher const launch_kernel(*this); if (!primaries.empty()) { diff --git a/src/celeritas/track/ExtendFromSecondariesAction.cc b/src/celeritas/track/ExtendFromSecondariesAction.cc index d8595f7378..139b98aa99 100644 --- a/src/celeritas/track/ExtendFromSecondariesAction.cc +++ b/src/celeritas/track/ExtendFromSecondariesAction.cc @@ -56,7 +56,6 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, CoreState& core_state) const { TrackInitStateData& init = core_state.ref().init; - CoreStateCounters& counters = core_state.counters(); // Launch a kernel to identify which track slots are still alive and count // the number of surviving secondaries per track @@ -64,14 +63,14 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, // Remove all elements in the vacancy vector that were flagged as active // tracks, leaving the (sorted) indices of the empty slots - counters.num_vacancies - = detail::remove_if_alive(init.vacancies, core_state.stream_id()); + detail::remove_if_alive(init, core_state.stream_id()); // The exclusive prefix sum of the number of secondaries produced by each // track is used to get the start index in the vector of track initializers // for each thread. Starting at that index, each thread creates track // initializers from all surviving secondaries produced in its // interaction. + auto counters = core_state.sync_get_counters(); counters.num_secondaries = detail::exclusive_scan_counts( init.secondary_counts, core_state.stream_id()); @@ -96,6 +95,7 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, // Launch a kernel to create track initializers from secondaries counters.num_alive = core_state.size() - counters.num_vacancies; + core_state.sync_put_counters(counters); this->process_secondaries(core_params, core_state); } @@ -122,9 +122,7 @@ void ExtendFromSecondariesAction::process_secondaries( { //! \todo Wrap with a regular track executor but without remapping slots? detail::ProcessSecondariesExecutor execute{ - core_params.ptr(), - core_state.ptr(), - core_state.counters()}; + core_params.ptr(), core_state.ptr()}; launch_action(*this, core_params, core_state, execute); } diff --git a/src/celeritas/track/ExtendFromSecondariesAction.cu b/src/celeritas/track/ExtendFromSecondariesAction.cu index 4ee5dacafa..5d501d24ae 100644 --- a/src/celeritas/track/ExtendFromSecondariesAction.cu +++ b/src/celeritas/track/ExtendFromSecondariesAction.cu @@ -62,9 +62,7 @@ void ExtendFromSecondariesAction::process_secondaries( using Executor = detail::ProcessSecondariesExecutor; static ActionLauncher launch(*this, "process-secondaries"); launch(core_state, - Executor{core_params.ptr(), - core_state.ptr(), - core_state.counters()}); + Executor{core_params.ptr(), core_state.ptr()}); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/track/InitializeTracksAction.cc b/src/celeritas/track/InitializeTracksAction.cc index 93c5516c33..a14930fc62 100644 --- a/src/celeritas/track/InitializeTracksAction.cc +++ b/src/celeritas/track/InitializeTracksAction.cc @@ -55,7 +55,7 @@ template void InitializeTracksAction::step_impl(CoreParams const& core_params, CoreState& core_state) const { - auto& counters = core_state.counters(); + auto counters = core_state.sync_get_counters(); // The number of new tracks to initialize is the smaller of the number of // empty slots in the track vector and the number of track initializers @@ -72,7 +72,6 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, // Partition indices by whether tracks are charged or neutral detail::partition_initializers(core_params, core_state.ref().init, - counters, num_new_tracks, core_state.stream_id()); } @@ -87,6 +86,7 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, // Store number of active tracks at the start of the loop counters.num_active = core_state.size() - counters.num_vacancies; + core_state.sync_put_counters(counters); } //---------------------------------------------------------------------------// @@ -100,10 +100,8 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, CoreStateHost& core_state, size_type num_new_tracks) const { - detail::InitTracksExecutor execute{core_params.ptr(), - core_state.ptr(), - num_new_tracks, - core_state.counters()}; + detail::InitTracksExecutor execute{ + core_params.ptr(), core_state.ptr(), num_new_tracks}; return launch_action( *this, num_new_tracks, core_params, core_state, execute); } diff --git a/src/celeritas/track/InitializeTracksAction.cu b/src/celeritas/track/InitializeTracksAction.cu index 980a9f0585..da65606798 100644 --- a/src/celeritas/track/InitializeTracksAction.cu +++ b/src/celeritas/track/InitializeTracksAction.cu @@ -22,10 +22,8 @@ void InitializeTracksAction::step_impl(CoreParams const& params, CoreStateDevice& state, size_type num_new_tracks) const { - detail::InitTracksExecutor execute_thread{params.ptr(), - state.ptr(), - num_new_tracks, - state.counters()}; + detail::InitTracksExecutor execute_thread{ + params.ptr(), state.ptr(), num_new_tracks}; static ActionLauncher const launch_kernel(*this); launch_kernel(num_new_tracks, state.stream_id(), execute_thread); } diff --git a/src/celeritas/track/TrackInitData.hh b/src/celeritas/track/TrackInitData.hh index a06f19703a..32a17628ed 100644 --- a/src/celeritas/track/TrackInitData.hh +++ b/src/celeritas/track/TrackInitData.hh @@ -18,6 +18,7 @@ #include "celeritas/phys/ParticleData.hh" #include "celeritas/phys/Primary.hh" +#include "CoreStateCounters.hh" #include "SimData.hh" namespace celeritas @@ -92,7 +93,9 @@ struct TrackInitializer * created per event. * - \c secondary_counts stores the number of secondaries created by each track * (with one remainder at the end for storing the accumulated number of - * secondaries) + * secondaries). + * - \c counters stores the number of tracks with a given status and is updated + * during each step of the simulation of the event. */ template struct TrackInitStateData @@ -117,6 +120,11 @@ struct TrackInitStateData // CoreStateCounters) Items initializers; + // Maintain the counters here to allow GPU-resident computation with + // synchronization between host and device only at the end of a step or + // when explicitly requested, such as in the tests + Items counters; + //// METHODS //// //! Whether the data are assigned @@ -124,7 +132,8 @@ struct TrackInitStateData { return (indices.size() == vacancies.size() || indices.empty()) && secondary_counts.size() == vacancies.size() + 1 - && !track_counters.empty() && !initializers.empty(); + && !track_counters.empty() && !initializers.empty() + && !counters.empty(); } //! Assign from another set of data @@ -139,6 +148,7 @@ struct TrackInitStateData vacancies = other.vacancies; initializers = other.initializers; + counters = other.counters; return *this; } @@ -168,6 +178,7 @@ void resize(TrackInitStateData* data, // Allocate device data resize(&data->secondary_counts, size + 1); resize(&data->track_counters, params.max_events); + resize(&data->counters, 1); if (params.track_order == TrackOrder::init_charge) { resize(&data->indices, size); @@ -183,6 +194,9 @@ void resize(TrackInitStateData* data, // Reserve space for initializers resize(&data->initializers, params.capacity); + // Initialize the counters for the step to zero + fill(CoreStateCounters{}, &data->counters); + CELER_ENSURE(*data); } diff --git a/src/celeritas/track/detail/InitTracksExecutor.hh b/src/celeritas/track/detail/InitTracksExecutor.hh index 9f4c6ab919..804bc5b305 100644 --- a/src/celeritas/track/detail/InitTracksExecutor.hh +++ b/src/celeritas/track/detail/InitTracksExecutor.hh @@ -8,7 +8,6 @@ #include "corecel/Assert.hh" #include "corecel/Macros.hh" -#include "corecel/cont/Span.hh" #include "corecel/sys/ThreadId.hh" #include "celeritas/Types.hh" #include "celeritas/geo/GeoMaterialView.hh" @@ -47,7 +46,6 @@ struct InitTracksExecutor ParamsPtr params; StatePtr state; size_type num_init{}; - CoreStateCounters counters; //// FUNCTIONS //// @@ -68,7 +66,7 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const CELER_EXPECT(tid < num_init); auto const& data = state->init; - + auto counters = state->init.counters.data().get(); // Get the track initializer from the back of the vector. Since new // initializers are pushed to the back of the vector, these will be the // most recently added and therefore the ones that still might have a @@ -79,9 +77,9 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const // Get the index into the track initializer or parent track slot ID // array from the sorted indices return data.indices[TrackSlotId(index_before(num_init, tid))] - + counters.num_initializers - num_init; + + counters->num_initializers - num_init; } - return index_before(counters.num_initializers, tid); + return index_before(counters->num_initializers, tid); }())]; // View to the new track to be initialized @@ -95,11 +93,11 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const } // Get the vacancy from the back of the track state return data.vacancies[TrackSlotId( - index_before(counters.num_vacancies, tid))]; + index_before(counters->num_vacancies, tid))]; }()}; // Clear parent IDs if new primaries were added this step - if (counters.num_generated) + if (counters->num_generated) { init.geo.parent = {}; } diff --git a/src/celeritas/track/detail/ProcessPrimariesExecutor.hh b/src/celeritas/track/detail/ProcessPrimariesExecutor.hh index 4af48caf45..e8d856f733 100644 --- a/src/celeritas/track/detail/ProcessPrimariesExecutor.hh +++ b/src/celeritas/track/detail/ProcessPrimariesExecutor.hh @@ -38,7 +38,6 @@ struct ProcessPrimariesExecutor ParamsPtr params; StatePtr state; - CoreStateCounters counters; Span primaries; @@ -55,7 +54,8 @@ struct ProcessPrimariesExecutor CELER_FUNCTION void ProcessPrimariesExecutor::operator()(ThreadId tid) const { CELER_EXPECT(tid < primaries.size()); - CELER_EXPECT(primaries.size() <= counters.num_initializers + tid.get()); + auto counters = state->init.counters.data().get(); + CELER_EXPECT(primaries.size() <= counters->num_initializers + tid.get()); Primary const& primary = primaries[tid.unchecked_get()]; @@ -73,7 +73,7 @@ CELER_FUNCTION void ProcessPrimariesExecutor::operator()(ThreadId tid) const ti.particle.energy = primary.energy; // Store the initializer - size_type idx = counters.num_initializers - primaries.size() + tid.get(); + size_type idx = counters->num_initializers - primaries.size() + tid.get(); state->init.initializers[ItemId(idx)] = ti; } diff --git a/src/celeritas/track/detail/ProcessSecondariesExecutor.hh b/src/celeritas/track/detail/ProcessSecondariesExecutor.hh index d1b2f5bb64..f8c7d88025 100644 --- a/src/celeritas/track/detail/ProcessSecondariesExecutor.hh +++ b/src/celeritas/track/detail/ProcessSecondariesExecutor.hh @@ -38,7 +38,6 @@ struct ProcessSecondariesExecutor ParamsPtr params; StatePtr state; - CoreStateCounters counters; //// FUNCTIONS //// @@ -77,8 +76,9 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const // Offset in the vector of track initializers auto& data = state->init; - CELER_ASSERT(data.secondary_counts[tid] <= counters.num_secondaries); - size_type offset = counters.num_secondaries - data.secondary_counts[tid]; + auto counters = state->init.counters.data().get(); + CELER_ASSERT(data.secondary_counts[tid] <= counters->num_secondaries); + size_type offset = counters->num_secondaries - data.secondary_counts[tid]; // Save the parent ID since it will be overwritten if a secondary is // initialized in this slot @@ -129,10 +129,11 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const } else { - CELER_ASSERT(offset > 0 && offset <= counters.num_initializers); + CELER_ASSERT(offset > 0 + && offset <= counters->num_initializers); - if (offset <= min(counters.num_secondaries, - counters.num_vacancies) + if (offset <= min(counters->num_secondaries, + counters->num_vacancies) && (params->init.track_order != TrackOrder::init_charge || sim.status() == TrackStatus::alive)) { @@ -147,7 +148,7 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const // Store the track initializer data.initializers[ItemId{ - counters.num_initializers - offset}] + counters->num_initializers - offset}] = ti; --offset; diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.cc b/src/celeritas/track/detail/TrackInitAlgorithms.cc index 65d72854b8..a1c7230e4d 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.cc +++ b/src/celeritas/track/detail/TrackInitAlgorithms.cc @@ -24,14 +24,16 @@ namespace detail * * \return New size of the vacancy vector */ -size_type remove_if_alive( - StateCollection const& - vacancies, +void remove_if_alive( + TrackInitStateData const& init, StreamId) { - auto* start = vacancies.data().get(); - auto* stop = std::remove_if(start, start + vacancies.size(), LogicalNot{}); - return stop - start; + auto* start = init.vacancies.data().get(); + auto* counters = init.counters.data().get(); + auto* stop + = std::remove_if(start, start + init.vacancies.size(), LogicalNot{}); + counters->num_vacancies = stop - start; + return; } //---------------------------------------------------------------------------// @@ -80,15 +82,15 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const& params, TrackInitStateData const& init, - CoreStateCounters const& counters, size_type count, StreamId) { // Partition the indices based on the track initializer charge - auto start = init.indices.data().get(); - auto end = start + count; - auto stencil = init.initializers.data().get() + counters.num_initializers - - count; + auto* start = init.indices.data().get(); + auto* end = start + count; + auto* counters = init.counters.data().get(); + auto* stencil = init.initializers.data().get() + counters->num_initializers + - count; std::stable_partition( start, end, IsNeutralStencil{params.ptr(), stencil}); } diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.cu b/src/celeritas/track/detail/TrackInitAlgorithms.cu index 0c55ce11c4..5121e7432c 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.cu +++ b/src/celeritas/track/detail/TrackInitAlgorithms.cu @@ -71,35 +71,43 @@ struct NotNull * Remove all elements in the vacancy vector that were flagged as active * tracks. */ -size_type remove_if_alive( - StateCollection const& - vacancies, +void remove_if_alive( + TrackInitStateData const& init, StreamId stream_id) { ScopedProfiling profile_this{"remove-if-alive"}; #if CELER_USE_THRUST - auto start = device_pointer_cast(vacancies.data()); + auto& stream = device().stream(stream_id); + auto start = device_pointer_cast(init.vacancies.data()); + auto counters = device_pointer_cast(init.counters.data()); + auto host_counters + = ItemCopier{stream_id}(counters.get()); auto end = thrust::remove_if(thrust_execute_on(stream_id), start, - start + vacancies.size(), + start + init.vacancies.size(), LogicalNot{}); CELER_DEVICE_API_CALL(PeekAtLastError()); // New size of the vacancy vector - return end - start; + host_counters.num_vacancies = end - start; + Copier copy{{counters.get(), 1}, + stream_id}; + copy(MemSpace::host, {&host_counters, 1}); + stream.sync(); + return; #else auto& stream = device().stream(stream_id); - DeviceVector num_not_active{1, stream_id}; // Calling with nullptr causes the function to return the amount of working // space needed instead of invoking the kernel. size_t temp_storage_bytes = 0; - auto data = device_pointer_cast(vacancies.data()); + auto data = device_pointer_cast(init.vacancies.data()); + auto counters = device_pointer_cast(init.counters.data()); // HIP defines hipCUB functions as [[nodiscard]], but we defer error checks auto cub_error_code = cub::DeviceSelect::If(nullptr, temp_storage_bytes, data, - num_not_active.data(), - vacancies.size(), + &(counters->num_vacancies), + init.vacancies.size(), NotNull{}, stream.get()); CELER_DISCARD(cub_error_code); @@ -109,17 +117,13 @@ size_type remove_if_alive( cub_error_code = cub::DeviceSelect::If(temp_storage.data(), temp_storage_bytes, data, - num_not_active.data(), - vacancies.size(), + &(counters->num_vacancies), + init.vacancies.size(), NotNull{}, stream.get()); CELER_DISCARD(cub_error_code); CELER_DEVICE_API_CALL(PeekAtLastError()); - - auto result = ItemCopier{stream_id}(num_not_active.data()); - - stream.sync(); - return result; + return; #endif } @@ -196,7 +200,6 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const& params, TrackInitStateData const& init, - CoreStateCounters const& counters, size_type count, StreamId stream_id) { @@ -207,8 +210,10 @@ void partition_initializers( // Partition the indices based on the track initializer charge auto start = device_pointer_cast(init.indices.data()); auto end = start + count; + auto counters = device_pointer_cast(init.counters.data()); + auto cpucntrs = ItemCopier{stream_id}(counters.get()); auto stencil = static_cast(init.initializers.data()) - + counters.num_initializers - count; + + cpucntrs.num_initializers - count; thrust::stable_partition( thrust_execute_on(stream_id), start, @@ -226,8 +231,10 @@ void partition_initializers( // // The initializers array is large. Use stencil to point to the start where // this array is being used + auto counters = device_pointer_cast(init.counters.data()); + auto cpucntrs = ItemCopier{stream_id}(counters.get()); auto stencil = static_cast(init.initializers.data()) - + counters.num_initializers - count; + + cpucntrs.num_initializers - count; DeviceVector flags{count, stream_id}; # if CELER_CUB_HAS_TRANSFORM || CELER_HIPCUB_HAS_TRANSFORM // HIP defines hipCUB functions as [[nodiscard]], but we defer error checks @@ -254,16 +261,15 @@ void partition_initializers( // because the indices are always sequential from zero auto start = thrust::make_counting_iterator(0); auto data = device_pointer_cast(init.indices.data()); - // Allocate storage for the number of neutral tracks (unused by celeritas) - DeviceVector num_neutral{1, stream_id}; - auto cub_error_code = cub::DevicePartition::Flagged(nullptr, - temp_storage_bytes, - start, - flags.data(), - data, - num_neutral.data(), - count, - stream.get()); + auto cub_error_code + = cub::DevicePartition::Flagged(nullptr, + temp_storage_bytes, + start, + flags.data(), + data, + &(counters->num_neutral), + count, + stream.get()); CELER_DISCARD(cub_error_code); // Allocate temporary storage DeviceVector temp_storage(temp_storage_bytes, stream_id); @@ -273,7 +279,7 @@ void partition_initializers( start, flags.data(), data, - num_neutral.data(), + &(counters->num_neutral), count, stream.get()); CELER_DISCARD(cub_error_code); diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.hh b/src/celeritas/track/detail/TrackInitAlgorithms.hh index bdea2b3253..81d0b53201 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.hh +++ b/src/celeritas/track/detail/TrackInitAlgorithms.hh @@ -39,11 +39,10 @@ struct IsNeutralStencil //---------------------------------------------------------------------------// // Remove all elements in the vacancy vector that were flagged as alive -size_type remove_if_alive( - StateCollection const&, - StreamId); -size_type remove_if_alive( - StateCollection const&, +void remove_if_alive( + TrackInitStateData const&, StreamId); +void remove_if_alive( + TrackInitStateData const&, StreamId); //---------------------------------------------------------------------------// @@ -60,23 +59,20 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId); void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId); //---------------------------------------------------------------------------// -// INLINE DEFINITIONS +// DEVICE-DISABLED IMPLEMENTATION //---------------------------------------------------------------------------// #if !CELER_USE_DEVICE -inline size_type remove_if_alive( - StateCollection const&, - StreamId) +inline void remove_if_alive( + TrackInitStateData const&, StreamId) { CELER_NOT_CONFIGURED("CUDA or HIP"); } @@ -91,7 +87,6 @@ inline size_type exclusive_scan_counts( inline void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId) { diff --git a/src/corecel/data/Filler.cu b/src/corecel/data/Filler.cu index 37e419278b..4b4eeecb27 100644 --- a/src/corecel/data/Filler.cu +++ b/src/corecel/data/Filler.cu @@ -4,6 +4,8 @@ //---------------------------------------------------------------------------// //! \file corecel/data/Filler.cu //---------------------------------------------------------------------------// +#include "celeritas/track/CoreStateCounters.hh" + #include "Filler.device.t.hh" namespace celeritas @@ -13,5 +15,6 @@ template class Filler; template class Filler; template class Filler; template class Filler; +template class Filler; //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/test/celeritas/global/Stepper.test.cc b/test/celeritas/global/Stepper.test.cc index 43d7626a64..0980a02993 100644 --- a/test/celeritas/global/Stepper.test.cc +++ b/test/celeritas/global/Stepper.test.cc @@ -372,8 +372,8 @@ TEST_F(StepperOrderTest, warm_up) EXPECT_EQ(0, dumstate.action_order.size()); step.warm_up(); - EXPECT_EQ(0, step.state().counters().num_active); - EXPECT_EQ(0, step.state().counters().num_alive); + EXPECT_EQ(0, step.state().sync_get_counters().num_active); + EXPECT_EQ(0, step.state().sync_get_counters().num_alive); static char const* const expected_action_order[] = {"user_start", "user_pre", "user_post"}; diff --git a/test/celeritas/track/TrackInit.test.cc b/test/celeritas/track/TrackInit.test.cc index 29aab0b16f..8c3b8750dd 100644 --- a/test/celeritas/track/TrackInit.test.cc +++ b/test/celeritas/track/TrackInit.test.cc @@ -63,14 +63,14 @@ RunResult RunResult::from_state(CoreState& state) data = state.ref().init; // Store the IDs of the vacant track slots - for (auto tid : range(TrackSlotId{state.counters().num_vacancies})) + for (auto tid : range(TrackSlotId{state.sync_get_counters().num_vacancies})) { result.vacancies.push_back(id_to_int(data.vacancies[tid])); } // Store the track IDs of the initializers - for (auto init_id : - range(ItemId{state.counters().num_initializers})) + for (auto init_id : range(ItemId{ + state.sync_get_counters().num_initializers})) { auto const& init = data.initializers[init_id]; result.init_ids.push_back(id_to_int(init.sim.track_id)); @@ -226,15 +226,15 @@ TYPED_TEST_SUITE(TrackInitTest, MemspaceTypes, MemspaceTypeString); TYPED_TEST(TrackInitTest, add_more_primaries) { this->build_states(16); - EXPECT_EQ(0, this->state().counters().num_initializers); + EXPECT_EQ(0, this->state().sync_get_counters().num_initializers); auto primaries = this->make_primaries(22); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(22, this->state().counters().num_initializers); + EXPECT_EQ(22, this->state().sync_get_counters().num_initializers); primaries = this->make_primaries(32); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(54, this->state().counters().num_initializers); + EXPECT_EQ(54, this->state().sync_get_counters().num_initializers); } //! Test that we can add more primaries than the first allocation @@ -247,8 +247,7 @@ TYPED_TEST(TrackInitTest, extend_primaries) auto primaries = this->make_primaries(2); this->insert_primaries(this->state(), make_span(primaries)); RunResult::from_state(this->state()); - - EXPECT_EQ(0, this->state().counters().num_initializers); + EXPECT_EQ(0, this->state().sync_get_counters().num_initializers); } { // Now initialize after adding @@ -415,8 +414,9 @@ TYPED_TEST(TrackInitTest, primaries) // Find vacancies and create track initializers from secondaries extend_from_secondaries.step(*this->core(), this->state()); EXPECT_EQ(i * num_tracks / 2, - this->state().counters().num_initializers); - EXPECT_EQ(num_tracks / 2, this->state().counters().num_vacancies); + this->state().sync_get_counters().num_initializers); + EXPECT_EQ(num_tracks / 2, + this->state().sync_get_counters().num_vacancies); } // Check the results @@ -474,8 +474,8 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) // Create track initializers on device from primary particles auto primaries = this->make_primaries(num_primaries); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(num_primaries, this->state().counters().num_initializers); - + EXPECT_EQ(num_primaries, + this->state().sync_get_counters().num_initializers); auto apply_actions = [&actions, this] { for (auto const& ea_interface : actions) { @@ -497,7 +497,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) static int const expected_geo_parent_ids[] = {0, 2}; EXPECT_VEC_EQ(expected_geo_parent_ids, result.geo_parent_ids); - // init ids may not be deterministic, but can guarantee they are in the + // init IDs may not be deterministic, but can guarantee they are in the // range 8<=x<=12 as we create 4 tracks per iteration, 2 in reused // slots from their parent, 2 as new inits EXPECT_EQ(2, result.init_ids.size()); @@ -506,7 +506,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) std::end(result.init_ids), [i](int id) { return id >= 8 + i * 4 && id <= 11 + i * 4; })); - // Track ids may not be deterministic, so only validate size and + // Track IDs may not be deterministic, so only validate size and // range. (Remember that we create 4 new tracks per iteration, with 2 // slots reused EXPECT_EQ(num_tracks, result.track_ids.size()); @@ -518,9 +518,9 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) + (i + 1) * 4; })); - // Parent ids may not be deterministic, but all non-killed tracks are - // guaranteed to be primaries at every iteration. At end of first - // iteration, will still have some primary ids as these are not cleared + // Parent IDs may not be deterministic, but all non-killed tracks are + // guaranteed to be primaries at every iteration. At end of the first + // iteration, will still have some primary IDs as these are not cleared // until the next iteration for (size_type pidx : range(num_tracks)) { @@ -529,7 +529,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) << "iteration " << i; } } -} // namespace test +} //---------------------------------------------------------------------------// } // namespace test From a3860aac101b774c47b0cc674591aeaa8f699d5a Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 3 Dec 2025 17:44:52 -0500 Subject: [PATCH 50/60] Add optical track-offload option and integrate with constructor --- src/accel/TrackingManagerConstructor.hh | 133 +++++++++++++---------- src/accel/detail/IntegrationSingleton.cc | 23 ++++ src/accel/detail/IntegrationSingleton.hh | 7 +- 3 files changed, 102 insertions(+), 61 deletions(-) diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 31b0537e8e..377fabbe0f 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -9,80 +9,93 @@ #include #include +#include "accel/SetupOptions.hh" + +<<<<<<< HEAD class G4ParticleDefinition; +======= #include "corecel/cont/Span.hh" #include "accel/TrackOffloadInterface.hh" #include "detail/IntegrationSingleton.hh" + >>>>>>> 29c8c140d (Add optical track-offload option and integrate with constructor) +#include "corecel/cont/Span.hh" +#include "accel/TrackOffloadInterface.hh" -namespace celeritas -{ -class TrackOffloadInterface; -class SharedParams; -class TrackingManagerIntegration; +#include "detail/IntegrationSingleton.hh" -//---------------------------------------------------------------------------// -/*! - * Construct a Celeritas tracking manager that offloads EM tracks. - * - * This should be composed with your physics list after it is constructed, - * before the simulation begins. By default this uses the \c - * celeritas::TrackingManagerIntegration helper: - * \code - auto* physics_list = new FTFP_BERT; - physics_list->RegisterPhysics(new TrackingManagerConstructor{ - &TrackingManagerIntegration::Instance()}); - \endcode - * - * but for manual integration it can be constructed with a function to get a - * reference to the thread-local \c LocalTransporter from the Geant4 thread ID: - * \code - auto* physics_list = new FTFP_BERT; - physics_list->RegisterPhysics(new TrackingManagerConstructor{ - shared_params, [](int){ return &local_transporter; }); - \endcode - * - * \note If Celeritas is globally disabled, it will not add the track manager. - * If Celeritas is configured to "kill offload" mode (for testing maximum - * theoretical performance) then the tracking manager will be added but will - * not send the tracks to Celeritas: it will simply kill them. - */ -class TrackingManagerConstructor final : public G4VPhysicsConstructor + namespace celeritas { - public: - //!@{ - //! \name Type aliases - using LocalTransporterFromThread - = std::function; - using VecG4PD = SetupOptions::VecG4PD; - //!@} + class TrackOffloadInterface; + class SharedParams; + class TrackingManagerIntegration; - public: - // Construct name and mode - TrackingManagerConstructor(SharedParams const* shared, - LocalTransporterFromThread get_local); + //---------------------------------------------------------------------------// + /*! + * Construct a Celeritas tracking manager that offloads EM tracks. + * + * This should be composed with your physics list after it is constructed, + * before the simulation begins. By default this uses the \c + * celeritas::TrackingManagerIntegration helper: + * \code + auto* physics_list = new FTFP_BERT; + physics_list->RegisterPhysics(new TrackingManagerConstructor{ + &TrackingManagerIntegration::Instance()}); + \endcode + * + * but for manual integration it can be constructed with a function to get + a + * reference to the thread-local \c LocalTransporter from the Geant4 thread + ID: + * \code + auto* physics_list = new FTFP_BERT; + physics_list->RegisterPhysics(new TrackingManagerConstructor{ + shared_params, [](int){ return &local_transporter; }); + \endcode + * + * \note If Celeritas is globally disabled, it will not add the track + manager. + * If Celeritas is configured to "kill offload" mode (for testing maximum + * theoretical performance) then the tracking manager will be added but + will + * not send the tracks to Celeritas: it will simply kill them. + */ + class TrackingManagerConstructor final : public G4VPhysicsConstructor + { + public: + //!@{ + //! \name Type aliases + using LocalTransporterFromThread + = std::function; + using VecG4PD = SetupOptions::VecG4PD; + //!@} - // Construct from tracking manager integration - explicit TrackingManagerConstructor(TrackingManagerIntegration* tmi); + public: + // Construct name and mode + TrackingManagerConstructor(SharedParams const* shared, + LocalTransporterFromThread get_local); - //! Build list of particles to be offloaded - void ConstructParticle() override; + // Construct from tracking manager integration + explicit TrackingManagerConstructor(TrackingManagerIntegration* tmi); - // Build and attach tracking manager - void ConstructProcess() override; + //! Build list of particles to be offloaded + void ConstructParticle() override; - //// ACCESSORS //// + // Build and attach tracking manager + void ConstructProcess() override; - //! Get the shared params associated with this TM - SharedParams const* shared_params() const { return shared_; } + //// ACCESSORS //// - // Get the track transporter associated with the current thread ID - TrackOffloadInterface* get_local_transporter() const; + //! Get the shared params associated with this TM + SharedParams const* shared_params() const { return shared_; } - private: - SharedParams const* shared_{nullptr}; - LocalTransporterFromThread get_local_{}; - VecG4PD offload_particles_; -}; + // Get the track transporter associated with the current thread ID + TrackOffloadInterface* get_local_transporter() const; -//---------------------------------------------------------------------------// + private: + SharedParams const* shared_{nullptr}; + LocalTransporterFromThread get_local_{}; + VecG4PD offload_particles_; + }; + + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 10f7ad0d3a..1761da2354 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -138,6 +138,24 @@ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() return *lt; } +//---------------------------------------------------------------------------// +/*! + * Static THREAD-LOCAL Celeritas optical state data. + */ +LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() +{ + auto& offload = IntegrationSingleton::local_offload_ptr(); + if (!offload) + { + offload = std::make_unique(); + } + auto* lt = dynamic_cast(offload.get()); + CELER_VALIDATE(lt, + << "Cannot access LocalOpticalTrtackOffload when " + "LocalTransporter is being used"); + return *lt; +} + //---------------------------------------------------------------------------// /*! * Access the thread-local offload interface. @@ -344,6 +362,11 @@ bool IntegrationSingleton::optical_offload() const options_.optical->generator); } +bool IntegrationSingleton::optical_track_offload() const +{ + return options_.optical && options_.optical->offload_tracks; +} + //---------------------------------------------------------------------------// /*! * Whether the local optical track offload is used. diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index e8d89617b9..e604981cab 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -58,6 +58,12 @@ class IntegrationSingleton // Static Thread-local Celeritas optical track offload static LocalOpticalTrackOffload& local_optical_track_offload(); + // Static Thread-local Celeritas optical track offload + static LocalOpticalTrackOffload& local_optical_track_offload(); + + // Thread-local offload object for particles handled by the tracking + // manager + TrackOffloadInterface& local_track_offload(); // Access thread-local track offload interface TrackOffloadInterface& local_track_offload(); //// ACCESSORS //// @@ -117,7 +123,6 @@ class IntegrationSingleton // Whether offloading optical distribution data is enabled bool optical_offload() const; - // Whether offloading optical track is enabled bool optical_track_offload() const; // Set up or update logging if the run manager is enabled From b127eb99439d697e77cf7ca3c4111c2fec65ca97 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Mon, 8 Dec 2025 17:14:49 -0500 Subject: [PATCH 51/60] WIP --- src/celeritas/setup/Problem.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index cedc6406ef..04061bdf8f 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -747,7 +747,8 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) { CELER_VALIDATE(imported.optical_models.empty(), << "optical physics models were imported but no " - "optical capacity was set. Either define optical " + "optical capacity was set. Either define " + "optical " "tracking loop parameters, or ignore optical " "physics"); } From 1d0f66f3c195c5990d42336f306a7fcab9518785 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Tue, 16 Dec 2025 09:55:58 -0500 Subject: [PATCH 52/60] Add OpticalTrackOffload option in problem --- src/celeritas/inp/Events.hh | 11 +++++++++-- src/celeritas/setup/Problem.cc | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index fca6538de9..198e6b6f8c 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -134,14 +134,21 @@ struct OpticalTrackOffload struct OpticalDirectGenerator { }; +//---------------------------------------------------------------------------// +/*! + * Generate optical photons track. + */ +struct OpticalTrackOffload +{ +}; //---------------------------------------------------------------------------// //! Mechanism for generating optical photons using OpticalGenerator = std::variant; + OpticalDirectGenerator, + OpticalTrackOffload>; //---------------------------------------------------------------------------// /*! diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 04061bdf8f..cedc6406ef 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -747,8 +747,7 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) { CELER_VALIDATE(imported.optical_models.empty(), << "optical physics models were imported but no " - "optical capacity was set. Either define " - "optical " + "optical capacity was set. Either define optical " "tracking loop parameters, or ignore optical " "physics"); } From 4a8cabace772059325ebf1c3bf5e41a83f4db16e Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Wed, 17 Dec 2025 14:42:11 -0500 Subject: [PATCH 53/60] Flush and Push logs --- src/accel/LocalOpticalTrackOffload.cc | 13 ++++- src/accel/UserActionIntegration.cc | 21 ++++++- src/accel/detail/IntegrationSingleton.cc | 2 + src/cmake_install.cmake | 74 ++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/cmake_install.cmake diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index 8bdef16cff..ff446e71d9 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -92,6 +92,15 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) CELER_EXPECT(id >= 0); event_id_ = id_cast(id); + + if (!(G4Threading::IsMultithreadedApplication() + && G4MTRunManager::SeedOncePerCommunication())) + { + // Since Geant4 schedules events dynamically, reseed the Celeritas RNGs + // using the Geant4 event ID for reproducibility. This guarantees that + // an event can be reproduced given the event ID. + state_->reseed(transport_->params()->rng(), id_cast(id)); + } } //---------------------------------------------------------------------------// @@ -100,6 +109,7 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) */ void LocalOpticalTrackOffload::Push(G4Track& g4track) { + CELER_LOG(info) << "Transport pointer: " << transport_; CELER_EXPECT(*this); ++num_pushed_; @@ -128,7 +138,8 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) void LocalOpticalTrackOffload::Flush() { CELER_EXPECT(*this); - + CELER_LOG(info) << "Flushing " << buffer_.size() + << " optical tracks to Celeritas"; if (buffer_.empty()) { return; diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index 7d94803614..03f7135fb8 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -7,6 +7,7 @@ #include "UserActionIntegration.hh" #include +#include #include #include @@ -59,6 +60,19 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) if (mode == SharedParams::Mode::disabled) return; + // Optical track offload path + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + CELER_LOG(debug) << "Entering optical offload in user action"; + auto& opt_local + = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt_local) + { + opt_local.Push(*track); + track->SetTrackStatus(fStopAndKill); + return; + } + } auto const& particles = singleton.shared_params().OffloadParticles(); if (std::find(particles.begin(), particles.end(), track->GetDefinition()) != particles.end()) @@ -92,7 +106,12 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) CELER_TRY_HANDLE( local.Flush(), ExceptionConverter("celer.event.flush", &singleton.shared_params())); - + auto& opt = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt && opt.GetBufferSize() > 0) + { + CELER_LOG(info) << "EOE flushing optical tracks"; + opt.Flush(); + } // Record the time for this event singleton.shared_params().timer()->RecordEventTime(get_event_time_()); } diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 1761da2354..135f0a549d 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -144,9 +144,11 @@ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() */ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() { + CELER_LOG(info) << "Entered integration singleton"; auto& offload = IntegrationSingleton::local_offload_ptr(); if (!offload) { + CELER_LOG(info) << "Optical offload is not empty"; offload = std::make_unique(); } auto* lt = dynamic_cast(offload.get()); diff --git a/src/cmake_install.cmake b/src/cmake_install.cmake new file mode 100644 index 0000000000..e58d66a914 --- /dev/null +++ b/src/cmake_install.cmake @@ -0,0 +1,74 @@ +# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "Release") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/corecel/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/geocel/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/orange/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/celeritas/cmake_install.cmake") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for the subdirectory. + include("/Users/r1i/Desktop/project/forked/celeritas/src/accel/cmake_install.cmake") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "development" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/src/" FILES_MATCHING REGEX ".*\\.hh?$") +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() From a3bdaa3a65b1ccddc56aa69df04837f0a2d142c6 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Thu, 18 Dec 2025 14:43:20 -0500 Subject: [PATCH 54/60] fixes for floating precision --- src/cmake_install.cmake | 74 ----------------------------------------- 1 file changed, 74 deletions(-) delete mode 100644 src/cmake_install.cmake diff --git a/src/cmake_install.cmake b/src/cmake_install.cmake deleted file mode 100644 index e58d66a914..0000000000 --- a/src/cmake_install.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# Install script for directory: /Users/r1i/Desktop/project/forked/celeritas/src - -# Set the install prefix -if(NOT DEFINED CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX "/usr/local") -endif() -string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") - -# Set the install configuration name. -if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) - if(BUILD_TYPE) - string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" - CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") - else() - set(CMAKE_INSTALL_CONFIG_NAME "Release") - endif() - message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") -endif() - -# Set the component getting installed. -if(NOT CMAKE_INSTALL_COMPONENT) - if(COMPONENT) - message(STATUS "Install component: \"${COMPONENT}\"") - set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") - else() - set(CMAKE_INSTALL_COMPONENT) - endif() -endif() - -# Is this installation the result of a crosscompile? -if(NOT DEFINED CMAKE_CROSSCOMPILING) - set(CMAKE_CROSSCOMPILING "FALSE") -endif() - -# Set path to fallback-tool for dependency-resolution. -if(NOT DEFINED CMAKE_OBJDUMP) - set(CMAKE_OBJDUMP "/usr/bin/objdump") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/corecel/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/geocel/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/orange/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/celeritas/cmake_install.cmake") -endif() - -if(NOT CMAKE_INSTALL_LOCAL_ONLY) - # Include the install script for the subdirectory. - include("/Users/r1i/Desktop/project/forked/celeritas/src/accel/cmake_install.cmake") -endif() - -if(CMAKE_INSTALL_COMPONENT STREQUAL "development" OR NOT CMAKE_INSTALL_COMPONENT) - file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY MESSAGE_LAZY FILES "/Users/r1i/Desktop/project/forked/celeritas/src/" FILES_MATCHING REGEX ".*\\.hh?$") -endif() - -string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT - "${CMAKE_INSTALL_MANIFEST_FILES}") -if(CMAKE_INSTALL_LOCAL_ONLY) - file(WRITE "/Users/r1i/Desktop/project/forked/celeritas/src/install_local_manifest.txt" - "${CMAKE_INSTALL_MANIFEST_CONTENT}") -endif() From 69cd0eba7bda1e29e4e467339a7cffe85e71efff Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Fri, 19 Dec 2025 15:06:08 -0500 Subject: [PATCH 55/60] cleanup --- src/accel/LocalOpticalTrackOffload.cc | 10 +- src/accel/SetupOptions.hh | 2 + src/accel/TrackingManagerConstructor.hh | 138 +++++++++++------------ src/accel/detail/IntegrationSingleton.cc | 29 ----- src/accel/detail/IntegrationSingleton.hh | 4 +- 5 files changed, 73 insertions(+), 110 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index ff446e71d9..72396c3c1b 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -92,12 +92,13 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) CELER_EXPECT(id >= 0); event_id_ = id_cast(id); - if (!(G4Threading::IsMultithreadedApplication() && G4MTRunManager::SeedOncePerCommunication())) { - // Since Geant4 schedules events dynamically, reseed the Celeritas RNGs - // using the Geant4 event ID for reproducibility. This guarantees that + // Since Geant4 schedules events dynamically, reseed the Celeritas + // RNGs + // using the Geant4 event ID for reproducibility. This guarantees + // that // an event can be reproduced given the event ID. state_->reseed(transport_->params()->rng(), id_cast(id)); } @@ -138,8 +139,7 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) void LocalOpticalTrackOffload::Flush() { CELER_EXPECT(*this); - CELER_LOG(info) << "Flushing " << buffer_.size() - << " optical tracks to Celeritas"; + if (buffer_.empty()) { return; diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index a3d57cb293..0aa25af525 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -128,6 +128,8 @@ struct OpticalSetupOptions inp::OpticalGenerator generator; //! Limits for the optical stepping loop inp::OpticalTrackingLimits limits; + //! Optical photon track offload option + bool offload_tracks{false}; }; //---------------------------------------------------------------------------// diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 377fabbe0f..cd6ac1d801 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -9,93 +9,85 @@ #include #include -#include "accel/SetupOptions.hh" - -<<<<<<< HEAD class G4ParticleDefinition; -======= -#include "corecel/cont/Span.hh" -#include "accel/TrackOffloadInterface.hh" - -#include "detail/IntegrationSingleton.hh" - >>>>>>> 29c8c140d (Add optical track-offload option and integrate with constructor) #include "corecel/cont/Span.hh" +#include "accel/SetupOptions.hh" #include "accel/TrackOffloadInterface.hh" #include "detail/IntegrationSingleton.hh" - namespace celeritas +namespace celeritas { - class TrackOffloadInterface; - class SharedParams; - class TrackingManagerIntegration; +class TrackOffloadInterface; +class SharedParams; +class TrackingManagerIntegration; - //---------------------------------------------------------------------------// - /*! - * Construct a Celeritas tracking manager that offloads EM tracks. - * - * This should be composed with your physics list after it is constructed, - * before the simulation begins. By default this uses the \c - * celeritas::TrackingManagerIntegration helper: - * \code - auto* physics_list = new FTFP_BERT; - physics_list->RegisterPhysics(new TrackingManagerConstructor{ - &TrackingManagerIntegration::Instance()}); - \endcode - * - * but for manual integration it can be constructed with a function to get - a - * reference to the thread-local \c LocalTransporter from the Geant4 thread - ID: - * \code - auto* physics_list = new FTFP_BERT; - physics_list->RegisterPhysics(new TrackingManagerConstructor{ - shared_params, [](int){ return &local_transporter; }); - \endcode - * - * \note If Celeritas is globally disabled, it will not add the track - manager. - * If Celeritas is configured to "kill offload" mode (for testing maximum - * theoretical performance) then the tracking manager will be added but - will - * not send the tracks to Celeritas: it will simply kill them. - */ - class TrackingManagerConstructor final : public G4VPhysicsConstructor - { - public: - //!@{ - //! \name Type aliases - using LocalTransporterFromThread - = std::function; - using VecG4PD = SetupOptions::VecG4PD; - //!@} +//---------------------------------------------------------------------------// +/*! + * Construct a Celeritas tracking manager that offloads EM tracks. + * + * This should be composed with your physics list after it is constructed, + * before the simulation begins. By default this uses the \c + * celeritas::TrackingManagerIntegration helper: + * \code + auto* physics_list = new FTFP_BERT; + physics_list->RegisterPhysics(new TrackingManagerConstructor{ + &TrackingManagerIntegration::Instance()}); + \endcode + * + * but for manual integration it can be constructed with a function to get + a + * reference to the thread-local \c LocalTransporter from the Geant4 thread + ID: + * \code + auto* physics_list = new FTFP_BERT; + physics_list->RegisterPhysics(new TrackingManagerConstructor{ + shared_params, [](int){ return &local_transporter; }); + \endcode + * + * \note If Celeritas is globally disabled, it will not add the track + manager. + * If Celeritas is configured to "kill offload" mode (for testing maximum + * theoretical performance) then the tracking manager will be added but + will + * not send the tracks to Celeritas: it will simply kill them. + */ +class TrackingManagerConstructor final : public G4VPhysicsConstructor +{ + public: + //!@{ + //! \name Type aliases + using LocalTransporterFromThread + = std::function; + using VecG4PD = SetupOptions::VecG4PD; + //!@} - public: - // Construct name and mode - TrackingManagerConstructor(SharedParams const* shared, - LocalTransporterFromThread get_local); + public: + // Construct name and mode + TrackingManagerConstructor(SharedParams const* shared, + LocalTransporterFromThread get_local); - // Construct from tracking manager integration - explicit TrackingManagerConstructor(TrackingManagerIntegration* tmi); + // Construct from tracking manager integration + explicit TrackingManagerConstructor(TrackingManagerIntegration* tmi); - //! Build list of particles to be offloaded - void ConstructParticle() override; + //! Build list of particles to be offloaded + void ConstructParticle() override; - // Build and attach tracking manager - void ConstructProcess() override; + // Build and attach tracking manager + void ConstructProcess() override; - //// ACCESSORS //// + //// ACCESSORS //// - //! Get the shared params associated with this TM - SharedParams const* shared_params() const { return shared_; } + //! Get the shared params associated with this TM + SharedParams const* shared_params() const { return shared_; } - // Get the track transporter associated with the current thread ID - TrackOffloadInterface* get_local_transporter() const; + // Get the track transporter associated with the current thread ID + TrackOffloadInterface* get_local_transporter() const; - private: - SharedParams const* shared_{nullptr}; - LocalTransporterFromThread get_local_{}; - VecG4PD offload_particles_; - }; + private: + SharedParams const* shared_{nullptr}; + LocalTransporterFromThread get_local_{}; + VecG4PD offload_particles_; +}; - //---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 135f0a549d..3c5975126c 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -138,26 +138,6 @@ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() return *lt; } -//---------------------------------------------------------------------------// -/*! - * Static THREAD-LOCAL Celeritas optical state data. - */ -LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() -{ - CELER_LOG(info) << "Entered integration singleton"; - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) - { - CELER_LOG(info) << "Optical offload is not empty"; - offload = std::make_unique(); - } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalOpticalTrtackOffload when " - "LocalTransporter is being used"); - return *lt; -} - //---------------------------------------------------------------------------// /*! * Access the thread-local offload interface. @@ -369,15 +349,6 @@ bool IntegrationSingleton::optical_track_offload() const return options_.optical && options_.optical->offload_tracks; } -//---------------------------------------------------------------------------// -/*! - * Whether the local optical track offload is used. - */ -bool IntegrationSingleton::optical_track_offload() const -{ - return options_.optical && options_.optical->offload_tracks; -} - //---------------------------------------------------------------------------// /*! * Create or update the number of threads for the logger. diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index e604981cab..31bdc02d7a 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -61,9 +61,6 @@ class IntegrationSingleton // Static Thread-local Celeritas optical track offload static LocalOpticalTrackOffload& local_optical_track_offload(); - // Thread-local offload object for particles handled by the tracking - // manager - TrackOffloadInterface& local_track_offload(); // Access thread-local track offload interface TrackOffloadInterface& local_track_offload(); //// ACCESSORS //// @@ -123,6 +120,7 @@ class IntegrationSingleton // Whether offloading optical distribution data is enabled bool optical_offload() const; + // Whether offloading optical track is enabled bool optical_track_offload() const; // Set up or update logging if the run manager is enabled From 11404cc4b7e4786aa660fc8dfb650888013a0084 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Sat, 24 Jan 2026 19:11:25 -0500 Subject: [PATCH 56/60] Added EndOfRunAction --- test/accel/UserActionIntegration.test.cc | 36 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index d1aa0db800..3978991592 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -297,7 +297,12 @@ TEST_F(LarSphereOpticalOffload, run) //---------------------------------------------------------------------------// class LSOOTrackingAction final : public G4UserTrackingAction { + public: void PreUserTrackingAction(G4Track const* track) final; + std::size_t num_pushed() const { return num_pushed_; } + + private: + std::size_t num_pushed_{0}; }; //---------------------------------------------------------------------------// @@ -309,10 +314,16 @@ class LarSphereOpticalTrackOffload : public LarSphere public: PhysicsInput make_physics_input() const override; SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; UPTrackAction make_tracking_action() override { - return std::make_unique(); + auto act = std::make_unique(); + tracking_.push_back(act.get()); + return act; } + + private: + std::vector tracking_; }; //---------------------------------------------------------------------------// @@ -382,6 +393,7 @@ void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) = detail::IntegrationSingleton::local_optical_track_offload(); if (opt_local) { + ++num_pushed_; auto* mutable_track = const_cast(track); opt_local.Push(*mutable_track); mutable_track->SetTrackStatus(fStopAndKill); @@ -390,11 +402,31 @@ void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) } } +//---------------------------------------------------------------------------// +/*! + * Test that the optical track offload was successful. + */ +void LarSphereOpticalTrackOffload::EndOfRunAction(G4Run const* run) +{ + if (G4Threading::IsMasterThread()) + { + std::size_t pushed{0}; + for (auto* ta : tracking_) + { + pushed += ta->num_pushed(); + } + EXPECT_EQ(pushed, 150459); + } + + // Continue cleanup and other checks at end of run + LarSphere::EndOfRunAction(run); +} + //---------------------------------------------------------------------------// TEST_F(LarSphereOpticalTrackOffload, run) { auto& rm = this->run_manager(); - rm.SetNumberOfThreads(1); + rm.SetNumberOfThreads(2); UAI::Instance().SetOptions(this->make_setup_options()); rm.Initialize(); From 965c34525dd27e67931bce8408e6921e6d2b8c82 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Sat, 24 Jan 2026 19:15:55 -0500 Subject: [PATCH 57/60] Integrate optical problem input changes --- src/accel/LocalOpticalTrackOffload.cc | 12 ++++++------ src/accel/TrackingManagerConstructor.cc | 2 +- src/celeritas/inp/Events.hh | 3 ++- src/celeritas/setup/Problem.cc | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index 72396c3c1b..b29a0ffd2f 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -32,17 +32,17 @@ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, "Celeritas " "offloading is disabled"); - // Check the thread ID and MT model - validate_geant_threading(params.Params()->max_streams()); - // Save a pointer to the optical transporter - transport_ = params.optical_transporter(); + transport_ = params.optical_problem_loaded().transporter; CELER_ASSERT(transport_); CELER_ASSERT(transport_->params()); auto const& optical_params = *transport_->params(); + // Check the thread ID and MT model + validate_geant_threading(optical_params.max_streams()); + CELER_EXPECT(options.optical); auto const& capacity = options.optical->capacity; auto_flush_ = capacity.tracks; @@ -63,10 +63,10 @@ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, } // Allocate auxiliary data - if (params.Params()->aux_reg()) + if (optical_params.aux_reg()) { state_->aux() = std::make_shared( - *params.Params()->aux_reg(), memspace, stream_id, capacity.tracks); + *optical_params.aux_reg(), memspace, stream_id, capacity.tracks); } CELER_ENSURE(*this); diff --git a/src/accel/TrackingManagerConstructor.cc b/src/accel/TrackingManagerConstructor.cc index e6a9e49022..8d6337fbe0 100644 --- a/src/accel/TrackingManagerConstructor.cc +++ b/src/accel/TrackingManagerConstructor.cc @@ -108,7 +108,7 @@ void TrackingManagerConstructor::ConstructProcess() shared_ && get_local_, << R"(invalid null inputs given to TrackingManagerConstructor)"); - LocalTransporter* transporter{nullptr}; + TrackOffloadInterface* transporter{nullptr}; if (G4Threading::IsWorkerThread() || !G4Threading::IsMultithreadedApplication()) diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index 198e6b6f8c..65c0d4cf51 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -134,9 +134,10 @@ struct OpticalTrackOffload struct OpticalDirectGenerator { }; + //---------------------------------------------------------------------------// /*! - * Generate optical photons track. + * Generate with individual optical photon tracks copied from CPU. */ struct OpticalTrackOffload { diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index cedc6406ef..a5dc5c081c 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -839,6 +839,7 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) [&](inp::OpticalDirectGenerator) { optical::DirectGeneratorAction::make_and_insert(*params); }, + [&](inp::OpticalTrackOffload) {}, }, p.generator); From 50efd878da5182de267c8a01b6ebe3de314ce577 Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Sat, 24 Jan 2026 19:16:49 -0500 Subject: [PATCH 58/60] Address comments --- src/accel/detail/IntegrationSingleton.cc | 9 ++++----- test/accel/CMakeLists.txt | 7 +++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 3c5975126c..5e98525bec 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -133,7 +133,7 @@ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() } auto* lt = dynamic_cast(offload.get()); CELER_VALIDATE(lt, - << "Cannot access LocalOpticalTrtackOffload when " + << "Cannot access LocalOpticalTrackOffload when " "LocalTransporter is being used"); return *lt; } @@ -144,10 +144,9 @@ LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() */ LocalOffloadInterface& IntegrationSingleton::local_offload() { - CELER_VALIDATE(!(this->optical_track_offload() && this->optical_offload()), - << "Cannot enable both optical generator offload and " - "optical " - "track offload at the same time"); + CELER_VALIDATE( + !(this->optical_track_offload() && this->optical_offload()), + << R"(Cannot enable both optical generator offload and optical track offload at the same time)"); if (this->optical_track_offload()) { diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index ff62bfa58a..cabbf8b7ac 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -151,4 +151,11 @@ celeritas_add_integration_tests( RMTYPE ${_optical_rm_type} ) +# Test with optical track offloading +celeritas_add_integration_tests( + UserActionIntegration LarSphereOpticalTrackOffload run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + #-----------------------------------------------------------------------------# From de37ae47aaf6a223a1b2f9100b6abedd51fd2d1a Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Thu, 29 Jan 2026 14:18:33 -0500 Subject: [PATCH 59/60] Add optical track offload interception --- src/accel/LocalOpticalTrackOffload.cc | 11 +- src/accel/LocalOpticalTrackOffload.hh | 6 +- src/accel/SetupOptions.hh | 4 +- src/accel/UserActionIntegration.cc | 19 +++- src/accel/detail/IntegrationSingleton.cc | 75 ++++++------- src/accel/detail/IntegrationSingleton.hh | 1 + src/celeritas/inp/Events.hh | 2 +- src/celeritas/setup/Problem.cc | 42 +++---- test/accel/CMakeLists.txt | 2 +- test/accel/UserActionIntegration.test.cc | 134 +++++++++++++++++++++++ 10 files changed, 219 insertions(+), 77 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index b29a0ffd2f..ffe645afbc 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -6,11 +6,15 @@ //---------------------------------------------------------------------------// #include "LocalOpticalTrackOffload.hh" +#include #include #include +#include "corecel/Assert.hh" #include "corecel/sys/ScopedProfiling.hh" #include "geocel/GeantUtils.hh" +#include "geocel/g4/Convert.hh" +#include "celeritas/ext/GeantUnits.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/optical/CoreParams.hh" #include "celeritas/optical/Transporter.hh" @@ -110,7 +114,6 @@ void LocalOpticalTrackOffload::InitializeEvent(int id) */ void LocalOpticalTrackOffload::Push(G4Track& g4track) { - CELER_LOG(info) << "Transport pointer: " << transport_; CELER_EXPECT(*this); ++num_pushed_; @@ -124,9 +127,8 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) ScopedProfiling profile_this{"push"}; buffer_.push_back(init); - pending_tracks_++; - if (pending_tracks_ >= auto_flush_) + if (buffer_.size() >= auto_flush_) { this->Flush(); } @@ -153,7 +155,6 @@ void LocalOpticalTrackOffload::Flush() // state_->insert_primaries(make_span(buffer_)); buffer_.clear(); - pending_tracks_ = 0; } //---------------------------------------------------------------------------// @@ -174,7 +175,7 @@ void LocalOpticalTrackOffload::Finalize() CELER_EXPECT(*this); CELER_VALIDATE(buffer_.empty(), - << pending_tracks_ << " optical tracks were not flushed"); + << buffer_.size() << " optical tracks were not flushed"); CELER_LOG(info) << "Finalizing Celeritas after " << num_pushed_ << " optical tracks pushed (over " << num_flushed_ diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index 6200d3175e..297d575596 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -61,7 +61,7 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface bool Initialized() const final { return static_cast(transport_); } // Number of buffered tracks - size_type GetBufferSize() const final { return pending_tracks_; } + size_type GetBufferSize() const final { return buffer_.size(); } // Get accumulated action times MapStrDbl GetActionTime() const final; @@ -69,6 +69,8 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface // Offload optical distribution track to Celeritas void Push(G4Track&) final; + // Number of optical tracks pushed to offload + size_type num_pushed() const { return num_pushed_; } private: // Transport pending optical tracks @@ -89,8 +91,6 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface // Accumulated number of tracks pushed over flushes size_type num_flushed_{}; - size_type pending_tracks_{}; - // Current event ID for obtaining it UniqueEventId event_id_; }; diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 0aa25af525..4f00743e67 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -128,8 +128,8 @@ struct OpticalSetupOptions inp::OpticalGenerator generator; //! Limits for the optical stepping loop inp::OpticalTrackingLimits limits; - //! Optical photon track offload option - bool offload_tracks{false}; + + bool offload_optical_tracks = false; }; //---------------------------------------------------------------------------// diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index 03f7135fb8..67e5b1adcd 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -14,10 +14,10 @@ #include "corecel/sys/Stopwatch.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" +#include "G4OpticalPhoton.hh" +#include "TimeOutput.hh" // IWYU pragma: keep #include "detail/IntegrationSingleton.hh" - namespace celeritas { //---------------------------------------------------------------------------// @@ -88,6 +88,21 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) // Either "pushed" or we're in kill_offload mode track->SetTrackStatus(fStopAndKill); + return; + } + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + if (mode == SharedParams::Mode::enabled) + { + // Celeritas is transporting this optical photon + CELER_TRY_HANDLE(detail::IntegrationSingleton::instance() + .local_track_offload() + .Push(*track), + ExceptionConverter("celer.track.push", + +&singleton.shared_params())); + } + // Kill the optical photon (offloaded or not) + track->SetTrackStatus(fStopAndKill); } } diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 5e98525bec..50d2bf8262 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include "IntegrationSingleton.hh" +#include #include #include @@ -14,6 +15,7 @@ #include "corecel/io/Logger.hh" #include "corecel/sys/ScopedMpiInit.hh" #include "geocel/GeantUtils.hh" +#include "accel/LocalOpticalTrackOffload.hh" #include "LoggerImpl.hh" #include "../ExceptionConverter.hh" @@ -89,53 +91,40 @@ TrackOffloadInterface& IntegrationSingleton::local_track_offload() LocalTransporter& IntegrationSingleton::local_transporter() { - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) - { - offload = std::make_unique(); - } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalTransporter when " - "LocalOpticalGenOffload is being used"); - return *lt; -} + static G4ThreadLocal UPOffload offload; -//---------------------------------------------------------------------------// -/*! - * Static THREAD-LOCAL Celeritas optical state data. - */ -LocalOpticalGenOffload& IntegrationSingleton::local_optical_offload() -{ - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) + if (CELER_UNLIKELY(!offload)) { - offload = std::make_unique(); - } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalOpticalGenOffload when " - "LocalTransporter is being used"); - return *lt; -} + if (!options_) + { + // Cannot construct offload before options are set + CELER_LOG_LOCAL(error) + << R"(cannot access offload before options are set)"; + } + if (options_.optical + && std::holds_alternative( + options_.optical->generator)) + { + CELER_LOG(info) << "optical gen offloading enabled"; + offload = std::make_unique(); + } + else if (options_.optical + && std::holds_alternative( + options_.optical->generator)) -//---------------------------------------------------------------------------// -/*! - * Static thread-local Celeritas optical track. - */ -LocalOpticalTrackOffload& IntegrationSingleton::local_optical_track_offload() -{ - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) - { - CELER_LOG(info) << "Optical track offload initialised"; - offload = std::make_unique(); + { + CELER_LOG(info) << "optical track offloading enabled"; + offload = std::make_unique(); + } + else + { + // TODO: if offloading direct optical tracks, return optical + // offload + offload = std::make_unique(); + } } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalOpticalTrackOffload when " - "LocalTransporter is being used"); - return *lt; + + return *offload; } //---------------------------------------------------------------------------// diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index 31bdc02d7a..debda2ca1f 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -12,6 +12,7 @@ #include "accel/LocalOpticalTrackOffload.hh" #include "../LocalOpticalGenOffload.hh" +#include "../LocalOpticalTrackOffload.hh" #include "../LocalTransporter.hh" #include "../SetupOptions.hh" #include "../SharedParams.hh" diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index 65c0d4cf51..f295312e09 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -137,7 +137,7 @@ struct OpticalDirectGenerator //---------------------------------------------------------------------------// /*! - * Generate with individual optical photon tracks copied from CPU. + * Offload optical photon tracks from Geant4 to Celeritas. */ struct OpticalTrackOffload { diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index a5dc5c081c..d11aee6266 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -822,26 +822,28 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) CELER_ASSERT(params); // Construct the optical generator - std::visit(Overload{ - [&](inp::OpticalEmGenerator) { - CELER_VALIDATE(false, - << "OpticalEmGenerator cannot be used " - "with only optical physics enabled"); - }, - [&](inp::OpticalOffloadGenerator) { - optical::GeneratorAction::make_and_insert( - *params, p.capacity.generators); - }, - [&](inp::OpticalPrimaryGenerator opg) { - optical::PrimaryGeneratorAction::make_and_insert( - *params, std::move(opg)); - }, - [&](inp::OpticalDirectGenerator) { - optical::DirectGeneratorAction::make_and_insert(*params); - }, - [&](inp::OpticalTrackOffload) {}, - }, - p.generator); + result.generator = std::visit( + Overload{ + [&](inp::OpticalEmGenerator) -> SPGeneratorBase { + CELER_VALIDATE(false, + << "OpticalEmGenerator cannot be used " + "with only optical physics enabled"); + return nullptr; + }, + [&](inp::OpticalOffloadGenerator) -> SPGeneratorBase { + return optical::GeneratorAction::make_and_insert( + *params, p.capacity.generators); + }, + [&](inp::OpticalPrimaryGenerator opg) -> SPGeneratorBase { + return optical::PrimaryGeneratorAction::make_and_insert( + *params, std::move(opg)); + }, + [&](inp::OpticalDirectGenerator) -> SPGeneratorBase { + return optical::DirectGeneratorAction::make_and_insert(*params); + }, + [&](inp::OpticalTrackOffload) -> SPGeneratorBase { return nullptr; }, + }, + p.generator); OpticalProblemLoaded result; diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index cabbf8b7ac..fdc247aef0 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -151,7 +151,7 @@ celeritas_add_integration_tests( RMTYPE ${_optical_rm_type} ) -# Test with optical track offloading +# Test with optical physics offloading distributions celeritas_add_integration_tests( UserActionIntegration LarSphereOpticalTrackOffload run OFFLOAD cpu gpu g4 diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 3978991592..afd3d83fb1 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -476,5 +476,139 @@ TEST_F(OpNoviceOptical, run) } //---------------------------------------------------------------------------// + +//---------------------------------------------------------------------------// +// LAR SPHERE WITH OPTICAL TRACK OFFLOAD +//---------------------------------------------------------------------------// +class LSOOTrackingAction final : public G4UserTrackingAction +{ + public: + void PreUserTrackingAction(G4Track const* track) final; + // std::size_t num_pushed() const { return num_pushed_; } + + private: + // std::size_t num_pushed_{0}; +}; + +//---------------------------------------------------------------------------// +/*! + * Offload optical tracks. + */ +class LarSphereOpticalTrackOffload : public LarSphere +{ + public: + PhysicsInput make_physics_input() const override; + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; + UPTrackAction make_tracking_action() override + { + auto act = std::make_unique(); + tracking_.push_back(act.get()); + return act; + } + + private: + std::vector tracking_; +}; + +//---------------------------------------------------------------------------// +/*! + * Enable optical physics + */ +auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput +{ + auto result = LarSphereIntegrationMixin::make_physics_input(); + + // Set default optical physics + auto& optical = result.optical; + optical = {}; + + optical.cherenkov.stack_photons = true; + optical.scintillation.stack_photons = true; + + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking offloading. + */ +auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions +{ + auto result = LarSphereIntegrationMixin::make_setup_options(); + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 32; + opt.capacity.generators = opt.capacity.tracks * 8; + opt.capacity.primaries = opt.capacity.tracks * 16; + opt.generator = inp::OpticalTrackOffload{}; + opt.offload_optical_tracks = true; + + return opt; + }(); + + // Don't offload any particles + result.offload_particles = SetupOptions::VecG4PD{}; + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Tracking action for pushing optical tracks to Celeritas. + */ +void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) +{ + CELER_EXPECT(track); + // Delegate to UserActionIntegration for standard handling + UAI::Instance().PreUserTrackingAction(const_cast(track)); + + // If UserActionIntegration killed it, don't process further + if (track->GetTrackStatus() == fStopAndKill) + { + return; + } +} + +//---------------------------------------------------------------------------// +/*! + * Test that the optical track offload was successful. + */ +void LarSphereOpticalTrackOffload::EndOfRunAction(G4Run const* run) +{ + auto& integration = detail::IntegrationSingleton::instance(); + auto& local = integration.local_offload(); + + auto* opt_offload = dynamic_cast(&local); + if (!G4Threading::IsMasterThread()) + { + if (opt_offload && opt_offload->Initialized()) + { + std::size_t pushed = opt_offload->num_pushed(); + + CELER_LOG(info) << "Total optical photon tracks pushed: " << pushed; + // Validate that we intercepted optical tracks + EXPECT_GT(pushed, 15048) << "should have pushed many optical " + "tracks"; + } + } + // Continue cleanup and other checks at end of run + LarSphere::EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +TEST_F(LarSphereOpticalTrackOffload, run) +{ + auto& rm = this->run_manager(); + rm.SetNumberOfThreads(1); + UAI::Instance().SetOptions(this->make_setup_options()); + + rm.Initialize(); + rm.BeamOn(1); +} } // namespace test } // namespace celeritas From 9318aaa5b7e6c294f8bf144ced69f1aea1a3e6fc Mon Sep 17 00:00:00 2001 From: Rashika-Gupta Date: Mon, 2 Feb 2026 10:30:58 -0500 Subject: [PATCH 60/60] Insert trcks to optical state --- src/accel/LocalOpticalTrackOffload.cc | 33 +++++++++++++++++++++------ src/accel/LocalOpticalTrackOffload.hh | 3 +++ src/celeritas/setup/Problem.cc | 4 +++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc index ffe645afbc..878f9a3d3c 100644 --- a/src/accel/LocalOpticalTrackOffload.cc +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -39,6 +39,11 @@ LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, // Save a pointer to the optical transporter transport_ = params.optical_problem_loaded().transporter; + // Save a pointer to the direct generator action to insert tracks + direct_gen_ + = std::dynamic_pointer_cast( + params.optical_problem_loaded().generator); + CELER_ASSERT(transport_); CELER_ASSERT(transport_->params()); @@ -121,9 +126,19 @@ void LocalOpticalTrackOffload::Push(G4Track& g4track) CELER_EXPECT(g4track.GetDefinition()); CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); - // TODO : Populate optical::TrackInitializer from Geant4 Track + // Convert Geant4 track to optical::TrackInitializer TrackData init; + init.energy = units::MevEnergy( + convert_from_geant(g4track.GetKineticEnergy(), CLHEP::MeV)); + + init.position = convert_from_geant(g4track.GetPosition(), CLHEP::cm); + + init.direction = convert_from_geant(g4track.GetMomentumDirection(), 1); + + init.time = convert_from_geant(g4track.GetGlobalTime(), CLHEP::second); + init.polarization = convert_from_geant(g4track.GetPolarization(), 1); + ScopedProfiling profile_this{"push"}; buffer_.push_back(init); @@ -147,16 +162,20 @@ void LocalOpticalTrackOffload::Flush() return; } - // Number of flushed optical tracks - ++num_flushed_; + ScopedProfiling profile_this("flush"); - // TODO insert buffered track into - // optical CoreState and execute optical transport. - // state_->insert_primaries(make_span(buffer_)); + // Insert tracks + if (direct_gen_) + { + direct_gen_->insert(*state_, make_span(buffer_)); + } + + // Transport tracks + (*transport_)(*state_); + ++num_flushed_; buffer_.clear(); } - //---------------------------------------------------------------------------// auto LocalOpticalTrackOffload::GetActionTime() const -> MapStrDbl { diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh index 297d575596..281878f4c2 100644 --- a/src/accel/LocalOpticalTrackOffload.hh +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -9,6 +9,7 @@ #include "corecel/Types.hh" #include "celeritas/Types.hh" #include "celeritas/optical/TrackInitializer.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" #include "TrackOffloadInterface.hh" @@ -79,6 +80,8 @@ class LocalOpticalTrackOffload final : public TrackOffloadInterface // Thread-local state data std::shared_ptr state_; + std::shared_ptr direct_gen_; + // Buffered tracks for offloading std::vector buffer_; diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index d11aee6266..1b2e5a0651 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -841,7 +841,9 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) [&](inp::OpticalDirectGenerator) -> SPGeneratorBase { return optical::DirectGeneratorAction::make_and_insert(*params); }, - [&](inp::OpticalTrackOffload) -> SPGeneratorBase { return nullptr; }, + [&](inp::OpticalTrackOffload) -> SPGeneratorBase { + return optical::DirectGeneratorAction::make_and_insert(*params); + }, }, p.generator);