Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ endif()
# Build Targets
#----------------------------------------------------------------------------#
set(ADEPT_G4_INTEGRATION_SRCS
src/AdePTG4HepEmState.cpp
src/G4HepEmTrackingManagerSpecialized.cc
src/AdePTTrackingManager.cc
src/G4EmStandardPhysics_AdePT.cc
Expand Down
80 changes: 80 additions & 0 deletions include/AdePT/core/AdePTG4HepEmState.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2026 CERN
// SPDX-License-Identifier: Apache-2.0

#ifndef ADEPT_G4_HEPEM_STATE_HH
#define ADEPT_G4_HEPEM_STATE_HH

#include <memory>

struct G4HepEmConfig;
struct G4HepEmData;
struct G4HepEmParameters;

namespace AsyncAdePT {

/// @brief Owns the prepared host-side G4HepEm inputs used by transport.
/// @details
/// The Geant4 integration side prepares one of these objects before the shared
/// transport is created. This wrapper owns both:
/// - the rebuilt `G4HepEmData`
/// - a deep copy of the `G4HepEmParameters` taken from the provided config
///
/// Cleanup is intentionally split:
/// - `DataDeleter` performs the deep cleanup of the owned `G4HepEmData`
/// and then deletes the outer `G4HepEmData` allocation.
/// - `ParametersDeleter` performs the deep cleanup of the owned
/// `G4HepEmParameters`, including the GPU mirror created by AdePT, and then
/// deletes the outer `G4HepEmParameters` allocation.
class AdePTG4HepEmState {
public:
/// @brief Build the AdePT-owned `G4HepEmData` and `G4HepEmParameters` copies from the supplied config.
explicit AdePTG4HepEmState(G4HepEmConfig *hepEmConfig);

/// @brief Destroy the owned `G4HepEmData` and `G4HepEmParameters` copies.
~AdePTG4HepEmState();

AdePTG4HepEmState(const AdePTG4HepEmState &) = delete;
AdePTG4HepEmState &operator=(const AdePTG4HepEmState &) = delete;

AdePTG4HepEmState(AdePTG4HepEmState &&) noexcept = default;
AdePTG4HepEmState &operator=(AdePTG4HepEmState &&) noexcept = default;

/// @brief Access the owned host-side HepEm data tables.
G4HepEmData *GetData() const { return fData.get(); }

/// @brief Access the owned HepEm parameter copy.
G4HepEmParameters *GetParameters() const { return fParameters.get(); }

private:
/// @brief Deletes the outer `G4HepEmData` object after first freeing all tables it owns.
/// @details
/// `FreeG4HepEmData` releases both the host-side tables and any device-side
/// mirrors embedded in the `G4HepEmData` object, but it does not delete the outer
/// `G4HepEmData` allocation itself. This deleter performs both steps for the
/// owned `fData` member. It does not touch the separately owned
/// `G4HepEmParameters` copy stored in `fParameters`.
struct DataDeleter {
void operator()(G4HepEmData *data) const;
};

/// @brief Deletes the outer `G4HepEmParameters` object after first freeing
/// all host/device allocations it owns.
/// @details
/// The copied parameter block owns its `fParametersPerRegion` host array and
/// the GPU mirror pointed to by `fParametersPerRegion_gpu` after transport
/// upload. `FreeG4HepEmParameters` releases those nested allocations, while
/// this deleter also deletes the outer `G4HepEmParameters` allocation.
struct ParametersDeleter {
void operator()(G4HepEmParameters *parameters) const;
};

/// Owned `G4HepEmData` rebuilt for AdePT.
std::unique_ptr<G4HepEmData, DataDeleter> fData;

/// Owned deep copy of `G4HepEmParameters` used to build and upload transport data.
std::unique_ptr<G4HepEmParameters, ParametersDeleter> fParameters;
};

} // namespace AsyncAdePT

#endif
64 changes: 22 additions & 42 deletions include/AdePT/core/AsyncAdePTTransport.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ using SteppingAction = adept::SteppingAction::Action;
#endif

#include <G4HepEmData.hh>
#include <G4HepEmConfig.hh>
#include <G4HepEmState.hh>
#include <G4HepEmStateInit.hh>
#include <G4HepEmParameters.hh>
#include <G4HepEmMatCutData.hh>
#include <G4HepEmParametersInit.hh>
Expand Down Expand Up @@ -536,48 +533,32 @@ void CopySurfaceModelToGPU()
#endif
}

