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 53 of file GenieElectroNuclearProcess.cxx.

55 : G4VDiscreteProcess(PROCESS_NAME, fElectromagnetic) {
56 // Use a distinct EM subtype so we don't collide with other EM processes.
57 // 64 is arbitrary but distinct from standard EM subtypes and the DarkBrem
58 // subtype (63).
59 SetProcessSubType(64);
60
61 // Read configuration (targets/abundances are optional — empty means
62 // auto-discover)
63 targets_ = params.get<std::vector<int>>("targets", {});
64 abundances_ = params.get<std::vector<double>>("abundances", {});
65 discover_volume_ = params.get<std::string>("discover_volume", "");
66 tune_ = params.get<std::string>("tune");
67 spline_file_ = params.get<std::string>("spline_file");
68 message_threshold_file_ = params.get<std::string>("message_threshold_file");
69 only_one_per_event_ = params.get<bool>("only_one_per_event");
70
71 // Initialize GENIE framework (tune, splines) — independent of targets
73
74 if (!targets_.empty()) {
75 // Manual mode: user-specified targets and abundances
76 // Normalize abundances
77 double abundance_sum = 0;
78 for (auto a : abundances_) abundance_sum += a;
79 if (abundance_sum > 0) {
80 for (auto& a : abundances_) a /= abundance_sum;
81 }
82
84
85 // Build the Z -> targets lookup map
86 for (size_t i = 0; i < targets_.size(); ++i) {
87 int z = (targets_[i] / 10000) % 1000;
88 z_to_targets_[z].emplace_back(static_cast<int>(i), abundances_[i]);
89 }
90
91 ldmx_log(info) << "GenieElectroNuclearProcess configured with "
92 << targets_.size() << " manual target(s), tune=" << tune_;
93 } else {
94 // Auto-discovery mode: targets are discovered once from the configured
95 // volume on the first GetMeanFreePath call (geometry exists by then).
96 if (discover_volume_.empty()) {
97 EXCEPTION_RAISE(
98 "ConfigurationException",
99 "GenieElectroNuclearProcess is in auto-discovery mode (no manual "
100 "'targets') but 'discover_volume' is empty. Set genie_nuclear."
101 "discover_volume to the volume to discover targets from (e.g. "
102 "\"target_region\"), or specify 'targets' and 'abundances' "
103 "manually.");
104 }
105 auto_discover_ = true;
106 ldmx_log(info) << "GenieElectroNuclearProcess configured for auto-discovery"
107 << " from volume '" << discover_volume_
108 << "', tune=" << tune_;
109 }
110}
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
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 112 of file GenieElectroNuclearProcess.cxx.

112 {
113 ldmx_log(info) << "--- GENIE Process Summary ---";
114 for (size_t i = 0; i < targets_.size(); ++i) {
115 ldmx_log(info) << " Target=" << targets_[i]
116 << " Abundance=" << abundances_[i];
117 }
118 ldmx_log(info) << "--- GENIE Process Summary END ---";
119}

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 294 of file GenieElectroNuclearProcess.cxx.

294 {
295 namespace vc = simcore::g4user::volumechecks;
296
297 // Select the same volume-matching test the ElectroNuclear bias operator
298 // uses, so the discovered targets correspond to the biased volume.
299 using Test = bool (*)(G4LogicalVolume*, const std::string&);
300 Test include_volume_test = nullptr;
301 if (discover_volume_ == "ecal") {
302 include_volume_test = &vc::isInEcal;
303 } else if (discover_volume_ == "old_ecal") {
304 include_volume_test = &vc::isInEcalOld;
305 } else if (discover_volume_ == "target") {
306 include_volume_test = &vc::isInTargetOnly;
307 } else if (discover_volume_ == "target_region") {
308 include_volume_test = &vc::isInTargetRegion;
309 } else if (discover_volume_ == "hcal") {
310 include_volume_test = &vc::isInHcal;
311 } else {
312 include_volume_test = &vc::nameContains;
313 }
314
315 int n_volumes = 0;
316 for (G4LogicalVolume* volume : *G4LogicalVolumeStore::GetInstance()) {
317 if (!include_volume_test(volume, discover_volume_)) continue;
318 ++n_volumes;
319
320 G4Material* mat = volume->GetMaterial();
321 if (!mat) continue;
322 const G4ElementVector* els = mat->GetElementVector();
323 for (size_t i = 0; i < mat->GetNumberOfElements(); ++i) {
324 // discoverIsotopesForElement de-duplicates by Z internally
326 }
327 }
328
329 if (evg_drivers_.empty()) {
330 ldmx_log(warn) << "Auto-discovery found no target isotopes in volume(s) "
331 << "matching '" << discover_volume_ << "' (" << n_volumes
332 << " volume(s) matched). Electronuclear interactions will "
333 << "not fire — check the discover_volume setting.";
334 } else {
335 ldmx_log(info) << "Auto-discovered " << evg_drivers_.size()
336 << " target isotope(s) from " << n_volumes
337 << " volume(s) matching '" << discover_volume_ << "'";
338 }
339}
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 209 of file GenieElectroNuclearProcess.cxx.

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

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

121 {
122 // Initialize RunOpt with EM event generator list
123 char* in_arr[3] = {const_cast<char*>(""),
124 const_cast<char*>("--event-generator-list"),
125 const_cast<char*>("EM")};
126 genie::RunOpt::Instance()->ReadFromCommandLine(3, in_arr);
127
128 // Set message thresholds
129 genie::utils::app_init::MesgThresholds(message_threshold_file_);
130
131 // Set tune
132 genie::RunOpt::Instance()->SetTuneName(tune_);
133 if (!genie::RunOpt::Instance()->Tune()) {
134 EXCEPTION_RAISE("ConfigurationException", "No TuneId in RunOption.");
135 }
136 genie::RunOpt::Instance()->BuildTune();
137
138 // Load cross-section spline file
139 genie::utils::app_init::XSecTable(spline_file_, true);
140
141 // Record which target nuclei actually have splines so we can skip the rest
143
144 genie::GHepRecord::SetPrintLevel(0);
145}
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 341 of file GenieElectroNuclearProcess.cxx.

341 {
342 return &p == G4Electron::Definition();
343}

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 147 of file GenieElectroNuclearProcess.cxx.

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

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

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

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 180 of file GenieElectroNuclearProcess.cxx.

180 {
181 // Fail open: if we could not parse the spline file, don't filter anything.
182 if (available_targets_.empty()) return true;
183 return available_targets_.count(target_code) > 0;
184}

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: