LDMX Software
GenieElectroNuclearProcess.cxx
Go to the documentation of this file.
1
7
8#include "SimCore/G4User/UserEventInformation.h"
9#include "SimCore/G4User/VolumeChecks.h"
10
11// GENIE
12#include "Framework/Conventions/Units.h"
13#include "Framework/EventGen/EventRecord.h"
14#include "Framework/GHEP/GHepParticle.h"
15#include "Framework/Interaction/InitialState.h"
16#include "Framework/Numerical/RandomGen.h"
17#include "Framework/Utils/AppInit.h"
18#include "Framework/Utils/RunOpt.h"
19#include "GENIE/Framework/Interaction/InitialState.h"
20#include "GENIE/Framework/Utils/RunOpt.h"
21#include "HepMC3/GenEvent.h"
22
23// Geant4
24#include "G4DynamicParticle.hh"
25#include "G4Electron.hh"
26#include "G4Element.hh"
27#include "G4EventManager.hh"
28#include "G4IonTable.hh"
29#include "G4Isotope.hh"
30#include "G4LogicalVolume.hh"
31#include "G4LogicalVolumeStore.hh"
32#include "G4Material.hh"
33#include "G4ParticleTable.hh"
34#include "G4ProcessTable.hh"
35#include "G4SystemOfUnits.hh"
36#include "G4Track.hh"
37#include "Randomize.hh"
38
39// ROOT
40#include <TLorentzVector.h>
41#include <TParticle.h>
42
43#include <algorithm>
44#include <cctype>
45#include <cfloat>
46#include <cmath>
47#include <fstream>
48
49namespace simcore {
50
51const std::string GenieElectroNuclearProcess::PROCESS_NAME = "electronNuclear";
52
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}
111
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}
120
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}
146
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}
179
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}
185
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}
208
210 const G4Element* element) {
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}
293
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}
340
341G4bool GenieElectroNuclearProcess::IsApplicable(const G4ParticleDefinition& p) {
342 return &p == G4Electron::Definition();
343}
344
346 const G4Track& track, G4double /*prevStepSize*/,
347 G4ForceCondition* /*condition*/) {
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}
407
409 const G4Track& track, const G4Step& step) {
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}
601
602} // namespace simcore
G4VDiscreteProcess that uses GENIE to simulate electronuclear interactions as the electron traverses ...
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
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)
G4bool IsApplicable(const G4ParticleDefinition &p) override
bool only_one_per_event_
Only allow one EN interaction per event (then deactivate)
bool genie_initialized_
Flag to lazily sync GENIE random seed on first event.
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::set< int > discovered_z_
Z values already checked for isotope discovery.
std::vector< std::unique_ptr< genie::GEVGDriver > > evg_drivers_
One GENIE event generator driver per target isotope.
void loadAvailableTargets()
Parse the spline file for the set of target nuclei that have cross-section splines available.
G4double GetMeanFreePath(const G4Track &track, G4double prevStepSize, G4ForceCondition *condition) override
Calculate the mean free path for the electronuclear process.
std::vector< double > abundances_
Relative abundances for each target.
std::string discover_volume_
Name of the volume to auto-discover targets from (e.g.
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...
bool auto_discover_
Whether targets are auto-discovered from the Geant4 geometry.
GenieElectroNuclearProcess(const framework::config::Parameters &params)
Constructor.
G4VParticleChange * PostStepDoIt(const G4Track &track, const G4Step &step) override
Called by Geant4 when this process fires.
std::map< int, std::vector< std::pair< int, double > > > z_to_targets_
Lookup map: element Z -> list of (driver_index, abundance).
std::set< int > available_targets_
Target nuclei (10LZZZAAAI codes) that have splines in the spline file.
genie::HepMC3Converter hep_mc3_converter_
Converter from GENIE EventRecord to HepMC3.
std::string message_threshold_file_
Path to GENIE message threshold configuration.
std::string spline_file_
Path to GENIE cross-section spline file.
void discoverIsotopesForElement(const G4Element *element)
Discover isotopes from a G4Element and set up GENIE drivers (auto mode)
Encapsulates user defined information associated with a Geant4 event.
Dynamically loadable photonuclear models either from SimCore or external libraries implementing this ...