G4HepEmState *InitG4HepEm(G4HepEmConfig *hepEmConfig)
void UploadG4HepEmToGPU(G4HepEmData *hepEmData, G4HepEmParameters *hepEmParameters)
{
// here we call everything from InitG4HepEmState, as we need to provide the parameters from the G4HepEmConfig and do
// not want to initialize to the default values
auto state = new G4HepEmState;

// Use the config-provided parameters
state->fParameters = hepEmConfig->GetG4HepEmParameters();

// Initialize data and fill each subtable using its initialize function
state->fData = new G4HepEmData;
InitG4HepEmData(state->fData);
InitMaterialAndCoupleData(state->fData, state->fParameters);

// electrons, positrons, gamma
InitElectronData(state->fData, state->fParameters, true);
InitElectronData(state->fData, state->fParameters, false);
InitGammaData(state->fData, state->fParameters);

G4HepEmMatCutData *cutData = state->fData->fTheMatCutData;
G4cout << "fNumG4MatCuts = " << cutData->fNumG4MatCuts << ", fNumMatCutData = " << cutData->fNumMatCutData << G4endl;
if (hepEmData == nullptr || hepEmParameters == nullptr) {
throw std::runtime_error("UploadG4HepEmToGPU requires non-null G4HepEmData and G4HepEmParameters.");
}

// Copy to GPU.
CopyG4HepEmDataToGPU(state->fData);
CopyG4HepEmParametersToGPU(state->fParameters);
// Copy the prepared host-side HepEm data to the GPU.
CopyG4HepEmDataToGPU(hepEmData);
CopyG4HepEmParametersToGPU(hepEmParameters);

// Create G4HepEmParameters with the device pointer
G4HepEmParameters parametersOnDevice = *state->fParameters;
parametersOnDevice.fParametersPerRegion = state->fParameters->fParametersPerRegion_gpu;
G4HepEmParameters parametersOnDevice = *hepEmParameters;
parametersOnDevice.fParametersPerRegion = hepEmParameters->fParametersPerRegion_gpu;
parametersOnDevice.fParametersPerRegion_gpu = nullptr;

ADEPT_DEVICE_API_CALL(MemcpyToSymbol(g4HepEmPars, &parametersOnDevice, sizeof(G4HepEmParameters)));

// Create G4HepEmData with the device pointers.
G4HepEmData dataOnDevice;
dataOnDevice.fTheMatCutData = state->fData->fTheMatCutData_gpu;
dataOnDevice.fTheMaterialData = state->fData->fTheMaterialData_gpu;
dataOnDevice.fTheElementData = state->fData->fTheElementData_gpu;
dataOnDevice.fTheElectronData = state->fData->fTheElectronData_gpu;
dataOnDevice.fThePositronData = state->fData->fThePositronData_gpu;
dataOnDevice.fTheSBTableData = state->fData->fTheSBTableData_gpu;
dataOnDevice.fTheGammaData = state->fData->fTheGammaData_gpu;
dataOnDevice.fTheMatCutData = hepEmData->fTheMatCutData_gpu;
dataOnDevice.fTheMaterialData = hepEmData->fTheMaterialData_gpu;
dataOnDevice.fTheElementData = hepEmData->fTheElementData_gpu;
dataOnDevice.fTheElectronData = hepEmData->fTheElectronData_gpu;
dataOnDevice.fThePositronData = hepEmData->fThePositronData_gpu;
dataOnDevice.fTheSBTableData = hepEmData->fTheSBTableData_gpu;
dataOnDevice.fTheGammaData = hepEmData->fTheGammaData_gpu;
// The other pointers should never be used.
dataOnDevice.fTheMatCutData_gpu = nullptr;
dataOnDevice.fTheMaterialData_gpu = nullptr;
Expand All @@ -588,8 +569,6 @@ G4HepEmState *InitG4HepEm(G4HepEmConfig *hepEmConfig)
dataOnDevice.fTheGammaData_gpu = nullptr;

ADEPT_DEVICE_API_CALL(MemcpyToSymbol(g4HepEmData, &dataOnDevice, sizeof(G4HepEmData)));

return state;
}

template <typename FieldType>
Expand Down Expand Up @@ -1828,8 +1807,8 @@ std::thread LaunchGPUWorker(int trackCapacity, int leakCapacity, int scoringCapa
hasWDTRegions};
}

