LDMX Software
simcore::bertini::LDMXCascadeInterface Class Reference

G4CascadeInterface that captures cascade history via preprocessor hack. More...

#include <LDMXCascadeInterface.h>

Public Member Functions

 LDMXCascadeInterface (const G4String &name="LDMXBertiniCascade")
 
G4HadFinalState * ApplyYourself (const G4HadProjectile &projectile, G4Nucleus &targetNucleus) override
 
void setEnergyThreshold (double threshold)
 
double getEnergyThreshold () const
 
const ldmx::CascadeHistorygetLastCascadeHistory () const
 Returns nullptr if no history was captured.
 
ldmx::CascadeHistory extractHistory ()
 Move the captured history out.
 
bool hasHistory () const
 
void setIncidentTrackId (int trackId)
 

Private Member Functions

void ensureCascadeHistoryExists ()
 Ensure the G4CascadeHistory object exists in the cascader Geant4 only creates this if G4CASCADE_SHOW_HISTORY envvar is set, so we force-create it here to enable history capture.
 
void captureHistory ()
 Extract history from the internal G4CascadeHistory Navigates: this->collider->theIntraNucleiCascader->theCascadeHistory.
 
void captureDeexcitationProducts (G4HadFinalState *finalState)
 Capture de-excitation products from G4HadFinalState.
 

Private Attributes

double energy_threshold_ {5000.0}
 Minimum photon energy threshold for recording [MeV].
 
int incident_track_id_ {-1}
 Track ID of incident particle.
 
ldmx::CascadeHistory last_history_
 Captured history from last cascade.
 

Detailed Description

G4CascadeInterface that captures cascade history via preprocessor hack.

After ApplyYourself(), call extractHistory() to get the cascade in LDMX format.

Definition at line 28 of file LDMXCascadeInterface.h.

Constructor & Destructor Documentation

◆ LDMXCascadeInterface()

simcore::bertini::LDMXCascadeInterface::LDMXCascadeInterface ( const G4String & name = "LDMXBertiniCascade")

Definition at line 203 of file LDMXCascadeInterface.cxx.

204 : G4CascadeInterface(name), incident_track_id_{-1} {}
int incident_track_id_
Track ID of incident particle.

Member Function Documentation

◆ ApplyYourself()

G4HadFinalState * simcore::bertini::LDMXCascadeInterface::ApplyYourself ( const G4HadProjectile & projectile,
G4Nucleus & targetNucleus )
override

Definition at line 208 of file LDMXCascadeInterface.cxx.

