Skip to content

Commit dd08850

Browse files
Copilotplexoos
andauthored
Add clang-format compliant dd4hepplugins files from PR #266
Agent-Logs-Url: https://github.com/BNLNPPS/eic-opticks/sessions/a9ee1bc2-5357-4ba2-8707-2fc40935a433 Co-authored-by: plexoos <5005079+plexoos@users.noreply.github.com>
1 parent 3415db4 commit dd08850

10 files changed

Lines changed: 920 additions & 0 deletions

dd4hepplugins/CMakeLists.txt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#------------------------------- -*- cmake -*- -------------------------------#
2+
# eic-opticks DD4hep integration plugins
3+
#
4+
# Builds DD4hep action plugins that integrate eic-opticks GPU-accelerated
5+
# optical photon simulation into DD4hep/Geant4.
6+
#
7+
# Works both as part of the top-level eic-opticks build (in-tree) and as a
8+
# standalone project against an installed eic-opticks.
9+
#
10+
# Usage from a DD4hep steering file:
11+
# OpticsRun -- initializes/finalizes G4CXOpticks per run
12+
# OpticsEvent -- triggers GPU simulation per event
13+
#----------------------------------------------------------------------------#
14+
15+
# Detect standalone vs in-tree build
16+
if(NOT TARGET G4CX)
17+
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
18+
project(ddeicopticks VERSION 0.1.0 LANGUAGES CXX)
19+
find_package(DD4hep REQUIRED COMPONENTS DDG4 DDCore)
20+
find_package(eic-opticks REQUIRED)
21+
find_package(Geant4 REQUIRED)
22+
set(_g4cx eic-opticks::G4CX)
23+
set(_u4 eic-opticks::U4)
24+
set(_sysrap eic-opticks::SysRap)
25+
else()
26+
# In-tree: DD4hep already found by parent, targets use local names
27+
find_package(Geant4 REQUIRED)
28+
set(_g4cx G4CX)
29+
set(_u4 U4)
30+
set(_sysrap SysRap)
31+
endif()
32+
33+
dd4hep_set_compiler_flags()
34+
35+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
36+
set(LIBRARY_OUTPUT_PATH "${CMAKE_CURRENT_BINARY_DIR}")
37+
38+
dd4hep_add_plugin(ddeicopticks
39+
SOURCES
40+
OpticsRun.cc
41+
OpticsEvent.cc
42+
OpticsSteppingAction.cc
43+
USES
44+
DD4hep::DDG4
45+
DD4hep::DDCore
46+
${_g4cx}
47+
${_u4}
48+
${_sysrap}
49+
)
50+
# STANDALONE changes class export macros in eic-opticks headers;
51+
# only needed when building outside the main eic-opticks tree.
52+
if(NOT TARGET G4CX)
53+
target_compile_definitions(ddeicopticks PRIVATE STANDALONE)
54+
endif()
55+
56+
install(TARGETS ddeicopticks
57+
LIBRARY DESTINATION lib
58+
)
59+
60+
install(FILES
61+
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_SHARED_MODULE_PREFIX}ddeicopticks.components
62+
DESTINATION lib
63+
)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#pragma once
2+
/**
3+
DD4hepSensorIdentifier.hh
4+
===========================
5+
6+
Custom sensor identifier for DD4hep geometries.
7+
8+
Unlike the default U4SensorIdentifierDefault which relies on
9+
GLOBAL_SENSOR_BOUNDARY_LIST env var for non-instanced geometries,
10+
this implementation directly checks G4VSensitiveDetector on volumes.
11+
12+
This works for DD4hep geometries where sensitive detectors are
13+
explicitly set via DetElement::setSensitiveDetector().
14+
**/
15+
16+
#include <iostream>
17+
#include <vector>
18+
19+
#include "G4PVPlacement.hh"
20+
#include "G4VSensitiveDetector.hh"
21+
22+
#include "U4SensorIdentifier.h"
23+
24+
struct DD4hepSensorIdentifier : public U4SensorIdentifier
25+
{
26+
int level = 0;
27+
int counter = 0; // auto-increment sensor ID (1-based; 0 means "not a sensor" in opticks)
28+
29+
void setLevel( int _level ) override
30+
{
31+
level = _level;
32+
}
33+
34+
/**
35+
getGlobalIdentity
36+
-------------------
37+
Checks if the physical volume has a G4VSensitiveDetector attached.
38+
Returns a unique 1-based sensor_id, or -1 if not sensitive.
39+
40+
Note: opticks treats sensor_id == 0 as "not a sensor", so IDs must be >= 1.
41+
PV copy numbers are not reliable (e.g. dRICH SiPMs all have copyNo=0).
42+
**/
43+
int getGlobalIdentity( const G4VPhysicalVolume* pv,
44+
const G4VPhysicalVolume* /*ppv*/ ) override
45+
{
46+
if( !pv )
47+
return -1;
48+
49+
const G4LogicalVolume* lv = pv->GetLogicalVolume();
50+
G4VSensitiveDetector* sd = lv->GetSensitiveDetector();
51+
52+
if( !sd )
53+
return -1;
54+
55+
int sensor_id = ++counter; // 1-based unique ID
56+
57+
if( level > 0 )
58+
std::cout << "DD4hepSensorIdentifier::getGlobalIdentity"
59+
<< " sensor_id " << sensor_id
60+
<< " sd " << sd->GetName()
61+
<< " pv " << pv->GetName()
62+
<< std::endl;
63+
64+
return sensor_id;
65+
}
66+
67+
/**
68+
getInstanceIdentity
69+
---------------------
70+
Same as default: recursively search for G4VSensitiveDetector
71+
within the instance subtree.
72+
**/
73+
int getInstanceIdentity( const G4VPhysicalVolume* instance_outer_pv ) const override
74+
{
75+
if( !instance_outer_pv )
76+
return -1;
77+
78+
std::vector<const G4VPhysicalVolume*> sdpv;
79+
FindSD_r( sdpv, instance_outer_pv, 0 );
80+
81+
if( sdpv.empty() )
82+
return -1;
83+
84+
const G4PVPlacement* pvp =
85+
dynamic_cast<const G4PVPlacement*>( instance_outer_pv );
86+
return pvp ? pvp->GetCopyNo() : 0;
87+
}
88+
89+
static void FindSD_r( std::vector<const G4VPhysicalVolume*>& sdpv,
90+
const G4VPhysicalVolume* pv, int depth )
91+
{
92+
const G4LogicalVolume* lv = pv->GetLogicalVolume();
93+
G4VSensitiveDetector* sd = lv->GetSensitiveDetector();
94+
if( sd )
95+
sdpv.push_back( pv );
96+
for( size_t i = 0; i < size_t( lv->GetNoDaughters() ); i++ )
97+
FindSD_r( sdpv, lv->GetDaughter( i ), depth + 1 );
98+
}
99+
};

dd4hepplugins/OpticsEvent.cc

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#include "OpticsEvent.hh"
2+
3+
#include <DD4hep/InstanceCount.h>
4+
#include <DDG4/Factories.h>
5+
#include <DDG4/Geant4Data.h>
6+
#include <DDG4/Geant4HitCollection.h>
7+
#include <DDG4/Geant4SensDetAction.h>
8+
#include <DDG4/Geant4Context.h>
9+
10+
#include <G4Event.hh>
11+
12+
#include <chrono>
13+
14+
#include <G4CXOpticks.hh>
15+
#include <SEvt.hh>
16+
#include <SComp.h>
17+
#include <sphoton.h>
18+
#include <NP.hh>
19+
#include <NPFold.h>
20+
#include <map>
21+
22+
namespace ddeicopticks
23+
{
24+
//---------------------------------------------------------------------------//
25+
OpticsEvent::OpticsEvent( dd4hep::sim::Geant4Context* ctxt,
26+
std::string const& name )
27+
: dd4hep::sim::Geant4EventAction( ctxt, name )
28+
{
29+
dd4hep::InstanceCount::increment( this );
30+
declareProperty( "Verbose", verbose_ );
31+
declareProperty( "PhotonThreshold", photon_threshold_ );
32+
}
33+
34+
//---------------------------------------------------------------------------//
35+
OpticsEvent::~OpticsEvent()
36+
{
37+
dd4hep::InstanceCount::decrement( this );
38+
}
39+
40+
//---------------------------------------------------------------------------//
41+
void OpticsEvent::begin( G4Event const* event )
42+
{
43+
int eventID = event->GetEventID();
44+
45+
if( verbose_ > 0 )
46+
info( "OpticsEvent::begin -- event #%d", eventID );
47+
48+
// In batch mode, only start a new SEvt at the beginning of each batch.
49+
// Skipping beginOfEvent between events lets gensteps accumulate
50+
// (SEvt::clear_output preserves gensteps by design).
51+
if( !batch_begun_ )
52+
{
53+
SEvt::CreateOrReuse_EGPU();
54+
SEvt* sev = SEvt::Get_EGPU();
55+
if( sev )
56+
sev->beginOfEvent( eventID );
57+
batch_begun_ = true;
58+
}
59+
}
60+
61+
//---------------------------------------------------------------------------//
62+
void OpticsEvent::end( G4Event const* event )
63+
{
64+
int eventID = event->GetEventID();
65+
66+
G4CXOpticks* gx = G4CXOpticks::Get();
67+
if( !gx )
68+
{
69+
error( "OpticsEvent::end -- G4CXOpticks not initialized" );
70+
return;
71+
}
72+
73+
SEvt* sev = SEvt::Get_EGPU();
74+
if( !sev )
75+
{
76+
error( "OpticsEvent::end -- no EGPU SEvt instance" );
77+
return;
78+
}
79+
80+
int64_t num_genstep = sev->getNumGenstepFromGenstep();
81+
int64_t num_photon = sev->getNumPhotonFromGenstep();
82+
83+
if( verbose_ > 0 || num_genstep > 0 )
84+
{
85+
info( "Event #%d: %lld gensteps, %lld photons accumulated",
86+
eventID,
87+
static_cast<long long>( num_genstep ),
88+
static_cast<long long>( num_photon ) );
89+
}
90+
91+
// Batch mode: keep accumulating until threshold reached
92+
if( photon_threshold_ > 0 && num_photon < photon_threshold_ )
93+
return;
94+
95+
if( num_genstep > 0 )
96+
{
97+
auto sim_t0 = std::chrono::high_resolution_clock::now();
98+
gx->simulate( eventID, /*reset=*/false );
99+
auto sim_t1 = std::chrono::high_resolution_clock::now();
100+
double simulate_ms =
101+
std::chrono::duration<double, std::milli>( sim_t1 - sim_t0 ).count();
102+
103+
unsigned num_hit = sev->getNumHit();
104+
info( "OPTICKS_GPU_TIME event=%d ms=%.3f photons=%lld hits=%u",
105+
eventID, simulate_ms,
106+
static_cast<long long>( num_photon ), num_hit );
107+
108+
// Debug: dump photon flag and boundary statistics (verbose >= 2)
109+
if( verbose_ >= 2 )
110+
{
111+
NPFold* tf = sev->topfold;
112+
const NP* photon = tf ? tf->get( "photon" ) : nullptr;
113+
if( photon )
114+
{
115+
int np = photon->shape[0];
116+
const sphoton* pp =
117+
(const sphoton*)photon->cvalues<float>();
118+
std::map<unsigned, int> flag_counts;
119+
std::map<unsigned, int> boundary_counts;
120+
for( int i = 0; i < np; i++ )
121+
{
122+
flag_counts[pp[i].flagmask]++;
123+
boundary_counts[pp[i].boundary()]++;
124+
}
125+
for( auto& [f, c] : flag_counts )
126+
info( " flagmask 0x%08x : %d photons", f, c );
127+
for( auto& [b, c] : boundary_counts )
128+
info( " boundary %u : %d photons", b, c );
129+
}
130+
}
131+
132+
// Inject hits only in per-event mode; batch mode cannot map
133+
// hits back to individual events.
134+
if( photon_threshold_ == 0 && num_hit > 0 )
135+
injectHits( event, sev, num_hit );
136+
137+
sev->endOfEvent( eventID );
138+
gx->reset( eventID );
139+
}
140+
else
141+
{
142+
if( verbose_ > 0 )
143+
info( "Event #%d: no gensteps, skipping GPU simulation", eventID );
144+
if( photon_threshold_ == 0 )
145+
sev->endOfEvent( eventID );
146+
}
147+
148+
batch_begun_ = false;
149+
}
150+
151+
//---------------------------------------------------------------------------//
152+
void OpticsEvent::injectHits( G4Event const* event,
153+
SEvt* sev,
154+
unsigned num_hit )
155+
{
156+
using dd4hep::sim::Geant4HitCollection;
157+
using dd4hep::sim::Geant4SensDetSequences;
158+
using dd4hep::sim::Geant4Tracker;
159+
160+
int eventID = event->GetEventID();
161+
162+
Geant4SensDetSequences& sens = context()->sensitiveActions();
163+
auto const& seqs = sens.sequences();
164+
165+
if( seqs.empty() )
166+
{
167+
warning( "Event #%d: no sensitive detectors registered -- "
168+
"call setupTracker() in steering script",
169+
eventID );
170+
return;
171+
}
172+
173+
if( seqs.size() > 1 )
174+
{
175+
warning( "Event #%d: %zu sensitive detectors registered -- "
176+
"hit routing by sensor identity not yet implemented, "
177+
"injecting into first SD only",
178+
eventID, seqs.size() );
179+
}
180+
181+
// Inject into the first SD with a valid collection.
182+
// TODO: route hits to the correct SD based on sensor identity
183+
// when multiple sensitive detectors are present.
184+
for( auto const& [det_name, seq] : seqs )
185+
{
186+
Geant4HitCollection* coll = seq->collection( 0 );
187+
if( !coll )
188+
continue;
189+
190+
for( unsigned i = 0; i < num_hit; i++ )
191+
{
192+
sphoton ph;
193+
sev->getHit( ph, i );
194+
coll->add( createTrackerHit( ph ) );
195+
}
196+
197+
info( "Event #%d: injected %u hits into '%s' collection",
198+
eventID, num_hit, det_name.c_str() );
199+
break;
200+
}
201+
}
202+
203+
//---------------------------------------------------------------------------//
204+
dd4hep::sim::Geant4Tracker::Hit*
205+
OpticsEvent::createTrackerHit( sphoton const& ph )
206+
{
207+
using dd4hep::sim::Geant4Tracker;
208+
209+
auto* hit = new Geant4Tracker::Hit();
210+
211+
hit->position = { ph.pos.x, ph.pos.y, ph.pos.z };
212+
hit->momentum = { ph.mom.x, ph.mom.y, ph.mom.z };
213+
hit->length = ph.wavelength;
214+
hit->energyDeposit = 0;
215+
hit->cellID = ph.identity;
216+
217+
hit->truth.trackID = ph.index;
218+
hit->truth.pdgID = 0;
219+
hit->truth.deposit = 0;
220+
hit->truth.time = ph.time;
221+
hit->truth.length = ph.wavelength;
222+
hit->truth.x = ph.pos.x;
223+
hit->truth.y = ph.pos.y;
224+
hit->truth.z = ph.pos.z;
225+
hit->truth.px = ph.mom.x;
226+
hit->truth.py = ph.mom.y;
227+
hit->truth.pz = ph.mom.z;
228+
229+
return hit;
230+
}
231+
232+
//---------------------------------------------------------------------------//
233+
} // namespace ddeicopticks
234+
235+
DECLARE_GEANT4ACTION_NS( ddeicopticks, OpticsEvent )

0 commit comments

Comments
 (0)