void FreeGPU(std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter> &gpuState, G4HepEmState &g4hepem_state,
std::thread &gpuWorker, adeptint::WDTDeviceBuffers &wdtDev)
void FreeGPU(std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter> &gpuState, std::thread &gpuWorker,
adeptint::WDTDeviceBuffers &wdtDev)
{
gpuState->runTransport = false;
gpuWorker.join();
Expand All @@ -1844,9 +1823,10 @@ void FreeGPU(std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter>
// Free resources.
gpuState.reset();

// Free G4HepEm data
FreeG4HepEmData(g4hepem_state.fData);
FreeG4HepEmParametersOnGPU(g4hepem_state.fParameters);
// Note: the GPU mirror of `G4HepEmParameters` is not released here.
// That cleanup happens when the transport-owned `AdePTG4HepEmState` dies,
// because it owns both the copied `G4HepEmParameters` object and the upload
// lifecycle attached to that copy.

// Free magnetic field
#ifdef ADEPT_USE_EXT_BFIELD
Expand Down
25 changes: 12 additions & 13 deletions include/AdePT/core/AsyncAdePTTransport.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
#define ASYNC_ADEPT_TRANSPORT_HH

#include <AdePT/core/AdePTConfiguration.hh>
#include <AdePT/core/AdePTG4HepEmState.hh>
#include <AdePT/core/AsyncAdePTTransportStruct.hh>
#include <AdePT/core/CommonStruct.h>
#include <AdePT/integration/AdePTGeant4Integration.hh>
#include <AdePT/integration/G4HepEmTrackingManagerSpecialized.hh>

#include <VecGeom/base/Config.h>
#include <VecGeom/management/CudaManager.h> // forward declares vecgeom::cxx::VPlacedVolume
Expand All @@ -27,8 +27,6 @@

class G4Region;
class G4VPhysicalVolume;
class G4HepEmConfig;
struct G4HepEmState;
namespace AsyncAdePT {
struct TrackBuffer;
struct GPUstate;
Expand All @@ -50,10 +48,11 @@ private:
unsigned short fLastNParticlesOnCPU{0}; ///< Number N of last N particles that are finished on CPU
unsigned short fMaxWDTIter{5}; ///< Maximum number of Woodcock tracking iterations per step
std::unique_ptr<GPUstate, GPUstateDeleter> fGPUstate{nullptr}; ///< CUDA state placeholder
std::unique_ptr<TrackBuffer> fBuffer{nullptr}; ///< Buffers for transferring tracks between host and device
std::unique_ptr<G4HepEmState> fg4hepem_state; ///< The HepEm state singleton
adeptint::WDTDeviceBuffers fWDTDev{}; ///< device buffers for Woodcock tracking data
std::thread fGPUWorker; ///< Thread to manage GPU
std::unique_ptr<TrackBuffer> fBuffer{nullptr}; ///< Buffers for transferring tracks between host and device
std::unique_ptr<AdePTG4HepEmState>
fAdePTG4HepEmState; ///< Transport-owned wrapper around `G4HepEmData` and copied `G4HepEmParameters`
adeptint::WDTDeviceBuffers fWDTDev{}; ///< device buffers for Woodcock tracking data
std::thread fGPUWorker; ///< Thread to manage GPU
std::condition_variable fCV_G4Workers; ///< Communicate with G4 workers
std::mutex fMutex_G4Workers; ///< Mutex associated to the condition variable
std::vector<std::atomic<EventState>> fEventStates; ///< State machine for each G4 worker
Expand All @@ -74,14 +73,17 @@ private:
///< Needed to stall the GPU, in case the nPartInFlight * fHitBufferSafetyFactor > available HitSlots
double fHitBufferSafetyFactor{1.5};

void Initialize(G4HepEmConfig *hepEmConfig);
void Initialize(adeptint::VolAuxData *auxData, const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues);
void InitBVH();
bool InitializeGeometry(const vecgeom::cxx::VPlacedVolume *world);
bool InitializePhysics(G4HepEmConfig *hepEmConfig);
bool InitializePhysics();
void InitWDTOnDevice(const adeptint::WDTHostPacked &src, adeptint::WDTDeviceBuffers &dev, unsigned short maxIter);

public:
AsyncAdePTTransport(AdePTConfiguration &configuration, G4HepEmConfig *hepEmConfig);
AsyncAdePTTransport(AdePTConfiguration &configuration, std::unique_ptr<AdePTG4HepEmState> adeptG4HepEmState,
adeptint::VolAuxData *auxData, const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues);
AsyncAdePTTransport(const AsyncAdePTTransport &other) = delete;
~AsyncAdePTTransport();

Expand All @@ -93,9 +95,6 @@ public:
bool GetCallUserActions() const { return fReturnFirstAndLastStep; }
std::vector<std::string> const *GetGPURegionNames() { return fGPURegionNames; }
std::vector<std::string> const *GetCPURegionNames() { return fCPURegionNames; }
G4HepEmState *GetHepEmState() const { return fg4hepem_state.get(); }
void CompleteInitialization(adeptint::VolAuxData *auxData, const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues);
/// Block until transport of the given event is done.
void Flush(int threadId, int eventId, AdePTGeant4Integration &g4Integration);
void ProcessGPUSteps(int threadId, int eventId, AdePTGeant4Integration &g4Integration);
Expand Down
54 changes: 25 additions & 29 deletions include/AdePT/core/AsyncAdePTTransport.icc
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
#include <G4TransportationManager.hh>

#include <G4HepEmData.hh>
#include <G4HepEmState.hh>
#include <G4HepEmStateInit.hh>
#include <G4HepEmParameters.hh>
#include <G4HepEmMatCutData.hh>
#include <G4LogicalVolumeStore.hh>

#include <iomanip>
Expand All @@ -34,7 +31,7 @@ namespace async_adept_impl {
void setDeviceLimits(int stackLimit = 0, int heapLimit = 0);
void CopySurfaceModelToGPU();
void InitWDTOnDevice(const adeptint::WDTHostPacked &, adeptint::WDTDeviceBuffers &, unsigned short);
G4HepEmState *InitG4HepEm(G4HepEmConfig *hepEmConfig);
void UploadG4HepEmToGPU(G4HepEmData *hepEmData, G4HepEmParameters *hepEmParameters);
std::shared_ptr<const std::vector<GPUHit>> GetGPUHits(unsigned int, AsyncAdePT::GPUstate &);
std::pair<GPUHit *, GPUHit *> GetGPUHitsFromBuffer(unsigned int, unsigned int, AsyncAdePT::GPUstate &, bool &);
void CloseGPUBuffer(unsigned int, AsyncAdePT::GPUstate &, GPUHit *, const bool);
Expand All @@ -45,7 +42,7 @@ std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter> InitializeGPU
int trackCapacity, int leakCapacity, int scoringCapacity, int numThreads, AsyncAdePT::TrackBuffer &trackBuffer,
double CPUCapacityFactor, double CPUCopyFraction, std::string &generalBfieldFile,
const std::vector<float> &uniformBfieldValues);
void FreeGPU(std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter> &, G4HepEmState &, std::thread &,
void FreeGPU(std::unique_ptr<AsyncAdePT::GPUstate, AsyncAdePT::GPUstateDeleter> &, std::thread &,
adeptint::WDTDeviceBuffers &);
} // namespace async_adept_impl

Expand All @@ -72,16 +69,20 @@ std::ostream &operator<<(std::ostream &stream, TrackDataWithIDs const &track)

// These definitions live in a header-included .icc file, so they must remain
// inline to avoid multiple definitions across translation units.
inline AsyncAdePTTransport::AsyncAdePTTransport(AdePTConfiguration &configuration, G4HepEmConfig *hepEmConfig)
inline AsyncAdePTTransport::AsyncAdePTTransport(AdePTConfiguration &configuration,
std::unique_ptr<AdePTG4HepEmState> adeptG4HepEmState,
adeptint::VolAuxData *auxData, const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues)
: fAdePTSeed{configuration.GetAdePTSeed()}, fNThread{(ushort)configuration.GetNumThreads()},
fTrackCapacity{(uint)(1024 * 1024 * configuration.GetMillionsOfTrackSlots())},
fLeakCapacity{(uint)(1024 * 1024 * configuration.GetMillionsOfLeakSlots())},
fScoringCapacity{(uint)(1024 * 1024 * configuration.GetMillionsOfHitSlots())},
fDebugLevel{configuration.GetVerbosity()}, fCUDAStackLimit{configuration.GetCUDAStackLimit()},
fCUDAHeapLimit{configuration.GetCUDAHeapLimit()}, fLastNParticlesOnCPU{configuration.GetLastNParticlesOnCPU()},
fMaxWDTIter{configuration.GetMaxWDTIter()}, fEventStates(fNThread), fGPUNetEnergy(fNThread, 0.0),
fTrackInAllRegions{configuration.GetTrackInAllRegions()}, fGPURegionNames{configuration.GetGPURegionNames()},
fCPURegionNames{configuration.GetCPURegionNames()}, fReturnAllSteps{configuration.GetCallUserSteppingAction()},
fMaxWDTIter{configuration.GetMaxWDTIter()}, fAdePTG4HepEmState(std::move(adeptG4HepEmState)),
fEventStates(fNThread), fGPUNetEnergy(fNThread, 0.0), fTrackInAllRegions{configuration.GetTrackInAllRegions()},
fGPURegionNames{configuration.GetGPURegionNames()}, fCPURegionNames{configuration.GetCPURegionNames()},
fReturnAllSteps{configuration.GetCallUserSteppingAction()},
fReturnFirstAndLastStep{configuration.GetCallUserTrackingAction() || configuration.GetCallUserSteppingAction()},
fBfieldFile{configuration.GetCovfieBfieldFile()}, fCPUCapacityFactor{configuration.GetCPUCapacityFactor()},
fCPUCopyFraction{configuration.GetHitBufferFlushThreshold()},
Expand All @@ -94,12 +95,12 @@ inline AsyncAdePTTransport::AsyncAdePTTransport(AdePTConfiguration &configuratio
std::atomic_init(&eventState, EventState::LeakedTracksRetrieved);
}

AsyncAdePTTransport::Initialize(hepEmConfig);
AsyncAdePTTransport::Initialize(auxData, wdtPacked, uniformFieldValues);
}

inline AsyncAdePTTransport::~AsyncAdePTTransport()
{
async_adept_impl::FreeGPU(std::ref(fGPUstate), *fg4hepem_state, fGPUWorker, fWDTDev);
async_adept_impl::FreeGPU(std::ref(fGPUstate), fGPUWorker, fWDTDev);
}

inline void AsyncAdePTTransport::AddTrack(int pdg, uint64_t trackId, uint64_t parentId, double energy, double x,
Expand Down Expand Up @@ -175,14 +176,20 @@ inline bool AsyncAdePTTransport::InitializeGeometry(const vecgeom::cxx::VPlacedV
return success;
}

inline bool AsyncAdePTTransport::InitializePhysics(G4HepEmConfig *hepEmConfig)
inline bool AsyncAdePTTransport::InitializePhysics()
{
// Initialize shared physics data
fg4hepem_state.reset(async_adept_impl::InitG4HepEm(hepEmConfig));
if (!fAdePTG4HepEmState) {
throw std::runtime_error("AsyncAdePTTransport::InitializePhysics: Missing AdePT-owned G4HepEm state.");
}

// Upload the transport-owned `G4HepEmData` and copied
// `G4HepEmParameters` to the device.
async_adept_impl::UploadG4HepEmToGPU(fAdePTG4HepEmState->GetData(), fAdePTG4HepEmState->GetParameters());
return true;
}

inline void AsyncAdePTTransport::Initialize(G4HepEmConfig *hepEmConfig)
inline void AsyncAdePTTransport::Initialize(adeptint::VolAuxData *auxData, const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues)
{
if (vecgeom::GeoManager::Instance().GetRegisteredVolumesCount() == 0)
throw std::runtime_error("AsyncAdePTTransport::Initialize: Number of geometry volumes is zero.");
Expand All @@ -196,23 +203,12 @@ inline void AsyncAdePTTransport::Initialize(G4HepEmConfig *hepEmConfig)
if (!InitializeGeometry(world))
throw std::runtime_error("AsyncAdePTTransport::Initialize: Cannot initialize geometry on GPU");

// Initialize G4HepEm
if (!InitializePhysics(hepEmConfig))
// Upload the prepared HepEm physics data to the device.
if (!InitializePhysics())
throw std::runtime_error("AsyncAdePTTransport::Initialize cannot initialize physics on GPU");
}

inline void AsyncAdePTTransport::CompleteInitialization(adeptint::VolAuxData *auxData,
const adeptint::WDTHostPacked &wdtPacked,
const std::vector<float> &uniformFieldValues)
{
// This is the second half of the split initialization. A non-zero volume count was already
// required in Initialize() before geometry upload, and it remains a hard precondition here
// before uploading any geometry-derived metadata to the device.
const auto numVolumes = vecgeom::GeoManager::Instance().GetRegisteredVolumesCount();
if (numVolumes == 0)
throw std::runtime_error("AsyncAdePTTransport::CompleteInitialization: Number of geometry volumes is zero.");

// Initialize volume auxiliary data on device
const auto numVolumes = vecgeom::GeoManager::Instance().GetRegisteredVolumesCount();
auto &volAuxArray = adeptint::VolAuxArray::GetInstance();
volAuxArray.fNumVolumes = numVolumes;
volAuxArray.fAuxData = auxData;
Expand Down
Loading
Loading