File indexing completed on 2025-04-07 08:03:28
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013 #include "DDG4/Geant4SteppingAction.h"
0014
0015
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
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
0040 namespace dd4hep {
0041
0042
0043 namespace sim {
0044
0045
0046
0047
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065 class FirebirdTrajectoryWriterSteppingAction : public Geant4SteppingAction {
0066 protected:
0067
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
0077 std::string m_componentName {"Geant4TrueTrajectories"};
0078
0079
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
0086 bool m_vertexCut {true};
0087 double m_vertexZMin {-4500};
0088 double m_vertexZMax {4500};
0089
0090
0091 bool m_stepCut {false};
0092 double m_stepZMin {-5000};
0093 double m_stepZMax {5000};
0094 double m_stepRMax {5000};
0095
0096
0097 std::vector<int> m_saveParticles {};
0098
0099
0100 double m_minTrackLength {0};
0101
0102
0103 std::once_flag m_initFlag;
0104 int m_prevEvent {-1};
0105 int m_prevTrackId {-1};
0106 bool m_skippingTrack {false};
0107
0108
0109 std::vector<std::string> m_eventEntries;
0110 std::vector<std::string> m_pointEntries;
0111
0112
0113 std::string m_currentEventEntry;
0114 bool m_firstTrackInEvent {true};
0115
0116
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
0126 double calculateR(const G4ThreeVector& position) const {
0127 return std::sqrt(position.x() * position.x() + position.y() * position.y());
0128 }
0129
0130
0131 void initializeOutput() {
0132
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
0161 m_output.open(m_outputFile);
0162 ensureOutputWritable();
0163 }
0164
0165
0166 void writeOutputFile() {
0167 ensureOutputWritable();
0168
0169
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
0174 m_output << "\"entries\":[";
0175
0176
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
0185 m_output << "]}";
0186 }
0187
0188
0189 std::string generateTrackParams(const G4Track* track) {
0190
0191 G4ThreeVector momentum = track->GetMomentum();
0192 G4double p = momentum.mag();
0193
0194
0195 if (p < 1e-10) {
0196 p = 1e-10;
0197 }
0198
0199
0200 int pdgCode = track->GetParticleDefinition()->GetPDGEncoding();
0201 std::string particleName = track->GetParticleDefinition()->GetParticleName();
0202 G4double charge = track->GetParticleDefinition()->GetPDGCharge();
0203
0204
0205 G4double theta = momentum.theta();
0206 G4double phi = momentum.phi();
0207
0208
0209 G4double qOverP = charge / (p / CLHEP::GeV);
0210
0211
0212 G4double px = momentum.x() / CLHEP::MeV;
0213 G4double py = momentum.y() / CLHEP::MeV;
0214 G4double pz = momentum.z() / CLHEP::MeV;
0215
0216
0217 G4ThreeVector vertex = track->GetVertexPosition();
0218
0219
0220 G4double vx = vertex.x() / CLHEP::mm;
0221 G4double vy = vertex.y() / CLHEP::mm;
0222 G4double vz = vertex.z() / CLHEP::mm;
0223
0224
0225 G4double time = track->GetGlobalTime() / CLHEP::ns;
0226
0227
0228 G4double locA = 0.0;
0229 G4double locB = 0.0;
0230
0231
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
0244
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
0254 std::string formatPoint(G4StepPoint* point) {
0255 G4ThreeVector position = point->GetPosition();
0256
0257
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
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
0270 return fmt::format("[{},{},{},{},{}]", x, y, z, time, 0);
0271 }
0272
0273
0274 void startNewEvent(int runNumber, int eventNumber) {
0275
0276 finalizeEvent();
0277
0278
0279 m_prevTrackId = -1;
0280 m_firstTrackInEvent = true;
0281 m_pointEntries.clear();
0282
0283
0284 m_currentEventEntry = fmt::format(R"({{"id":{},"components":[)", eventNumber);
0285
0286
0287 m_currentEventEntry += fmt::format(R"({{"name":"{}","type":"TrackerLinePointTrajectory",)", m_componentName);
0288
0289
0290 m_currentEventEntry += R"("originType":["G4Track","G4StepPoint"],)";
0291
0292
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
0296 m_currentEventEntry += R"("pointColumns":["x","y","z","t","aux"],)";
0297
0298
0299 m_currentEventEntry += R"("lines":[)";
0300
0301 m_totalEvents++;
0302 fmt::print("Started processing event: {} (run: {})\n", eventNumber, runNumber);
0303 }
0304
0305
0306 void finalizeEvent() {
0307 if (m_prevEvent >= 0) {
0308
0309 if (!m_firstTrackInEvent) {
0310
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
0322 bool passesFilters(const G4Track* track) {
0323
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
0333 if (particleName == "opticalphoton" && m_saveOptical) {
0334 return true;
0335 }
0336
0337
0338 if (m_onlyPrimary && parentID != 0) {
0339 return false;
0340 }
0341
0342
0343 if (p < m_minMomentum || p > m_maxMomentum) {
0344 return false;
0345 }
0346
0347
0348 if (m_vertexCut && (vz < m_vertexZMin || vz > m_vertexZMax)) {
0349 return false;
0350 }
0351
0352
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;
0363 }
0364 }
0365
0366
0367 return true;
0368 }
0369
0370
0371 bool pointPassesFilters(const G4ThreeVector& position) {
0372 if (!m_stepCut) {
0373 return true;
0374 }
0375
0376
0377 double z = position.z() / CLHEP::mm;
0378 double r = calculateR(position) / CLHEP::mm;
0379
0380
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
0390 void startNewTrack(const G4Track* track) {
0391
0392 int trackId = track->GetTrackID();
0393 m_prevTrackId = trackId;
0394
0395
0396 if (!passesFilters(track)) {
0397 m_skippingTrack = true;
0398 m_filteredTracks++;
0399 return;
0400 }
0401
0402 m_skippingTrack = false;
0403
0404
0405 if (!m_firstTrackInEvent) {
0406 m_currentEventEntry += ",";
0407 } else {
0408 m_firstTrackInEvent = false;
0409 }
0410
0411
0412 m_currentEventEntry += "{\"points\":[";
0413
0414
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
0428 void addStepPoint(G4StepPoint* point) {
0429 if (m_skippingTrack) return;
0430
0431
0432 if (!pointPassesFilters(point->GetPosition())) {
0433 return;
0434 }
0435
0436
0437 std::string pointEntry = formatPoint(point);
0438
0439
0440 if (!m_pointEntries.empty()) {
0441 m_pointEntries.push_back("," + pointEntry);
0442 } else {
0443 m_pointEntries.push_back(pointEntry);
0444 }
0445 }
0446
0447
0448 void finalizeTrack(const G4Track* track) {
0449 if (m_skippingTrack) return;
0450
0451
0452 if (m_pointEntries.empty()) {
0453 m_skippingTrack = true;
0454 m_filteredTracks++;
0455 return;
0456 }
0457
0458
0459 if (m_minTrackLength > 0 && m_pointEntries.size() > 1) {
0460
0461 G4ThreeVector prevPos = track->GetVertexPosition();
0462
0463
0464 for (size_t i = 0; i < m_pointEntries.size(); i++) {
0465 m_currentEventEntry += m_pointEntries[i];
0466 }
0467
0468
0469 m_currentEventEntry += "]";
0470
0471
0472 m_currentEventEntry += ",\"params\":";
0473 m_currentEventEntry += generateTrackParams(track);
0474
0475
0476 m_currentEventEntry += "}";
0477
0478 m_totalTracks++;
0479 }
0480 }
0481
0482 public:
0483
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
0505 ~FirebirdTrajectoryWriterSteppingAction() override
0506 {
0507
0508 finalizeEvent();
0509
0510
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
0519 if (m_output.is_open() && m_output.good()) {
0520 m_output.close();
0521 }
0522
0523
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
0533 void operator()(const G4Step* step, G4SteppingManager*) override
0534 {
0535
0536 std::call_once(m_initFlag, [this]() { initializeOutput(); });
0537
0538
0539 auto runNum = context()->run().run().GetRunID();
0540 auto eventNum = context()->event().event().GetEventID();
0541
0542
0543 if (eventNum != m_prevEvent) {
0544 startNewEvent(runNum, eventNum);
0545 m_prevEvent = eventNum;
0546 }
0547
0548
0549 auto track = step->GetTrack();
0550 int trackId = track->GetTrackID();
0551
0552
0553 if (trackId != m_prevTrackId) {
0554
0555 if (m_prevTrackId > 0 && !m_skippingTrack) {
0556 finalizeTrack(track);
0557 }
0558
0559
0560 startNewTrack(track);
0561
0562
0563 if (!m_skippingTrack) {
0564 addStepPoint(step->GetPreStepPoint());
0565 }
0566 }
0567
0568
0569 if (!m_skippingTrack) {
0570 addStepPoint(step->GetPostStepPoint());
0571 }
0572
0573
0574 m_totalSteps++;
0575 }
0576 };
0577
0578 }
0579 }
0580
0581
0582 #include "DDG4/Factories.h"
0583 DECLARE_GEANT4ACTION_NS(dd4hep::sim,FirebirdTrajectoryWriterSteppingAction)