LDMX Software
simcore::GenieElectroNuclearProcess Class Reference

Geant4 discrete process that fires GENIE electronuclear interactions. More...

#include <GenieElectroNuclearProcess.h>

Public Member Functions

 GenieElectroNuclearProcess (const framework::config::Parameters &params)
 Constructor.
 
 ~GenieElectroNuclearProcess () override
 Destructor.
 
G4bool IsApplicable (const G4ParticleDefinition &p) override
 
G4VParticleChange * PostStepDoIt (const G4Track &track, const G4Step &step) override
 Called by Geant4 when this process fires.
 
void setOnlyOnePerEvent (bool val)
 Set whether to allow only one interaction per event.
 

Static Public Attributes

static const std::string PROCESS_NAME = "electronNuclear"
 Process name — matches the built-in so the bias operator works unchanged.
 

Protected Member Functions

G4double GetMeanFreePath (const G4Track &track, G4double prevStepSize, G4ForceCondition *condition) override
 Calculate the mean free path for the electronuclear process.
 

Private Member Functions

void initializeGENIE ()
 Initialize the GENIE framework (tune, message thresholds, splines)
 
void loadAvailableTargets ()
 Parse the spline file for the set of target nuclei that have cross-section splines available.
 
bool splineAvailable (int target_code) const
 
void setupDrivers ()
 Set up GEVGDrivers for each target isotope (manual mode)
 
void discoverIsotopesForElement (const G4Element *element)
 Discover isotopes from a G4Element and set up GENIE drivers (auto mode)
 
void discoverFromVolume ()
 One-shot auto-discovery of target isotopes from the configured volume.
 

Private Attributes

std::vector< std::unique_ptr< genie::GEVGDriver > > evg_drivers_
 One GENIE event generator driver per target isotope.
 
genie::HepMC3Converter hep_mc3_converter_
 Converter from GENIE EventRecord to HepMC3.
 
std::vector< int > targets_
 GENIE target codes (10LZZZAAAI format)
 
std::vector< double > abundances_
 Relative abundances for each target.
 
std::string tune_
 GENIE tune name.
 
std::string spline_file_
 Path to GENIE cross-section spline file.
 
std::string message_threshold_file_
 Path to GENIE message threshold configuration.
 
bool only_one_per_event_ {true}
 Only allow one EN interaction per event (then deactivate)
 
bool genie_initialized_ {false}
 Flag to lazily sync GENIE random seed on first event.
 
bool auto_discover_ {false}
 Whether targets are auto-discovered from the Geant4 geometry.
 
