99bool primaryServer_sendShutdownPermission(fair::mq::Channel& channel)
102 std::unique_ptr<fair::mq::Message> reply(channel.NewMessage());
104 int timeoutinMS = 100;
105 if (channel.Send(request, timeoutinMS) > 0) {
106 LOG(info) <<
"Sending Shutdown permission to particle server";
107 if (channel.Receive(reply, timeoutinMS) > 0) {
109 LOG(info) <<
"Shutdown permission was acknowledged";
111 LOG(error) <<
"No answer received within " << timeoutinMS <<
"ms\n";
123 mInitialOutputDir = std::filesystem::current_path().string();
124 mCurrentOutputDir = mInitialOutputDir;
129 FairSystemInfo sysinfo;
130 LOG(info) <<
"TIME-STAMP " << mTimer.RealTime() <<
"\t";
132 LOG(info) <<
"MEM-STAMP " << sysinfo.GetCurrentMemory() / (1024. * 1024) <<
" "
133 << sysinfo.GetMaxMemory() <<
" MB\n";
136void O2HitMerger::InitTask()
138 LOG(info) <<
"INIT HIT MERGER";
139 ROOT::EnableThreadSafety();
141 std::string outfilename(
"o2sim_merged_hits.root");
148 LOG(fatal) <<
"No configuration received. Aborting";
154 mOutFileName = outfilename.c_str();
156 mOutFile =
new TFile(outfilename.c_str(),
"RECREATE");
157 mOutTree =
new TTree(
"o2sim",
"o2sim");
158 mOutTree->SetDirectory(mOutFile);
161 mMCHeaderTree =
new TTree(
"o2sim",
"o2sim");
162 mMCHeaderTree->SetDirectory(mMCHeaderOnlyOutFile);
165 if (mDetectorInstances.size() == 0) {
173 auto pipeenv = getenv(
"ALICE_O2SIMMERGERTODRIVER_PIPE");
175 mPipeToDriver = atoi(pipeenv);
176 LOG(info) <<
"ASSIGNED PIPE HANDLE " << mPipeToDriver;
178 LOG(warning) <<
"DID NOT FIND ENVIRONMENT VARIABLE TO INIT PIPE";
182 if (mNExpectedEvents == 0) {
184 waitForControlInput();
186 LOG(info) <<
"NOT EXPECTING ANY DATA; SHUTTING DOWN";
192bool O2HitMerger::setWorkingDirectory(std::string
const& dir)
194 namespace fs = std::filesystem;
204 fs::current_path(fs::path(mInitialOutputDir));
206 auto absolutePath = fs::absolute(fs::path(dir));
207 if (!fs::exists(absolutePath)) {
208 if (!fs::create_directory(absolutePath)) {
209 LOG(error) <<
"Could not create directory " << absolutePath.string();
214 fs::current_path(absolutePath.string().c_str());
215 mCurrentOutputDir = fs::current_path().string();
217 LOG(info) <<
"FINAL PATH " << mCurrentOutputDir;
218 }
catch (std::exception e) {
219 LOG(error) <<
" could not change path to " << dir;
229 if (!setWorkingDirectory(reconfig.
outputDir)) {
233 std::string outfilename(
"o2sim_merged_hits.root");
235 mNExpectedEvents = reconfig.
nEvents;
236 mOutFileName = outfilename.c_str();
238 mOutFile =
new TFile(outfilename.c_str(),
"RECREATE");
239 mOutTree =
new TTree(
"o2sim",
"o2sim");
240 mOutTree->SetDirectory(mOutFile);
243 mMCHeaderTree =
new TTree(
"o2sim",
"o2sim");
244 mMCHeaderTree->SetDirectory(mMCHeaderOnlyOutFile);
250 mPartsCheckSum.clear();
254 mMCTrackBuffer.clear();
255 mTrackRefBuffer.clear();
256 mSubEventInfoBuffer.clear();
257 mFlushableEvents.clear();
263template <
typename T,
typename V>
264V O2HitMerger::insertAdd(std::map<T, V>&
m, T
const&
key, V
value)
266 const auto iter =
m.find(
key);
268 if (iter !=
m.end()) {
269 iter->second +=
value;
270 accum = iter->second;
279bool O2HitMerger::isDataComplete(T checksum, T nparts)
281 return checksum == nparts * (nparts + 1) / 2;
284void O2HitMerger::consumeHits(
int eventID, fair::mq::Parts&
data,
int&
index)
286 auto headermessage = std::move(
data.At(
index++));
291 LOG(debug2) <<
"I1 " <<
header.detID <<
" NAME " <<
id.getName() <<
" MB "
292 <<
data.At(
index)->GetSize() / 1024. / 1024.;
295 auto detector = mDetectorInstances[
id].get();
302template <
typename T,
typename BT>
303void O2HitMerger::consumeData(
int eventID, fair::mq::Parts&
data,
int&
index, BT&
buffer)
305 auto decodeddata = o2::base::decodeTMessage<T*>(
data,
index);
307 buffer[eventID] =
typename BT::mapped_type();
309 buffer[eventID].push_back(decodeddata);
316 if (mSubEventInfoBuffer.find(
info.eventID) == mSubEventInfoBuffer.end()) {
317 mSubEventInfoBuffer[
info.eventID] = std::list<o2::data::SubEventInfo*>();
319 mSubEventInfoBuffer[
info.eventID].push_back(&info);
322bool O2HitMerger::waitForControlInput()
326 auto factory = fair::mq::TransportFactory::CreateTransportFactory(
"zeromq");
327 auto channel = fair::mq::Channel{
"o2sim-control",
"sub", factory};
328 auto controlsocketname = getenv(
"ALICE_O2SIMCONTROL");
329 LOG(info) <<
"SOCKETNAME " << controlsocketname;
330 channel.Connect(std::string(controlsocketname));
332 std::unique_ptr<fair::mq::Message> reply(channel.NewMessage());
334 LOG(info) <<
"WAITING FOR INPUT";
335 if (channel.Receive(reply) > 0) {
336 auto data = reply->GetData();
337 auto size = reply->GetSize();
339 std::string command(
reinterpret_cast<char const*
>(
data),
size);
340 LOG(info) <<
"message: " << command;
344 return ReInit(reconfig);
346 LOG(info) <<
"NOTHING RECEIVED";
351bool O2HitMerger::ConditionalRun()
353 auto& channel = GetChannels().at(
"simdata").at(0);
354 fair::mq::Parts request;
355 auto bytes = channel.Receive(request);
357 LOG(error) <<
"Some error occurred on socket during receive on sim data";
362 auto more = handleSimData(request, 0);
363 LOG(info) <<
"HitMerger processing took " << timer.RealTime();
364 if (!more && mAsService) {
365 LOG(info) <<
" CONTROL ";
371 static bool initAcknowledged =
false;
372 if (!initAcknowledged) {
373 primaryServer_sendShutdownPermission(GetChannels().at(
"o2sim-primserv-info").at(0));
374 initAcknowledged =
true;
380bool O2HitMerger::handleSimData(fair::mq::Parts&
data,
int )
382 bool expectmore =
true;
384 auto infoptr = o2::base::decodeTMessage<o2::data::SubEventInfo*>(
data,
index++);
387 const auto eventID =
info.eventID;
388 const auto maxEvents =
info.maxEvents;
389 auto accum = insertAdd<uint32_t, uint32_t>(mPartsCheckSum,
info.eventID, (uint32_t)
info.part);
391 LOG(info) <<
"SIMDATA channel got " <<
data.Size() <<
" parts for event " <<
info.eventID <<
" part " <<
info.part <<
" out of " <<
info.nparts;
393 fillSubEventInfoEntry(info);
394 consumeData<std::vector<o2::MCTrack>>(
info.eventID,
data,
index, mMCTrackBuffer);
395 consumeData<std::vector<o2::TrackReference>>(
info.eventID,
data,
index, mTrackRefBuffer);
400 if (isDataComplete<uint32_t>(accum,
info.nparts)) {
401 LOG(info) <<
"Event " <<
info.eventID <<
" complete. Marking as flushable";
402 mFlushableEvents[
info.eventID] =
true;
408 if (!mergingInProgress) {
409 if (mMergerIOThread.joinable()) {
410 mMergerIOThread.join();
413 mMergerIOThread = std::thread([
this]() { mergingInProgress =
true; mergeAndFlushData(); mergingInProgress =
false; });
416 mEventChecksum += eventID;
418 if (isDataComplete<uint32_t>(mEventChecksum, maxEvents)) {
419 LOG(info) <<
"ALL EVENTS HERE; CHECKSUM " << mEventChecksum;
422 if (mMergerIOThread.joinable()) {
423 mMergerIOThread.join();
425 mMergerIOThread = std::thread([
this]() { mergingInProgress =
true; mergeAndFlushData(); mergingInProgress =
false; });
426 if (mMergerIOThread.joinable()) {
427 mMergerIOThread.join();
433 if (mPipeToDriver != -1) {
434 if (write(mPipeToDriver, &eventID,
sizeof(eventID)) == -1) {
435 LOG(error) <<
"FAILED WRITING TO PIPE";
442void O2HitMerger::cleanEvent(
int eventID)
444 auto release = [eventID](
auto&
buffer) {
445 auto iter =
buffer.find(eventID);
446 if (iter !=
buffer.end()) {
447 for (
auto ptr : iter->second) {
453 release(mMCTrackBuffer);
454 release(mTrackRefBuffer);
455 release(mSubEventInfoBuffer);
459void O2HitMerger::backInsert(T
const& from, T& to)
461 std::copy(from.begin(), from.end(), std::back_inserter(to));
464void O2HitMerger::reorderAndMergeMCTracks(
int eventID, TTree*
target,
const std::vector<int>& nprimaries,
const std::vector<int>& nsubevents, std::function<
void(std::vector<MCTrack>
const&)> tracks_analysis_hook,
o2::dataformats::MCEventHeader const* mceventheader)
467 std::vector<MCTrack>* mcTracksPerSubEvent =
nullptr;
468 auto targetdata = std::make_unique<std::vector<MCTrack>>();
470 auto& vectorOfSubEventMCTracks = mMCTrackBuffer[eventID];
471 const auto entries = vectorOfSubEventMCTracks.size();
475 for (
auto tracks : vectorOfSubEventMCTracks) {
476 ntracks +=
tracks->size();
478 targetdata->reserve(ntracks);
485 nprimTot += nprimaries[
index];
486 for (
int i = 0;
i < nprimaries[
index];
i++) {
487 auto&
track = (*vectorOfSubEventMCTracks[
index])[
i];
488 if (
track.isTransported()) {
489 track.SetFirstDaughterTrackId(-1);
490 track.SetLastDaughterTrackId(-1);
492 targetdata->push_back(track);
498 Int_t idelta1 = nprimTot;
503 auto& subEventTracks = *(vectorOfSubEventMCTracks[
index]);
505 Int_t npart = (
int)(subEventTracks.size());
506 Int_t nprim = nprimaries[
index];
509 for (Int_t
i = nprim;
i < npart;
i++) {
510 auto&
track = subEventTracks[
i];
511 Int_t cId =
track.getMotherTrackId();
517 track.SetMotherTrackId(cId);
518 track.SetFirstDaughterTrackId(-1);
520 Int_t hwm = (
int)(targetdata->size());
521 auto& mother = (*targetdata)[cId];
522 if (mother.getFirstDaughterTrackId() == -1) {
523 mother.SetFirstDaughterTrackId(hwm);
525 mother.SetLastDaughterTrackId(hwm);
527 targetdata->push_back(track);
535 auto filladdr = (
entries > 1) ? targetdata.get() : vectorOfSubEventMCTracks[0];
539 tracks_analysis_hook(*filladdr);
541 if (mWriteToDisc &&
target) {
543 targetbr->SetAddress(&filladdr);
545 targetbr->ResetAddress();
549 auto free_tmessage = [](
void*
data,
void* hint) {
delete static_cast<TMessage*
>(hint); };
550 auto& channel = GetChannels().at(
"kineforward").at(0);
552 tmsg->WriteObjectAny((
void*)filladdr, TClass::GetClass(
"std::vector<o2::MCTrack>"));
553 std::unique_ptr<fair::mq::Message> trackmessage(channel.NewMessage(tmsg->Buffer(), tmsg->BufferSize(), free_tmessage, tmsg));
555 tmsg->WriteObjectAny((
void*)mceventheader, TClass::GetClass(
"o2::dataformats::MCEventHeader"));
556 std::unique_ptr<fair::mq::Message> headermessage(channel.NewMessage(tmsg->Buffer(), tmsg->BufferSize(), free_tmessage, tmsg));
557 fair::mq::Parts reply;
558 reply.AddPart(std::move(headermessage));
559 reply.AddPart(std::move(trackmessage));
561 LOG(info) <<
"Forward publish MC tracks on channel";
565template <
typename T,
typename M>
566void O2HitMerger::remapTrackIdsAndMerge(std::string brname,
int eventID, TTree&
target,
const std::vector<int>& trackoffsets,
const std::vector<int>& nprimaries,
const std::vector<int>& subevOrdered, M& mapOfVectorOfTs)
573 T* incomingdata =
nullptr;
574 std::unique_ptr<T> targetdata(
nullptr);
575 auto& vectorOfT = mapOfVectorOfTs[eventID];
576 const auto entries = vectorOfT.size();
580 incomingdata = vectorOfT[0];
582 targetdata = std::make_unique<T>();
584 for (
auto data : vectorOfT) {
585 nentries +=
data->size();
587 targetdata->reserve(nentries);
591 nprimTot += nprimaries[
entry];
594 Int_t idelta1 = nprimTot;
597 Int_t nprim = nprimaries[
index];
598 incomingdata = vectorOfT[
index];
600 for (
auto&
data : *incomingdata) {
601 updateTrackIdWithOffset(
data, nprim, idelta0, idelta1);
602 targetdata->push_back(
data);
605 idelta1 += trackoffsets[
index];
608 auto dataaddr = (
entries == 1) ? incomingdata : targetdata.
get();
610 targetbr->SetAddress(&dataaddr);
612 targetbr->ResetAddress();
615void O2HitMerger::updateTrackIdWithOffset(
MCTrack& track, Int_t nprim, Int_t idelta0, Int_t idelta1)
617 Int_t cId =
track.getMotherTrackId();
618 Int_t ioffset = (cId < nprim) ? idelta0 : idelta1;
620 track.SetMotherTrackId(cId + ioffset);
624void O2HitMerger::updateTrackIdWithOffset(TrackReference&
ref, Int_t nprim, Int_t idelta0, Int_t idelta1)
629void O2HitMerger::initHitTreeAndOutFile(std::string prefix,
int detID)
632 if (mDetectorOutFiles.find(detID) != mDetectorOutFiles.end() && mDetectorOutFiles[detID]) {
633 LOG(warn) <<
"Hit outfile for detID " <<
DetID::getName(detID) <<
" already initialized --> Reopening";
634 mDetectorOutFiles[detID]->Close();
635 delete mDetectorOutFiles[detID];
639 mDetectorOutFiles[detID] =
new TFile(
name.c_str(),
"RECREATE");
640 mDetectorToTTreeMap[detID] =
new TTree(
"o2sim",
"o2sim");
641 mDetectorToTTreeMap[detID]->SetDirectory(mDetectorOutFiles[detID]);
643 mDetectorOutFiles[detID] =
nullptr;
644 mDetectorToTTreeMap[detID] =
nullptr;
648bool O2HitMerger::mergeAndFlushData()
650 auto isFlushable = [
this](
int eventID) {
651 auto iter = mFlushableEvents.find(eventID);
652 return iter != mFlushableEvents.end() && iter->second;
655 LOG(info) <<
"Launching merge kernel ";
656 if (!isFlushable(mNextFlushID)) {
659 for (; isFlushable(mNextFlushID); ++mNextFlushID) {
660 auto flusheventID = mNextFlushID;
661 LOG(info) <<
"Merge and flush event " << flusheventID;
662 auto iter = mSubEventInfoBuffer.find(flusheventID);
663 if (iter == mSubEventInfoBuffer.end() || iter->second.size() == 0 || mNExpectedEvents == 0) {
664 LOG(error) <<
"No data entries found for event " << flusheventID;
667 auto& subEventInfoList = iter->second;
676 std::vector<int> trackoffsets;
678 std::vector<int> nprimaries;
680 std::vector<int> nsubevents;
685 for (
auto info : subEventInfoList) {
686 assert(
info->npersistenttracks >= 0);
687 trackoffsets.emplace_back(
info->npersistenttracks);
688 nprimaries.emplace_back(
info->nprimarytracks);
689 nsubevents.emplace_back(
info->part);
690 if (eventheader ==
nullptr) {
691 eventheader = &
info->mMCEventHeader;
698 if (confref.isFilterOutNoHitEvents()) {
700 LOG(info) <<
" Taking out event " << flusheventID <<
" due to no hits ";
701 cleanEvent(flusheventID);
712 const auto entries = subEventInfoList.size();
713 std::vector<int> subevOrdered((
int)(nsubevents.size()));
724 auto mcheaderhook = [eventheader](std::vector<MCTrack>
const&
tracks) {
725 int eta1Point2Counter = 0;
726 int eta1Point0Counter = 0;
727 int eta0Point8Counter = 0;
728 int eta1Point2CounterPi = 0;
729 int eta1Point0CounterPi = 0;
730 int eta0Point8CounterPi = 0;
733 if (tr.isPrimary()) {
735 const auto eta = tr.GetEta();
738 if (std::abs(tr.GetPdgCode()) == 211) {
739 eta1Point2CounterPi++;
744 if (std::abs(tr.GetPdgCode()) == 211) {
745 eta1Point0CounterPi++;
750 if (std::abs(tr.GetPdgCode()) == 211) {
751 eta0Point8CounterPi++;
760 eventheader->
putInfo(
"prims_eta_1.2", eta1Point2Counter);
761 eventheader->
putInfo(
"prims_eta_1.0", eta1Point0Counter);
762 eventheader->
putInfo(
"prims_eta_0.8", eta0Point8Counter);
763 eventheader->
putInfo(
"prims_eta_1.2_pi", eta1Point2CounterPi);
764 eventheader->
putInfo(
"prims_eta_1.0_pi", eta1Point0CounterPi);
765 eventheader->
putInfo(
"prims_eta_0.8_pi", eta0Point8CounterPi);
766 eventheader->
putInfo(
"prims_total", prims);
770 std::vector<std::thread> tasks;
771 tasks.emplace_back([&]() {
772 reorderAndMergeMCTracks(flusheventID, mOutTree, nprimaries, subevOrdered, mcheaderhook, eventheader);
776 remapTrackIdsAndMerge<std::vector<o2::TrackReference>>(
"TrackRefs", flusheventID, *mOutTree, trackoffsets, nprimaries, subevOrdered, mTrackRefBuffer);
779 for (
auto tree : {mOutTree, mMCHeaderTree}) {
781 headerbr->SetAddress(&eventheader);
783 headerbr->ResetAddress();
787 mOutTree->SetEntries(mOutTree->GetEntries() + 1);
788 mMCHeaderTree->SetEntries(mMCHeaderTree->GetEntries() + 1);
795 for (
int id = 0;
id < mDetectorInstances.size(); ++
id) {
796 auto& det = mDetectorInstances[
id];
797 auto hittree = det ? mDetectorToTTreeMap[
id] :
nullptr;
799 tasks.emplace_back([&, det = det.get(), hittree]() {
800 det->mergeHitEntriesAndFlush(flusheventID, *hittree, trackoffsets, nprimaries, subevOrdered);
801 hittree->SetEntries(hittree->GetEntries() + 1);
805 for (
auto& t : tasks) {
809 cleanEvent(flusheventID);
810 LOG(info) <<
"Merge/flush for event " << flusheventID <<
" took " << timer.RealTime();
812 if (mWriteToDisc && mOutFile) {
813 LOG(info) <<
"Writing TTrees";
814 std::vector<TFile*> files{mOutFile, mMCHeaderOnlyOutFile};
815 for (
int id = 0;
id < mDetectorInstances.size(); ++
id) {
816 if (mDetectorInstances[
id] && mDetectorOutFiles[
id]) {
817 files.push_back(mDetectorOutFiles[
id]);
820 std::vector<std::thread> writers;
821 for (
auto file : files) {
822 writers.emplace_back([file]() {
file->Write(
"", TObject::kOverwrite); });
824 for (
auto& t : writers) {
831void O2HitMerger::initHitFiles(std::string prefix)
836 auto isActivated = [](std::string
s) ->
bool {
839 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
847 initHitTreeAndOutFile(prefix,
i);
852 for (
auto detID : mExternalDetIDs) {
853 initHitTreeAndOutFile(prefix, detID);
858void O2HitMerger::initDetInstances()
863 auto isActivated = [](std::string
s) ->
bool {
866 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
870 using Factory = std::function<std::unique_ptr<o2::base::Detector>()>;
871 const std::map<int, Factory> factories{
872 {DetID::TPC, [] {
return std::make_unique<o2::tpc::Detector>(
true); }},
873 {DetID::ITS, [] {
return std::make_unique<o2::its::Detector>(
true); }},
874 {DetID::MFT, [] {
return std::make_unique<o2::mft::Detector>(
true); }},
875 {DetID::TRD, [] {
return std::make_unique<o2::trd::Detector>(
true); }},
876 {DetID::PHS, [] {
return std::make_unique<o2::phos::Detector>(
true); }},
877 {DetID::CPV, [] {
return std::make_unique<o2::cpv::Detector>(
true); }},
878 {DetID::EMC, [] {
return std::make_unique<o2::emcal::Detector>(
true); }},
879 {DetID::HMP, [] {
return std::make_unique<o2::hmpid::Detector>(
true); }},
880 {DetID::TOF, [] {
return std::make_unique<o2::tof::Detector>(
true); }},
881 {DetID::FT0, [] {
return std::make_unique<o2::ft0::Detector>(
true); }},
882 {DetID::FV0, [] {
return std::make_unique<o2::fv0::Detector>(
true); }},
883 {DetID::FDD, [] {
return std::make_unique<o2::fdd::Detector>(
true); }},
884 {DetID::MCH, [] {
return std::make_unique<o2::mch::Detector>(
true); }},
885 {DetID::MID, [] {
return std::make_unique<o2::mid::Detector>(
true); }},
886 {DetID::ZDC, [] {
return std::make_unique<o2::zdc::Detector>(
true); }},
888 TString sName =
"$O2_ROOT/share/Detectors/Geometry/FOC/geometryFiles/geometry_Sheets.txt";
889 gSystem->ExpandPathName(sName);
890 return std::make_unique<o2::focal::Detector>(
true, sName.Data());
892#ifdef ENABLE_UPGRADES
893 {DetID::IT3, [] {
return std::make_unique<o2::its::Detector>(
true,
"IT3"); }},
894 {DetID::TRK, [] {
return std::make_unique<o2::trk::Detector>(
true); }},
895 {DetID::FT3, [] {
return std::make_unique<o2::ft3::Detector>(
true); }},
896 {DetID::FCT, [] {
return std::make_unique<o2::fct::Detector>(
true); }},
897 {DetID::TF3, [] {
return std::make_unique<o2::iotof::Detector>(
true); }},
898 {DetID::RCH, [] {
return std::make_unique<o2::rich::Detector>(
true); }},
899 {DetID::MI3, [] {
return std::make_unique<o2::mi3::Detector>(
true); }},
900 {DetID::ECL, [] {
return std::make_unique<o2::ecal::Detector>(
true); }},
901 {DetID::FD3, [] {
return std::make_unique<o2::fd3::Detector>(
true); }},
905 mDetectorInstances.resize(DetID::nDetectors);
910 auto factory = factories.find(
i);
911 if (factory == factories.end()) {
912 LOG(warning) <<
"O2HitMerger: no hit merging available for readout detector " <<
DetID::getName(
i);
915 mDetectorInstances[
i] = factory->second();
920 initExternalDetInstances();
928void O2HitMerger::initExternalDetInstances()
933 const auto extGeomFile = simConfig.getExtGeomFilename();
934 if (extGeomFile.empty()) {
940 auto const& activeModules = simConfig.getActiveModules();
941 auto isActivated = [&activeModules](std::string
const&
s) ->
bool {
942 return std::find(activeModules.begin(), activeModules.end(), s) != activeModules.end();
945 for (
auto* extdet :
o2::ext::ExternalDetector::createFromJSON(extGeomFile)) {
946 const std::string
name = extdet->GetName();
947 if (!isActivated(
name)) {
951 const int detID = extdet->GetDetId();
953 LOG(error) <<
"O2HitMerger: external detector " <<
name <<
" has invalid DetID " << detID <<
"; skipping";
957 if (mDetectorInstances[detID]) {
958 LOG(error) <<
"O2HitMerger: DetID " <<
DetID::getName(detID) <<
" requested by external detector " <<
name
959 <<
" is already occupied; its hits will not be persisted. Assign a free DetID.";
963 mDetectorInstances[detID].reset(extdet);
964 mExternalDetIDs.emplace_back(detID);
965 LOG(info) <<
"O2HitMerger: registered external detector " <<
name <<
" on DetID " <<
DetID::getName(detID)
966 <<
" (branch " <<
name <<
"Hit)";