diff --git a/src/accel/CMakeLists.txt b/src/accel/CMakeLists.txt index 62442698fa..80c5f6a21f 100644 --- a/src/accel/CMakeLists.txt +++ b/src/accel/CMakeLists.txt @@ -31,6 +31,7 @@ list(APPEND SOURCES GeantStepDiagnostic.cc IntegrationBase.cc LocalOpticalGenOffload.cc + LocalOpticalTrackOffload.cc LocalTransporter.cc Logger.cc PGPrimaryGeneratorAction.cc diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc new file mode 100644 index 0000000000..878f9a3d3c --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.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 accel/LocalOpticalTrackOffload.cc +//---------------------------------------------------------------------------// +#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" + +#include "SetupOptions.hh" +#include "SharedParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Offload Geant4 optical photon tracks to 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"); + + // 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()); + + 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; + + 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 (optical_params.aux_reg()) + { + state_->aux() = std::make_shared( + *optical_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 optical tracks. + */ +void LocalOpticalTrackOffload::Push(G4Track& g4track) +{ + CELER_EXPECT(*this); + + ++num_pushed_; + + CELER_EXPECT(g4track.GetDefinition()); + CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); + + // 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); + + if (buffer_.size() >= auto_flush_) + { + this->Flush(); + } +} + +//---------------------------------------------------------------------------// +/*! + * Flush buffered optical photon tracks. + */ +void LocalOpticalTrackOffload::Flush() +{ + CELER_EXPECT(*this); + + if (buffer_.empty()) + { + return; + } + + ScopedProfiling profile_this("flush"); + + // 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 +{ + 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()); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize the local optical track offload state + */ +void LocalOpticalTrackOffload::Finalize() +{ + CELER_EXPECT(*this); + + CELER_VALIDATE(buffer_.empty(), + << buffer_.size() << " optical tracks were not flushed"); + + CELER_LOG(info) << "Finalizing Celeritas after " << num_pushed_ + << " optical tracks pushed (over " << num_flushed_ + << " ) flushes"; + + *this = {}; + + CELER_ENSURE(!*this); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh new file mode 100644 index 0000000000..281878f4c2 --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -0,0 +1,102 @@ +//------------------------------- -*- 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/gen/DirectGeneratorAction.hh" + +#include "TrackOffloadInterface.hh" + +namespace celeritas +{ +namespace optical +{ +class CoreStateBase; +class Transporter; +} // namespace optical + +struct SetupOptions; +class SharedParams; + +//---------------------------------------------------------------------------// +/*! + * 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; + + // Construct with shared (across threads) params + LocalOpticalTrackOffload(SetupOptions const& options, SharedParams& params); + + //!@{ + //! \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 + 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(transport_); } + + // Number of buffered tracks + size_type GetBufferSize() const final { return buffer_.size(); } + + // Get accumulated action times + MapStrDbl GetActionTime() const final; + //!@} + + // 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 + std::shared_ptr transport_; + + // Thread-local state data + std::shared_ptr state_; + + std::shared_ptr direct_gen_; + + // Buffered tracks for offloading + std::vector buffer_; + + // Number of photons tracks to buffer before offloading + size_type auto_flush_{}; + + // Accumulated number of optical photon tracks + size_type num_pushed_{}; + + // Accumulated number of tracks pushed over flushes + size_type num_flushed_{}; + + // Current event ID for obtaining it + UniqueEventId event_id_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index 42c9c4cabe..e6bf1f2bb3 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; + + bool offload_optical_tracks = false; }; //---------------------------------------------------------------------------// diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index 52b0d8b2c3..2705787fc8 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -13,10 +13,10 @@ #include "corecel/sys/Stopwatch.hh" #include "ExceptionConverter.hh" +#include "G4OpticalPhoton.hh" #include "TimeOutput.hh" // IWYU pragma: keep #include "detail/IntegrationSingleton.hh" - namespace celeritas { //---------------------------------------------------------------------------// @@ -75,6 +75,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 ea103bc351..cfe0b05caf 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" @@ -104,8 +106,17 @@ LocalOffloadInterface& IntegrationSingleton::local_offload() && 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)) + + { + CELER_LOG(info) << "optical track offloading enabled"; + offload = std::make_unique(); + } else { // TODO: if offloading direct optical tracks, return optical diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index 196fc5b8c6..6648a2bb4a 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -11,6 +11,7 @@ #include "corecel/sys/Stopwatch.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 eec05beb2c..d8980fefc0 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -128,12 +128,21 @@ struct OpticalDirectGenerator { }; +//---------------------------------------------------------------------------// +/*! + * Offload optical photon tracks from Geant4 to Celeritas. + */ +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 d1893d0549..1e3ed7e596 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -845,6 +845,9 @@ problem(inp::OpticalProblem const& p, ImportData const& imported) [&](inp::OpticalDirectGenerator) -> SPGeneratorBase { return optical::DirectGeneratorAction::make_and_insert(*params); }, + [&](inp::OpticalTrackOffload) -> SPGeneratorBase { + return optical::DirectGeneratorAction::make_and_insert(*params); + }, }, p.generator); diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index 0b1ff43e2e..9f1436459c 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 physics offloading distributions +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 da230dc1c4..bb5ddbedeb 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -335,5 +335,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