std::string discover_volume_
 Name of the volume to auto-discover targets from (e.g.
 
std::set< int > discovered_z_
 Z values already checked for isotope discovery.
 
std::set< int > available_targets_
 Target nuclei (10LZZZAAAI codes) that have splines in the spline file.
 
std::map< int, std::vector< std::pair< int, double > > > z_to_targets_
 Lookup map: element Z -> list of (driver_index, abundance).
 
std::vector< double > partial_sum_sigma_
 Partial cross-section sums per element, used to select the target isotope in PostStepDoIt via weighted random sampling.
 

Detailed Description

Geant4 discrete process that fires GENIE electronuclear interactions.

Replaces the built-in Geant4 "electronNuclear" process so that the existing ElectroNuclear bias operator works without modification. Follows the same architectural pattern as G4DarkBremsstrahlung.

The electron is tracked normally through upstream material (tagger, etc.) with natural energy loss. When Geant4 selects this process to fire, GENIE generates the interaction using the electron's actual energy at that point. The GENIE final-state particles are injected as Geant4 secondaries and the primary electron is killed.

Definition at line 40 of file GenieElectroNuclearProcess.h.

Constructor & Destructor Documentation

◆ GenieElectroNuclearProcess()

simcore::GenieElectroNuclearProcess::GenieElectroNuclearProcess ( const framework::config::Parameters & params)

Constructor.

Initializes GENIE (tune, splines, event generator drivers) and builds the Z-to-target lookup map for fast material matching.

Parameters
paramsConfiguration parameters (targets, abundances, tune, etc.)

Definition at line 48 of file GenieElectroNuclearProcess.cxx.

50 : G4VDiscreteProcess(PROCESS_NAME, fElectromagnetic) {
51 // Use a distinct EM subtype so we don't collide with other EM processes.
52 // 64 is arbitrary but distinct from standard EM subtypes and the DarkBrem
53 // subtype (63).
54 SetProcessSubType(64);
55
56 // Read configuration (targets/abundances are optional — empty means
57 // auto-discover)
58 targets_ = params.get<std::vector<int>>("targets", {});
59 abundances_ = params.get<std::vector<double>>("abundances", {});
60 discover_volume_ = params.get<std::string>("discover_volume", "");
61 tune_ = params.get<std::string>("tune");
62 spline_file_ = params.get<std::string>("spline_file");
63 message_threshold_file_ = params.get<std::string>("message_threshold_file");
64 only_one_per_event_ = params.get<bool>("only_one_per_event");
65
66 // Initialize GENIE framework (tune, splines) — independent of targets
68
69 if (!targets_.empty()) {
70 // Manual mode: user-specified targets and abundances
71 // Normalize abundances
72 double abundance_sum = 0;
73 for (auto a : abundances_) abundance_sum += a;
74 if (abundance_sum > 0) {
75 for (auto& a : abundances_) a /= abundance_sum;
76 }
77
79
80 // Build the Z -> targets lookup map
81 for (size_t i = 0; i < targets_.size(); ++i) {
82 int z = (targets_[i] / 10000) % 1000;
83 z_to_targets_[z].emplace_back(static_cast<int>(i), abundances_[i]);
84 }
85
86 ldmx_log(info) << "GenieElectroNuclearProcess configured with "
87 << targets_.size() << " manual target(s), tune=" << tune_;
88 } else {
89 // Auto-discovery mode: targets are discovered once from the configured
90 // volume on the first GetMeanFreePath call (geometry exists by then).
91 if (discover_volume_.empty()) {
92 EXCEPTION_RAISE(
93 "ConfigurationException",
94 "GenieElectroNuclearProcess is in auto-discovery mode (no manual "
95 "'targets') but 'discover_volume' is empty. Set genie_nuclear."
96 "discover_volume to the volume to discover targets from (e.g. "
97 "\"target_region\"), or specify 'targets' and 'abundances' "
98 "manually.");
99 }
100 auto_discover_ = true;
101 ldmx_log(info) << "GenieElectroNuclearProcess configured for auto-discovery"
102 << " from volume '" << discover_volume_
103 << "', tune=" << tune_;
104 }
105}
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
void initializeGENIE()
Initialize the GENIE framework (tune, message thresholds, splines)
bool only_one_per_event_
Only allow one EN interaction per event (then deactivate)
static const std::string PROCESS_NAME
Process name — matches the built-in so the bias operator works unchanged.
void setupDrivers()
Set up GEVGDrivers for each target isotope (manual mode)
std::vector< int > targets_
GENIE target codes (10LZZZAAAI format)
std::vector< double > abundances_
Relative abundances for each target.
std::string discover_volume_
Name of the volume to auto-discover targets from (e.g.
bool auto_discover_
Whether targets are auto-discovered from the Geant4 geometry.
std::map< int, std::vector< std::pair< int, double > > > z_to_targets_
Lookup map: element Z -> list of (driver_index, abundance).
std::string message_threshold_file_
Path to GENIE message threshold configuration.
std::string spline_file_
Path to GENIE cross-section spline file.

References abundances_, auto_discover_, discover_volume_, framework::config::Parameters::get(), initializeGENIE(), message_threshold_file_, only_one_per_event_, setupDrivers(), spline_file_, targets_, tune_, and z_to_targets_.

◆ ~GenieElectroNuclearProcess()

simcore::GenieElectroNuclearProcess::~GenieElectroNuclearProcess ( )
override

Destructor.

Definition at line 107 of file GenieElectroNuclearProcess.cxx.

107 {
108 ldmx_log(info) << "--- GENIE Process Summary ---";
109 for (size_t i = 0; i < targets_.size(); ++i) {
110 ldmx_log(info) << " Target=" << targets_[i]
111 << " Abundance=" << abundances_[i];
112 }
113 ldmx_log(info) << "--- GENIE Process Summary END ---";
114}

References abundances_, and targets_.

Member Function Documentation

◆ discoverFromVolume()

void simcore::GenieElectroNuclearProcess::discoverFromVolume ( )
private

One-shot auto-discovery of target isotopes from the configured volume.

Looks up every G4LogicalVolume matching discover_volume_ (using the same name/region tests as the ElectroNuclear bias operator), and sets up GENIE drivers for the elements of their materials. Called once on the first GetMeanFreePath, after the geometry has been built.

Definition at line 289 of file GenieElectroNuclearProcess.cxx.

289 {
290 namespace vc = simcore::g4user::volumechecks;
291
292 // Select the same volume-matching test the ElectroNuclear bias operator
293 // uses, so the discovered targets correspond to the biased volume.
294 using Test = bool (*)(G4LogicalVolume*, const std::string&);
295 Test include_volume_test = nullptr;
296 if (discover_volume_ == "ecal") {
297 include_volume_test = &vc::isInEcal;
298 } else if (discover_volume_ == "old_ecal") {
299 include_volume_test = &vc::isInEcalOld;
300 } else if (discover_volume_ == "target") {
301 include_volume_test = &vc::isInTargetOnly;
302 } else if (discover_volume_ == "target_region") {
303 include_volume_test = &vc::isInTargetRegion;
304 } else if (discover_volume_ == "hcal") {
305 include_volume_test = &vc::isInHcal;
306 } else {
307 include_volume_test = &vc::nameContains;
308 }
309
310 int n_volumes = 0;
311 for (G4LogicalVolume* volume : *G4LogicalVolumeStore::GetInstance()) {
312 if (!include_volume_test(volume, discover_volume_)) continue;
313 ++n_volumes;
314
315 G4Material* mat = volume->GetMaterial();
316 if (!mat) continue;
317 const G4ElementVector* els = mat->GetElementVector();
318 for (size_t i = 0; i < mat->GetNumberOfElements(); ++i) {
319 // discoverIsotopesForElement de-duplicates by Z internally
321 }
322 }
323
324 if (evg_drivers_.empty()) {
325 ldmx_log(warn) << "Auto-discovery found no target isotopes in volume(s) "
326 << "matching '" << discover_volume_ << "' (" << n_volumes
327 << " volume(s) matched). Electronuclear interactions will "
328 << "not fire — check the discover_volume setting.";
329 } else {
330 ldmx_log(info) << "Auto-discovered " << evg_drivers_.size()
331 << " target isotope(s) from " << n_volumes
332 << " volume(s) matching '" << discover_volume_ << "'";
333 }
334}
std::vector< std::unique_ptr< genie::GEVGDriver > > evg_drivers_
One GENIE event generator driver per target isotope.
void discoverIsotopesForElement(const G4Element *element)
Discover isotopes from a G4Element and set up GENIE drivers (auto mode)

References discover_volume_, discoverIsotopesForElement(), and evg_drivers_.

Referenced by GetMeanFreePath().

◆ discoverIsotopesForElement()

void simcore::GenieElectroNuclearProcess::discoverIsotopesForElement ( const G4Element * element)
private

Discover isotopes from a G4Element and set up GENIE drivers (auto mode)

Definition at line 204 of file GenieElectroNuclearProcess.cxx.

205 {
206 int z = static_cast<int>(element->GetZ());
207
208 // Already checked this element
209 if (!discovered_z_.insert(z).second) return;
210
211 size_t n_isotopes = element->GetNumberOfIsotopes();
212
213 if (n_isotopes > 0) {
214 // Element has explicit isotope data — use it
215 const G4IsotopeVector* isotopes = element->GetIsotopeVector();
216 const G4double* rel_ab = element->GetRelativeAbundanceVector();
217
218 for (size_t j = 0; j < n_isotopes; ++j) {
219 int iso_z = (*isotopes)[j]->GetZ();
220 int a = (*isotopes)[j]->GetN();
221 double abundance = rel_ab[j];
222 if (abundance <= 0) continue;
223
224 int target_code = 1000000000 + iso_z * 10000 + a * 10;
225
226 if (!splineAvailable(target_code)) {
227 ldmx_log(warn) << "No cross-section spline available for target "
228 << target_code << " (Z=" << iso_z << ", A=" << a
229 << ") from element " << element->GetName()
230 << " — skipping it (computing on the fly would be "
231 << "prohibitively slow).";
232 continue;
233 }
234
235 int driver_idx = static_cast<int>(evg_drivers_.size());
236
237 targets_.push_back(target_code);
238 abundances_.push_back(abundance);
239
240 auto driver = std::make_unique<genie::GEVGDriver>();
241 genie::InitialState initial_state(target_code, 11);
242 driver->SetEventGeneratorList(
243 genie::RunOpt::Instance()->EventGeneratorList());
244 driver->SetUnphysEventMask(*genie::RunOpt::Instance()->UnphysEventMask());
245 driver->Configure(initial_state);
246 driver->UseSplines();
247 evg_drivers_.push_back(std::move(driver));
248
249 z_to_targets_[z].emplace_back(driver_idx, abundance);
250
251 ldmx_log(info) << "Auto-discovered target " << target_code << " (Z=" << z
252 << ", A=" << a << ", abundance=" << abundance << ")";
253 }
254 } else {
255 // No explicit isotopes — use Z and rounded atomic mass as single target
256 int a = static_cast<int>(std::round(element->GetAtomicMassAmu()));
257 int target_code = 1000000000 + z * 10000 + a * 10;
258
259 if (!splineAvailable(target_code)) {
260 ldmx_log(warn) << "No cross-section spline available for target "
261 << target_code << " (Z=" << z << ", A=" << a
262 << ") from element " << element->GetName()
263 << " — skipping it (computing on the fly would be "
264 << "prohibitively slow).";
265 return;
266 }
267
268 int driver_idx = static_cast<int>(evg_drivers_.size());
269
270 targets_.push_back(target_code);
271 abundances_.push_back(1.0);
272
273 auto driver = std::make_unique<genie::GEVGDriver>();
274 genie::InitialState initial_state(target_code, 11);
275 driver->SetEventGeneratorList(
276 genie::RunOpt::Instance()->EventGeneratorList());
277 driver->SetUnphysEventMask(*genie::RunOpt::Instance()->UnphysEventMask());
278 driver->Configure(initial_state);
279 driver->UseSplines();
280 evg_drivers_.push_back(std::move(driver));
281
282 z_to_targets_[z].emplace_back(driver_idx, 1.0);
283
284 ldmx_log(info) << "Auto-discovered target " << target_code << " (Z=" << z
285 << ", A=" << a << ") from element " << element->GetName();
286 }
287}
std::set< int > discovered_z_
Z values already checked for isotope discovery.

References abundances_, discovered_z_, evg_drivers_, splineAvailable(), targets_, and z_to_targets_.

Referenced by discoverFromVolume().

◆ GetMeanFreePath()

G4double simcore::GenieElectroNuclearProcess::GetMeanFreePath ( const G4Track & track,
G4double prevStepSize,
G4ForceCondition * condition )
overrideprotected

Calculate the mean free path for the electronuclear process.

Loops over elements in the current material, looks up matching GENIE targets by Z, queries GENIE for the cross section, and returns 1/SIGMA.

Definition at line 340 of file GenieElectroNuclearProcess.cxx.

342 {
343 if (!IsApplicable(*track.GetParticleDefinition())) return DBL_MAX;
344
345 // One-shot auto-discovery: the geometry is guaranteed to be built by the
346 // first call to GetMeanFreePath, so we look up the configured volume's
347 // materials once and set up only the relevant GENIE drivers.
348 if (auto_discover_) {
350 auto_discover_ = false;
351 }
352
353 // Get electron total energy in GeV (GENIE units)
354 G4double energy_geant4 = track.GetDynamicParticle()->GetTotalEnergy();
355 double energy_gev = energy_geant4 / CLHEP::GeV;
356
357 if (energy_gev < 0.001) return DBL_MAX; // threshold guard
358
359 // Get direction from the track
360 G4ThreeVector dir = track.GetMomentumDirection();
361
362 // Build electron 4-momentum for GENIE
363 double electron_mass_gev = 0.000510999;
364 double elec_p = std::sqrt(energy_gev * energy_gev -
365 electron_mass_gev * electron_mass_gev);
366 if (elec_p <= 0) return DBL_MAX;
367
368 TLorentzVector e_p4(elec_p * dir.x(), elec_p * dir.y(), elec_p * dir.z(),
369 energy_gev);
370
371 G4Material* material = track.GetMaterial();
372 const G4ElementVector* elements = material->GetElementVector();
373 const G4double* atom_density = material->GetVecNbOfAtomsPerVolume();
374 size_t n_elements = material->GetNumberOfElements();
375
376 G4double sigma = 0;
377 partial_sum_sigma_.resize(n_elements);
378
379 for (size_t i = 0; i < n_elements; ++i) {
380 int z = static_cast<int>((*elements)[i]->GetZ());
381
382 G4double element_sigma = 0;
383 auto it = z_to_targets_.find(z);
384 if (it != z_to_targets_.end()) {
385 for (const auto& [driver_idx, abundance] : it->second) {
386 // Query GENIE for this target's cross section
387 double xsec_genie = evg_drivers_[driver_idx]->XSecSum(e_p4);
388 // Convert from GENIE internal units to Geant4 units
389 double xsec_g4 =
390 (xsec_genie / genie::units::millibarn) * CLHEP::millibarn;
391 element_sigma += abundance * xsec_g4;
392 }
393 }
394 // else: no GENIE target for this Z, contributes 0
395
396 sigma += atom_density[i] * element_sigma;
397 partial_sum_sigma_[i] = sigma;
398 }
399
400 return sigma > DBL_MIN ? 1.0 / sigma : DBL_MAX;
401}
G4bool IsApplicable(const G4ParticleDefinition &p) override
void discoverFromVolume()
One-shot auto-discovery of target isotopes from the configured volume.
std::vector< double > partial_sum_sigma_
Partial cross-section sums per element, used to select the target isotope in PostStepDoIt via weighte...

References auto_discover_, discoverFromVolume(), evg_drivers_, IsApplicable(), partial_sum_sigma_, and z_to_targets_.

◆ initializeGENIE()

void simcore::GenieElectroNuclearProcess::initializeGENIE ( )
private

Initialize the GENIE framework (tune, message thresholds, splines)

Definition at line 116 of file GenieElectroNuclearProcess.cxx.

116 {
117 // Initialize RunOpt with EM event generator list
118 char* in_arr[3] = {const_cast<char*>(""),
119 const_cast<char*>("--event-generator-list"),
120 const_cast<char*>("EM")};
121 genie::RunOpt::Instance()->ReadFromCommandLine(3, in_arr);
122
123 // Set message thresholds
124 genie::utils::app_init::MesgThresholds(message_threshold_file_);
125
126 // Set tune
127 genie::RunOpt::Instance()->SetTuneName(tune_);
128 if (!genie::RunOpt::Instance()->Tune()) {
129 EXCEPTION_RAISE("ConfigurationException", "No TuneId in RunOption.");
130 }
131 genie::RunOpt::Instance()->BuildTune();
132
133 // Load cross-section spline file
134 genie::utils::app_init::XSecTable(spline_file_, true);
135
136 // Record which target nuclei actually have splines so we can skip the rest
138
139 genie::GHepRecord::SetPrintLevel(0);
140}
void loadAvailableTargets()
Parse the spline file for the set of target nuclei that have cross-section splines available.

References loadAvailableTargets(), message_threshold_file_, spline_file_, and tune_.

Referenced by GenieElectroNuclearProcess().

◆ IsApplicable()

G4bool simcore::GenieElectroNuclearProcess::IsApplicable ( const G4ParticleDefinition & p)
override
Returns
true if the particle is an electron

Definition at line 336 of file GenieElectroNuclearProcess.cxx.

336 {
337 return &p == G4Electron::Definition();
338}

Referenced by GetMeanFreePath().

◆ loadAvailableTargets()

void simcore::GenieElectroNuclearProcess::loadAvailableTargets ( )
private

Parse the spline file for the set of target nuclei that have cross-section splines available.

Used to skip discovered isotopes without splines, which GENIE would otherwise compute on the fly (prohibitively slow).

Definition at line 142 of file GenieElectroNuclearProcess.cxx.

142 {
143 std::ifstream in(spline_file_);
144 if (!in) {
145 ldmx_log(warn) << "Could not open spline file '" << spline_file_
146 << "' to determine available targets; all discovered "
147 << "isotopes will be attempted.";
148 return;
149 }
150
151 // Spline keys embed the target nucleus as a "tgt:<10-digit-code>" token.
152 const std::string token = "tgt:";
153 std::string line;
154 while (std::getline(in, line)) {
155 size_t pos = 0;
156 while ((pos = line.find(token, pos)) != std::string::npos) {
157 pos += token.size();
158 size_t end = pos;
159 while (end < line.size() &&
160 std::isdigit(static_cast<unsigned char>(line[end]))) {
161 ++end;
162 }
163 if (end > pos) {
164 available_targets_.insert(std::stoi(line.substr(pos, end - pos)));
165 }
166 pos = end;
167 }
168 }
169
170 ldmx_log(info) << "Found cross-section splines for "
171 << available_targets_.size() << " target nuclei in "
172 << spline_file_;
173}
std::set< int > available_targets_
Target nuclei (10LZZZAAAI codes) that have splines in the spline file.

References available_targets_, and spline_file_.

Referenced by initializeGENIE().

◆ PostStepDoIt()

G4VParticleChange * simcore::GenieElectroNuclearProcess::PostStepDoIt ( const G4Track & track,
const G4Step & step )
override

Called by Geant4 when this process fires.

Generates a GENIE event at the electron's current energy, injects final-state particles as secondaries, stores the HepMC3 record, and kills the primary electron.

Definition at line 403 of file GenieElectroNuclearProcess.cxx.

404 {
405 // Lazy initialization of GENIE random seed on first event
406 if (!genie_initialized_) {
407 auto seed = G4Random::getTheEngine()->getSeed();
408 ldmx_log(debug) << "Initializing GENIE random seed: " << seed;
409 genie::utils::app_init::RandGen(seed);
410 genie_initialized_ = true;
411 }
412
413 aParticleChange.Initialize(track);
414
415 // If only one per event, deactivate this process
417 G4ProcessManager* pman = track.GetDefinition()->GetProcessManager();
418 for (int i_proc = 0; i_proc < pman->GetProcessList()->size(); i_proc++) {
419 G4VProcess* p = (*(pman->GetProcessList()))[i_proc];
420 if (p->GetProcessName().contains(PROCESS_NAME)) {
421 pman->SetProcessActivation(p, false);
422 break;
423 }
424 }
425 }
426
427 // Get electron energy at the post-step point
428 G4double energy_geant4 = step.GetPostStepPoint()->GetTotalEnergy();
429 double energy_gev = energy_geant4 / CLHEP::GeV;
430
431 G4ThreeVector dir = track.GetMomentumDirection();
432
433 ldmx_log(info) << "GENIE electronuclear interaction at E = " << energy_gev
434 << " GeV";
435
436 // Build electron 4-momentum for GENIE
437 double electron_mass_gev = 0.000510999;
438 double elec_p = std::sqrt(energy_gev * energy_gev -
439 electron_mass_gev * electron_mass_gev);
440 TLorentzVector e_p4(elec_p * dir.x(), elec_p * dir.y(), elec_p * dir.z(),
441 energy_gev);
442
443 // Select target isotope via weighted random from partial sums
444 G4Material* material = track.GetMaterial();
445 const G4ElementVector* elements = material->GetElementVector();
446 size_t n_elements = material->GetNumberOfElements();
447
448 int selected_driver = -1;
449
450 if (n_elements == 1) {
451 // Only one element — pick from matching GENIE targets by abundance
452 int z = static_cast<int>((*elements)[0]->GetZ());
453 auto it = z_to_targets_.find(z);
454 if (it != z_to_targets_.end() && !it->second.empty()) {
455 if (it->second.size() == 1) {
456 selected_driver = it->second[0].first;
457 } else {
458 // Weighted random among isotopes
459 double total_ab = 0;
460 for (const auto& [idx, ab] : it->second) total_ab += ab;
461 double rand_val = G4UniformRand() * total_ab;
462 double running = 0;
463 for (const auto& [idx, ab] : it->second) {
464 running += ab;
465 if (rand_val <= running) {
466 selected_driver = idx;
467 break;
468 }
469 }
470 if (selected_driver < 0) selected_driver = it->second.back().first;
471 }
472 }
473 } else {
474 // Multiple elements — use partial_sum_sigma_ to pick element first,
475 // then pick isotope within that element
476 double rand_val = G4UniformRand() * partial_sum_sigma_[n_elements - 1];
477 int selected_element = static_cast<int>(n_elements) - 1;
478 for (size_t i = 0; i < n_elements - 1; ++i) {
479 if (rand_val <= partial_sum_sigma_[i]) {
480 selected_element = static_cast<int>(i);
481 break;
482 }
483 }
484
485 int z = static_cast<int>((*elements)[selected_element]->GetZ());
486 auto it = z_to_targets_.find(z);
487 if (it != z_to_targets_.end() && !it->second.empty()) {
488 if (it->second.size() == 1) {
489 selected_driver = it->second[0].first;
490 } else {
491 // Weighted random among isotopes for this Z
492 // Weight by abundance * xsec
493 std::vector<double> weights;
494 double total_w = 0;
495 for (const auto& [idx, ab] : it->second) {
496 double xsec = evg_drivers_[idx]->XSecSum(e_p4);
497 double w = ab * xsec;
498 weights.push_back(w);
499 total_w += w;
500 }
501 double r = G4UniformRand() * total_w;
502 double running = 0;
503 for (size_t j = 0; j < it->second.size(); ++j) {
504 running += weights[j];
505 if (r <= running) {
506 selected_driver = it->second[j].first;
507 break;
508 }
509 }
510 if (selected_driver < 0) selected_driver = it->second.back().first;
511 }
512 }
513 }
514
515 if (selected_driver < 0) {
516 // Fallback: should not happen if GetMeanFreePath returned finite
517 ldmx_log(error) << "No matching GENIE target found for material "
518 << material->GetName() << " — returning unchanged track";
519 return G4VDiscreteProcess::PostStepDoIt(track, step);
520 }
521
522 // Generate the GENIE event
523 genie::EventRecord* genie_event = nullptr;
524 while (!genie_event) {
525 genie_event = evg_drivers_[selected_driver]->GenerateEvent(e_p4);
526 }
527
528 // Store HepMC3 event record in UserEventInformation
529 auto* ev_info = static_cast<UserEventInformation*>(
530 G4EventManager::GetEventManager()->GetUserInformation());
531 if (ev_info) {
532 auto hepmc3_genie = hep_mc3_converter_.ConvertToHepMC3(*genie_event);
533 ldmx::HepMC3GenEvent hepmc3_ldmx_genie;
534 hepmc3_genie->write_data(hepmc3_ldmx_genie);
535 ev_info->addHepMC3GenEvent(hepmc3_ldmx_genie);
536
537 // Propagate event weight
538 ev_info->incWeight(genie_event->Weight());
539 }
540
541 // Count final-state particles
542 int n_entries = genie_event->GetEntries();
543 int n_secondaries = 0;
544 for (int i = 0; i < n_entries; ++i) {
545 auto* p = static_cast<genie::GHepParticle*>((*genie_event)[i]);
546 if (p->Status() == 1) ++n_secondaries;
547 }
548
549 aParticleChange.SetNumberOfSecondaries(n_secondaries);
550
551 // Interaction position
552 G4ThreeVector position = step.GetPostStepPoint()->GetPosition();
553 G4double global_time = step.GetPostStepPoint()->GetGlobalTime();
554
555 // Add final-state particles as secondaries
556 for (int i = 0; i < n_entries; ++i) {
557 auto* p = static_cast<genie::GHepParticle*>((*genie_event)[i]);
558 if (p->Status() != 1) continue;
559
560 int pdg = p->Pdg();
561 G4ParticleDefinition* particle_def = nullptr;
562
563 // Handle nuclear fragments / ions
564 if (pdg > 1000000000) {
565 int ion_z = (pdg / 10000) % 1000;
566 int ion_a = (pdg / 10) % 1000;
567 particle_def = G4IonTable::GetIonTable()->GetIon(ion_z, ion_a, 0.);
568 } else {
569 particle_def = G4ParticleTable::GetParticleTable()->FindParticle(pdg);
570 }
571
572 if (!particle_def) {
573 ldmx_log(warn) << "Could not find G4ParticleDefinition for PDG=" << pdg
574 << " — skipping this secondary";
575 continue;
576 }
577
578 G4ThreeVector momentum(p->Px() * CLHEP::GeV, p->Py() * CLHEP::GeV,
579 p->Pz() * CLHEP::GeV);
580
581 auto* dynamic_particle = new G4DynamicParticle(particle_def, momentum);
582 auto* secondary_track =
583 new G4Track(dynamic_particle, global_time, position);
584 secondary_track->SetParentID(track.GetTrackID());
585
586 aParticleChange.AddSecondary(secondary_track);
587 }
588
589 // Kill the primary electron
590 aParticleChange.ProposeTrackStatus(fStopAndKill);
591
592 delete genie_event;
593
594 return G4VDiscreteProcess::PostStepDoIt(track, step);
595}
bool genie_initialized_
Flag to lazily sync GENIE random seed on first event.
genie::HepMC3Converter hep_mc3_converter_
Converter from GENIE EventRecord to HepMC3.

References evg_drivers_, genie_initialized_, hep_mc3_converter_, only_one_per_event_, partial_sum_sigma_, PROCESS_NAME, and z_to_targets_.

◆ setOnlyOnePerEvent()

void simcore::GenieElectroNuclearProcess::setOnlyOnePerEvent ( bool val)
inline

Set whether to allow only one interaction per event.

Definition at line 74 of file GenieElectroNuclearProcess.h.

74{ only_one_per_event_ = val; }

References only_one_per_event_.

◆ setupDrivers()

void simcore::GenieElectroNuclearProcess::setupDrivers ( )
private

Set up GEVGDrivers for each target isotope (manual mode)

Definition at line 181 of file GenieElectroNuclearProcess.cxx.

181 {
182 for (size_t i = 0; i < targets_.size(); ++i) {
183 if (!splineAvailable(targets_[i])) {
184 ldmx_log(error) << "No cross-section spline available for manually "
185 << "specified target " << targets_[i]
186 << " in spline file " << spline_file_;
187 EXCEPTION_RAISE(
188 "GenieSplineMissing",
189 "No GENIE cross-section spline for target " +
190 std::to_string(targets_[i]) +
191 ". Add it to the spline file or remove it from 'targets'.");
192 }
193 auto driver = std::make_unique<genie::GEVGDriver>();
194 genie::InitialState initial_state(targets_[i], 11); // electron probe
195 driver->SetEventGeneratorList(
196 genie::RunOpt::Instance()->EventGeneratorList());
197 driver->SetUnphysEventMask(*genie::RunOpt::Instance()->UnphysEventMask());
198 driver->Configure(initial_state);
199 driver->UseSplines();
200 evg_drivers_.push_back(std::move(driver));
201 }
202}

References evg_drivers_, spline_file_, splineAvailable(), and targets_.

Referenced by GenieElectroNuclearProcess().

◆ splineAvailable()

bool simcore::GenieElectroNuclearProcess::splineAvailable ( int target_code) const
private
Returns
true if a cross-section spline is available for target_code, or if the available-target list could not be determined (fail open).

Definition at line 175 of file GenieElectroNuclearProcess.cxx.

175 {
176 // Fail open: if we could not parse the spline file, don't filter anything.
177 if (available_targets_.empty()) return true;
178 return available_targets_.count(target_code) > 0;
179}

References available_targets_.

Referenced by discoverIsotopesForElement(), and setupDrivers().

Member Data Documentation

◆ abundances_

std::vector<double> simcore::GenieElectroNuclearProcess::abundances_
private

Relative abundances for each target.

Definition at line 130 of file GenieElectroNuclearProcess.h.

Referenced by discoverIsotopesForElement(), GenieElectroNuclearProcess(), and ~GenieElectroNuclearProcess().

◆ auto_discover_

bool simcore::GenieElectroNuclearProcess::auto_discover_ {false}
private

Whether targets are auto-discovered from the Geant4 geometry.

Definition at line 148 of file GenieElectroNuclearProcess.h.

148{false};

Referenced by GenieElectroNuclearProcess(), and GetMeanFreePath().

◆ available_targets_

std::set<int> simcore::GenieElectroNuclearProcess::available_targets_
private

Target nuclei (10LZZZAAAI codes) that have splines in the spline file.

Empty if the spline file could not be parsed (then no filtering is done).

Definition at line 159 of file GenieElectroNuclearProcess.h.

Referenced by loadAvailableTargets(), and splineAvailable().

◆ discover_volume_

std::string simcore::GenieElectroNuclearProcess::discover_volume_
private

Name of the volume to auto-discover targets from (e.g.

"target_region"). Uses the same naming convention as the ElectroNuclear bias operator.

Definition at line 152 of file GenieElectroNuclearProcess.h.

Referenced by discoverFromVolume(), and GenieElectroNuclearProcess().

◆ discovered_z_

std::set<int> simcore::GenieElectroNuclearProcess::discovered_z_
private

Z values already checked for isotope discovery.

Definition at line 155 of file GenieElectroNuclearProcess.h.

Referenced by discoverIsotopesForElement().

◆ evg_drivers_

std::vector<std::unique_ptr<genie::GEVGDriver> > simcore::GenieElectroNuclearProcess::evg_drivers_
private

One GENIE event generator driver per target isotope.

Definition at line 121 of file GenieElectroNuclearProcess.h.

Referenced by discoverFromVolume(), discoverIsotopesForElement(), GetMeanFreePath(), PostStepDoIt(), and setupDrivers().

◆ genie_initialized_

bool simcore::GenieElectroNuclearProcess::genie_initialized_ {false}
private

Flag to lazily sync GENIE random seed on first event.

Definition at line 145 of file GenieElectroNuclearProcess.h.

145{false};

Referenced by PostStepDoIt().

◆ hep_mc3_converter_

genie::HepMC3Converter simcore::GenieElectroNuclearProcess::hep_mc3_converter_
private

Converter from GENIE EventRecord to HepMC3.

Definition at line 124 of file GenieElectroNuclearProcess.h.

Referenced by PostStepDoIt().

◆ message_threshold_file_

std::string simcore::GenieElectroNuclearProcess::message_threshold_file_
private

Path to GENIE message threshold configuration.

Definition at line 139 of file GenieElectroNuclearProcess.h.

Referenced by GenieElectroNuclearProcess(), and initializeGENIE().

◆ only_one_per_event_

bool simcore::GenieElectroNuclearProcess::only_one_per_event_ {true}
private

Only allow one EN interaction per event (then deactivate)

Definition at line 142 of file GenieElectroNuclearProcess.h.

142{true};

Referenced by GenieElectroNuclearProcess(), PostStepDoIt(), and setOnlyOnePerEvent().

◆ partial_sum_sigma_

std::vector<double> simcore::GenieElectroNuclearProcess::partial_sum_sigma_
private

Partial cross-section sums per element, used to select the target isotope in PostStepDoIt via weighted random sampling.

Indexed the same as the material's element vector.

Definition at line 173 of file GenieElectroNuclearProcess.h.

Referenced by GetMeanFreePath(), and PostStepDoIt().

◆ PROCESS_NAME

const std::string simcore::GenieElectroNuclearProcess::PROCESS_NAME = "electronNuclear"
static

Process name — matches the built-in so the bias operator works unchanged.

Definition at line 43 of file GenieElectroNuclearProcess.h.

Referenced by PostStepDoIt(), and simcore::RunManager::TerminateOneEvent().

◆ spline_file_

std::string simcore::GenieElectroNuclearProcess::spline_file_
private

Path to GENIE cross-section spline file.

Definition at line 136 of file GenieElectroNuclearProcess.h.

Referenced by GenieElectroNuclearProcess(), initializeGENIE(), loadAvailableTargets(), and setupDrivers().

◆ targets_

std::vector<int> simcore::GenieElectroNuclearProcess::targets_
private

GENIE target codes (10LZZZAAAI format)

Definition at line 127 of file GenieElectroNuclearProcess.h.

Referenced by discoverIsotopesForElement(), GenieElectroNuclearProcess(), setupDrivers(), and ~GenieElectroNuclearProcess().

◆ tune_

std::string simcore::GenieElectroNuclearProcess::tune_
private

GENIE tune name.

Definition at line 133 of file GenieElectroNuclearProcess.h.

Referenced by GenieElectroNuclearProcess(), and initializeGENIE().

◆ z_to_targets_

std::map<int, std::vector<std::pair<int, double> > > simcore::GenieElectroNuclearProcess::z_to_targets_
private

Lookup map: element Z -> list of (driver_index, abundance).

Built at construction time for fast material matching in GetMeanFreePath.

Definition at line 166 of file GenieElectroNuclearProcess.h.

Referenced by discoverIsotopesForElement(), GenieElectroNuclearProcess(), GetMeanFreePath(), and PostStepDoIt().


The documentation for this class was generated from the following files: