Project
Loading...
Searching...
No Matches
ExternalDetector.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14#include "DetectorsBase/Stack.h"
19
20#include <FairRootManager.h>
21#include <FairVolume.h>
22#include <fairlogger/Logger.h>
23
24#include <TGeoManager.h>
25#include <TGeoMatrix.h>
26#include <TGeoMedium.h>
27#include <TGeoNode.h>
28#include <TGeoVolume.h>
29#include <TVirtualMC.h>
30#include <TVector3.h>
31
32#include <rapidjson/document.h>
33#include <rapidjson/error/en.h>
34#include <rapidjson/istreamwrapper.h>
35
36#include <fstream>
37
38namespace o2::ext
39{
40
41ExternalDetector::ExternalDetector(const char* name, const char* title, ExternalDetectorOptions options)
42 : o2::base::DetImpl<ExternalDetector>(name, true),
43 mOptions(options),
44 mTrackData(),
45 mHits(o2::utils::createSimVector<o2::ext::Hit>())
46{
47 (void)title; // the FairModule title is the second base ctor argument; kept for symmetry with other detectors
48 // Decouple the user-facing FairModule name (e.g. "IRIS") from the DetId: the base
49 // ctor derives fDetId from the name which is generally not a registered DetID, so we
50 // explicitly tie this detector to the configured (existing) DetID. This is what makes
51 // the hit output format / identity well defined, as discussed.
52 fDetId = mOptions.detID;
53}
54
56 : o2::base::DetImpl<ExternalDetector>("EXTDET", true),
57 mTrackData(),
58 mHits(o2::utils::createSimVector<o2::ext::Hit>())
59{
60}
61
63 : o2::base::DetImpl<ExternalDetector>(rhs),
64 mOptions(rhs.mOptions),
65 mSensitiveVolumeNames(rhs.mSensitiveVolumeNames),
66 mSensitiveVolIDs(rhs.mSensitiveVolIDs),
67 mVolID2SensorID(rhs.mVolID2SensorID),
68 mTrackData(),
69 mHits(o2::utils::createSimVector<o2::ext::Hit>())
70{
71}
72
79
80void ExternalDetector::collectSensitiveVolumeNames(TGeoVolume* vol, std::set<TGeoVolume*>& visited)
81{
82 if (!vol || visited.count(vol)) {
83 return;
84 }
85 visited.insert(vol);
86
87 bool sensitive = false;
88 // match by volume name
89 const std::string volname = vol->GetName();
90 for (const auto& token : mOptions.sensitiveVolumes) {
91 if (!token.empty() && volname.find(token) != std::string::npos) {
92 sensitive = true;
93 break;
94 }
95 }
96 // otherwise match by medium name
97 if (!sensitive) {
98 if (auto medium = vol->GetMedium()) {
99 const std::string medname = medium->GetName();
100 for (const auto& token : mOptions.sensitiveMedia) {
101 if (!token.empty() && medname.find(token) != std::string::npos) {
102 sensitive = true;
103 break;
104 }
105 }
106 }
107 }
108 if (sensitive) {
109 mSensitiveVolumeNames.emplace_back(volname);
110 }
111
112 const int nd = vol->GetNdaughters();
113 for (int i = 0; i < nd; ++i) {
114 if (auto node = vol->GetNode(i)) {
115 collectSensitiveVolumeNames(node->GetVolume(), visited);
116 }
117 }
118}
119
121{
122 // build the CAD geometry and obtain its top volume
123 auto module_top = o2::base::buildCADVolumeFromMacro(mOptions.root_macro_file, GetName());
124 if (!module_top) {
125 LOG(error) << "No geometry could be built for external detector " << GetName();
126 return;
127 }
128
129 // bring the CAD media under O2's MaterialManager
130 o2::base::remapCADMedia(module_top, GetName());
131
132 // determine which volumes should become sensitive (selected by medium name)
133 mSensitiveVolumeNames.clear();
134 std::set<TGeoVolume*> visited;
135 collectSensitiveVolumeNames(module_top, visited);
136 if (mSensitiveVolumeNames.empty()) {
137 LOG(warning) << "External detector " << GetName() << ": no volume matched the configured sensitive media; "
138 << "no hits will be produced";
139 } else {
140 LOG(info) << "External detector " << GetName() << ": " << mSensitiveVolumeNames.size()
141 << " sensitive volume(s) selected";
142 }
143
144 // place it into the provided anchor volume (needs to exist)
145 auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str());
146 if (!anchor) {
147 LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting";
148 return;
149 }
150 anchor->AddNode(module_top, 1, const_cast<TGeoMatrix*>(mOptions.placement));
151}
152
154{
155 // resolve the MC volume IDs of the sensitive volumes and register them with FairRoot
156 mSensitiveVolIDs.clear();
157 mVolID2SensorID.clear();
158 int sensorID = 0;
159 for (const auto& name : mSensitiveVolumeNames) {
160 const int volID = registerSensitiveVolumeAndGetVolID(name);
161 if (volID <= 0) {
162 continue;
163 }
164 mSensitiveVolIDs.insert(volID);
165 mVolID2SensorID[volID] = sensorID++;
166 LOG(info) << "External detector " << GetName() << ": registered sensitive volume '" << name
167 << "' (MC volID " << volID << ", sensor " << mVolID2SensorID[volID] << ")";
168 }
169
170 // optionally load a user-provided sensitive action from a ROOT macro (same mechanism as
171 // generator/stepping hooks). When given, it fully replaces the built-in action.
172 if (!mOptions.sensitiveMacro.empty()) {
174 const auto func = mOptions.sensitiveFunction.empty() ? std::string("sensitiveAction()") : mOptions.sensitiveFunction;
175 const auto unique = std::string("o2ext_sensitive_action_") + GetName();
176 mSensitiveAction = o2::conf::GetFromMacro<SensitiveFcn>(file, func, "o2::ext::ExternalDetector::SensitiveFcn", unique);
177 if (mSensitiveAction) {
178 LOG(info) << "External detector " << GetName() << ": using sensitive action '" << func
179 << "' from macro '" << file << "'";
180 } else {
181 LOG(fatal) << "External detector " << GetName() << ": could not load sensitive action '" << func
182 << "' from macro '" << file << "'";
183 }
184 }
185}
186
187Bool_t ExternalDetector::ProcessHits(FairVolume* vol)
188{
189 // This method is called from the MC stepping for the registered sensitive volumes.
190 // Remember the current volume so the action helpers (currentSensorID()) can resolve it,
191 // then either run the user-provided action or the built-in one.
192 mCurrentVolume = vol;
193 ++mStepCount; // probe: count stepping calls inside our sensitive volumes
194 if (mSensitiveAction) {
195 return mSensitiveAction(this) ? kTRUE : kFALSE;
196 }
197 return defaultProcessHits();
198}
199
201{
202 if (!(fMC->TrackCharge())) {
203 return kFALSE;
204 }
205
206 const int sensorID = currentSensorID();
207 if (sensorID < 0) {
208 return kFALSE; // not one of our sensitive volumes
209 }
210
211 bool startHit = false, stopHit = false;
212 unsigned char status = 0;
213 if (fMC->IsTrackEntering()) {
215 }
216 if (fMC->IsTrackInside()) {
218 }
219 if (fMC->IsTrackExiting()) {
221 }
222 if (fMC->IsTrackOut()) {
223 status |= o2::ext::Hit::kTrackOut;
224 }
225 if (fMC->IsTrackStop()) {
227 }
228 if (fMC->IsTrackAlive()) {
230 }
231
232 // track is entering or created in the volume
234 startHit = true;
236 stopHit = true;
237 }
238
239 // increment energy loss at all steps except entrance
240 if (!startHit) {
241 mTrackData.mEnergyLoss += fMC->Edep();
242 }
243 if (!(startHit | stopHit)) {
244 return kFALSE; // do nothing
245 }
246
247 if (startHit) {
249 fMC->TrackMomentum(mTrackData.mMomentumStart);
250 fMC->TrackPosition(mTrackData.mPositionStart);
252 mTrackData.mHitStarted = true;
253 }
254 if (stopHit) {
255 TLorentzVector positionStop;
256 fMC->TrackPosition(positionStop);
257 addHit(currentTrackID(), sensorID, mTrackData.mPositionStart.Vect(), positionStop.Vect(),
258 mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(),
259 mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status, fMC->TrackPid(), fMC->TrackLength());
260 mTrackData.mHitStarted = false;
261 }
262 return kTRUE;
263}
264
266{
267 const int volID = mCurrentVolume ? mCurrentVolume->getMCid() : -1;
268 auto it = mVolID2SensorID.find(volID);
269 return it == mVolID2SensorID.end() ? -1 : it->second;
270}
271
273{
274 return static_cast<o2::data::Stack*>(fMC->GetStack())->GetCurrentTrackNumber();
275}
276
277o2::ext::Hit* ExternalDetector::addHit(int trackID, int sensorID, const TVector3& startPos, const TVector3& endPos,
278 const TVector3& startMom, double startE, double endTime, double eLoss,
279 unsigned char startStatus, unsigned char endStatus, int pdg, float length)
280{
281 mHits->emplace_back(trackID, sensorID, startPos, endPos, startMom, startE, endTime, eLoss,
282 startStatus, endStatus, pdg, length);
283 // register that this track left a hit in our detector (sets the hit bit on the MCTrack)
284 static_cast<o2::data::Stack*>(fMC->GetStack())->addHit(GetDetId());
285 return &(mHits->back());
286}
287
289{
290 // Create a branch (named "<name>Hit") holding the produced hits.
291 if (FairRootManager::Instance()) {
292 FairRootManager::Instance()->RegisterAny(addNameTo("Hit").data(), mHits, kTRUE);
293 }
294}
295
297{
298 if (!o2::utils::ShmManager::Instance().isOperational()) {
299 mHits->clear();
300 }
301}
302
304{
305 // probe: report how often our sensitive volumes were stepped through and how many hits resulted
306 LOG(info) << "External detector " << GetName() << " EndOfEvent: " << mStepCount
307 << " sensitive step(s) -> " << (mHits ? mHits->size() : 0) << " hit(s)";
308 mStepCount = 0;
309 Reset();
310}
311
312namespace
313{
314// Build a TGeoCombiTrans from an optional JSON "placement" object carrying
315// "translation":[x,y,z] (cm) and/or "rotation_deg":[rx,ry,rz] (deg, applied X,Y,Z).
316TGeoMatrix* makePlacementFromJSON(const rapidjson::Value& placement)
317{
318 auto combi = new TGeoCombiTrans();
319 if (placement.HasMember("rotation_deg") && placement["rotation_deg"].IsArray()) {
320 const auto& r = placement["rotation_deg"];
321 if (r.Size() == 3) {
322 combi->RotateX(r[0].GetDouble());
323 combi->RotateY(r[1].GetDouble());
324 combi->RotateZ(r[2].GetDouble());
325 } else {
326 LOG(warning) << "ExternalDetector placement 'rotation_deg' must have 3 entries; ignoring";
327 }
328 }
329 if (placement.HasMember("translation") && placement["translation"].IsArray()) {
330 const auto& t = placement["translation"];
331 if (t.Size() == 3) {
332 combi->SetDx(t[0].GetDouble());
333 combi->SetDy(t[1].GetDouble());
334 combi->SetDz(t[2].GetDouble());
335 } else {
336 LOG(warning) << "ExternalDetector placement 'translation' must have 3 entries; ignoring";
337 }
338 }
339 return combi;
340}
341} // namespace
342
343std::vector<ExternalDetector*> ExternalDetector::createFromJSON(const std::string& jsonfile)
344{
345 std::vector<ExternalDetector*> result;
346
347 auto expanded = o2::utils::expandShellVarsInFileName(jsonfile);
348 std::ifstream fileStream(expanded, std::ios::in);
349 if (!fileStream.is_open()) {
350 LOG(error) << "Cannot open external geometry config file '" << expanded << "'";
351 return result;
352 }
353
354 rapidjson::IStreamWrapper isw(fileStream);
355 rapidjson::Document doc;
356 doc.ParseStream(isw);
357 if (doc.HasParseError()) {
358 LOG(error) << "Error parsing external geometry JSON '" << expanded << "': "
359 << rapidjson::GetParseError_En(doc.GetParseError())
360 << " (offset " << doc.GetErrorOffset() << ")";
361 return result;
362 }
363 // the array of sensitive external detectors is optional (the same file may only
364 // configure passive external modules)
365 if (!doc.HasMember("externalDetectors")) {
366 return result;
367 }
368 if (!doc["externalDetectors"].IsArray()) {
369 LOG(error) << "External geometry JSON '" << expanded << "': 'externalDetectors' must be an array";
370 return result;
371 }
372
373 auto getString = [](const rapidjson::Value& v, const char* key) -> std::string {
374 if (v.HasMember(key) && v[key].IsString()) {
375 return v[key].GetString();
376 }
377 return std::string();
378 };
379
380 for (const auto& entry : doc["externalDetectors"].GetArray()) {
381 if (!entry.IsObject()) {
382 LOG(error) << "Skipping non-object entry in 'externalDetectors'";
383 continue;
384 }
385 const auto name = getString(entry, "name");
386 if (name.empty()) {
387 LOG(error) << "Skipping external detector entry without 'name'";
388 continue;
389 }
391 options.root_macro_file = getString(entry, "macro");
392 options.anchor_volume = getString(entry, "anchor");
393 if (options.root_macro_file.empty() || options.anchor_volume.empty()) {
394 LOG(error) << "External detector '" << name << "' requires both 'macro' and 'anchor'; skipping";
395 continue;
396 }
397
398 if (entry.HasMember("sensitiveMedia") && entry["sensitiveMedia"].IsArray()) {
399 for (const auto& m : entry["sensitiveMedia"].GetArray()) {
400 if (m.IsString()) {
401 options.sensitiveMedia.emplace_back(m.GetString());
402 }
403 }
404 }
405 if (entry.HasMember("sensitiveVolumes") && entry["sensitiveVolumes"].IsArray()) {
406 for (const auto& v : entry["sensitiveVolumes"].GetArray()) {
407 if (v.IsString()) {
408 options.sensitiveVolumes.emplace_back(v.GetString());
409 }
410 }
411 }
412 if (options.sensitiveMedia.empty() && options.sensitiveVolumes.empty()) {
413 LOG(error) << "External detector '" << name
414 << "' requires a non-empty 'sensitiveMedia' or 'sensitiveVolumes' array; skipping";
415 continue;
416 }
417
418 const auto detIDName = getString(entry, "detID");
419 if (!detIDName.empty()) {
420 const auto did = o2::detectors::DetID::nameToID(detIDName.c_str());
421 if (did < 0 || did >= o2::detectors::DetID::nDetectors) {
422 LOG(error) << "External detector '" << name << "': unknown detID '" << detIDName << "'; skipping";
423 continue;
424 }
425 options.detID = did;
426 }
427
428 if (entry.HasMember("placement") && entry["placement"].IsObject()) {
429 options.placement = makePlacementFromJSON(entry["placement"]);
430 }
431
432 // optional user-provided sensitive action (a ROOT macro). When absent, the built-in
433 // generic entrance/exit hit action is used.
434 options.sensitiveMacro = getString(entry, "sensitiveMacro");
435 options.sensitiveFunction = getString(entry, "sensitiveFunction");
436
437 auto title = getString(entry, "title");
438 if (title.empty()) {
439 title = name;
440 }
441 LOG(info) << "Configured external detector '" << name << "' from macro '" << options.root_macro_file
442 << "' anchored to '" << options.anchor_volume << "', tied to DetID '"
443 << o2::detectors::DetID::getName(options.detID) << "'";
444 result.push_back(new ExternalDetector(name.c_str(), title.c_str(), options));
445 }
446 return result;
447}
448
449} // namespace o2::ext
450
Helpers to inject CAD-derived (TGeo) geometry into O2 simulation.
Definition of the Stack class.
std::unique_ptr< expressions::Node > node
Sensitive detector built from an externally provided (CAD-derived) geometry.
int32_t i
ClassImp(IdPath)
StringRef key
int registerSensitiveVolumeAndGetVolID(std::string const &name)
Definition Detector.cxx:190
std::string addNameTo(const char *ext) const
Definition Detector.h:150
static constexpr const char * getName(ID id)
names of defined detectors
Definition DetID.h:146
static constexpr int nDetectors
number of defined detectors
Definition DetID.h:97
static constexpr int nameToID(char const *name, int id=First)
Definition DetID.h:155
int currentTrackID() const
MCTrack number of the track currently being stepped.
static std::vector< ExternalDetector * > createFromJSON(const std::string &jsonfile)
std::vector< o2::ext::Hit > * mHits
void InitializeO2Detector() override
Resolve the Monte Carlo volume IDs of the sensitive volumes.
int mStepCount
volume currently passed to ProcessHits (for the action helpers)
std::unordered_map< int, int > mVolID2SensorID
MC volume IDs of the sensitive volumes.
o2::ext::Hit * addHit(int trackID, int sensorID, const TVector3 &startPos, const TVector3 &endPos, const TVector3 &startMom, double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus, int pdg=0, float length=0.f)
ExternalDetectorOptions mOptions
void Register() override
Register the hit collection with the FairRootManager.
SensitiveFcn mSensitiveAction
container for produced hits
FairVolume * mCurrentVolume
optional user-provided sensitive action (loaded from a macro)
void collectSensitiveVolumeNames(TGeoVolume *vol, std::set< TGeoVolume * > &visited)
recursively collect names of volumes whose medium matches the configured sensitive media
Bool_t ProcessHits(FairVolume *v=nullptr) override
Called for each tracking step; produces hits in the sensitive volumes.
void ConstructGeometry() override
Build the CAD geometry, remap its media and register the sensitive volumes.
std::vector< std::string > mSensitiveVolumeNames
Bool_t defaultProcessHits()
the built-in sensitive action used when no macro is configured (generic entrance/exit hit)
struct o2::ext::ExternalDetector::TrackData mTrackData
std::set< int > mSensitiveVolIDs
names of the volumes to be made sensitive (filled at geometry build)
@ kTrackOut
Definition Hit.h:45
@ kTrackExiting
Definition Hit.h:44
@ kTrackInside
Definition Hit.h:43
@ kTrackEntering
Definition Hit.h:42
@ kTrackAlive
Definition Hit.h:47
@ kTrackStopped
Definition Hit.h:46
static ShmManager & Instance()
Definition ShmManager.h:61
const GLfloat * m
Definition glcorearb.h:4066
GLenum func
Definition glcorearb.h:778
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint entry
Definition glcorearb.h:5735
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean * data
Definition glcorearb.h:298
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLboolean r
Definition glcorearb.h:1233
TGeoVolume * buildCADVolumeFromMacro(const std::string &macroFile, const std::string &instanceTag)
void remapCADMedia(TGeoVolume *top, const char *modulename)
void freeSimVector(std::vector< T > *ptr)
std::string expandShellVarsInFileName(std::string const &input)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
Common utility functions.
Configuration of a single sensitive external detector.
std::vector< std::string > sensitiveMedia
std::vector< std::string > sensitiveVolumes
TLorentzVector mPositionStart
track status flag at entrance
double mEnergyLoss
momentum at entrance
unsigned char mTrkStatusStart
hit creation started
TLorentzVector mMomentumStart
position at entrance
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"