209 {
210 double photon_energy = projectile.GetTotalEnergy();
211
212 ldmx_log(debug) << "LDMXCascadeInterface::ApplyYourself";
213 ldmx_log(debug) << " Track ID: " << incident_track_id_;
214 ldmx_log(debug) << " Photon energy: " << photon_energy << " MeV";
215 ldmx_log(debug) << " Energy threshold: " << energy_threshold_ << " MeV";
216
217 last_history_.clear();
218
219 bool should_record = (photon_energy >= energy_threshold_);
220
221 if (should_record) {
222 ldmx_log(debug) << " Recording history (above threshold)";
223 // Geant4 only creates the history object if G4CASCADE_SHOW_HISTORY is set,
224 // so we force-create it here
226 } else {
227 ldmx_log(debug) << " Skipping history (below threshold)";
228 }
229
230 G4HadFinalState* result =
231 G4CascadeInterface::ApplyYourself(projectile, targetNucleus);
232
233 if (should_record) {
235 last_history_.setIncidentEnergy(photon_energy);
236
237 ldmx_log(debug) << " Captured " << last_history_.getSteps().size()
238 << " steps";
239
240 // De-excitation (evaporation, gamma) happens after the cascade via
241 // G4ExcitationHandler. These products appear in the final state but
242 // are not in G4CascadeHistory, so we capture them separately.
244
245 ldmx_log(debug) << " Total steps: " << last_history_.getSteps().size();
246
247 if (!last_history_.empty()) {
248 CascadeHistoryStore::getInstance().addHistory(incident_track_id_,
250 }
251 }
252
253 return result;
254}
ldmx::CascadeHistory last_history_
Captured history from last cascade.
void captureDeexcitationProducts(G4HadFinalState *finalState)
Capture de-excitation products from G4HadFinalState.
void ensureCascadeHistoryExists()
Ensure the G4CascadeHistory object exists in the cascader Geant4 only creates this if G4CASCADE_SHOW_...
void captureHistory()
Extract history from the internal G4CascadeHistory Navigates: this->collider->theIntraNucleiCascader-...
double energy_threshold_
Minimum photon energy threshold for recording [MeV].

◆ captureDeexcitationProducts()

void simcore::bertini::LDMXCascadeInterface::captureDeexcitationProducts ( G4HadFinalState * finalState)
private

Capture de-excitation products from G4HadFinalState.

De-excitation (evaporation, gamma, fission) is handled by G4ExcitationHandler after the cascade and isn't recorded in G4CascadeHistory. Identifies them by comparing final state secondaries against cascade escapees.

Definition at line 439 of file LDMXCascadeInterface.cxx.

440 {
441 if (!finalState) return;
442
443 int n_secondaries = finalState->GetNumberOfSecondaries();
444 if (n_secondaries == 0) return;
445
446 // Match final state particles against cascade escapees to identify
447 // de-excitation products. Energy matching has 1 MeV tolerance for recoil.
448 struct EscapedParticle {
449 int pdg_;
450 double energy_;
451 bool matched_;
452 };
453 std::vector<EscapedParticle> cascade_escaped;
454
455 for (const auto& step : last_history_.getSteps()) {
456 if (step.didEscape()) {
457 cascade_escaped.push_back({step.getPdgId(), step.getEnergy(), false});
458 }
459 }
460
461 int next_history_id = 0;
462 for (const auto& step : last_history_.getSteps()) {
463 if (step.getHistoryId() >= next_history_id) {
464 next_history_id = step.getHistoryId() + 1;
465 }
466 }
467
468 for (int i = 0; i < n_secondaries; ++i) {
469 G4HadSecondary* secondary = finalState->GetSecondary(i);
470 if (!secondary) continue;
471
472 const G4DynamicParticle* dyn_particle = secondary->GetParticle();
473 if (!dyn_particle) continue;
474
475 int pdg = dyn_particle->GetPDGcode();
476 double energy = dyn_particle->GetTotalEnergy(); // MeV
477
478 bool is_deexcitation = true;
479 const double energy_tolerance = 1.0;
480
481 for (auto& escaped : cascade_escaped) {
482 if (!escaped.matched_ && escaped.pdg_ == pdg &&
483 std::abs(escaped.energy_ - energy) < energy_tolerance) {
484 escaped.matched_ = true;
485 is_deexcitation = false;
486 break;
487 }
488 }
489
490 if (is_deexcitation) {
492 step.setHistoryId(next_history_id++);
493 step.setParentId(-2); // marker for de-excitation origin
494 step.setPdgId(pdg);
495
496 G4ThreeVector mom = dyn_particle->GetMomentum();
497 step.setMomentum(mom.x(), mom.y(), mom.z(), energy);
498 step.setPosition(0, 0, 0);
499 step.setGeneration(-1); // marker for de-excitation
500 step.setZone(0);
501 step.setInteracted(false);
502 step.setEscaped(true);
503 step.setStage(ldmx::CascadeStage::DEEXCITATION);
504
505 last_history_.addStep(std::move(step));
506 }
507 }
508}
Single particle state in the Bertini intranuclear cascade.
Definition CascadeStep.h:39

◆ captureHistory()

void simcore::bertini::LDMXCascadeInterface::captureHistory ( )
private

Extract history from the internal G4CascadeHistory Navigates: this->collider->theIntraNucleiCascader->theCascadeHistory.

Definition at line 280 of file LDMXCascadeInterface.cxx.

280 {
281 // Navigate: this->collider->theIntraNucleiCascader->theCascadeHistory
282 if (!collider) return;
283
284 G4IntraNucleiCascader* cascader = collider->theIntraNucleiCascader;
285 if (!cascader) return;
286
287 G4CascadeHistory* g4_history = cascader->theCascadeHistory;
288 if (!g4_history) return;
289
290 last_history_.setIncidentTrackId(incident_track_id_);
291
292 if (cascader->tnuclei) {
293 last_history_.setTargetNucleus(cascader->tnuclei->getA(),
294 cascader->tnuclei->getZ());
295 }
296
297 const std::vector<G4CascadeHistory::HistoryEntry>& entries =
298 g4_history->theHistory;
299
300 if (entries.empty()) {
301 return;
302 }
303
304 // Build parent ID map from daughter lists
305 std::vector<int> parent_ids(entries.size(), -1);
306
307 for (size_t i = 0; i < entries.size(); ++i) {
308 const auto& entry = entries[i];
309 for (int d = 0; d < entry.n && d < 10; ++d) {
310 int daughter_id = entry.dId[d];
311 if (daughter_id >= 0 &&
312 static_cast<size_t>(daughter_id) < entries.size()) {
313 parent_ids[daughter_id] = static_cast<int>(i);
314 }
315 }
316 }
317
318 // First pass: convert G4 entries to LDMX steps
319 std::vector<ldmx::CascadeStep> steps;
320 steps.reserve(entries.size());
321
322 for (size_t i = 0; i < entries.size(); ++i) {
323 const auto& entry = entries[i];
324 int parent_id = parent_ids[i];
325
327
328 const G4CascadParticle& cpart = entry.cpart;
329 const G4InuclElementaryParticle& particle = cpart.getParticle();
330
331 step.setHistoryId(cpart.getHistoryId());
332 step.setParentId(parent_id);
333 step.setPdgId(getPdgCode(particle.type()));
334
335 // Geant4 Bertini uses GeV for momentum, fm for position
336 G4LorentzVector mom = cpart.getMomentum();
337 step.setMomentum(mom.px() * 1000.0, mom.py() * 1000.0, mom.pz() * 1000.0,
338 mom.e() * 1000.0);
339
340 const G4ThreeVector& pos = cpart.getPosition();
341 step.setPosition(pos.x(), pos.y(), pos.z());
342
343 step.setGeneration(cpart.getGeneration());
344 step.setZone(cpart.getCurrentZone());
345
346 std::vector<int> daughter_ids;
347 for (int d = 0; d < entry.n && d < 10; ++d) {
348 daughter_ids.push_back(entry.dId[d]);
349 }
350 step.setDaughterIds(daughter_ids);
351
352 bool interacted = (entry.n > 0);
353 step.setInteracted(interacted);
354
355 // Escaped if didn't interact (simplification - true escape needs more
356 // tracking)
357 bool escaped = !interacted && cpart.getGeneration() >= 0;
358 step.setEscaped(escaped);
359 step.setTargetPdgId(0); // inferred in second pass
360
361 ldmx::CascadeStage stage = ldmx::CascadeStage::UNKNOWN;
362 int generation = cpart.getGeneration();
363
364 if (generation == 0) {
365 stage = ldmx::CascadeStage::INCIDENT;
366 } else if (generation == 1) {
367 stage = ldmx::CascadeStage::PRIMARY;
368 } else if (!interacted && !escaped) {
369 stage = ldmx::CascadeStage::ABSORBED;
370 } else if (escaped && generation <= 2) {
371 stage = ldmx::CascadeStage::PREEQUILIBRIUM;
372 } else {
373 stage = ldmx::CascadeStage::CASCADE;
374 }
375 step.setStage(stage);
376
377 steps.push_back(std::move(step));
378 }
379
380 // Second pass: infer target nucleon from charge/baryon conservation
381 for (auto& step : steps) {
382 if (!step.didInteract()) continue;
383
384 int bullet_charge = getCharge(step.getPdgId());
385 int bullet_baryon = getBaryonNumber(step.getPdgId());
386 int daughter_charge = 0;
387 int daughter_baryon = 0;
388
389 for (int daughter_id : step.getDaughterIds()) {
390 for (const auto& s : steps) {
391 if (s.getHistoryId() == daughter_id) {
392 daughter_charge += getCharge(s.getPdgId());
393 daughter_baryon += getBaryonNumber(s.getPdgId());
394 break;
395 }
396 }
397 }
398
399 int delta_charge = daughter_charge - bullet_charge;
400 int delta_baryon = daughter_baryon - bullet_baryon;
401 step.setTargetPdgId(inferTargetPdg(delta_charge, delta_baryon));
402 }
403
404 last_history_.reserve(steps.size());
405 for (auto& step : steps) {
406 last_history_.addStep(std::move(step));
407 }
408
409 // Excitation energy approximation: E_incident - sum(KE of escaped)
410 double total_escaped_energy = 0.0;
411 int escaped_protons = 0;
412 int escaped_neutrons = 0;
413
414 for (const auto& step : last_history_.getSteps()) {
415 if (step.didEscape()) {
416 total_escaped_energy += step.getKineticEnergy();
417 int pdg = step.getPdgId();
418 if (pdg == 2212)
419 escaped_protons++;
420 else if (pdg == 2112)
421 escaped_neutrons++;
422 }
423 }
424
425 double excitation_energy =
426 last_history_.getIncidentEnergy() - total_escaped_energy;
427 if (excitation_energy < 0) excitation_energy = 0;
428 last_history_.setExcitationEnergy(excitation_energy);
429
430 int target_a = last_history_.getTargetA();
431 int target_z = last_history_.getTargetZ();
432 int residual_a = target_a - escaped_protons - escaped_neutrons;
433 int residual_z = target_z - escaped_protons;
434 if (residual_a < 0) residual_a = 0;
435 if (residual_z < 0) residual_z = 0;
436 last_history_.setResidualNucleus(residual_a, residual_z);
437}
CascadeStage
Classification of cascade particle stages.
Definition CascadeStep.h:20

◆ ensureCascadeHistoryExists()

void simcore::bertini::LDMXCascadeInterface::ensureCascadeHistoryExists ( )
private

Ensure the G4CascadeHistory object exists in the cascader Geant4 only creates this if G4CASCADE_SHOW_HISTORY envvar is set, so we force-create it here to enable history capture.

Definition at line 256 of file LDMXCascadeInterface.cxx.

256 {
257 // Force-create the cascade history object if it doesn't exist
258 // Geant4 normally only creates this if G4CASCADE_SHOW_HISTORY envvar is set
259
260 if (!collider) {
261 ldmx_log(debug) << " ensureCascadeHistoryExists: no collider yet";
262 return;
263 }
264
265 G4IntraNucleiCascader* cascader = collider->theIntraNucleiCascader;
266 if (!cascader) {
267 ldmx_log(debug) << " ensureCascadeHistoryExists: no cascader yet";
268 return;
269 }
270
271 if (!cascader->theCascadeHistory) {
272 ldmx_log(debug)
273 << " ensureCascadeHistoryExists: creating G4CascadeHistory";
274 cascader->theCascadeHistory = new G4CascadeHistory;
275 } else {
276 ldmx_log(debug) << " ensureCascadeHistoryExists: history already exists";
277 }
278}

◆ extractHistory()

ldmx::CascadeHistory simcore::bertini::LDMXCascadeInterface::extractHistory ( )
inline

Move the captured history out.

Definition at line 45 of file LDMXCascadeInterface.h.

45{ return std::move(last_history_); }

References last_history_.

◆ getEnergyThreshold()

double simcore::bertini::LDMXCascadeInterface::getEnergyThreshold ( ) const
inline

Definition at line 37 of file LDMXCascadeInterface.h.

37{ return energy_threshold_; }

◆ getLastCascadeHistory()

const ldmx::CascadeHistory * simcore::bertini::LDMXCascadeInterface::getLastCascadeHistory ( ) const
inline

Returns nullptr if no history was captured.

Definition at line 40 of file LDMXCascadeInterface.h.

40 {
41 return last_history_.empty() ? nullptr : &last_history_;
42 }

References last_history_.

◆ hasHistory()

bool simcore::bertini::LDMXCascadeInterface::hasHistory ( ) const
inline

Definition at line 47 of file LDMXCascadeInterface.h.

47{ return !last_history_.empty(); }

◆ setEnergyThreshold()

void simcore::bertini::LDMXCascadeInterface::setEnergyThreshold ( double threshold)
inline

Definition at line 36 of file LDMXCascadeInterface.h.

36{ energy_threshold_ = threshold; }

◆ setIncidentTrackId()

void simcore::bertini::LDMXCascadeInterface::setIncidentTrackId ( int trackId)
inline

Definition at line 49 of file LDMXCascadeInterface.h.

49{ incident_track_id_ = trackId; }

Member Data Documentation

◆ energy_threshold_

double simcore::bertini::LDMXCascadeInterface::energy_threshold_ {5000.0}
private

Minimum photon energy threshold for recording [MeV].

Definition at line 76 of file LDMXCascadeInterface.h.

76{5000.0};

◆ incident_track_id_

int simcore::bertini::LDMXCascadeInterface::incident_track_id_ {-1}
private

Track ID of incident particle.

Definition at line 79 of file LDMXCascadeInterface.h.

79{-1};

◆ last_history_

ldmx::CascadeHistory simcore::bertini::LDMXCascadeInterface::last_history_
private

Captured history from last cascade.

Definition at line 82 of file LDMXCascadeInterface.h.

Referenced by extractHistory(), and getLastCascadeHistory().


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