ATLAS Offline Software
Loading...
Searching...
No Matches
ActsFatrasG4Tool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5#include <algorithm>
6#include <random>
7
8// Amg includes
9// These need to go before ActsFatras to not break definitions
12
13// class header
14#include "ActsFatrasG4Tool.h"
15
16// CLHEP
17#include "CLHEP/Random/RandFlat.h"
18#include "CLHEP/Random/RandomEngine.h"
19
20//ATLAS includes
23#include "MCTruth/TrackHelper.h"
24
25// ACTS
27#include "TrkSurfaces/Surface.h"
28#include "Acts/ActsVersion.hpp"
29#include <Acts/Utilities/StringHelpers.hpp>
30
31// Geant4
32#include "G4ParticleTable.hh"
33#include "G4ParticleDefinition.hh"
34#include "G4VSolid.hh"
35#include "G4LogicalVolume.hh"
36#include "G4Region.hh"
37#include "G4VPhysicalVolume.hh"
38
39using namespace Acts::UnitLiterals;
40
41
43 // print version name
44 ATH_MSG_INFO(name() << " initialize start" );
45 ATH_MSG_INFO("ActsFatrasG4Tool updated with ACTS version: v"
46 << Acts::VersionMajor << "." << Acts::VersionMinor << "."
47 << Acts::VersionPatch << " [" << Acts::CommitHash.value_or("unknown hash") << "]");
48
49 // setup logger
50 m_logger = makeActsAthenaLogger(this, std::string("ActsFatras"),std::string("ActsFatrasG4Tool"));
51
52 // retrieve tracking geo tool
54 m_trackingGeometry = m_trackingGeometrySvc->trackingGeometry();
55 ATH_MSG_DEBUG("Tracking geometry OK");
56 ATH_CHECK(m_ctxProvider.initialize());
57
58
59 // Random number service
60 if (m_rngSvc.retrieve().isFailure()) {
61 ATH_MSG_FATAL("Could not retrieve " << m_rngSvc);
62 return StatusCode::FAILURE;
63 }
64 // Get own engine with own seeds
65 m_randomEngine = m_rngSvc->getEngine(this, m_randomEngineName.value());
66 if (!m_randomEngine) {
67 ATH_MSG_FATAL("Could not get random engine '" << m_randomEngineName.value() << "'");
68 return StatusCode::FAILURE;
69 }
70 ATH_MSG_DEBUG("Random number services OK");
71
72 // Get the Pixel Identifier-helper, must be in initialize() after calling detector store
73 ATH_CHECK(detStore()->retrieve(m_pixIdHelper, "PixelID"));
74 // Get the SCT Identifier-helper
75 ATH_CHECK(detStore()->retrieve(m_sctIdHelper, "SCT_ID"));
76 ATH_MSG_DEBUG("Pixel and SCT identifiers OK");
77
78 return StatusCode::SUCCESS;
79}
80
82{
83 ATH_MSG_INFO("Configuring Fatras simulator...");
84 // protection against multiple calls
85 if (m_simulator) {
86 ATH_MSG_DEBUG("Simulator already configured — skipping");
87 return StatusCode::SUCCESS;
88 }
89
90 // construct the ACTS simulator
91 // Magnetic field
92 m_bField = std::make_shared<ATLASMagneticFieldWrapper>();
93 // Navigator (needs tracking geometry ready)
94 m_navigator = std::make_unique<Navigator>(Navigator::Config{ m_trackingGeometry }, m_logger);
95 // Steppers
96 ChargedStepper chargedStepper(m_bField);
97 NeutralStepper neutralStepper;
98 // Propagators
99 ChargedPropagator chargedPropagator(std::move(chargedStepper), *m_navigator, m_logger);
100 NeutralPropagator neutralPropagator(std::move(neutralStepper), *m_navigator, m_logger);
101 // Single-particle simulations
102
103 // Single particle simulations
104 ChargedSimulation simCharged(std::move(chargedPropagator), m_logger);
105 NeutralSimulation simNeutral(std::move(neutralPropagator), m_logger);
106
107 // construct the ACTS simulator/dispatcher
108 m_simulator = std::make_unique<Simulation>(std::move(simCharged), std::move(simNeutral));
109
110 // Acts propagater options (charged) particles
111 m_simulator->charged.maxStepSize = m_maxStepSize;
112 m_simulator->charged.maxStep = m_maxStep;
113 m_simulator->charged.pathLimit = m_pathLimit;
114 m_simulator->charged.maxRungeKuttaStepTrials = m_maxRungeKuttaStepTrials;
115 m_simulator->charged.loopProtection = m_loopProtection;
116 m_simulator->charged.loopFraction = m_loopFraction;
117 m_simulator->charged.targetTolerance = m_tolerance;
118 m_simulator->charged.stepSizeCutOff = m_stepSizeCutOff;
119
120 // Create interaction list
121 ATH_MSG_VERBOSE(name() << " Min pT for interaction " << m_interact_minPt * Acts::UnitConstants::MeV << " MeV");
122 m_simulator->charged.interactions = ActsFatras::makeStandardChargedElectroMagneticInteractions(m_interact_minPt * Acts::UnitConstants::MeV);
123
124 ATH_MSG_VERBOSE("Simulator configured:");
125 ATH_MSG_VERBOSE(" maxStepSize = " << m_maxStepSize);
126 ATH_MSG_VERBOSE(" maxStep = " << m_maxStep);
127 ATH_MSG_INFO("Fatras simulator configured successfully.");
128
129 return StatusCode::SUCCESS;
130}
131
133 ATH_MSG_INFO("ActsFatrasG4Tool::initializePhysics() called");
134 // make simulator
136 return StatusCode::SUCCESS;
137}
138
139
140std::vector<ActsFatras::Particle> ActsFatrasG4Tool::buildActsInputFromG4(const G4Track& track)
141{
142 TrackHelper helper(&track);
143
144 int barcode = helper.GetBarcode();
145 if (barcode == 0){
146 barcode = track.GetTrackID();
147 ATH_MSG_DEBUG("Track barcode is 0, using TrackID " << barcode
148 << " PDG=" << track.GetDefinition()->GetPDGEncoding()
149 << " Ekin=" << track.GetKineticEnergy()
150 );
151 }
152
153 ActsFatras::Barcode fatrasBarcode = ActsFatras::Barcode().withVertexPrimary(0).withParticle(barcode);
154 auto fatrasPDG = static_cast<Acts::PdgParticle>(track.GetDefinition()->GetPDGEncoding());
155 double fatrasCharge = track.GetDefinition()->GetPDGCharge();
156 double fatrasMass = track.GetDefinition()->GetPDGMass(); // already MeV
157
158 ActsFatras::Particle particle(fatrasBarcode, fatrasPDG, fatrasCharge, fatrasMass);
159
160 // ---- Direction magnitude ----
161 particle.setDirection(track.GetMomentum().x(), track.GetMomentum().y(), track.GetMomentum().z());
162
163 // ---- Momentum magnitude ----
164 double momentumMeV = track.GetMomentum().mag(); // already MeV
165 particle.setAbsoluteMomentum(momentumMeV);
166
167 // ---- Unit direction ----
168 G4ThreeVector momDir = track.GetMomentumDirection(); // already unit
169 particle.setDirection(momDir.x(), momDir.y(), momDir.z());
170
171 // ---- 4-Position (mm, ns already) ----
172 particle.setPosition4(Acts::Vector4(
173 track.GetPosition().x(),
174 track.GetPosition().y(),
175 track.GetPosition().z(),
176 track.GetGlobalTime()));
177
178 ATH_MSG_DEBUG("Converted G4->Acts:"
179 << " p(MeV)=" << momentumMeV
180 << " mass(MeV)=" << fatrasMass);
181
182 return { particle };
183}
184
185void ActsFatrasG4Tool::handleSimulationFailures(const Acts::Result<ActsFatras::SingleParticleSimulationResult>& result)
186{
187 if (!result.ok()) {
188 ATH_MSG_ERROR("Fatras simulation failed: " << result.error().message());
189 }
190}
191
193 const G4Track&,
194 const std::vector<ActsFatras::Particle>& simulatedFinalParticles,
195 G4FastStep& fastStep)
196{
197 if (simulatedFinalParticles.empty()){return StatusCode::SUCCESS;}
198
199 const auto& primary = simulatedFinalParticles.front();
200
201 if (!primary.isAlive()) { // if not alive, kill the track in G4
202 fastStep.KillPrimaryTrack();
203 return StatusCode::SUCCESS;
204 }
205
206 double p = primary.absoluteMomentum(); // MeV
207 double m = primary.mass(); // MeV
208
209 double energy = std::sqrt(p*p + m*m); // total energy in MeV
210 double kinetic = (energy - m) * CLHEP::MeV; // convert back to kinetic energy in MeV for G4
211
212 fastStep.ProposePrimaryTrackFinalKineticEnergy(kinetic);
213
214 fastStep.ProposePrimaryTrackFinalMomentumDirection(
215 G4ThreeVector(primary.direction().x(), primary.direction().y(), primary.direction().z()).unit()
216 );
217
218 fastStep.ProposePrimaryTrackFinalPosition(
219 G4ThreeVector(primary.position().x(), primary.position().y(), primary.position().z())
220 );
221 return StatusCode::SUCCESS;
222}
223
225 const std::vector<ActsFatras::Particle>& simulatedFinal,
226 const G4Track&,
227 G4FastStep& fastStep)
228{
229 for (size_t i = 1; i < simulatedFinal.size(); ++i) {
230 const auto& sec = simulatedFinal[i];
231
232 G4ParticleDefinition* def = G4ParticleTable::GetParticleTable()->FindParticle(sec.pdg());
233
234 if (!def){continue;}
235
236 G4ThreeVector dir(sec.direction().x(), sec.direction().y(), sec.direction().z());
237 dir = dir.unit();
238 double momentumMeV = sec.absoluteMomentum();
239
240 G4DynamicParticle dyn(def, dir, momentumMeV);
241 fastStep.CreateSecondaryTrack(
242 dyn,
243 G4ThreeVector(sec.position().x(),
244 sec.position().y(),
245 sec.position().z()),
246 sec.time(),
247 true // is local time
248 );
249 }
250 return StatusCode::SUCCESS;
251}
252
253void ActsFatrasG4Tool::debugTrackRegion(const G4FastTrack& fastTrack) const
254{
255 const G4Track& track = *fastTrack.GetPrimaryTrack();
256
257 ATH_MSG_DEBUG("=========== FATRAS REGION DEBUG ===========");
258 // FastSim envelope solid
259 const G4VSolid* envelope = fastTrack.GetEnvelopeSolid();
260 if (envelope) {ATH_MSG_DEBUG("FastSim envelope solid: " << envelope->GetName());}
261 else {ATH_MSG_DEBUG("FastSim envelope solid: NONE");}
262
263 // Physical volume
264 const G4VPhysicalVolume* pv = track.GetVolume();
265 if (pv) {
266 ATH_MSG_DEBUG("Physical volume: " << pv->GetName());
267 const G4LogicalVolume* lv = pv->GetLogicalVolume();
268 if (lv) {
269 const G4Region* region = lv->GetRegion();
270 if (region) {ATH_MSG_DEBUG("Region: " << region->GetName());}
271 else {ATH_MSG_DEBUG("Region: NONE");}
272 }
273 } else {
274 ATH_MSG_DEBUG("Physical volume: NONE");
275 }
276
277 const G4ThreeVector& pos = track.GetPosition();
278 double r = std::sqrt(pos.x()*pos.x() + pos.y()*pos.y());
279
280 ATH_MSG_DEBUG("Position (mm): (" << pos.x()/CLHEP::mm << ", " << pos.y()/CLHEP::mm << ", " << pos.z()/CLHEP::mm << ")");
281 ATH_MSG_DEBUG("R (mm): " << r / CLHEP::mm);
282 ATH_MSG_DEBUG("Ekin (MeV): " << track.GetKineticEnergy()/CLHEP::MeV);
283 ATH_MSG_DEBUG("PDG: " << track.GetDefinition()->GetPDGEncoding());
284 ATH_MSG_DEBUG("===========================================");
285}
286
288 const EventContext& ctx,
289 const G4Track& track,
290 const Acts::GeometryContext& anygctx,
291 const Acts::MagneticFieldContext& mctx,
292 Generator& generator,
293 G4FastStep& fastStep)
294{
295 // NOTE: block below is only meant for DEBUGGING,
296 // Retained here for future debugging purposes
297 // -- It injects a muon particle in InDet,
298 // --- to test if FATRAS properly simulates it and produces/writes expected hits.
299 // DO NOT USE in actual production
300 // PLEASE set m_debugInject in header or jobOption config to false for actual production use.
301
302 ATH_MSG_DEBUG("### DEBUG INJECTION ACTIVE ###");
303
304 // --- Build fake muon ---
305 ActsFatras::Barcode bc = ActsFatras::Barcode().withVertexPrimary(0).withParticle(1);
306
307 Acts::PdgParticle pdg = Acts::PdgParticle(13); // mu-
308 double charge = -1.0;
309 double mass = 105.7; // MeV
310
311 ActsFatras::Particle fakeMuon(bc, pdg, charge, mass);
312
313 fakeMuon.setPosition4({50., 0., 0., 0.});
314 fakeMuon.setDirection(0., 1., 0.);
315 fakeMuon.setAbsoluteMomentum(10000.0); // 10 GeV
316
317 // create input particle with ActsFatras::Particle
318 std::vector<ActsFatras::Particle> inputParticle{ fakeMuon };
319 // declare variables to be passed
320 std::vector<ActsFatras::Particle> simulatedInitialParticles;
321 std::vector<ActsFatras::Particle> simulatedFinalParticles;
322 std::vector<ActsFatras::Hit> hits;
323
324 if (!m_simulator) {
325 ATH_MSG_ERROR("Simulator not configured");
326 return StatusCode::FAILURE;
327 }
328
329 auto result = m_simulator->simulate(anygctx, mctx, generator, inputParticle, simulatedInitialParticles, simulatedFinalParticles, hits);
330
331 if (!result.ok()) {
332 ATH_MSG_ERROR("Fatras debug injection failed: " << result.error().message());
333 fastStep.KillPrimaryTrack();
334 return StatusCode::FAILURE;
335 }
336
337 ATH_MSG_DEBUG("Injected muon hits produced: " << hits.size());
338
339 if (createHitsFromG4(ctx, track, *m_trackingGeometry, hits).isFailure()) {
340 ATH_MSG_ERROR("Failed to write debug injected hits");
341 fastStep.KillPrimaryTrack();
342 return StatusCode::FAILURE;
343 }
344
345 fastStep.KillPrimaryTrack();
346 // Do not call here below methods, only for simple injection:
347 // applyPrimaryUpdate()
348 // spawnSecondaries()
349 // buildActsInputFromG4()
350
351 ATH_MSG_DEBUG("===================================");
352
353 return StatusCode::SUCCESS;
354}
355
356void ActsFatrasG4Tool::simulateFatrasTrack(const G4FastTrack& fastTrack, G4FastStep& fastStep)
357{
358 // Get primary track
359 const G4Track& track = *fastTrack.GetPrimaryTrack();
360 TrackHelper helper(&track);
361
362 int trackID = track.GetTrackID();
363 int parentID = track.GetParentID();
364 int barcode = helper.GetBarcode();
365 int pdg = track.GetDefinition()->GetPDGEncoding();
366 double ekin = track.GetKineticEnergy() / CLHEP::MeV;
367
368 ATH_MSG_DEBUG("==== G4 Track entering Fatras ====");
369 ATH_MSG_DEBUG("TrackID = " << trackID);
370 ATH_MSG_DEBUG("ParentID = " << parentID);
371 ATH_MSG_DEBUG("Barcode = " << barcode);
372 ATH_MSG_DEBUG("PDG = " << pdg);
373 ATH_MSG_DEBUG("Ekin(MeV)= " << ekin);
374 ATH_MSG_DEBUG("===================================");
375
376 // get EventContext
377 const EventContext& ctx = Gaudi::Hive::currentContext();
378
379 // random seeds
380 m_randomEngine->setSeed(m_randomEngineName, ctx);
381 CLHEP::HepRandomEngine* randomEngine = m_randomEngine->getEngine(ctx);
382
383 // RNG
384 Generator generator(CLHEP::RandFlat::shoot(randomEngine->flat()));
385
386 // get Mag field context, and Geo context
387 ATH_MSG_VERBOSE(name() << " Getting per event Geo and Mag map");
388 auto mctx = m_ctxProvider.getMagneticFieldContext(ctx);
389 auto anygctx = m_ctxProvider.getGeometryContext(ctx);
390
391 // Build Acts input from G4 track
392 std::vector<ActsFatras::Particle> inputParticle;
393
394 // Skip very low energy particles (e.g 1MeV)
395 if (ekin < 1.0) { // in MeV
396 ATH_MSG_DEBUG("Skipping and killing low energy track");
397 fastStep.KillPrimaryTrack();
398 return;
399 }
400
401 if (msgLvl(MSG::DEBUG)){ATH_MSG_DEBUG("m_debugInject runtime value = " << m_debugInject);}
402 if (m_debugInject) {
403 // run the debug injection
404 if (runDebugInjection(ctx, track, anygctx, mctx, generator, fastStep).isFailure()){
405 ATH_MSG_DEBUG("runDebugInjection failed");
406 }
407 return; // return so we escape scope after run, and so we don't enter production loop below
408 } else {
409 // Actual production code: build input from G4 track
410 inputParticle = buildActsInputFromG4(track);
411 }
412
413 // safeguard against geometry pointer returned null or undefined (usually with being outside defined geometry)
414 Acts::Vector3 startPos(track.GetPosition().x(), track.GetPosition().y(), track.GetPosition().z());
415 auto startVolume = m_trackingGeometry->resolveLowestTrackingVolume(anygctx, startPos);
416
417 if(not startVolume.ok() or *startVolume == nullptr){
418 ATH_MSG_DEBUG("Could not resolve the lowest tracking volume, skip FATRAS.");
419 return;
420 }
421
422 // debug region
423 if (msgLvl(MSG::DEBUG)) {
424 const G4VSolid* envelope = fastTrack.GetEnvelopeSolid();
425 if (envelope) {ATH_MSG_DEBUG("FastSim envelope solid name: " << envelope->GetName());}
426 const G4VPhysicalVolume* pv = track.GetVolume();
427 if (pv) {
428 const G4LogicalVolume* lv = pv->GetLogicalVolume();
429 if (lv) {
430 const G4Region* region = lv->GetRegion();
431 if (region) {
432 ATH_MSG_DEBUG("Region name: " << region->GetName());
433 }
434 }
435 }
436 }
437
438 // declare variables to be passed
439 std::vector<ActsFatras::Particle> simulatedInitialParticles;
440 std::vector<ActsFatras::Particle> simulatedFinalParticles;
441 std::vector<ActsFatras::Hit> hits;
442
443 // safety: returns early if m_simulator not called in initializePhysics, or if failed for some reason
444 if (!m_simulator) {
445 ATH_MSG_ERROR("Simulator not configured");
446 return;
447 }
448
449 // do the actual Fatras simulation
450 auto result = m_simulator->simulate(anygctx, mctx, generator, inputParticle, simulatedInitialParticles, simulatedFinalParticles, hits);
451 if (!result.ok()) {
452 const std::string& msg = result.error().message();
453 // volume error
454 if (msg.find("No Volume") != std::string::npos) {
455 const G4VSolid* envelope = fastTrack.GetEnvelopeSolid();
456 const G4ThreeVector& pos = track.GetPosition();
457 double r = std::sqrt(pos.x()*pos.x() + pos.y()*pos.y());
458 auto inside = envelope->Inside(pos);
459 ATH_MSG_ERROR("G4 envelope Inside() = " << inside << ", "
460 << "(Position mm, R mm) = (("
461 << pos.x()/CLHEP::mm << ", "
462 << pos.y()/CLHEP::mm << ", "
463 << pos.z()/CLHEP::mm << "), "
464 << r/CLHEP::mm << ")"
465 );
466 ATH_MSG_ERROR("ActsFatras simulation failed with 'No Volume' error: " << msg);
467 if (msgLvl(MSG::DEBUG)) {
468 const G4ThreeVector& mom = track.GetMomentumDirection();
469 ATH_MSG_DEBUG("=== FATRAS NO VOLUME DEBUG ===");
470 ATH_MSG_DEBUG("PDG = " << track.GetDefinition()->GetPDGEncoding());
471 ATH_MSG_DEBUG("Ekin MeV = " << track.GetKineticEnergy() / CLHEP::MeV);
472 ATH_MSG_DEBUG("Position mm = (" << pos.x()/CLHEP::mm << ", " << pos.y()/CLHEP::mm << ", " << pos.z()/CLHEP::mm << ")");
473 ATH_MSG_DEBUG("R (mm) = " << r / CLHEP::mm);
474 ATH_MSG_DEBUG("Z (mm) = " << pos.z() / CLHEP::mm);
475 ATH_MSG_DEBUG("Direction = (" << mom.x() << ", " << mom.y() << ", " << mom.z() << ")");
476 ATH_MSG_DEBUG("==============================");
477 }
478 ATH_MSG_DEBUG("Track outside Acts tracking geometry -> let G4 continue.");
479 return; // do not kill track, let to G4
480 }
481 // other kinds of error
482 else {
483 // real fatal error
484 ATH_MSG_ERROR("ActsFatras simulation failed: " << msg);
485 fastStep.KillPrimaryTrack();
486 return;
487 }
488 }
489
490 // =========================================================
491 // ======= FATRAS DEBUG PRINTING ===========================
492 // =========================================================
493 if (msgLvl(MSG::DEBUG)) {
494 if (!simulatedInitialParticles.empty()) {
495 const auto& initial = simulatedInitialParticles.front();
496 double totalEdep = 0.;
497 for (const auto& h : hits) {totalEdep += h.depositedEnergy() / Acts::UnitConstants::MeV;}
498 ATH_MSG_DEBUG("===============================================");
499 ATH_MSG_DEBUG("FATRAS TRACK DEBUG");
500 ATH_MSG_DEBUG("PDG: " << static_cast<int>(initial.pdg()));
501 ATH_MSG_DEBUG("Initial momentum [MeV]: " << initial.absoluteMomentum());
502 ATH_MSG_DEBUG("Initial mass [MeV]: " << initial.mass());
503 ATH_MSG_DEBUG("Hits produced: " << hits.size());
504 ATH_MSG_DEBUG("Final particles: " << simulatedFinalParticles.size());
505 ATH_MSG_DEBUG("Total deposited energy [MeV]: " << totalEdep);
506 ATH_MSG_DEBUG("===============================================");
507 }
508
509 // some sanity printing, prints simulatedInitialParticles[0] if it exists, and the number of hits
510 if (!simulatedInitialParticles.empty()) {
511 ATH_MSG_DEBUG(name() << " initial particle " << simulatedInitialParticles.front());
512 }
513 ATH_MSG_DEBUG(name() << " ActsFatras simulator hits: " << hits.size());
514 }
515
516 // Actual part to create hits: fill MT cache only of non-zero detector hits
517 if (!hits.empty()) {
518 if (createHitsFromG4(ctx, track, *m_trackingGeometry, hits).isFailure()) {
519 ATH_MSG_ERROR("Failed to create hits");
520 return;
521 }
522 }
523
524 // Apply propagation result to G4
525 if (applyPrimaryUpdate(track, simulatedFinalParticles, fastStep).isFailure()) {
526 ATH_MSG_ERROR("Failed to apply primary update");
527 return;
528 }
529
530 // Spawn secondaries in Geant4
531 if (spawnSecondaries(simulatedFinalParticles, track, fastStep).isFailure()) {
532 ATH_MSG_ERROR("Failed to spawn secondaries");
533 return;
534 }
535}
536
537// ====================================
538// related to the hits caches / saving
539// ====================================
540const std::vector<SiHit>& ActsFatrasG4Tool::getPixelHitsCache(const EventContext& ctx) const
541{
542 const EventCache* cache = m_eventCache.get(ctx);
543 static const std::vector<SiHit> empty;
544 if (!cache) { return empty; }
545 return cache->pixelHits;
546}
547
548const std::vector<SiHit>& ActsFatrasG4Tool::getSCTHitsCache(const EventContext& ctx) const
549{
550 const EventCache* cache = m_eventCache.get(ctx);
551 static const std::vector<SiHit> empty;
552 if (!cache) { return empty; }
553 return cache->sctHits;
554}
555
556void ActsFatrasG4Tool::clearCaches(const EventContext& ctx) const
557{
558 EventCache* cache = m_eventCache.get(ctx);
559 if (!cache) { return; }
560 cache->pixelHits.clear();
561 cache->sctHits.clear();
562}
563
565 return *m_eventCache.get(ctx);
566}
567// ====================================
568
570 const EventContext& ctx,
571 const G4Track& track,
572 const Acts::TrackingGeometry& trackingGeometry,
573 const std::vector<ActsFatras::Hit>& hits
574) const
575{
576 if (hits.empty()) {
577 return StatusCode::SUCCESS;
578 }
579
580 // ============================================
581 // Get per-slot event cache (MT safe)
582 // ============================================
583 EventCache& evtCache = getCache(ctx);
584
585 // ============================================
586 // Truth handling via TrackHelper (from G4Track case)
587 // ============================================
588 TrackHelper helper(&track);
589 int barcode = helper.GetBarcode();
590
591 // initialize partLink
593
594 if (barcode != 0 && barcode != HepMC::UNDEFINED_ID && barcode != HepMC::INVALID_PARTICLE_ID) {
596 }
597
598 // ============================================
599 // Loop over Fatras hits
600 // ============================================
601 for (const auto& hit : hits) {
602
603 double energyDeposit = hit.depositedEnergy() / Acts::UnitConstants::MeV;
604 double time = ActsTrk::timeToAthena(hit.time());
605 auto geoID = hit.geometryId();
606
607 try {
608
609 auto acts_surface = trackingGeometry.findSurface(geoID);
610 if (!acts_surface) continue;
611
612 const auto *acts_de = getActsDetectorElement(acts_surface);
613 if (!acts_de) continue;
614
615 const Trk::Surface& atlasSurface = acts_de->atlasSurface();
616 Identifier hitId = atlasSurface.associatedDetectorElementIdentifier();
617
618 const Trk::TrkDetElementBase* detBase = atlasSurface.associatedDetectorElement();
619 const InDetDD::SiDetectorElement* siDet = dynamic_cast<const InDetDD::SiDetectorElement*>(detBase);
620
621 if (!siDet) continue;
622
623 // ============================================
624 // Global -> local intersection
625 // ============================================
626 auto intersection = atlasSurface.globalToLocal(hit.position());
627 if (!intersection) continue;
628
629 double interX = (*intersection)(0);
630 double interY = (*intersection)(1);
631 double thickness = siDet->thickness();
632
633 // ============================================
634 // Direction handling, compute entry/exit positions
635 // ============================================
636 // get hit direction
637 const auto& actsDir = hit.direction();
638 // Convert to Amg only when applying transform
639 Amg::Vector3D amgDir(actsDir.x(), actsDir.y(), actsDir.z());
640
641 // ATLAS surface transform using Amg
642 const Amg::Transform3D surfaceTransform = atlasSurface.transform();
643 const Amg::Transform3D invSurfaceTransform = surfaceTransform.inverse();
644 Amg::Vector3D localDirAmg = invSurfaceTransform.linear() * amgDir;
645
646 // calculate cosTheta using norm
647 double norm = localDirAmg.norm();
648 if (norm < 1e-9) continue;
649 double cosTheta = localDirAmg.z() / norm;
650 if (std::abs(cosTheta) < 1e-6) continue;
651
652 // calculate local entry and exit in X and Y based on intersection and distance
653 localDirAmg *= thickness / std::abs(cosTheta);
654
655 int movingDir = localDirAmg.z() > 0. ? 1 : -1;
656
657 double distX = localDirAmg.x();
658 double distY = localDirAmg.y();
659
660 double localEntryX = interX - 0.5 * distX;
661 double localEntryY = interY - 0.5 * distY;
662 double localExitX = interX + 0.5 * distX;
663 double localExitY = interY + 0.5 * distY;
664
665 // calculate hit transform with silicon detector using Amg
666 const Amg::Transform3D& hitTransform = siDet->transformHit().inverse();
667 // create entry and exit surfaces
668 Amg::Vector3D surfaceEntry(localEntryX, localEntryY, -0.5 * movingDir * thickness);
669 Amg::Vector3D surfaceExit(localExitX, localExitY, 0.5 * movingDir * thickness);
670
671 Amg::Vector3D globalEntry = surfaceTransform * surfaceEntry;
672 Amg::Vector3D globalExit = surfaceTransform * surfaceExit;
673 Amg::Vector3D localEntry = hitTransform * globalEntry;
674 Amg::Vector3D localExit = hitTransform * globalExit;
675
676 HepGeom::Point3D<double> entryHep(localEntry.x(), localEntry.y(), localEntry.z());
677 HepGeom::Point3D<double> exitHep(localExit.x(), localExit.y(), localExit.z());
678
679 bool isPixel = siDet->isPixel();
680
681 // ============================================
682 // Create SiHit
683 // ============================================
684 SiHit siHit(
685 entryHep,
686 exitHep,
687 energyDeposit,
688 time,
689 partLink,
690 isPixel ? 0 : 1,
691 isPixel ? m_pixIdHelper->barrel_ec(hitId) : m_sctIdHelper->barrel_ec(hitId),
692 isPixel ? m_pixIdHelper->layer_disk(hitId) : m_sctIdHelper->layer_disk(hitId),
693 isPixel ? m_pixIdHelper->eta_module(hitId) : m_sctIdHelper->eta_module(hitId),
694 isPixel ? m_pixIdHelper->phi_module(hitId) : m_sctIdHelper->phi_module(hitId),
695 isPixel ? 0 : m_sctIdHelper->side(hitId)
696 );
697
698 // ============================================
699 // Store into MT-safe cache
700 // ============================================
701 if (isPixel) {
702 evtCache.pixelHits.emplace_back(std::move(siHit));
703 } else {
704 evtCache.sctHits.emplace_back(std::move(siHit));
705 }
706 ATH_MSG_VERBOSE(name() << " convert and store 1 hit, total " << evtCache.pixelHits.size() << " Pixel | " << evtCache.sctHits.size() << " SCT hits stored.");
707 }
708 catch (const std::exception& e) {
709 ATH_MSG_DEBUG(name() << "Can not find Acts Surface (" << e.what() << ")...Skip...");
710 continue;
711 }
712 }
713 ATH_MSG_VERBOSE("Cache now contains " << evtCache.pixelHits.size() << " pixel hits and " << evtCache.sctHits.size() << " SCT hits");
714 return StatusCode::SUCCESS;
715}
716
718{
719 ATH_MSG_DEBUG( "[ActsFatrasG4Tool] finalize() starting" );
720 ATH_MSG_DEBUG( "[ActsFatrasG4Tool] finalize() successful" );
721 return StatusCode::SUCCESS;
722}
const ActsDetectorElement * getActsDetectorElement(const Acts::Surface &surf)
Attempts to retrieve the ActsDetectorElement associated to the passed ActsSurface.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_DEBUG(x)
double charge(const T &p)
Definition AtlasPID.h:1003
bool hit(const Container &ids, int pdgId)
std::unique_ptr< const Acts::Logger > makeActsAthenaLogger(IMessageSvc *svc, const std::string &name, int level, std::optional< std::string > parent_name)
Definition Logger.cxx:64
static const Attributes_t empty
virtual const std::vector< SiHit > & getPixelHitsCache(const EventContext &ctx) const override
for saving si hits caches, expose to the interface
std::unique_ptr< Navigator > m_navigator
SingleParticleSimulation< ChargedPropagator, ChargedInteractions, HitSurfaceSelector, ActsFatras::NoDecay > ChargedSimulation
std::ranlux48 Generator
Gaudi::Property< double > m_pathLimit
std::shared_ptr< const Acts::TrackingGeometry > m_trackingGeometry
Gaudi::Property< bool > m_debugInject
StatusCode spawnSecondaries(const std::vector< ActsFatras::Particle > &simulatedFinal, const G4Track &, G4FastStep &fastStep)
const SCT_ID * m_sctIdHelper
the SCT ID helper
Gaudi::Property< double > m_maxRungeKuttaStepTrials
const PixelID * m_pixIdHelper
the Pixel ID helper
Gaudi::Property< double > m_maxStep
Acts::Propagator< NeutralStepper, Navigator > NeutralPropagator
Gaudi::Property< double > m_maxStepSize
StatusCode configureSimulator()
StatusCode applyPrimaryUpdate(const G4Track &, const std::vector< ActsFatras::Particle > &simulatedFinalParticles, G4FastStep &fastStep)
ActsTrk::ContextUtility m_ctxProvider
Context provider for geometry, magnetic field and calibration contexts.
EventCache & getCache(const EventContext &ctx) const
ServiceHandle< IAthRNGSvc > m_rngSvc
Gaudi::Property< double > m_loopFraction
Acts::EigenStepper< Acts::EigenStepperDefaultExtension > ChargedStepper
virtual StatusCode finalize() override
AlgTool finalize method.
ServiceHandle< ActsTrk::ITrackingGeometrySvc > m_trackingGeometrySvc
SingleParticleSimulation< NeutralPropagator, NeutralInteractions, ActsFatras::NoSurface, ActsFatras::NoDecay > NeutralSimulation
StatusCode initializePhysics() override
AlgTool initializePhysics method.
virtual StatusCode initialize() override
AlgTool initialize method.
Gaudi::Property< std::string > m_randomEngineName
virtual void clearCaches(const EventContext &ctx) const override
Gaudi::Property< double > m_interact_minPt
std::shared_ptr< ATLASMagneticFieldWrapper > m_bField
Acts::StraightLineStepper NeutralStepper
void handleSimulationFailures(const Acts::Result< ActsFatras::SingleParticleSimulationResult > &result)
Gaudi::Property< double > m_stepSizeCutOff
Gaudi::Property< bool > m_loopProtection
Gaudi::Property< double > m_tolerance
StatusCode runDebugInjection(const EventContext &ctx, const G4Track &track, const Acts::GeometryContext &anygctx, const Acts::MagneticFieldContext &mctx, Generator &generator, G4FastStep &fastStep)
void debugTrackRegion(const G4FastTrack &fastTrack) const
Acts::Propagator< ChargedStepper, Navigator > ChargedPropagator
std::shared_ptr< const Acts::Logger > m_logger
virtual const std::vector< SiHit > & getSCTHitsCache(const EventContext &ctx) const override
std::unique_ptr< Simulation > m_simulator
std::vector< ActsFatras::Particle > buildActsInputFromG4(const G4Track &track)
StatusCode createHitsFromG4(const EventContext &ctx, const G4Track &track, const Acts::TrackingGeometry &trackingGeometry, const std::vector< ActsFatras::Hit > &hits) const
virtual void simulateFatrasTrack(const G4FastTrack &fastTrack, G4FastStep &fastStep) override
create ActsFatras track, exposed via interface
Class to hold geometrical description of a silicon detector element.
const GeoTrf::Transform3D & transformHit() const
Local (simulation/hit frame) to global transform.
Definition SiHit.h:19
Abstract Base Class for tracking surfaces.
Definition Surface.h:79
virtual bool globalToLocal(const Amg::Vector3D &glob, const Amg::Vector3D &mom, Amg::Vector2D &loc) const =0
Specified by each surface type: GlobalToLocal method without dynamic memory allocation - boolean chec...
const TrkDetElementBase * associatedDetectorElement() const
return associated Detector Element
const Amg::Transform3D & transform() const
Returns HepGeom::Transform3D by reference.
Identifier associatedDetectorElementIdentifier() const
return Identifier of the associated Detector Element
This is the base class for all tracking detector elements with read-out relevant information.
std::vector< std::string > intersection(std::vector< std::string > &v1, std::vector< std::string > &v2)
int r
Definition globals.cxx:22
constexpr double timeToAthena(T actsT)
Converts a time unit from Acts to Athena units.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
constexpr int INVALID_PARTICLE_ID
constexpr int UNDEFINED_ID
std::vector< SiHit > pixelHits
MsgStream & msg
Definition testRead.cxx:32