Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-07 08:03:28

0001 //==========================================================================
0002 //  AIDA Detector description implementation
0003 //--------------------------------------------------------------------------
0004 // Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN)
0005 // All rights reserved.
0006 //
0007 // For the licensing terms see $DD4hepINSTALL/LICENSE.
0008 // For the list of contributors see $DD4hepINSTALL/doc/CREDITS.
0009 //
0010 //==========================================================================
0011 
0012 // Framework include files
0013 #include "DDG4/Geant4SteppingAction.h"
0014 
0015 // Geant4 headers
0016 #include <G4Step.hh>
0017 #include <G4RunManager.hh>
0018 #include <G4TrackStatus.hh>
0019 #include <G4Run.hh>
0020 #include <G4Event.hh>
0021 #include <G4UnitsTable.hh>
0022 #include <G4ParticleDefinition.hh>
0023 #include <G4ThreeVector.hh>
0024 #include <CLHEP/Units/SystemOfUnits.h>
0025 
0026 // C/C++ headers
0027 #include <fstream>
0028 #include <iostream>
0029 #include <sstream>
0030 #include <string>
0031 #include <vector>
0032 #include <map>
0033 #include <stack>
0034 #include <cmath>
0035 #include <limits>
0036 #include <fmt/core.h>
0037 #include <fmt/format.h>
0038 
0039 /// Namespace for the AIDA detector description toolkit
0040 namespace dd4hep {
0041 
0042   /// Namespace for the Geant4 based simulation part of the AIDA detector description toolkit
0043   namespace sim {
0044 
0045     /**
0046      * \addtogroup Geant4SteppingAction
0047      * @{
0048      * \package FirebirdTrajectoryWriterSteppingAction
0049      * \brief Stepping action that writes trajectories in Firebird JSON format for visualization
0050      *
0051      * This action processes steps and outputs them in Firebird JSON format
0052      * for direct visualization in the Firebird event display. It combines the
0053      * time extraction approach from TextDumpingSteppingAction with the JSON formatting
0054      * from FirebirdTrajectoryWriterEventAction.
0055      *
0056      * @}
0057      */
0058 
0059     /// Firebird JSON format stepping action for dd4hep simulation
0060     /** This action writes filtered steps to a JSON file compatible with Firebird
0061      *
0062      *  \version 1.0
0063      *  \ingroup DD4HEP_SIMULATION
0064      */
0065     class FirebirdTrajectoryWriterSteppingAction : public Geant4SteppingAction {
0066     protected:
0067       // Output file and counters
0068       std::string m_outputFile {"trajectories.firebird.json"};
0069       std::ofstream m_output;
0070       std::size_t m_totalSteps {0UL};
0071       std::size_t m_totalTracks {0UL};
0072       std::size_t m_totalEvents {0UL};
0073       std::size_t m_filteredTracks {0UL};
0074       std::size_t m_stepsFiltered {0UL};
0075 
0076       // Component name for tracks (configurable)
0077       std::string m_componentName {"Geant4TrueTrajectories"};
0078 
0079       // Filter properties
0080       double m_minMomentum {300 * CLHEP::MeV};
0081       double m_maxMomentum {10000 * CLHEP::TeV};
0082       bool m_saveOptical {false};
0083       bool m_onlyPrimary {true};
0084 
0085       // Vertex filtering
0086       bool m_vertexCut {true};
0087       double m_vertexZMin {-4500};
0088       double m_vertexZMax {4500};
0089 
0090       // Step filtering - added to match EventAction
0091       bool m_stepCut {false};
0092       double m_stepZMin {-5000};
0093       double m_stepZMax {5000};
0094       double m_stepRMax {5000};
0095 
0096       // Particle type filtering - added to match EventAction
0097       std::vector<int> m_saveParticles {};
0098 
0099       // Track length filtering - added to match EventAction
0100       double m_minTrackLength {0};
0101 
0102       // Track processing state
0103       std::once_flag m_initFlag;
0104       int m_prevEvent {-1};
0105       int m_prevTrackId {-1};
0106       bool m_skippingTrack {false};
0107 
0108       // JSON entries collection
0109       std::vector<std::string> m_eventEntries;
0110       std::vector<std::string> m_pointEntries;
0111 
0112       // Current event data
0113       std::string m_currentEventEntry;
0114       bool m_firstTrackInEvent {true};
0115 
0116       /// Helper method to check if file is open and writable
0117       void ensureOutputWritable() {
0118         if (!m_output.is_open() || !m_output.good()) {
0119           auto errMsg = fmt::format("Failed to open the file or file stream is in a bad state. File name: '{}'", m_outputFile);
0120           error(errMsg.c_str());
0121           throw std::runtime_error(errMsg);
0122         }
0123       }
0124 
0125       /// Helper method to calculate radial distance from Z axis
0126       double calculateR(const G4ThreeVector& position) const {
0127         return std::sqrt(position.x() * position.x() + position.y() * position.y());
0128       }
0129 
0130       /// Initialize output file
0131       void initializeOutput() {
0132         // Log configuration
0133         fmt::print("Plugin {} info: \n", typeid(*this).name());
0134         fmt::print("   OutputFile     {}\n", m_outputFile);
0135         fmt::print("   ComponentName  {}\n", m_componentName);
0136         fmt::print("   MinMomentum    {}\n", m_minMomentum);
0137         fmt::print("   MaxMomentum    {}\n", m_maxMomentum);
0138         fmt::print("   SaveOptical    {}\n", m_saveOptical);
0139         fmt::print("   OnlyPrimary    {}\n", m_onlyPrimary);
0140         fmt::print("   VertexCut      {}\n", m_vertexCut);
0141         fmt::print("   VertexZMin     {}\n", m_vertexZMin);
0142         fmt::print("   VertexZMax     {}\n", m_vertexZMax);
0143         fmt::print("   StepCut        {}\n", m_stepCut);
0144         fmt::print("   StepZMin       {}\n", m_stepZMin);
0145         fmt::print("   StepZMax       {}\n", m_stepZMax);
0146         fmt::print("   StepRMax       {}\n", m_stepRMax);
0147         fmt::print("   TrackLengthMin {}\n", m_minTrackLength);
0148 
0149         if (!m_saveParticles.empty()) {
0150           std::stringstream ss;
0151           for (size_t i = 0; i < m_saveParticles.size(); ++i) {
0152             if (i > 0) ss << ", ";
0153             ss << m_saveParticles[i];
0154           }
0155           fmt::print("   SaveParticles  {}\n", ss.str());
0156         } else {
0157           fmt::print("   SaveParticles  [all]\n");
0158         }
0159 
0160         // Open the output file for later writing
0161         m_output.open(m_outputFile);
0162         ensureOutputWritable();
0163       }
0164 
0165       /// Write the final JSON file
0166       void writeOutputFile() {
0167         ensureOutputWritable();
0168 
0169         // Write the header of the JSON file
0170         m_output << fmt::format(R"({{"type":"firebird-dex-json","version":"0.03","origin":{{"file":"{}","entries_count":{}}},)",
0171                              m_outputFile, m_eventEntries.size());
0172 
0173         // Write the entries array
0174         m_output << "\"entries\":[";
0175 
0176         // Write each event entry
0177         for (size_t i = 0; i < m_eventEntries.size(); ++i) {
0178           m_output << m_eventEntries[i];
0179           if (i < m_eventEntries.size() - 1) {
0180             m_output << ",";
0181           }
0182         }
0183 
0184         // Close the JSON structure
0185         m_output << "]}";
0186       }
0187 
0188       /// Generate track parameters from G4Track
0189       std::string generateTrackParams(const G4Track* track) {
0190         // Extract momentum components
0191         G4ThreeVector momentum = track->GetMomentum();
0192         G4double p = momentum.mag();
0193 
0194         // Ensure momentum is not zero to avoid division by zero
0195         if (p < 1e-10) {
0196           p = 1e-10;
0197         }
0198 
0199         // Get particle information
0200         int pdgCode = track->GetParticleDefinition()->GetPDGEncoding();
0201         std::string particleName = track->GetParticleDefinition()->GetParticleName();
0202         G4double charge = track->GetParticleDefinition()->GetPDGCharge();
0203 
0204         // Calculate angular parameters
0205         G4double theta = momentum.theta();
0206         G4double phi = momentum.phi();
0207 
0208         // Calculate q/p - charge over momentum (in GeV/c)
0209         G4double qOverP = charge / (p / CLHEP::GeV);
0210 
0211         // Convert momentum to MeV/c
0212         G4double px = momentum.x() / CLHEP::MeV;
0213         G4double py = momentum.y() / CLHEP::MeV;
0214         G4double pz = momentum.z() / CLHEP::MeV;
0215 
0216         // Get the vertex position
0217         G4ThreeVector vertex = track->GetVertexPosition();
0218 
0219         // Convert vertex position to mm
0220         G4double vx = vertex.x() / CLHEP::mm;
0221         G4double vy = vertex.y() / CLHEP::mm;
0222         G4double vz = vertex.z() / CLHEP::mm;
0223 
0224         // Get initial time
0225         G4double time = track->GetGlobalTime() / CLHEP::ns;
0226 
0227         // Placeholder values for local parameters (loc_a, loc_b)
0228         G4double locA = 0.0;
0229         G4double locB = 0.0;
0230 
0231         // Handle potential infinities or NaNs which are invalid in JSON
0232         if (std::isinf(px) || std::isnan(px)) px = 0.0;
0233         if (std::isinf(py) || std::isnan(py)) py = 0.0;
0234         if (std::isinf(pz) || std::isnan(pz)) pz = 0.0;
0235         if (std::isinf(vx) || std::isnan(vx)) vx = 0.0;
0236         if (std::isinf(vy) || std::isnan(vy)) vy = 0.0;
0237         if (std::isinf(vz) || std::isnan(vz)) vz = 0.0;
0238         if (std::isinf(theta) || std::isnan(theta)) theta = 0.0;
0239         if (std::isinf(phi) || std::isnan(phi)) phi = 0.0;
0240         if (std::isinf(qOverP) || std::isnan(qOverP)) qOverP = 0.0;
0241         if (std::isinf(time) || std::isnan(time)) time = 0.0;
0242 
0243         // Format parameters as a JSON array
0244         // Order: pdg, type, charge, px, py, pz, vx, vy, vz, theta, phi, q_over_p, loc_a, loc_b, time
0245         return fmt::format("[{},\"{}\",{},{},{},{},{},{},{},{},{},{},{},{},{}]",
0246                           pdgCode, particleName, charge,
0247                           px, py, pz,
0248                           vx, vy, vz,
0249                           theta, phi, qOverP,
0250                           locA, locB, time);
0251       }
0252 
0253       /// Format a point (pre or post step) as a JSON array
0254       std::string formatPoint(G4StepPoint* point) {
0255         G4ThreeVector position = point->GetPosition();
0256 
0257         // Convert position to mm and time to ns
0258         double x = position.x() / CLHEP::mm;
0259         double y = position.y() / CLHEP::mm;
0260         double z = position.z() / CLHEP::mm;
0261         double time = point->GetGlobalTime() / CLHEP::ns;
0262 
0263         // Handle potential infinities or NaNs which are invalid in JSON
0264         if (std::isinf(x) || std::isnan(x)) x = 0.0;
0265         if (std::isinf(y) || std::isnan(y)) y = 0.0;
0266         if (std::isinf(z) || std::isnan(z)) z = 0.0;
0267         if (std::isinf(time) || std::isnan(time)) time = 0.0;
0268 
0269         // Add auxiliary value (0 for regular points)
0270         return fmt::format("[{},{},{},{},{}]", x, y, z, time, 0);
0271       }
0272 
0273       /// Start a new event entry
0274       void startNewEvent(int runNumber, int eventNumber) {
0275         // Finalize previous event if there is one
0276         finalizeEvent();
0277 
0278         // Reset track state
0279         m_prevTrackId = -1;
0280         m_firstTrackInEvent = true;
0281         m_pointEntries.clear();
0282 
0283         // Create the beginning of an event entry with header
0284         m_currentEventEntry = fmt::format(R"({{"id":{},"components":[)", eventNumber);
0285 
0286         // Add component for track segments
0287         m_currentEventEntry += fmt::format(R"({{"name":"{}","type":"TrackerLinePointTrajectory",)", m_componentName);
0288 
0289         // Add origin type information
0290         m_currentEventEntry += R"("originType":["G4Track","G4StepPoint"],)";
0291 
0292         // Define parameter columns
0293         m_currentEventEntry += R"("paramColumns":["pdg","type","charge","px","py","pz","vx","vy","vz","theta","phi","q_over_p","loc_a","loc_b","time"],)";
0294 
0295         // Define point columns
0296         m_currentEventEntry += R"("pointColumns":["x","y","z","t","aux"],)";
0297 
0298         // Start the lines array
0299         m_currentEventEntry += R"("lines":[)";
0300 
0301         m_totalEvents++;
0302         fmt::print("Started processing event: {} (run: {})\n", eventNumber, runNumber);
0303       }
0304 
0305       /// Finalize the current event and add it to the event entries
0306       void finalizeEvent() {
0307         if (m_prevEvent >= 0) {
0308           // Only save the event if it has at least one track
0309           if (!m_firstTrackInEvent) {
0310             // Close the lines array, component, and components array
0311             m_currentEventEntry += "]}]}";
0312             m_eventEntries.push_back(m_currentEventEntry);
0313 
0314             fmt::print("Finalized event {} with {} tracks\n", m_prevEvent, m_totalTracks - m_filteredTracks);
0315           } else {
0316             fmt::print("Skipping empty event {}\n", m_prevEvent);
0317           }
0318         }
0319       }
0320 
0321       /// Check if track passes filtering criteria
0322       bool passesFilters(const G4Track* track) {
0323         // Get track information
0324         std::string particleName = track->GetParticleDefinition()->GetParticleName();
0325         int pdgCode = track->GetParticleDefinition()->GetPDGEncoding();
0326         int parentID = track->GetParentID();
0327         G4ThreeVector momentum = track->GetMomentum();
0328         double p = momentum.mag();
0329         G4ThreeVector vertex = track->GetVertexPosition();
0330         double vz = vertex.z() / CLHEP::mm;
0331 
0332         // Special case for optical photons
0333         if (particleName == "opticalphoton" && m_saveOptical) {
0334           return true; // Always save optical photons if requested
0335         }
0336 
0337         // Check primary track filter
0338         if (m_onlyPrimary && parentID != 0) {
0339           return false; // Skip non-primary tracks
0340         }
0341 
0342         // Check momentum thresholds
0343         if (p < m_minMomentum || p > m_maxMomentum) {
0344           return false; // Skip tracks outside momentum range
0345         }
0346 
0347         // Check vertex position if required
0348         if (m_vertexCut && (vz < m_vertexZMin || vz > m_vertexZMax)) {
0349           return false; // Skip if vertex Z is outside range
0350         }
0351 
0352         // Check if this particle type should be saved
0353         if (!m_saveParticles.empty()) {
0354           bool found = false;
0355           for (int code : m_saveParticles) {
0356             if (pdgCode == code) {
0357               found = true;
0358               break;
0359             }
0360           }
0361           if (!found) {
0362             return false; // Skip particles not in the save list
0363           }
0364         }
0365 
0366         // All filters passed
0367         return true;
0368       }
0369 
0370       /// Check if step point passes position filters
0371       bool pointPassesFilters(const G4ThreeVector& position) {
0372         if (!m_stepCut) {
0373           return true; // No filtering if step cut is disabled
0374         }
0375 
0376         // Convert to mm
0377         double z = position.z() / CLHEP::mm;
0378         double r = calculateR(position) / CLHEP::mm;
0379 
0380         // Check Z and R against limits
0381         if (z < m_stepZMin || z > m_stepZMax || r > m_stepRMax) {
0382           m_stepsFiltered++;
0383           return false;
0384         }
0385 
0386         return true;
0387       }
0388 
0389       /// Start a new track in the current event
0390       void startNewTrack(const G4Track* track) {
0391         // Get track ID
0392         int trackId = track->GetTrackID();
0393         m_prevTrackId = trackId;
0394 
0395         // Check if track passes filters
0396         if (!passesFilters(track)) {
0397           m_skippingTrack = true;
0398           m_filteredTracks++;
0399           return;
0400         }
0401 
0402         m_skippingTrack = false;
0403 
0404         // Add comma if not the first track in the event
0405         if (!m_firstTrackInEvent) {
0406           m_currentEventEntry += ",";
0407         } else {
0408           m_firstTrackInEvent = false;
0409         }
0410 
0411         // Start a new line object
0412         m_currentEventEntry += "{\"points\":[";
0413 
0414         // Reset point entries for the new track
0415         m_pointEntries.clear();
0416 
0417         if (trackId < 1000) {
0418           G4ThreeVector vertex = track->GetVertexPosition();
0419           fmt::print("Processing track: {}, {} (parent: {}), vertex: ({:.2f}, {:.2f}, {:.2f})\n",
0420                     trackId,
0421                     track->GetParticleDefinition()->GetParticleName(),
0422                     track->GetParentID(),
0423                     vertex.x(), vertex.y(), vertex.z());
0424         }
0425       }
0426 
0427       /// Add a step point to the current track
0428       void addStepPoint(G4StepPoint* point) {
0429         if (m_skippingTrack) return;
0430 
0431         // Check if the point passes position filters
0432         if (!pointPassesFilters(point->GetPosition())) {
0433           return; // Skip this point
0434         }
0435 
0436         // Format the point and add to the collection
0437         std::string pointEntry = formatPoint(point);
0438 
0439         // Add comma if not the first point
0440         if (!m_pointEntries.empty()) {
0441           m_pointEntries.push_back("," + pointEntry);
0442         } else {
0443           m_pointEntries.push_back(pointEntry);
0444         }
0445       }
0446 
0447       /// Finalize the current track with track parameters
0448       void finalizeTrack(const G4Track* track) {
0449         if (m_skippingTrack) return;
0450 
0451         // Skip if no points passed the filters
0452         if (m_pointEntries.empty()) {
0453           m_skippingTrack = true;
0454           m_filteredTracks++;
0455           return;
0456         }
0457 
0458         // Check track length if required
0459         if (m_minTrackLength > 0 && m_pointEntries.size() > 1) {
0460           // We need at least 2 points to calculate length
0461           G4ThreeVector prevPos = track->GetVertexPosition();
0462 
0463           // Add points to the track and calculate length
0464           for (size_t i = 0; i < m_pointEntries.size(); i++) {
0465             m_currentEventEntry += m_pointEntries[i];
0466           }
0467 
0468           // Close the points array
0469           m_currentEventEntry += "]";
0470 
0471           // Add track parameters
0472           m_currentEventEntry += ",\"params\":";
0473           m_currentEventEntry += generateTrackParams(track);
0474 
0475           // Close the line object
0476           m_currentEventEntry += "}";
0477 
0478           m_totalTracks++;
0479         }
0480       }
0481 
0482     public:
0483       /// Standard constructor
0484       FirebirdTrajectoryWriterSteppingAction(Geant4Context* context, const std::string& name = "FirebirdTrajectoryWriterSteppingAction")
0485         : Geant4SteppingAction(context, name)
0486       {
0487         declareProperty("OutputFile", m_outputFile);
0488         declareProperty("ComponentName", m_componentName);
0489         declareProperty("MomentumMin", m_minMomentum);
0490         declareProperty("MomentumMax", m_maxMomentum);
0491         declareProperty("SaveOptical", m_saveOptical);
0492         declareProperty("OnlyPrimary", m_onlyPrimary);
0493         declareProperty("VertexCut", m_vertexCut);
0494         declareProperty("VertexZMin", m_vertexZMin);
0495         declareProperty("VertexZMax", m_vertexZMax);
0496         declareProperty("StepCut", m_stepCut);
0497         declareProperty("StepZMin", m_stepZMin);
0498         declareProperty("StepZMax", m_stepZMax);
0499         declareProperty("StepRMax", m_stepRMax);
0500         declareProperty("TrackLengthMin", m_minTrackLength);
0501         declareProperty("SaveParticles", m_saveParticles);
0502       }
0503 
0504       /// Destructor
0505       ~FirebirdTrajectoryWriterSteppingAction() override
0506       {
0507         // Finalize the last event if needed
0508         finalizeEvent();
0509 
0510         // Write the output file if we have collected any events
0511         if (!m_eventEntries.empty()) {
0512           writeOutputFile();
0513           info("[firebird-stepping-writer] Successfully wrote JSON trajectories to: %s", m_outputFile.c_str());
0514         } else {
0515           warning("[firebird-stepping-writer] No events were processed. Output file not created.");
0516         }
0517 
0518         // Close the output file
0519         if (m_output.is_open() && m_output.good()) {
0520           m_output.close();
0521         }
0522 
0523         // Print statistics
0524         info("[firebird-stepping-writer] Statistics:");
0525         info("[firebird-stepping-writer] Total Events: %ld", m_totalEvents);
0526         info("[firebird-stepping-writer] Total Tracks: %ld (filtered: %ld, written: %ld)",
0527              m_totalTracks + m_filteredTracks, m_filteredTracks, m_totalTracks);
0528         info("[firebird-stepping-writer] Total Steps: %ld (filtered: %ld)",
0529              m_totalSteps, m_stepsFiltered);
0530       }
0531 
0532       /// Stepping callback
0533       void operator()(const G4Step* step, G4SteppingManager*) override
0534       {
0535         // Initialize output file if first call
0536         std::call_once(m_initFlag, [this]() { initializeOutput(); });
0537 
0538         // Get event and run information
0539         auto runNum = context()->run().run().GetRunID();
0540         auto eventNum = context()->event().event().GetEventID();
0541 
0542         // Check if this is a new event
0543         if (eventNum != m_prevEvent) {
0544           startNewEvent(runNum, eventNum);
0545           m_prevEvent = eventNum;
0546         }
0547 
0548         // Get the track
0549         auto track = step->GetTrack();
0550         int trackId = track->GetTrackID();
0551 
0552         // Check if this is a new track
0553         if (trackId != m_prevTrackId) {
0554           // If we were processing a track before, finalize it
0555           if (m_prevTrackId > 0 && !m_skippingTrack) {
0556             finalizeTrack(track);
0557           }
0558 
0559           // Start a new track
0560           startNewTrack(track);
0561 
0562           // For a new track, add the pre-step point which is the first point
0563           if (!m_skippingTrack) {
0564             addStepPoint(step->GetPreStepPoint());
0565           }
0566         }
0567 
0568         // Add the post-step point if not skipping this track
0569         if (!m_skippingTrack) {
0570           addStepPoint(step->GetPostStepPoint());
0571         }
0572 
0573         // Count steps
0574         m_totalSteps++;
0575       }
0576     };
0577 
0578   } // namespace sim
0579 } // namespace dd4hep
0580 
0581 // Register the plugin with DD4hep
0582 #include "DDG4/Factories.h"
0583 DECLARE_GEANT4ACTION_NS(dd4hep::sim,FirebirdTrajectoryWriterSteppingAction)