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/Utils/AppInit.h"
17#include "Framework/Utils/RunOpt.h"
18#include "GENIE/Framework/Interaction/InitialState.h"
19#include "GENIE/Framework/Utils/RunOpt.h"
20
21// Geant4
22#include "G4DynamicParticle.hh"
23#include "G4Electron.hh"
24#include "G4Element.hh"
25#include "G4EventManager.hh"
26#include "G4IonTable.hh"
27#include "G4Isotope.hh"
28#include "G4LogicalVolume.hh"
29#include "G4LogicalVolumeStore.hh"
30#include "G4Material.hh"
31#include "G4ParticleTable.hh"
32#include "G4SystemOfUnits.hh"
33#include "G4Track.hh"
34#include "Randomize.hh"
35
36// ROOT
37#include <TLorentzVector.h>
38
39#include <cctype>
40#include <cfloat>
41#include <cmath>
42#include <fstream>
43
44namespace simcore {
45
46const std::string GenieElectroNuclearProcess::PROCESS_NAME = "electronNuclear";
47
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}
106
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}
115
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}
141
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}
174
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}
180
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}
203
205 const G4Element* element) {
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}
288
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}
335
336G4bool GenieElectroNuclearProcess::IsApplicable(const G4ParticleDefinition& p) {
337 return &p == G4Electron::Definition();
338}
339
341 const G4Track& track, G4double /*prevStepSize*/,
342 G4ForceCondition* /*condition*/) {
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}
402
404 const G4Track& track, const G4Step& step) {
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}
596
597} // namespace simcore
G4VDiscreteProcess that uses GENIE to simulate electronuclear interactions as the electron traverses ...
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
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)
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 ...