From 9faecc62d8751c50faa0a569bef342dd64f714f3 Mon Sep 17 00:00:00 2001 From: shahor02 Date: Mon, 3 Aug 2026 16:45:06 +0400 Subject: [PATCH 01/22] GeometryManager::getSensID supports up to 2^17 sensors for DetID>FOCAL (#15656) For detectors up to Focal inclusively only 1^15 chips per detector can be referred by this method (for the backward compatibility with existing geometry files). --- Detectors/Base/include/DetectorsBase/GeometryManager.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Detectors/Base/include/DetectorsBase/GeometryManager.h b/Detectors/Base/include/DetectorsBase/GeometryManager.h index 5e296a4045984..938c4d127ed15 100644 --- a/Detectors/Base/include/DetectorsBase/GeometryManager.h +++ b/Detectors/Base/include/DetectorsBase/GeometryManager.h @@ -70,7 +70,7 @@ class GeometryManager : public TObject static int getSensID(o2::detectors::DetID detid, int sensid) { /// compose combined detector+sensor ID for sensitive volumes - return (detid << sDetOffset) | (sensid & sSensorMask); + return detid <= o2::detectors::DetID::FOC ? ((detid << sDetOffset) | (sensid & sSensorMask)) : ((detid << sDetOffsetLarge) | (sensid & sSensorMaskLarge)); } /// Default destructor @@ -140,8 +140,9 @@ class GeometryManager : public TObject private: /// sensitive volume identifier composed from (det_ID< Date: Fri, 31 Jul 2026 15:15:44 +0200 Subject: [PATCH 02/22] Honour the CCDB time machine for the mu(bc) distribution The mu(bc) lookup in CollisionContextTool uses its own CCDBManagerInstance, which -- unlike BasicCCDBManager -- never picks up the time-machine constraint from ALICEO2_CCDB_CONDITION_NOT_AFTER. FT0/Calib/EventsPerBc was therefore resolved against the present even when a time machine was requested, quietly switching the sampler and changing the collision context at an unchanged seed. Bunch filling and mean vertex, which go through BasicCCDBManager, were pinned correctly. Now carry the constraint over explicitly. See O2-7093. --- Steer/src/CollisionContextTool.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Steer/src/CollisionContextTool.cxx b/Steer/src/CollisionContextTool.cxx index e97eeada3fd0c..f0abe6b7c5d03 100644 --- a/Steer/src/CollisionContextTool.cxx +++ b/Steer/src/CollisionContextTool.cxx @@ -440,6 +440,10 @@ int main(int argc, char* argv[]) // for now construct a specific CCDBManager for this query o2::ccdb::CCDBManagerInstance ccdb_inst(ccdb_info.server + std::string(":") + ccdb_info.port); ccdb_inst.setFatalWhenNull(false); + // this is a private instance, so it does not inherit the time-machine + // constraint that BasicCCDBManager picks up from the environment; + // carry it over explicitly (a 0 here means "unconstrained" anyway) + ccdb_inst.setCreatedNotAfter(o2::ccdb::BasicCCDBManager::instance().getCreatedNotAfter()); auto local_hist = ccdb_inst.getForTimeStamp(ccdb_info.fullPath, options.timestamp); if (local_hist) { // case in which CCDB object contains directly a ROOT histogram From 52400413f9f4eb3d7c9eccb250a75b48a40f35fa Mon Sep 17 00:00:00 2001 From: shahor02 Date: Mon, 3 Aug 2026 21:11:21 +0400 Subject: [PATCH 03/22] tpc-time-series equiped with internal readers and HBFUtilsInitializer (#15659) The input cluster sources will be deduced from the track source. The InputHelper will be invoked if --disable-root-inputs option is absent. Possibility to prepend the workflow with the rate/maxTF limiter in standalone mode (i.e. w/o --disable-root-inputs). E.g GLOSET="--shm-segment-size 10000000000 --timeframes-rate-limit 2 --timeframes-rate-limit-ipcid 1234 --hbfutils-config o2_tfidinfo.root,upstream" o2-reader-driver-workflow --max-tf ${MAXTF:--1} $GLOSET | \ o2-tpc-time-series-workflow $GLOSET --enable-unbinned-root-output -b --run --- Detectors/TPC/workflow/CMakeLists.txt | 4 +- .../TPC/workflow/src/tpc-time-series.cxx | 40 +++++++++++++++++-- prodtests/full-system-test/calib-workflow.sh | 4 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index f64a223f683d8..44bcfd23ebbc2 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -231,7 +231,9 @@ o2_add_executable(merge-integrate-cluster-workflow o2_add_executable(time-series-workflow COMPONENT_NAME tpc SOURCES src/tpc-time-series.cxx - PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow + O2::GlobalTrackingWorkflowReaders + O2::GlobalTrackingWorkflowHelpers) o2_add_executable(scaler-workflow COMPONENT_NAME tpc diff --git a/Detectors/TPC/workflow/src/tpc-time-series.cxx b/Detectors/TPC/workflow/src/tpc-time-series.cxx index 782e45fb04673..06c3094f679c3 100644 --- a/Detectors/TPC/workflow/src/tpc-time-series.cxx +++ b/Detectors/TPC/workflow/src/tpc-time-series.cxx @@ -14,12 +14,25 @@ #include "TPCWorkflow/TPCTimeSeriesSpec.h" #include "TPCWorkflow/TPCTimeSeriesWriterSpec.h" +#include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" #include "TPCReaderWorkflow/TPCSectorCompletionPolicy.h" +#include "DetectorsBase/DPLWorkflowUtils.h" +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "DetectorsRaw/HBFUtilsInitializer.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "Framework/ConfigParamSpec.h" #include "GPUDebugStreamer.h" using namespace o2::framework; +using GID = o2::dataformats::GlobalTrackID; +using DetID = o2::detectors::DetID; + +// ------------------------------------------------------------------ +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} void customize(std::vector& workflowOptions) { @@ -27,10 +40,12 @@ void customize(std::vector& workflowOptions) std::vector options{ ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"disable-root-output", VariantType::Bool, false, {"disable root-files output writers"}}, + {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"enable-unbinned-root-output", VariantType::Bool, false, {"writing out unbinned track data"}}, - {"track-sources", VariantType::String, std::string{o2::dataformats::GlobalTrackID::ALL}, {"comma-separated list of sources to use"}}, + {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of sources to use"}}, {"material-type", VariantType::Int, 2, {"Type for the material budget during track propagation: 0=None, 1=Geo, 2=LUT"}}}; - + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -42,11 +57,28 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); const bool disableWriter = config.options().get("disable-root-output"); const bool enableUnbinnedWriter = config.options().get("enable-unbinned-root-output"); - auto src = o2::dataformats::GlobalTrackID::getSourcesMask(config.options().get("track-sources")); + GID::mask_t allowedSources = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF,FT0"); + auto srcTrc = allowedSources & GID::getSourcesMask(config.options().get("track-sources")); + o2::dataformats::GlobalTrackID::mask_t srcCls = GID::getSourcesMask("TPC"); + if (GID::includesDet(DetID::ITS, srcTrc)) { + srcCls |= GID::getSourcesMask("ITS"); + } + if (GID::includesDet(DetID::TRD, srcTrc)) { + srcCls |= GID::getSourcesMask("TRD"); + } + if (GID::includesDet(DetID::TOF, srcTrc)) { + srcCls |= GID::getSourcesMask("TOF"); + } + auto materialType = static_cast(config.options().get("material-type")); - workflow.emplace_back(o2::tpc::getTPCTimeSeriesSpec(disableWriter, materialType, enableUnbinnedWriter, src)); + + o2::globaltracking::InputHelper::addInputSpecs(config, workflow, srcCls, srcTrc, srcTrc, false); + o2::globaltracking::InputHelper::addInputSpecsPVertex(config, workflow, false); // P-vertex is always needed + + workflow.emplace_back(o2::tpc::getTPCTimeSeriesSpec(disableWriter, materialType, enableUnbinnedWriter, srcTrc)); if (!disableWriter) { workflow.emplace_back(o2::tpc::getTPCTimeSeriesWriterSpec()); } + o2::raw::HBFUtilsInitializer hbfIni(config, workflow); return workflow; } diff --git a/prodtests/full-system-test/calib-workflow.sh b/prodtests/full-system-test/calib-workflow.sh index 72f7a5aa47056..a14ff3b620d45 100644 --- a/prodtests/full-system-test/calib-workflow.sh +++ b/prodtests/full-system-test/calib-workflow.sh @@ -23,7 +23,7 @@ fi if [[ "${CALIB_TPC_SCDCALIB_SENDTRKDATA:-}" == "1" ]]; then ENABLE_TRKDATA_OUTPUT="--send-track-data"; else ENABLE_TRKDATA_OUTPUT=""; fi # specific calibration workflows -if [[ $CALIB_TPC_SCDCALIB == 1 ]]; then add_W o2-tpc-scdcalib-interpolation-workflow "--vtx-sources $VERTEX_TRACK_MATCHING_SOURCES --tracking-sources $TRACK_SOURCES ${CALIB_TPC_SCDCALIB_SLOTLENGTH:+"--sec-per-slot $CALIB_TPC_SCDCALIB_SLOTLENGTH"} $ENABLE_TRKDATA_OUTPUT $DISABLE_ROOT_OUTPUT --disable-root-input --pipeline $(get_N tpc-track-interpolation TPC REST)"; fi +if [[ $CALIB_TPC_SCDCALIB == 1 ]]; then add_W o2-tpc-scdcalib-interpolation-workflow "--vtx-sources $VERTEX_TRACK_MATCHING_SOURCES --tracking-sources $TRACK_SOURCES ${CALIB_TPC_SCDCALIB_SLOTLENGTH:+"--sec-per-slot $CALIB_TPC_SCDCALIB_SLOTLENGTH"} $ENABLE_TRKDATA_OUTPUT $DISABLE_ROOT_OUTPUT $DISABLE_ROOT_INPUT --pipeline $(get_N tpc-track-interpolation TPC REST)"; fi if [[ $CALIB_TPC_TIMEGAIN == 1 ]]; then : ${SCALEEVENTS_TPC_TIMEGAIN:=40} : ${SCALETRACKS_TPC_TIMEGAIN:=1000} @@ -77,7 +77,7 @@ if [[ $CALIB_ASYNC_EXTRACTTIMESERIES == 1 ]] ; then CONFIG_TPCTIMESERIES+=" --min-cluster ${TPCTIMESERIES_MIN_CLUSTER}" CONFIG_TPCTIMESERIES+=" --max-tgl ${TPCTIMESERIES_MAX_TGL}" CONFIG_TPCTIMESERIES+=" --mult-max ${TPCTIMESERIES_MULT_MAX}" - add_W o2-tpc-time-series-workflow "${CONFIG_TPCTIMESERIES}" + add_W o2-tpc-time-series-workflow "$DISABLE_ROOT_INPUT ${CONFIG_TPCTIMESERIES}" fi # output-proxy for aggregator From 907c011d05f08ec2112e86fd27f0932b6242da5e Mon Sep 17 00:00:00 2001 From: Marian Ivanov Date: Tue, 4 Aug 2026 04:44:10 +0200 Subject: [PATCH 04/22] TPC TimeSeries: fix silent track loss from binning overflow (#15658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TPC TimeSeries: fix silent track loss from binning overflow Bin indices for tgl, phi, qPt, and multiplicity were used as implicit track selection cuts: tracks outside histogram range were silently dropped (return). Replace with std::clamp — edge bins become overflow bins (standard ROOT convention). No change for tracks within range. Bug: changing --max-qPt or --mult-max removed tracks from ALL outputs (DCA, dEdx, etc.), not just the binned histograms. * TPC TimeSeries: clamp fix + ITS cluster sizes + TRD tracklets + TRD matching Phase 0.2 — binning overflow fix: - Replace bounds-check-and-return with std::clamp on all 4 bin indices - Edge bins act as saturated overflow; no tracks silently dropped Phase 0.3 D1 — ITS cluster sizes (per-track, unbinned): - itsClusterSizes: packed 4-bit per layer (bit 28 kSharedClusters masked) - itsHasSharedClusters, itsPattern: 7-bit layer hit pattern Phase 0.3 D2 — TRD tracklet objects (per-track, unbinned): - Native Tracklet64[6] and CalibratedTracklet[6] per layer - trdPattern (6-bit validity mask), nTRDTracklets - requestTRDTracklets added to DataRequest Phase 0.3 D3 — TRD matching fraction (per-TF): - nITSTPCBasedPVContributors, nITSTPCWithTRDPVContributors, fracTRD - NaN for zero denominator. ClassDefNV 7 -> 8. * TPC TimeSeries: store TRD tracklets as native objects (std::vector) Replace flat primitive arrays with std::vector and std::vector. std::array failed ROOT serialization (missing ShowMember); std::vector with ROOT dictionary works. * Clang --------- Co-authored-by: miranov25 --- .../IntegratedClusterCalibrator.h | 9 +- .../TPC/workflow/src/TPCTimeSeriesSpec.cxx | 117 ++++++++++++++++-- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h b/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h index 8e6948ca5a418..1c0a46b68d840 100644 --- a/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h +++ b/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h @@ -365,6 +365,10 @@ struct TimeSeriesITSTPC { std::vector vertexY_ITSTPC_RMS; ///< vertex y RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95 std::vector vertexZ_ITSTPC_RMS; ///< vertex z RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95 + std::vector nITSTPCBasedPVContributors; ///< number of ITS-TPC-based PV contributors (denominator for TRD matching fraction) + std::vector nITSTPCWithTRDPVContributors; ///< number of ITS-TPC-TRD PV contributors (numerator for TRD matching fraction) + std::vector fracTRD; ///< fraction of ITS-TPC PV contributors with TRD match (NaN if denominator=0) + int quantileValues = 23; /// nVertexContributors_Quantiles; ///< number of primary vertices for quantiles 0.1, 0.2, ... 0.9 and truncated mean values 0.05->0.95, 0.1->0.9, 0.2->0.8 @@ -498,12 +502,15 @@ struct TimeSeriesITSTPC { vertexX_ITSTPC_RMS.resize(nTotalVtx); vertexY_ITSTPC_RMS.resize(nTotalVtx); vertexZ_ITSTPC_RMS.resize(nTotalVtx); + nITSTPCBasedPVContributors.resize(nTotalVtx); + nITSTPCWithTRDPVContributors.resize(nTotalVtx); + fracTRD.resize(nTotalVtx); const int nTotalQ = quantileValues * nTotal / mTSTPC.getNBins(); nVertexContributors_Quantiles.resize(nTotalQ); } - ClassDefNV(TimeSeriesITSTPC, 7); + ClassDefNV(TimeSeriesITSTPC, 8); }; } // end namespace tpc diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index dcb53c9f80ff8..317db93985ce3 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -41,6 +41,9 @@ #include #include "DataFormatsTPC/PIDResponse.h" #include "DataFormatsITS/TrackITS.h" +#include "DataFormatsTRD/TrackTRD.h" +#include "DataFormatsTRD/Tracklet64.h" +#include "DataFormatsTRD/CalibratedTracklet.h" #include "TROOT.h" #include "ReconstructionDataFormats/MatchInfoTOF.h" #include "DataFormatsTOF/Cluster.h" @@ -63,6 +66,13 @@ namespace tpc class TPCTimeSeries : public Task { public: + /// D2: per-track TRD tracklet lookup data + struct TRDTrackletData { + uint8_t trdPattern = 0; + uint8_t nTRDTracklets = 0; + int trackletIndices[6] = {-1, -1, -1, -1, -1, -1}; + }; + /// \constructor TPCTimeSeries(std::shared_ptr req, const bool disableWriter, const o2::base::Propagator::MatCorrType matType, const bool enableUnbinnedWriter, const bool tpcOnly, std::shared_ptr dr) : mCCDBRequest(req), mDisableWriter(disableWriter), mMatType(matType), mUnbinnedWriter(enableUnbinnedWriter), mTPCOnly(tpcOnly), mDataRequest(dr) {}; @@ -303,6 +313,38 @@ class TPCTimeSeries : public Task // find nearest vertex of tracks which have no vertex assigned findNearesVertex(tracksTPC, vertices); + // D2: build TPC track index → TRD tracklet data map (for unbinned output) + // For each TPC track that has a TRD match, store the TrackTRD tracklet indices + std::unordered_map tpcToTRDMap; + auto trdTracklets = mTPCOnly ? gsl::span() : recoData.getTRDTracklets(); + auto trdCalibTracklets = mTPCOnly ? gsl::span() : recoData.getTRDCalibratedTracklets(); + if (mUnbinnedWriter && !mTPCOnly) { + // scan ITS-TPC-TRD tracks + auto itstpctrdTracks = recoData.getITSTPCTRDTracks(); + for (unsigned int ig = 0; ig < itstpctrdTracks.size(); ++ig) { + auto gid = GTrackID(ig, GTrackID::ITSTPCTRD); + auto refTPC = recoData.getTPCContributorGID(gid); + if (!refTPC.isIndexSet()) { + continue; + } + auto refTRD = recoData.getSingleDetectorRefs(gid)[GTrackID::TRD]; + if (!refTRD.isIndexSet()) { + continue; + } + const auto& trdTrack = recoData.getTrack(refTRD); + TRDTrackletData trdData; + for (int iLay = 0; iLay < 6; ++iLay) { + auto trkltId = trdTrack.getTrackletIndex(iLay); + if (trkltId >= 0) { + trdData.trdPattern |= (1 << iLay); + trdData.nTRDTracklets++; + trdData.trackletIndices[iLay] = trkltId; + } + } + tpcToTRDMap[refTPC] = trdData; + } + } + // getting cluster references for cluster bitmask if (mUnbinnedWriter) { mTPCTrackClIdx = pc.inputs().get>("trackTPCClRefs"); @@ -472,7 +514,7 @@ class TPCTimeSeries : public Task auto myThread = [&](int iThread) { for (size_t i = iThread; i < loopEnd; i += mNThreads) { if (acceptTrack(tracksTPC[i])) { - fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters); + fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets); } } }; @@ -489,7 +531,7 @@ class TPCTimeSeries : public Task auto myThread = [&](int iThread) { for (size_t i = iThread; i < loopEnd; i += mNThreads) { if (acceptTrack(tracksTPC[i])) { - fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters); + fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets); } } }; @@ -1133,7 +1175,7 @@ class TPCTimeSeries : public Task return isGoodTrack; } - void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters) + void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters, const std::unordered_map& tpcToTRDMap, const gsl::span trdTracklets, const gsl::span trdCalibTracklets) { const auto& trackFull = tracksTPC[iTrk]; const bool isGoodTrack = checkTrack(trackFull); @@ -1179,21 +1221,22 @@ class TPCTimeSeries : public Task return; } - const int tglBin = mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl + mPhiBins; - const int phiBin = mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI; + // Saturate bin indices — edge bins act as overflow (Phase 0.2 fix) + const int tglBin = std::clamp(static_cast(mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl) + mPhiBins, + mPhiBins, mPhiBins + mTglBins - 1); + const int phiBin = std::clamp(static_cast(mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI), + 0, mPhiBins - 1); const int offsQPtBin = mPhiBins + mTglBins; - const int qPtBin = offsQPtBin + mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt); + const int qPtBin = std::clamp(offsQPtBin + static_cast(mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt)), + offsQPtBin, offsQPtBin + mQPtBins - 1); const int localMult = mNTracksWindow[iTrk]; const int offsMult = offsQPtBin + mQPtBins; - const int multBin = offsMult + mMultBins * localMult / mMultMax; + const int multBin = std::clamp(offsMult + static_cast(mMultBins * localMult / mMultMax), + offsMult, offsMult + mMultBins - 1); const int nBins = getNBins(); - if ((phiBin < 0) || (phiBin > mPhiBins) || (tglBin < mPhiBins) || (tglBin > offsQPtBin) || (qPtBin < offsQPtBin) || (qPtBin > offsMult) || (multBin < offsMult) || (multBin > offsMult + mMultBins)) { - return; - } - float sigmaY2 = 0; float sigmaZ2 = 0; const int sector = o2::math_utils::angle2Sector(trackTmp.getPhiPos()); @@ -1354,6 +1397,30 @@ class TPCTimeSeries : public Task const float chi2match_ITSTPC = hasITSTPC ? tracksITSTPC[idxITSTPC.front()].getChi2Match() : -1; const int nClITS = idxITSCheck ? tracksITS[idxITSTrack].getNClusters() : -1; const int chi2ITS = idxITSCheck ? tracksITS[idxITSTrack].getChi2() : -1; + // D1: ITS cluster sizes (4-bit per layer, mask bit 28 = kSharedClusters) + const uint32_t itsClusterSizes = idxITSCheck ? (static_cast(tracksITS[idxITSTrack].getClusterSizes()) & 0x0FFFFFFFu) : 0u; + const bool itsHasSharedClusters = idxITSCheck ? tracksITS[idxITSTrack].hasSharedClusters() : false; + const uint32_t itsPattern = idxITSCheck ? (tracksITS[idxITSTrack].getPattern() & 0x7Fu) : 0u; + + // D2: TRD tracklet data — native objects per layer + uint8_t trdPattern = 0; + uint8_t nTRDTracklets = 0; + std::vector trdTrackletVec(6); + std::vector trdCalibVec(6); + auto itTRD = tpcToTRDMap.find(iTrk); + if (itTRD != tpcToTRDMap.end()) { + const auto& trdData = itTRD->second; + trdPattern = trdData.trdPattern; + nTRDTracklets = trdData.nTRDTracklets; + for (int iLay = 0; iLay < 6; ++iLay) { + if (trdData.trackletIndices[iLay] >= 0) { + trdTrackletVec[iLay] = trdTracklets[trdData.trackletIndices[iLay]]; + if (trdData.trackletIndices[iLay] < static_cast(trdCalibTracklets.size())) { + trdCalibVec[iLay] = trdCalibTracklets[trdData.trackletIndices[iLay]]; + } + } + } + } int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1488,6 +1555,14 @@ class TPCTimeSeries : public Task << "mX_ITS=" << mx_ITS << "nClITS=" << nClITS << "chi2ITS=" << chi2ITS + << "itsClusterSizes=" << itsClusterSizes + << "itsHasSharedClusters=" << itsHasSharedClusters + << "itsPattern=" << itsPattern + // D2: TRD tracklet data + << "trdPattern=" << trdPattern + << "nTRDTracklets=" << nTRDTracklets + << "trdTracklets=" << trdTrackletVec + << "trdCalibTracklets=" << trdCalibVec << "chi2match_ITSTPC=" << chi2match_ITSTPC << "PID=" << trkOrig.getPID().getID() // TPC cov at vertex (without vertex constrained) @@ -1680,6 +1755,7 @@ class TPCTimeSeries : public Task std::unordered_map nContributors_ITS; // ITS: vertex ID -> n contributors std::unordered_map nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors + std::unordered_map nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors // loop over collisions if (!vertices.empty()) { @@ -1700,6 +1776,10 @@ class TPCTimeSeries : public Task if (refITSTPC.isIndexSet()) { indicesITSTPC_vtx[refITSTPC] = vID; ++nContributors_ITSTPC[vID]; + // count TRD-matched PV contributors + if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF) { + ++nContributors_TRD[vID]; + } } else { ++nContributors_ITS[vID]; } @@ -1761,6 +1841,17 @@ class TPCTimeSeries : public Task mBufferDCA.vertexY_ITSTPC_RMS.front() = avgVtxITSTPC[1].getStdDev(); mBufferDCA.vertexZ_ITSTPC_RMS.front() = avgVtxITSTPC[2].getStdDev(); + // TRD matching fraction (summed over all vertices in this TF) + int sumITSTPCBased = 0; + int sumWithTRD = 0; + for (int ivtx = 0; ivtx < vertices.size(); ++ivtx) { + sumITSTPCBased += nContributors_ITSTPC[ivtx]; + sumWithTRD += nContributors_TRD[ivtx]; + } + mBufferDCA.nITSTPCBasedPVContributors.front() = sumITSTPCBased; + mBufferDCA.nITSTPCWithTRDPVContributors.front() = sumWithTRD; + mBufferDCA.fracTRD.front() = (sumITSTPCBased > 0) ? static_cast(sumWithTRD) / sumITSTPCBased : std::nanf(""); + // quantiles and truncated mean RobustAverage avg(vertices.size(), false); for (const auto& vtx : vertices) { @@ -1850,6 +1941,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, if (src[GTrackID::TPC]) { dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); } + // D2: request TRD tracklets for tracks with TRD contribution + if (srcTracks[GTrackID::ITSTPCTRD] || srcTracks[GTrackID::ITSTPCTRDTOF]) { + dataRequest->requestTRDTracklets(useMC); + } bool tpcOnly = srcTracks == GTrackID::getSourcesMask("TPC"); if (srcTracks.any() && !tpcOnly) { From 319d397a3148c2b8db334e707a5aa3835068f241 Mon Sep 17 00:00:00 2001 From: Tristan Wenzel Date: Fri, 31 Jul 2026 15:33:36 +0200 Subject: [PATCH 05/22] Support to use VecGeom geometry navigation in the material scan This commit provides support to use VecGeom as an alternative geometry backend to perform the material budget LUT creation. It is the first time that ALICE interfaces full VecGeom navigation. This should be a useful milestone towards adoption at detector simulation level. First scans indicate performance advantages of 2x over TGeo but a precise performance investigation will be done later. This commit will be the foundation for this and/or serve for debugging issues or driving further development in VecGeom itself. Note that the original method name `populateFromTGeo` remains untouched for API stability, despite the fact that the method can now internally use VecGeom. --- Detectors/Base/CMakeLists.txt | 13 ++ .../include/DetectorsBase/GeometryManager.h | 17 +++ .../Base/include/DetectorsBase/MatLayerCyl.h | 7 +- .../include/DetectorsBase/MatLayerCylSet.h | 8 +- Detectors/Base/src/DetectorsBaseLinkDef.h | 1 + Detectors/Base/src/GeometryManager.cxx | 142 ++++++++++++++++++ Detectors/Base/src/MatLayerCyl.cxx | 17 ++- Detectors/Base/src/MatLayerCylSet.cxx | 101 ++++++++----- Detectors/Base/test/README.md | 11 +- Detectors/Base/test/buildMatBudLUT.C | 27 +++- Detectors/Base/test/compareMatBudLUT.C | 72 ++++++++- 11 files changed, 363 insertions(+), 53 deletions(-) diff --git a/Detectors/Base/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index 74d2d02a9246c..9830d0c1175ce 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -10,7 +10,13 @@ # or submit itself to any jurisdiction. #add_compile_options(-O0 -g -fPIC) +# Optional VecGeom backend for material-budget LUT filling, off by default when TGeo2VecGeom +# isn't installed (guarded by O2_WITH_VECGEOM). Linked PRIVATE: VecGeom types never appear in +# DetectorsBase's public headers, so consumers need neither VecGeom headers nor VecGeom itself. +find_package(TGeo2VecGeom CONFIG QUIET) + o2_add_library(DetectorsBase + TARGETVARNAME targetDetectorsBase SOURCES src/Detector.cxx src/GeometryManager.cxx src/MaterialManager.cxx @@ -51,6 +57,13 @@ o2_add_library(DetectorsBase ROOT::Gdml ) +if(TGeo2VecGeom_FOUND) + target_compile_definitions(${targetDetectorsBase} PRIVATE O2_WITH_VECGEOM) + target_link_libraries(${targetDetectorsBase} PRIVATE TGeo2VecGeom::TGeo2VecGeom) +else() + message(STATUS "TGeo2VecGeom not found: DetectorsBase built without the optional VecGeom material-budget backend") +endif() + o2_target_root_dictionary(DetectorsBase HEADERS include/DetectorsBase/Detector.h include/DetectorsBase/GeometryManager.h diff --git a/Detectors/Base/include/DetectorsBase/GeometryManager.h b/Detectors/Base/include/DetectorsBase/GeometryManager.h index 938c4d127ed15..f105d137c8742 100644 --- a/Detectors/Base/include/DetectorsBase/GeometryManager.h +++ b/Detectors/Base/include/DetectorsBase/GeometryManager.h @@ -40,6 +40,12 @@ class AlignParam; namespace base { +/// Backend used to compute material budget for LUT filling: ROOT/TGeo (default, always +/// available) or VecGeom (requires O2 to be built against the optional TGeo2VecGeom +/// package; see GeometryManager::isVecGeomAvailable()). +enum class MatbudGeomBackend : int { ROOT = 0, + VECGEOM = 1 }; + /// Class for interfacing to the geometry; it also builds and manages the look-up tables for fast /// access to geometry and alignment information for sensitive alignable volumes: /// 1) the look-up table mapping unique volume ids to TGeoPNEntries. This allows to access @@ -121,6 +127,17 @@ class GeometryManager : public TObject return meanMaterialBudgetExt(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z()); } + /// Whether this build of O2 was configured with the optional VecGeom material-budget + /// backend (i.e. TGeo2VecGeom was found at CMake configure time). +#ifdef O2_WITH_VECGEOM + static constexpr bool isVecGeomAvailable() { return true; } + /// Mean material budget between two points, using the VecGeom backend. On first call, + /// lazily converts the currently loaded TGeo geometry to VecGeom (once per process). + static o2::base::MatBudget vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1); +#else + static constexpr bool isVecGeomAvailable() { return false; } +#endif + private: /// Default constructor GeometryManager() = default; diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h index 26de279468477..04aefed06f010 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h @@ -20,6 +20,9 @@ #include #endif #include "GPUCommonDef.h" +#ifndef GPUCA_ALIGPUCODE +#include "DetectorsBase/GeometryManager.h" // for MatbudGeomBackend +#endif #include "FlatObject.h" #include "GPUCommonRtypes.h" #include "GPUCommonMath.h" @@ -67,8 +70,8 @@ class MatLayerCyl : public o2::gpu::FlatObject void initSegmentation(float rMin, float rMax, float zHalfSpan, int nz, int nphi); void initSegmentation(float rMin, float rMax, float zHalfSpan, float dzMin, float drphiMin); - void populateFromTGeo(int ntrPerCell = 10); - void populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr); + void populateFromTGeo(int ntrPerCell = 10, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); + void populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); void print(bool data = false) const; #endif // !GPUCA_ALIGPUCODE diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h index 4408261dc740a..f2e8937ae8448 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h @@ -73,9 +73,11 @@ class MatLayerCylSet : public o2::gpu::FlatObject #ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version void print(bool data = false) const; void addLayer(float rmin, float rmax, float zmax, float dz, float drphi); - /// Populate the LUT from TGeo. nThreads > 1 fills the cells in parallel (one TGeoNavigator - /// per thread); nThreads < 0 takes the count from the NTHREADS_MATBUD environment variable. - void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1); + /// Populate the LUT from TGeo or VecGeom. nThreads > 1 fills cells in parallel (one + /// TGeoNavigator per thread for ROOT; VecGeom navigation is thread-safe on its own); + /// nThreads < 0 takes the count from NTHREADS_MATBUD. VECGEOM requires O2 built against + /// TGeo2VecGeom, see GeometryManager::isVecGeomAvailable(). + void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); static int getNThreadsFromEnv(); void optimizePhiSlices(float maxRelDiff = 0.05); diff --git a/Detectors/Base/src/DetectorsBaseLinkDef.h b/Detectors/Base/src/DetectorsBaseLinkDef.h index 8255c143ebb4a..2da1d5bdfad15 100644 --- a/Detectors/Base/src/DetectorsBaseLinkDef.h +++ b/Detectors/Base/src/DetectorsBaseLinkDef.h @@ -24,6 +24,7 @@ #pragma link C++ class o2::base::GeometryManager + ; #pragma link C++ class o2::base::GeometryManager::MatBudgetExt + ; +#pragma link C++ enum o2::base::MatbudGeomBackend; #pragma link C++ class o2::base::MaterialManager + ; #pragma link C++ class o2::MaterialManagerParam + ; #pragma link C++ class o2::GeometryManagerParam + ; diff --git a/Detectors/Base/src/GeometryManager.cxx b/Detectors/Base/src/GeometryManager.cxx index 8aaf902aa95c5..225f21d8239a1 100644 --- a/Detectors/Base/src/GeometryManager.cxx +++ b/Detectors/Base/src/GeometryManager.cxx @@ -30,6 +30,22 @@ #include "CommonUtils/NameConf.h" #include "DetectorsBase/Aligner.h" +#ifdef O2_WITH_VECGEOM +#include "TGeo2VecGeom/RootGeoManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + using namespace o2::detectors; using namespace o2::base; @@ -536,3 +552,129 @@ void GeometryManager::loadGeometry(std::string_view simPrefix, bool applyMisalig applyMisalignent(applyMisalignment); } } + +#ifdef O2_WITH_VECGEOM + +namespace +{ +/// Converts the currently loaded TGeo geometry to VecGeom and sets up navigators, once per +/// process, the first time the VecGeom backend is requested. Not part of loadGeometry(), +/// which every job calls regardless of whether it ever uses the VecGeom backend. +void ensureVecGeomWorldBuilt() +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, []() { + if (!gGeoManager) { + LOG(fatal) << "Cannot build VecGeom geometry: no TGeo geometry loaded (call GeometryManager::loadGeometry() first)"; + } + // Translate geometry and material pointers, then build acceleration structures. + tgeo2vecgeom::RootGeoManager::Instance().SetMaterialConversionHook([](TGeoMaterial const* m) { return (void*)m; }); + tgeo2vecgeom::RootGeoManager::Instance().SetFlattenAssemblies(true); + tgeo2vecgeom::RootGeoManager::Instance().LoadRootGeometry(); + + // Acceleration structures must be built before the navigators/locators reference them. + vecgeom::ABBoxManager::Instance().InitABBoxesForCompleteGeometry(); + // Builds a BVH per logical volume from the ABBoxes computed above. + vecgeom::BVHManager::Init(); + + // For each logical volume, set both a navigator (used for ComputeStep) and a matched + // level locator (used for point relocation after a boundary crossing via GlobalLocator); + // volumes with very few daughters are cheaper to brute-force than to accelerate. + for (auto& lvol : vecgeom::GeoManager::Instance().GetLogicalVolumesMap()) { + auto* vol = lvol.second; + if (vol->GetDaughtersp()->size() <= 2) { + vol->SetNavigator(vecgeom::NewSimpleNavigator<>::Instance()); + vol->SetLevelLocator(vecgeom::SimpleLevelLocator::GetInstance()); + } else { + vol->SetNavigator(vecgeom::BVHNavigator<>::Instance()); + vol->SetLevelLocator(vecgeom::BVHLevelLocator::GetInstance()); + } + } + }); +} +} // namespace + +//_____________________________________________________________________________________ +o2::base::MatBudget GeometryManager::vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1) +{ + // Mean material budget between "0" and "1" via VecGeom's BVH-accelerated ray/boundary + // intersection, instead of TGeo. + ensureVecGeomWorldBuilt(); + + using Vector3D = vecgeom::Vector3D; + + double length, start[3] = {x0, y0, z0}; + double dir[3] = {x1 - x0, y1 - y0, z1 - z0}; + if ((length = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]) < TGeoShape::Tolerance() * TGeoShape::Tolerance()) { + return o2::base::MatBudget(); // return empty struct + } + length = std::sqrt(length); + double invlen = 1. / length; + for (int i = 3; i--;) { + dir[i] *= invlen; + } + + thread_local static vecgeom::NavigationState* newnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static vecgeom::NavigationState* currnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static vecgeom::NavigationState* startCache = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static bool startCacheValid = false; + + Vector3D currPoint(x0, y0, z0); + Vector3D dirr(dir[0], dir[1], dir[2]); + constexpr double kPush = 1.E-6; // mimick the nudging of TGeo's FindNextBoundaryAndStep + auto world = vecgeom::GeoManager::Instance().GetWorld(); + o2::base::MatBudget budTot, budStep; + budStep.length = length; + + // Locate the starting volume, reusing the path from the previous call when still valid. + if (startCacheValid && !startCache->IsOutside()) { + startCache->CopyTo(currnavstate); + vecgeom::Transformation3D m; + currnavstate->TopMatrix(m); + vecgeom::GlobalLocator::RelocatePointFromPath(m.Transform(currPoint), *currnavstate); + } else { + currnavstate->Clear(); + vecgeom::GlobalLocator::LocateGlobalPoint(world, currPoint, *currnavstate, true); + } + if (currnavstate->IsOutside() || currnavstate->Top() == nullptr) { + LOG(error) << "start point out of geometry: " << x0 << ':' << y0 << ':' << z0; + startCacheValid = false; + return o2::base::MatBudget(); + } + currnavstate->CopyTo(startCache); + startCacheValid = true; + + double stepTot = 0.; + double remainingDist = length; + Int_t nzero = 0; + while (remainingDist > 1.E-10) { + auto* lvol = currnavstate->Top()->GetLogicalVolume(); + accountMaterial(static_cast(lvol->GetMaterialPtr()), budStep); + vecgeom::VNavigator const* navigator = lvol->GetNavigator(); + double step = static_cast(navigator->ComputeStepAndPropagatedState(currPoint, dirr, remainingDist, *currnavstate, *newnavstate)); + if (step < 2.E-10) { + nzero++; + } else { + nzero = 0; + } + if (nzero > 3) { + // This means navigation has problems on one boundary + LOG(warning) << "Cannot cross boundary at (" << currPoint[0] << ',' << currPoint[1] << ',' << currPoint[2] << ')'; + budTot.meanRho /= stepTot; + budTot.length = stepTot; + return o2::base::MatBudget(budTot); + } + + remainingDist -= step; + stepTot += step; + budTot.meanRho += step * budStep.meanRho; + budTot.meanX2X0 += step / budStep.meanX2X0; + currPoint = currPoint + (step + kPush) * dirr; + std::swap(currnavstate, newnavstate); + } + budTot.meanRho /= stepTot; + budTot.length = stepTot; + return o2::base::MatBudget(budTot); +} + +#endif // O2_WITH_VECGEOM diff --git a/Detectors/Base/src/MatLayerCyl.cxx b/Detectors/Base/src/MatLayerCyl.cxx index dee179a84ca06..c35293d07c10e 100644 --- a/Detectors/Base/src/MatLayerCyl.cxx +++ b/Detectors/Base/src/MatLayerCyl.cxx @@ -109,7 +109,7 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int } //________________________________________________________________________________ -void MatLayerCyl::populateFromTGeo(int ntrPerCell) +void MatLayerCyl::populateFromTGeo(int ntrPerCell, MatbudGeomBackend backend) { /// populate layer with info extracted from TGeometry, using ntrPerCell test tracks per cell assert(mConstructionMask != Constructed); @@ -117,13 +117,13 @@ void MatLayerCyl::populateFromTGeo(int ntrPerCell) ntrPerCell = ntrPerCell > 1 ? ntrPerCell : 1; for (int iz = getNZBins(); iz--;) { for (int ip = getNPhiBins(); ip--;) { - populateFromTGeo(ip, iz, ntrPerCell); + populateFromTGeo(ip, iz, ntrPerCell, nullptr, backend); } } } //________________________________________________________________________________ -void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav) +void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav, MatbudGeomBackend backend) { /// populate cell with info extracted from TGeometry, using ntrPerCell test tracks per cell @@ -136,7 +136,16 @@ void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator float dzt = zs > 0.f ? 0.25 * dz : -0.25 * dz; // to avoid 90 degree polar angle for (int isp = ntrPerCell; isp--;) { o2::math_utils::sincos(phmn + (isp + 0.5) * getDPhi() / ntrPerCell, sn, cs); - auto bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav); + o2::base::MatBudget bud; + if (backend == MatbudGeomBackend::ROOT) { + bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav); + } else { +#ifdef O2_WITH_VECGEOM + bud = o2::base::GeometryManager::vecGeomMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt); +#else + LOG(fatal) << "MatbudGeomBackend::VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; +#endif + } if (bud.length > 0.) { meanRho += bud.length * bud.meanRho; meanX2X0 += bud.meanX2X0; // we store actually not X2X0 but 1./X0 diff --git a/Detectors/Base/src/MatLayerCylSet.cxx b/Detectors/Base/src/MatLayerCylSet.cxx index b85e3042cc379..b65a7472fbcf6 100644 --- a/Detectors/Base/src/MatLayerCylSet.cxx +++ b/Detectors/Base/src/MatLayerCylSet.cxx @@ -94,12 +94,16 @@ int MatLayerCylSet::getNThreadsFromEnv() } //________________________________________________________________________________ -void MatLayerCylSet::populateFromTGeo(int ntrPerCell, int nThreads) +void MatLayerCylSet::populateFromTGeo(int ntrPerCell, int nThreads, MatbudGeomBackend backend) { ///< populate layers, using ntrPerCell test tracks per cell. ///< nThreads < 0 takes the number of threads from the NTHREADS_MATBUD environment variable. assert(mConstructionMask == InProgress); + if (backend == MatbudGeomBackend::VECGEOM && !GeometryManager::isVecGeomAvailable()) { + LOG(fatal) << "MatbudGeomBackend::VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; + } + int nlr = getNLayers(); if (!nlr) { LOG(error) << "The LUT is not yet initialized"; @@ -124,13 +128,22 @@ void MatLayerCylSet::populateFromTGeo(int ntrPerCell, int nThreads) LOG(info) << "Populating with " << ntrPerCell << " trials Lr " << i; get()->mLayers[i].print(); } + const auto tSetupStart = Clock::now(); +#ifdef O2_WITH_VECGEOM + if (backend == MatbudGeomBackend::VECGEOM) { + // Trigger the lazy VecGeom world build/BVH init here so it counts as "setup" below, + // not as fill time for whichever cell happens first. + GeometryManager::vecGeomMaterialBudget(0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + } +#endif const auto tFillStart = Clock::now(); for (int i = 0; i < nlr; i++) { - get()->mLayers[i].populateFromTGeo(ntrPerCell); + get()->mLayers[i].populateFromTGeo(ntrPerCell, backend); } const auto tFillEnd = Clock::now(); finalizeStructures(); - LOG(info) << "LUT fill: 1 thread, cells " << seconds(tFillStart, tFillEnd) << " s"; + LOG(info) << "LUT fill: 1 thread, setup " << seconds(tSetupStart, tFillStart) + << " s, cells " << seconds(tFillStart, tFillEnd) << " s"; return; } @@ -148,37 +161,57 @@ void MatLayerCylSet::populateFromTGeo(int ntrPerCell, int nThreads) const auto tSetupStart = Clock::now(); - // TGeo has to be told that several threads will navigate it, and each thread needs its own - // navigator. SetMaxThreads() is one-way -- TGeoManager has no API to return to - // single-threaded mode -- so we do not pretend to restore it; that is harmless because - // meanMaterialBudget() decides whether to lock from its own argument, not from this global. - // The navigators we book are ours, though, so those we do give back. - gGeoManager->SetMaxThreads(nThreads); - - tbb::enumerable_thread_specific threadNavigators( - []() { return gGeoManager->AddNavigator(); }); - - const auto tFillStart = Clock::now(); - { - tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); - tbb::parallel_for(tbb::blocked_range(0, totalCells), - [this, ntrPerCell, &layerOffsets, &threadNavigators](const tbb::blocked_range& range) { - TGeoNavigator* nav = threadNavigators.local(); - for (size_t idx = range.begin(); idx != range.end(); ++idx) { - auto it = std::upper_bound(layerOffsets.begin(), layerOffsets.end(), idx); - const int layerIdx = int(std::distance(layerOffsets.begin(), it)) - 1; - const size_t cellInLayer = idx - layerOffsets[layerIdx]; - auto& layer = this->get()->mLayers[layerIdx]; - const int nphi = layer.getNPhiBins(); - layer.populateFromTGeo(int(cellInLayer % nphi), int(cellInLayer / nphi), ntrPerCell, nav); - } - }); - } - - const auto tFillEnd = Clock::now(); - - for (TGeoNavigator* nav : threadNavigators) { - gGeoManager->RemoveNavigator(nav); + auto fillRange = [this, ntrPerCell, backend, &layerOffsets](const tbb::blocked_range& range, TGeoNavigator* nav) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) { + auto it = std::upper_bound(layerOffsets.begin(), layerOffsets.end(), idx); + const int layerIdx = int(std::distance(layerOffsets.begin(), it)) - 1; + const size_t cellInLayer = idx - layerOffsets[layerIdx]; + auto& layer = this->get()->mLayers[layerIdx]; + const int nphi = layer.getNPhiBins(); + layer.populateFromTGeo(int(cellInLayer % nphi), int(cellInLayer / nphi), ntrPerCell, nav, backend); + } + }; + + Clock::time_point tFillStart, tFillEnd; + if (backend == MatbudGeomBackend::ROOT) { + // TGeo has to be told that several threads will navigate it, and each thread needs its own + // navigator. SetMaxThreads() is one-way -- TGeoManager has no API to return to + // single-threaded mode -- so we do not pretend to restore it; that is harmless because + // meanMaterialBudget() decides whether to lock from its own argument, not from this global. + // The navigators we book are ours, though, so those we do give back. + gGeoManager->SetMaxThreads(nThreads); + + tbb::enumerable_thread_specific threadNavigators( + []() { return gGeoManager->AddNavigator(); }); + + tFillStart = Clock::now(); + { + tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); + tbb::parallel_for(tbb::blocked_range(0, totalCells), + [&fillRange, &threadNavigators](const tbb::blocked_range& range) { + fillRange(range, threadNavigators.local()); + }); + } + tFillEnd = Clock::now(); + + for (TGeoNavigator* nav : threadNavigators) { + gGeoManager->RemoveNavigator(nav); + } + } else { + // VecGeom navigation needs no per-thread navigator bookkeeping. Trigger the lazy world + // build/BVH init before tFillStart so it counts as "setup", not fill time. +#ifdef O2_WITH_VECGEOM + GeometryManager::vecGeomMaterialBudget(0.f, 0.f, 0.f, 0.f, 0.f, 1.f); +#endif + tFillStart = Clock::now(); + { + tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); + tbb::parallel_for(tbb::blocked_range(0, totalCells), + [&fillRange](const tbb::blocked_range& range) { + fillRange(range, nullptr); + }); + } + tFillEnd = Clock::now(); } finalizeStructures(); diff --git a/Detectors/Base/test/README.md b/Detectors/Base/test/README.md index 97e8c7f569fd1..ca7fea02949ba 100644 --- a/Detectors/Base/test/README.md +++ b/Detectors/Base/test/README.md @@ -14,7 +14,7 @@ root -b -q O2/Detectors/Base/test/buildMatBudLUT.C+ The generation is quite time consuming (may take ~30 min). It can be filled in parallel, one `TGeoNavigator` per thread, by passing a thread count as the -5th argument of `buildMatBudLUT` or by setting the environment variable: +7th argument of `buildMatBudLUT` or by setting the environment variable: ``` export NTHREADS_MATBUD=16 ``` @@ -23,6 +23,15 @@ ROOT >= v6-36-10-alice3, which removes the per-query thread-id lookup and the fa between the per-thread scratch buffers of TGeo shapes; with older ROOT the parallel path is still correct, just slower. +An alternative VecGeom geometry backend can be selected via the 8th argument (`"ROOT"` +or `"VECGEOM"`), e.g. +``` +root -b -q 'O2/Detectors/Base/test/buildMatBudLUT.C(60, -1, "matbud.root", "o2sim", "", 16, "VECGEOM")' +``` +This requires O2 to have been built against the optional `TGeo2VecGeom` package +(`o2::base::GeometryManager::isVecGeomAvailable()`); it is otherwise a build-time no-op that +does not affect the default ROOT/TGeo path in any way. + The optimized LUT will be stored in the matbud.root file. Load it as: diff --git a/Detectors/Base/test/buildMatBudLUT.C b/Detectors/Base/test/buildMatBudLUT.C index 44019198685f8..2b371b90effa3 100644 --- a/Detectors/Base/test/buildMatBudLUT.C +++ b/Detectors/Base/test/buildMatBudLUT.C @@ -23,14 +23,18 @@ #include #endif +using MatbudGeomBackend = o2::base::MatbudGeomBackend; + o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); +MatbudGeomBackend parseBackend(const std::string& s); /// Build the material budget LUT. nThreads < 0 takes the thread count from NTHREADS_MATBUD. +/// geomBackend is "ROOT" (default) or "VECGEOM" (requires O2 built against TGeo2VecGeom). bool buildMatBudLUT(int nTst = 60, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomNamePrefix = "o2sim", const std::string& opts = "", - int nThreads = -1); + int nThreads = -1, const std::string& geomBackend = "ROOT"); struct LrData { float rMin = 0.f; @@ -46,8 +50,9 @@ std::vector lrData; void configLayers(); bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std::string& geomNamePrefix, - const std::string& opts, int nThreads) + const std::string& opts, int nThreads, const std::string& geomBackend) { + MatbudGeomBackend backend = parseBackend(geomBackend); auto geomName = o2::base::NameConf::getGeomFileName(geomNamePrefix); if (gSystem->AccessPathName(geomName.c_str())) { // if needed, create geometry std::cout << geomName << " does not exist. Will create it on the fly\n"; @@ -71,7 +76,7 @@ bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std:: } TStopwatch sw; - mbLUT.populateFromTGeo(nTst, nThreads); + mbLUT.populateFromTGeo(nTst, nThreads, backend); mbLUT.optimizePhiSlices(); // move to populateFromTGeo mbLUT.flatten(); // move to populateFromTGeo @@ -401,3 +406,19 @@ void configLayers() lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 500); } + +//_______________________________________________________________________ +MatbudGeomBackend parseBackend(const std::string& s) +{ + if (s == "ROOT") { + return MatbudGeomBackend::ROOT; + } + if (s == "VECGEOM") { + if (!o2::base::GeometryManager::isVecGeomAvailable()) { + LOG(fatal) << "geomBackend=VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; + } + return MatbudGeomBackend::VECGEOM; + } + LOG(fatal) << "Unknown geomBackend '" << s << "', expected ROOT or VECGEOM"; + return MatbudGeomBackend::ROOT; +} diff --git a/Detectors/Base/test/compareMatBudLUT.C b/Detectors/Base/test/compareMatBudLUT.C index 7abaa9bd7182a..a58695d05d22c 100644 --- a/Detectors/Base/test/compareMatBudLUT.C +++ b/Detectors/Base/test/compareMatBudLUT.C @@ -12,21 +12,47 @@ /// \file compareMatBudLUT.C /// \brief Compare two material budget LUTs cell by cell /// -/// Used to check that filling the LUT in parallel gives the same result as filling it serially: +/// Used to check that filling the LUT in parallel gives the same result as filling it serially, +/// or that the VecGeom and ROOT geometry backends agree within a given tolerance: /// /// root -b -q 'compareMatBudLUT.C("matbud_serial.root","matbud_parallel.root")' +/// root -b -q 'compareMatBudLUT.C("matbud_ROOT.root","matbud_VECGEOM.root", 0.01, 20, "sweep.csv")' #if !defined(__CLING__) || defined(__ROOTCLING__) #include "DetectorsBase/MatLayerCylSet.h" #include "GPUCommonLogger.h" +#include #include #include +#include +#include #endif +namespace +{ +struct CellDiff { + int layer, iz, ip; + float rhoA, rhoB, x2x0A, x2x0B; + double rRho, rX; + double score() const { return std::max(rRho, rX); } +}; + +struct LayerStat { + float rMin = 0.f, rMax = 0.f; + size_t nBad = 0; + double maxRelRho = 0., maxRelX2X0 = 0.; +}; +} // namespace + /// Returns true if the two LUTs agree everywhere within tol (relative). +/// nWorst: number of worst-offending cells to print, ranked by max(relRho, relX2X0) over the +/// whole comparison, not just the first ones found in scan order. +/// csvSummary: if non-empty, append one summary row to this CSV file (header written once). bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", const std::string& fileB = "matbud_parallel.root", - float tol = 0.f) + float tol = 0.f, + int nWorst = 10, + const std::string& csvSummary = "") { auto* lutA = o2::base::MatLayerCylSet::loadFromFile(fileA); auto* lutB = o2::base::MatLayerCylSet::loadFromFile(fileB); @@ -46,6 +72,8 @@ bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", size_t nCells = 0, nBad = 0; double maxRelRho = 0., maxRelX2X0 = 0.; + std::vector layerStats(lutA->getNLayers()); + std::vector allCells; for (int il = 0; il < lutA->getNLayers(); il++) { const auto& la = lutA->getLayer(il); @@ -56,6 +84,10 @@ bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", << lb.getNZBins() << "x" << lb.getNPhiBins(); return false; } + auto& ls = layerStats[il]; + ls.rMin = la.getRMin(); + ls.rMax = la.getRMax(); + for (int iz = 0; iz < la.getNZBins(); iz++) { for (int ip = 0; ip < la.getNPhiBins(); ip++) { const auto& ca = la.getCellPhiBin(ip, iz); @@ -70,20 +102,48 @@ bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", const double rX = rel(ca.meanX2X0, cb.meanX2X0); maxRelRho = std::max(maxRelRho, rRho); maxRelX2X0 = std::max(maxRelX2X0, rX); + ls.maxRelRho = std::max(ls.maxRelRho, rRho); + ls.maxRelX2X0 = std::max(ls.maxRelX2X0, rX); if (rRho > tol || rX > tol) { - if (nBad < 10) { - printf("Lr %3d iz %4d ip %4d : rho %.9g vs %.9g (rel %.3g) | x2x0 %.9g vs %.9g (rel %.3g)\n", - il, iz, ip, ca.meanRho, cb.meanRho, rRho, ca.meanX2X0, cb.meanX2X0, rX); - } + ls.nBad++; nBad++; } + allCells.push_back({il, iz, ip, ca.meanRho, cb.meanRho, ca.meanX2X0, cb.meanX2X0, rRho, rX}); } } } + const int nw = std::min(nWorst, (int)allCells.size()); + std::partial_sort(allCells.begin(), allCells.begin() + nw, allCells.end(), + [](const CellDiff& a, const CellDiff& b) { return a.score() > b.score(); }); + printf("--- %d worst cells (by max relative deviation) ---\n", nw); + for (int i = 0; i < nw; i++) { + const auto& c = allCells[i]; + printf("Lr %3d iz %4d ip %4d : rho %.9g vs %.9g (rel %.3g) | x2x0 %.9g vs %.9g (rel %.3g)\n", + c.layer, c.iz, c.ip, c.rhoA, c.rhoB, c.rRho, c.x2x0A, c.x2x0B, c.rX); + } + + printf("--- per-layer summary (%d layers) ---\n", lutA->getNLayers()); + for (int il = 0; il < lutA->getNLayers(); il++) { + const auto& ls = layerStats[il]; + printf("Lr %3d %8.3fgetNLayers()); printf("Max relative difference: meanRho %.3g, meanX2X0 %.3g (tolerance %.3g)\n", maxRelRho, maxRelX2X0, tol); + + if (!csvSummary.empty()) { + const bool writeHeader = gSystem->AccessPathName(csvSummary.c_str()); // true if it does NOT exist + std::ofstream csv(csvSummary, std::ios::app); + if (writeHeader) { + csv << "fileA,fileB,nLayers,nCells,nBad,maxRelRho,maxRelX2X0,tol\n"; + } + csv << fileA << "," << fileB << "," << lutA->getNLayers() << "," << nCells << "," << nBad << "," + << maxRelRho << "," << maxRelX2X0 << "," << tol << "\n"; + } + if (nBad) { LOG(error) << nBad << " cells differ beyond tolerance"; return false; From aff1d86fae6cfbb3e8d18ada564af8807a637218 Mon Sep 17 00:00:00 2001 From: swenzel Date: Wed, 22 Jul 2026 14:34:03 +0200 Subject: [PATCH 06/22] CAD->TGeo: support multiple external modules and sensitive detectors Improve the CAD->TGeo integration path for o2-sim with a configurable JSON-driven setup for externally provided CAD geometry. The configuration can describe multiple passive external modules and sensitive external detectors, both activated through the normal detector/module list. Add a --extGeomFile option carrying the external geometry configuration. Passive CAD modules no longer need to be hardcoded in build_geometry.C, and several configured CAD modules can be injected in one simulation. Introduce shared CAD geometry utilities to JIT-load O2_CADtoTGeo.py-style ROOT macros into unique namespaces, allowing several CAD modules to coexist in one Cling session despite identical exported builder symbols. The utilities also remap imported CAD media into the O2 MaterialManager. Add o2::ext::ExternalDetector, a generic sensitive detector implementation for externally supplied CAD geometry. Configured volumes or media can be marked sensitive, the detector is tied to a free DetID slot, and hits flow through the standard o2-sim forwarding and merging machinery. Add o2::ext::Hit as a detector-agnostic external hit payload storing entrance/exit position, momentum, energy, energy loss, time, length, PDG code and track status flags. All external detector instances share this wire format, so the hit merger only needs one external hit type. Support optional per-detector sensitive actions loaded from ROOT macros at runtime via GetFromMacro. When no custom action is configured, a built-in charged-track entrance/exit action is used. Register active external detectors in O2HitMerger so their hits are persisted in parallel mode, and key DetImpl hit collection buffers by detector instance to support multiple detectors sharing the same C++ type. Add a self-contained SimExample with two artificial sensitive external detectors, demonstrating configurable geometry injection, built-in and custom sensitive actions, and parallel-mode hit persistence. --- .clang-format | 4 + .../SimConfig/include/SimConfig/SimConfig.h | 4 +- Common/SimConfig/src/SimConfig.cxx | 4 +- Detectors/Base/CMakeLists.txt | 1 + .../include/DetectorsBase/CADGeometryUtils.h | 50 ++ .../Base/include/DetectorsBase/Detector.h | 11 +- Detectors/Base/src/CADGeometryUtils.cxx | 202 ++++++++ Detectors/CMakeLists.txt | 1 + Detectors/External/CMakeLists.txt | 22 + .../ExternalDetectors/ExternalDetector.h | 176 +++++++ .../External/include/ExternalDetectors/Hit.h | 140 ++++++ .../macro/sensitiveActionExample.macro | 68 +++ Detectors/External/src/ExternalDetector.cxx | 451 ++++++++++++++++++ .../External/src/ExternalDetectorsLinkDef.h | 23 + Detectors/Passive/CMakeLists.txt | 3 +- .../include/DetectorsPassive/ExternalModule.h | 15 +- Detectors/Passive/src/ExternalModule.cxx | 228 ++++----- macro/build_geometry.C | 36 +- run/CMakeLists.txt | 1 + run/O2HitMerger.h | 60 +++ .../External_Sensitive_Detectors/README.md | 54 +++ .../detectorlist.json | 6 + .../externalDetectors.json | 23 + .../geometry_innerCylinder.macro | 35 ++ .../geometry_outerDisk.macro | 35 ++ .../inspect_hits.macro | 50 ++ .../External_Sensitive_Detectors/run.sh | 32 ++ .../sensitive_action.macro | 44 ++ scripts/geometry/README.md | 236 ++++++++- scripts/geometry/TODO.md | 4 + 30 files changed, 1853 insertions(+), 166 deletions(-) create mode 100644 Detectors/Base/include/DetectorsBase/CADGeometryUtils.h create mode 100644 Detectors/Base/src/CADGeometryUtils.cxx create mode 100644 Detectors/External/CMakeLists.txt create mode 100644 Detectors/External/include/ExternalDetectors/ExternalDetector.h create mode 100644 Detectors/External/include/ExternalDetectors/Hit.h create mode 100644 Detectors/External/macro/sensitiveActionExample.macro create mode 100644 Detectors/External/src/ExternalDetector.cxx create mode 100644 Detectors/External/src/ExternalDetectorsLinkDef.h create mode 100644 run/SimExamples/External_Sensitive_Detectors/README.md create mode 100644 run/SimExamples/External_Sensitive_Detectors/detectorlist.json create mode 100644 run/SimExamples/External_Sensitive_Detectors/externalDetectors.json create mode 100644 run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro create mode 100644 run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro create mode 100644 run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro create mode 100755 run/SimExamples/External_Sensitive_Detectors/run.sh create mode 100644 run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro create mode 100644 scripts/geometry/TODO.md diff --git a/.clang-format b/.clang-format index 93ba0f7b0c187..f0eb7d31df8e3 100644 --- a/.clang-format +++ b/.clang-format @@ -54,3 +54,7 @@ UseTab: Never # Do not format protobuf files Language: Proto DisableFormat: true +--- +# Do not format JSON configuration files +Language: Json +DisableFormat: true diff --git a/Common/SimConfig/include/SimConfig/SimConfig.h b/Common/SimConfig/include/SimConfig/SimConfig.h index be88d9fbd8c33..0be82ba1921b5 100644 --- a/Common/SimConfig/include/SimConfig/SimConfig.h +++ b/Common/SimConfig/include/SimConfig/SimConfig.h @@ -88,8 +88,9 @@ struct SimConfigData { bool mForwardKine = false; // true if tracks and event headers are to be published on a FairMQ channel (for reading by other consumers) bool mWriteToDisc = true; // whether we write simulation products (kine, hits) to disc VertexMode mVertexMode = VertexMode::kDiamondParam; // by default we should use die InteractionDiamond parameter + std::string mExtGeomFile = ""; // optional path to a JSON file describing external (CAD) geometry modules to inject - ClassDefNV(SimConfigData, 4); + ClassDefNV(SimConfigData, 5); }; // A singleton class which can be used @@ -178,6 +179,7 @@ class SimConfig bool forwardKine() const { return mConfigData.mForwardKine; } bool writeToDisc() const { return mConfigData.mWriteToDisc; } VertexMode getVertexMode() const { return mConfigData.mVertexMode; } + std::string getExtGeomFilename() const { return mConfigData.mExtGeomFile; } // returns the pair of collision context filename as well as event prefix encoded // in the mFromCollisionContext string. Returns empty string if information is not available or set. diff --git a/Common/SimConfig/src/SimConfig.cxx b/Common/SimConfig/src/SimConfig.cxx index 15879687872d5..14f99dd4ea580 100644 --- a/Common/SimConfig/src/SimConfig.cxx +++ b/Common/SimConfig/src/SimConfig.cxx @@ -75,7 +75,8 @@ void SimConfig::initOptions(boost::program_options::options_description& options "asservice", bpo::value()->default_value(false), "run in service/server mode")( "noGeant", bpo::bool_switch(), "prohibits any Geant transport/physics (by using tight cuts)")( "forwardKine", bpo::bool_switch(), "forward kinematics on a FairMQ channel")( - "noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)"); + "noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)")( + "extGeomFile", bpo::value()->default_value(""), "Path to a JSON file describing external (CAD) geometry modules to inject (see Detectors/Passive ExternalModule). Modules are added when their 'name' is part of the active module list."); options.add_options()("fromCollContext", bpo::value()->default_value(""), "Use a pregenerated collision context to infer number of events to simulate, how to embedd them, the vertex position etc. Takes precedence of other options such as \"--nEvents\". The format is COLLISIONCONTEXTFILE.root[:SIGNALNAME] where SIGNALNAME is the event part in the context which is relevant."); } @@ -354,6 +355,7 @@ bool SimConfig::resetFromParsedMap(boost::program_options::variables_map const& if (vm.count("noemptyevents")) { mConfigData.mFilterNoHitEvents = true; } + mConfigData.mExtGeomFile = vm["extGeomFile"].as(); mConfigData.mFromCollisionContext = vm["fromCollContext"].as(); auto collcontext_simprefix = getCollContextFilenameAndEventPrefix(); adjustFromCollContext(collcontext_simprefix.first, collcontext_simprefix.second); diff --git a/Detectors/Base/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index 9830d0c1175ce..e2d8114bc30fa 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -37,6 +37,7 @@ o2_add_library(DetectorsBase src/GlobalParams.cxx src/O2Tessellated.cxx src/TGeoGeometryUtils.cxx + src/CADGeometryUtils.cxx PUBLIC_LINK_LIBRARIES FairRoot::Base O2::CommonUtils O2::DetectorsCommonDataFormats diff --git a/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h b/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h new file mode 100644 index 0000000000000..12d626d0eb572 --- /dev/null +++ b/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CADGeometryUtils.h +/// \brief Helpers to inject CAD-derived (TGeo) geometry into O2 simulation +/// +/// These utilities are shared between purely passive external modules +/// (o2::passive::ExternalModule) and sensitive external detectors +/// (o2::ext::ExternalDetector). They deal with the geometry produced by +/// scripts/geometry/O2_CADtoTGeo.py, which is emitted as a ROOT macro. + +#ifndef ALICEO2_BASE_CADGEOMETRYUTILS_H +#define ALICEO2_BASE_CADGEOMETRYUTILS_H + +#include + +class TGeoVolume; + +namespace o2::base +{ + +/// JIT-compile a CAD-derived ROOT geometry macro (as produced by O2_CADtoTGeo.py) +/// and execute it to obtain the top TGeoVolume of the described module. +/// +/// The macro body is wrapped into a unique namespace (derived from \a instanceTag) +/// so that several such macros — which all export identically named symbols +/// (build(), get_builder_hook_unchecked(), ...) — can coexist in the same Cling +/// session without colliding. Returns nullptr on failure. +/// +/// \param macroFile path to the geometry macro (shell variables are expanded) +/// \param instanceTag a short tag used to build a unique, human-readable namespace +TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag); + +/// Re-register the TGeo media used in the volume tree rooted at \a top into the O2 +/// MaterialManager under ownership of \a modulename, rewiring the volumes to the +/// newly created media. This brings the CAD-imported media under O2's media/cut +/// handling (so that e.g. tracking cuts apply consistently). +void remapCADMedia(TGeoVolume* top, const char* modulename); + +} // namespace o2::base + +#endif diff --git a/Detectors/Base/include/DetectorsBase/Detector.h b/Detectors/Base/include/DetectorsBase/Detector.h index f1744086d6a05..54e264dd22666 100644 --- a/Detectors/Base/include/DetectorsBase/Detector.h +++ b/Detectors/Base/include/DetectorsBase/Detector.h @@ -530,9 +530,14 @@ class DetImpl : public o2::base::Detector { using Hit_t = typename std::remove_pointer(this)->Det::getHits(0))>::type; using Collector_t = tbb::concurrent_unordered_map>>>; - static Collector_t hitcollector; // note: we can't put this as member because - // decltype type deduction doesn't seem to work for class members; so we use a static member - // and will use some pointer member to communicate this data to other functions + // note: we can't put this as a member because decltype type deduction doesn't seem to work for + // class members; so we use a static and communicate it to other functions via a pointer member. + // The collector must be kept *per detector instance* (keyed by 'this'): for most detectors there + // is a single instance per C++ type, but several external detectors share the same type + // (o2::ext::ExternalDetector) and would otherwise clobber/double-free each other's buffers. + // tbb::concurrent_unordered_map is node-based, so the reference stays valid across insertions. + static tbb::concurrent_unordered_map hitcollectors; + auto& hitcollector = hitcollectors[this]; mHitCollectorBufferPtr = (char*)&hitcollector; int probe = 0; diff --git a/Detectors/Base/src/CADGeometryUtils.cxx b/Detectors/Base/src/CADGeometryUtils.cxx new file mode 100644 index 0000000000000..84c1890d844b5 --- /dev/null +++ b/Detectors/Base/src/CADGeometryUtils.cxx @@ -0,0 +1,202 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "DetectorsBase/CADGeometryUtils.h" +#include "DetectorsBase/MaterialManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::base +{ + +TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag) +{ + if (macroFile.empty()) { + return nullptr; + } + auto expandedHookFileName = o2::utils::expandShellVarsInFileName(macroFile); + if (!std::filesystem::exists(expandedHookFileName)) { + LOG(error) << "External geometry macro " << expandedHookFileName << " does not exist"; + return nullptr; + } + + // We JIT the macro into a *unique* namespace per call. This is essential when several + // external geometries are present at the same time: every macro produced by + // O2_CADtoTGeo.py exports identically named symbols (build(), get_builder_hook_unchecked(), + // LoadFacets(), ...). Loading them all into the single global Cling scope would collide + // (the first definition wins and subsequent macros are silently ignored). By wrapping each + // macro body in its own namespace we keep the symbols separate. The preprocessor #include + // lines must stay at global scope, so we hoist them out of the namespace. + std::ifstream macroStream(expandedHookFileName, std::ios::in); + if (!macroStream.is_open()) { + LOG(error) << "Cannot open external geometry macro " << expandedHookFileName; + return nullptr; + } + std::string preamble; // #include (and other top-level preprocessor) lines -> global scope + std::string body; // everything else -> wrapped into a unique namespace + std::string line; + while (std::getline(macroStream, line)) { + auto firstNonSpace = line.find_first_not_of(" \t"); + if (firstNonSpace != std::string::npos && line[firstNonSpace] == '#') { + preamble += line + "\n"; + } else { + body += line + "\n"; + } + } + + // build a unique, valid C++ identifier for the namespace + static std::atomic instanceCounter{0}; + std::string ns = std::string("o2_cadgeom_") + instanceTag + "_" + std::to_string(instanceCounter++); + for (auto& c : ns) { + if (!std::isalnum(static_cast(c)) && c != '_') { + c = '_'; + } + } + + const std::string wrapped = preamble + "\nnamespace " + ns + " {\n" + body + "\n}\n"; + if (!gInterpreter->Declare(wrapped.c_str())) { + LOG(error) << "Failed to JIT external geometry macro " << expandedHookFileName; + return nullptr; + } + + // retrieve the builder hook from the unique namespace + const std::string globalName = "__" + ns + "_hook__"; + gROOT->ProcessLine(Form("std::function %s = %s::get_builder_hook_unchecked();", + globalName.c_str(), ns.c_str())); + auto global = gROOT->GetGlobal(globalName.c_str()); + if (!global) { + LOG(error) << "Could not retrieve geometry builder hook from macro " << expandedHookFileName; + return nullptr; + } + auto hook = *reinterpret_cast*>(global->GetAddress()); + LOG(info) << "CAD geometry hook initialized from file " << expandedHookFileName << " (namespace " << ns << ")"; + + auto top = hook(); + if (!top) { + LOG(error) << "CAD geometry macro " << expandedHookFileName << " did not return a top volume"; + } + return top; +} + +void remapCADMedia(TGeoVolume* top, const char* modulename) +{ + std::unordered_map medium_ptr_mapping; + std::unordered_set volumes_already_treated; + int counter = 1; + + // The transformer function + auto transform_media = [&](TGeoVolume* vol_) { + if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) { + // this volume was already transformed + return; + } + volumes_already_treated.insert(vol_); + + if (dynamic_cast(vol_)) { + // do nothing for assemblies (they don't have a medium) + return; + } + + auto medium = vol_->GetMedium(); + if (!medium) { + return; + } + + auto iter = medium_ptr_mapping.find(medium); + if (iter != medium_ptr_mapping.end()) { + // This medium has already been transformed, so + // we just update the volume + vol_->SetMedium(iter->second); + return; + } else { + LOG(info) << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName(); + + // we found a medium, not yet treated + auto curr_mat = medium->GetMaterial(); + auto& matmgr = o2::base::MaterialManager::Instance(); + + matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen()); + // TGeo medium params are stored in a flat array with the following convention + // fParams[0] = isvol; + // fParams[1] = ifield; + // fParams[2] = fieldm; + // fParams[3] = tmaxfd; + // fParams[4] = stemax; + // fParams[5] = deemax; + // fParams[6] = epsil; + // fParams[7] = stmin; + const auto isvol = medium->GetParam(0); + const auto isxfld = medium->GetParam(1); + const auto sxmgmx = medium->GetParam(2); + const auto tmaxfd = medium->GetParam(3); + const auto stemax = medium->GetParam(4); + const auto deemax = medium->GetParam(5); + const auto epsil = medium->GetParam(6); + const auto stmin = medium->GetParam(7); + + matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin); + + // there will be new Material and Medium objects; fetch them + auto new_med = matmgr.getTGeoMedium(modulename, counter); + + // insert into cache + medium_ptr_mapping[medium] = new_med; + vol_->SetMedium(new_med); + counter++; + } + }; // end transformer lambda + + // a generic volume walker + std::function visit_volume; + visit_volume = [&](TGeoVolume* vol) -> void { + if (!vol) { + return; + } + + // call the transformer + transform_media(vol); + + // Recurse into daughters + const int nd = vol->GetNdaughters(); + for (int i = 0; i < nd; ++i) { + TGeoNode* node = vol->GetNode(i); + if (!node) { + continue; + } + TGeoVolume* child = node->GetVolume(); + if (!child) { + continue; + } + + visit_volume(child); + } + }; + + visit_volume(top); +} + +} // namespace o2::base diff --git a/Detectors/CMakeLists.txt b/Detectors/CMakeLists.txt index eef692ff18ca7..9143761508998 100644 --- a/Detectors/CMakeLists.txt +++ b/Detectors/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(TOF) add_subdirectory(ZDC) add_subdirectory(ITSMFT) +add_subdirectory(External) # sensitive external (CAD-derived) detectors; uses ITSMFT hit type add_subdirectory(TRD) add_subdirectory(MUON) diff --git a/Detectors/External/CMakeLists.txt b/Detectors/External/CMakeLists.txt new file mode 100644 index 0000000000000..ea5f0b53e2b8e --- /dev/null +++ b/Detectors/External/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(ExternalDetectors + SOURCES src/ExternalDetector.cxx + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + O2::SimulationDataFormat + O2::CommonUtils + RapidJSON::RapidJSON) + +o2_target_root_dictionary(ExternalDetectors + HEADERS include/ExternalDetectors/Hit.h + include/ExternalDetectors/ExternalDetector.h + LINKDEF src/ExternalDetectorsLinkDef.h) diff --git a/Detectors/External/include/ExternalDetectors/ExternalDetector.h b/Detectors/External/include/ExternalDetectors/ExternalDetector.h new file mode 100644 index 0000000000000..fbfc30bd5e148 --- /dev/null +++ b/Detectors/External/include/ExternalDetectors/ExternalDetector.h @@ -0,0 +1,176 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file ExternalDetector.h +/// \brief Sensitive detector built from an externally provided (CAD-derived) geometry +/// +/// ExternalDetector is the sensitive counterpart of o2::passive::ExternalModule. +/// It injects a CAD-derived TGeo geometry (produced by scripts/geometry/O2_CADtoTGeo.py) +/// and turns a configurable set of its volumes (selected by medium or volume name) into +/// sensitive volumes which produce hits. It derives from o2::base::DetImpl, so it +/// transparently participates in the full o2-sim hit forwarding/merging machinery +/// (FairMQ serialization, sub-event merging, ...). +/// +/// All instances share one generic hit type (o2::ext::Hit) so that an arbitrary number +/// of external detectors can coexist (each tied to a different o2::detectors::DetID) +/// without the hit merger needing to know more than this single wire format. +/// +/// The sensitive action itself is configurable: by default a generic entrance/exit hit +/// is produced, but a user can instead provide a ROOT macro (loaded at runtime via +/// o2::conf::GetFromMacro, the same mechanism used for generator/stepping hooks) whose +/// function receives the detector instance and may query the TVirtualMC singleton and +/// call addHit(...) to implement an arbitrary sensitive action -- without recompiling O2. + +#ifndef ALICEO2_EXT_EXTERNALDETECTOR_H +#define ALICEO2_EXT_EXTERNALDETECTOR_H + +#include "DetectorsBase/Detector.h" // for DetImpl +#include "DetectorsCommonDataFormats/DetID.h" // for DetID +#include "ExternalDetectors/Hit.h" // for the generic external hit type + +#include "Rtypes.h" +#include "TLorentzVector.h" + +#include +#include +#include +#include +#include + +class FairVolume; +class TGeoMatrix; +class TVector3; + +namespace o2::ext +{ + +/// Configuration of a single sensitive external detector. +struct ExternalDetectorOptions { + std::string root_macro_file; // ROOT macro describing the CAD geometry (O2_CADtoTGeo.py output) + std::string anchor_volume; // existing volume into which the geometry is hooked + TGeoMatrix const* placement = nullptr; // placement of the geometry inside the anchor (may be null) + std::vector sensitiveMedia; // media (substring match on the medium name) to be made sensitive + std::vector sensitiveVolumes; // volumes (substring match on the volume name) to be made sensitive + int detID = o2::detectors::DetID::ITS; // DetID this detector's hits are tied to (identity / output format) + std::string sensitiveMacro; // optional ROOT macro implementing the sensitive action + std::string sensitiveFunction; // global function in the macro returning the action (default "sensitiveAction()") +}; + +class ExternalDetector : public o2::base::DetImpl +{ + public: + /// Signature of a (JIT-able) sensitive action. The function is handed the detector + /// instance and is expected to query the TVirtualMC singleton (TVirtualMC::GetMC()) + /// for the current step and to call addHit(...) to produce hits. Returning true means + /// a hit-relevant step was processed (mirrors the ProcessHits return value). + using SensitiveFcn = std::function; + + ExternalDetector(const char* name, const char* title, ExternalDetectorOptions options); + ExternalDetector(); + ~ExternalDetector() override; + + /// Build a list of sensitive external detectors from a JSON description file. + /// The file must contain an "externalDetectors" array; each entry needs at least + /// "name", "macro", "anchor" and at least one of "sensitiveMedia" / "sensitiveVolumes" + /// (arrays of substrings matched against medium / volume names); an optional + /// "detID" (name, default "ITS") ties the hit output to an existing detector, and + /// an optional "placement" object may carry "translation"/"rotation_deg". + /// Ownership of the returned detectors is transferred to the caller. + static std::vector createFromJSON(const std::string& jsonfile); + + /// Build the CAD geometry, remap its media and register the sensitive volumes. + void ConstructGeometry() override; + + /// Resolve the Monte Carlo volume IDs of the sensitive volumes. + void InitializeO2Detector() override; + + /// Called for each tracking step; produces hits in the sensitive volumes. + Bool_t ProcessHits(FairVolume* v = nullptr) override; + + /// Register the hit collection with the FairRootManager. + void Register() override; + + /// Get the produced hit collection (probe interface used by DetImpl). + std::vector* getHits(Int_t iColl) const + { + if (iColl == 0) { + return mHits; + } + return nullptr; + } + + void Reset() override; + void EndOfEvent() override; + + void FinishPrimary() override {} + void BeginPrimary() override {} + void PostTrack() override {} + void PreTrack() override {} + + /// \name Helpers usable from a user-provided sensitive-action macro + /// These wrap the bookkeeping a sensitive action typically needs so that a macro can + /// stay focused on physics and the TVirtualMC queries. + ///@{ + /// Append a hit to the output collection and flag the MCTrack as having left a hit + /// in this detector. Returns a pointer to the stored hit. + 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); + + /// Running sensor index of the volume currently being processed, or -1 if the current + /// volume is not one of the configured sensitive volumes. + int currentSensorID() const; + + /// MCTrack number of the track currently being stepped. + int currentTrackID() const; + ///@} + + protected: + /// the built-in sensitive action used when no macro is configured (generic entrance/exit hit) + Bool_t defaultProcessHits(); + + /// recursively collect names of volumes whose medium matches the configured sensitive media + void collectSensitiveVolumeNames(TGeoVolume* vol, std::set& visited); + + ExternalDetectorOptions mOptions; + + std::vector mSensitiveVolumeNames; //! names of the volumes to be made sensitive (filled at geometry build) + std::set mSensitiveVolIDs; //! MC volume IDs of the sensitive volumes + std::unordered_map mVolID2SensorID; //! dense sensor index per sensitive MC volume ID + + /// transient data about a track passing a sensor (mirrors the ITS approach) + struct TrackData { + bool mHitStarted; //! hit creation started + unsigned char mTrkStatusStart; //! track status flag at entrance + TLorentzVector mPositionStart; //! position at entrance + TLorentzVector mMomentumStart; //! momentum at entrance + double mEnergyLoss; //! accumulated energy loss + } mTrackData; //! + + std::vector* mHits = nullptr; //! container for produced hits + + SensitiveFcn mSensitiveAction; //! optional user-provided sensitive action (loaded from a macro) + FairVolume* mCurrentVolume = nullptr; //! volume currently passed to ProcessHits (for the action helpers) + + int mStepCount = 0; //! number of stepping calls inside our sensitive volumes this event (probe) + + private: + ExternalDetector(const ExternalDetector&); + ExternalDetector& operator=(const ExternalDetector&); + + template + friend class o2::base::DetImpl; + ClassDefOverride(ExternalDetector, 1); +}; + +} // namespace o2::ext + +#endif diff --git a/Detectors/External/include/ExternalDetectors/Hit.h b/Detectors/External/include/ExternalDetectors/Hit.h new file mode 100644 index 0000000000000..4a77f85050f06 --- /dev/null +++ b/Detectors/External/include/ExternalDetectors/Hit.h @@ -0,0 +1,140 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file Hit.h +/// \brief Generic hit type for externally injected (CAD-derived) sensitive detectors +/// +/// o2::ext::Hit is a deliberately rich, detector-agnostic hit. A single external +/// hit type lets an arbitrary number of o2::ext::ExternalDetector instances (each +/// tied to a different DetID) share one wire format, so that the o2-sim hit merger +/// only ever has to know how to (de)serialize this one type, independently of how +/// many external detectors are configured or what their sensitive action does. +/// +/// It stores entrance and exit position, the momentum, energy and energy loss, the +/// time, the track length in the volume, the PDG code and the MC status flags at +/// entrance/exit, so that most information an external sensitive action might want +/// to keep is available downstream. + +#ifndef ALICEO2_EXT_HIT_H +#define ALICEO2_EXT_HIT_H + +#include "SimulationDataFormat/BaseHits.h" // for BasicXYZEHit +#include "CommonUtils/ShmAllocator.h" +#include "Rtypes.h" +#include "TVector3.h" +#include + +namespace o2::ext +{ + +class Hit : public o2::BasicXYZEHit +{ + public: + enum HitStatus_t { + kTrackEntering = 0x1, + kTrackInside = 0x1 << 1, + kTrackExiting = 0x1 << 2, + kTrackOut = 0x1 << 3, + kTrackStopped = 0x1 << 4, + kTrackAlive = 0x1 << 5 + }; + + Hit() = default; + + /// \param trackID index of the MCTrack + /// \param sensorID index of the sensitive volume (per-detector running id) + /// \param startPos coordinates at entrance to the active volume [cm] + /// \param endPos coordinates at exit of the active volume [cm] + /// \param startMom momentum of the track at entrance [GeV] + /// \param startE total energy at entrance [GeV] + /// \param endTime time at exit [ns] + /// \param eLoss energy deposited in the volume [GeV] + /// \param startStatus MC status flags at entrance + /// \param endStatus MC status flags at exit + /// \param pdg PDG code of the track (optional) + /// \param length track length inside the volume [cm] (optional) + Hit(int trackID, unsigned short 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) + : BasicXYZEHit(endPos.X(), endPos.Y(), endPos.Z(), endTime, eLoss, trackID, sensorID), + mMomentum(startMom.Px(), startMom.Py(), startMom.Pz()), + mPosStart(startPos.X(), startPos.Y(), startPos.Z()), + mE(startE), + mLength(length), + mPdg(pdg), + mTrackStatusEnd(endStatus), + mTrackStatusStart(startStatus) + { + } + + // entrance position + math_utils::Point3D GetPosStart() const { return mPosStart; } + float GetStartX() const { return mPosStart.X(); } + float GetStartY() const { return mPosStart.Y(); } + float GetStartZ() const { return mPosStart.Z(); } + void SetPosStart(const math_utils::Point3D& p) { mPosStart = p; } + + // momentum / energy + math_utils::Vector3D GetMomentum() const { return mMomentum; } + math_utils::Vector3D& GetMomentum() { return mMomentum; } + float GetPx() const { return mMomentum.X(); } + float GetPy() const { return mMomentum.Y(); } + float GetPz() const { return mMomentum.Z(); } + float GetE() const { return mE; } + float GetTotalEnergy() const { return mE; } + + // extra bookkeeping + float GetLength() const { return mLength; } + void SetLength(float l) { mLength = l; } + int GetPdg() const { return mPdg; } + void SetPdg(int pdg) { mPdg = pdg; } + + // status flags + unsigned char GetStatusStart() const { return mTrackStatusStart; } + unsigned char GetStatusEnd() const { return mTrackStatusEnd; } + bool IsEntering() const { return mTrackStatusEnd & kTrackEntering; } + bool IsInside() const { return mTrackStatusEnd & kTrackInside; } + bool IsExiting() const { return mTrackStatusEnd & kTrackExiting; } + bool IsOut() const { return mTrackStatusEnd & kTrackOut; } + bool IsStopped() const { return mTrackStatusEnd & kTrackStopped; } + bool IsAlive() const { return mTrackStatusEnd & kTrackAlive; } + + friend std::ostream& operator<<(std::ostream& of, const Hit& point) + { + of << "-I- o2::ext::Hit for track " << point.GetTrackID() << " in sensor " << point.GetDetectorID(); + return of; + } + + private: + math_utils::Vector3D mMomentum; ///< momentum at entrance + math_utils::Point3D mPosStart; ///< position at entrance (base mPos holds the exit position) + float mE; ///< total energy at entrance + float mLength; ///< track length inside the volume + int mPdg; ///< PDG code of the track + unsigned char mTrackStatusEnd; ///< MC status flag at exit + unsigned char mTrackStatusStart; ///< MC status flag at entrance + + ClassDefNV(Hit, 1); +}; + +} // namespace o2::ext + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; +} // namespace std +#endif + +#endif // ALICEO2_EXT_HIT_H diff --git a/Detectors/External/macro/sensitiveActionExample.macro b/Detectors/External/macro/sensitiveActionExample.macro new file mode 100644 index 0000000000000..0f496bfcece0a --- /dev/null +++ b/Detectors/External/macro/sensitiveActionExample.macro @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file sensitiveActionExample.macro +/// \brief Example sensitive action for an o2::ext::ExternalDetector +/// +/// Point this macro at an "externalDetectors" entry via the "sensitiveMacro" key +/// (an absolute path, or one using shell variables such as $O2_ROOT, is resolved at runtime): +/// "sensitiveMacro": ".../Detectors/External/macro/sensitiveActionExample.macro" +/// +/// The global function sensitiveAction() returns the callable that o2-sim invokes for +/// every tracking step inside one of the detector's sensitive volumes. It is loaded at +/// runtime with o2::conf::GetFromMacro (the same just-in-time mechanism used for +/// generator and stepping hooks), so the sensitive logic can be changed without +/// recompiling O2. +/// +/// Inside the action you have the full TVirtualMC singleton available (exactly like a +/// hand-written ProcessHits) plus a few convenience helpers on the detector: +/// - det->currentSensorID() : running index of the current sensitive volume (-1 if none) +/// - det->currentTrackID() : MCTrack number of the track being stepped +/// - det->addHit(...) : append an o2::ext::Hit and flag the MCTrack +/// +/// This trivial example records one hit each time a charged particle enters a sensitive +/// volume. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/ExternalDetector.h" +#include "ExternalDetectors/Hit.h" +#include +#include +#include +#endif + +// NOTE: the return type must be spelled exactly as the typedef name passed to +// GetFromMacro ("o2::ext::ExternalDetector::SensitiveFcn") -- the loader compares the +// function's return-type name textually, so std::function<...> would not match. +o2::ext::ExternalDetector::SensitiveFcn sensitiveAction() +{ + return [](o2::ext::ExternalDetector* det) -> bool { + auto vmc = TVirtualMC::GetMC(); + if (vmc->TrackCharge() == 0) { + return false; // ignore neutral particles + } + if (!vmc->IsTrackEntering()) { + return false; // record only the entrance point in this example + } + const int sensor = det->currentSensorID(); + if (sensor < 0) { + return false; // current volume is not one of our sensitive volumes + } + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + // a point-like hit at the entrance (start == end position, no accumulated energy loss) + det->addHit(det->currentTrackID(), sensor, pos.Vect(), pos.Vect(), mom.Vect(), + mom.E(), pos.T(), 0. /*eLoss*/, o2::ext::Hit::kTrackEntering, o2::ext::Hit::kTrackEntering, + vmc->TrackPid(), vmc->TrackLength()); + return true; + }; +} diff --git a/Detectors/External/src/ExternalDetector.cxx b/Detectors/External/src/ExternalDetector.cxx new file mode 100644 index 0000000000000..f79d6ce6f2505 --- /dev/null +++ b/Detectors/External/src/ExternalDetector.cxx @@ -0,0 +1,451 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "ExternalDetectors/ExternalDetector.h" +#include "DetectorsBase/CADGeometryUtils.h" +#include "DetectorsBase/Stack.h" +#include "CommonUtils/ConfigurationMacroHelper.h" +#include "CommonUtils/FileSystemUtils.h" +#include "CommonUtils/ShmManager.h" +#include "CommonUtils/ShmAllocator.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace o2::ext +{ + +ExternalDetector::ExternalDetector(const char* name, const char* title, ExternalDetectorOptions options) + : o2::base::DetImpl(name, true), + mOptions(options), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ + (void)title; // the FairModule title is the second base ctor argument; kept for symmetry with other detectors + // Decouple the user-facing FairModule name (e.g. "IRIS") from the DetId: the base + // ctor derives fDetId from the name which is generally not a registered DetID, so we + // explicitly tie this detector to the configured (existing) DetID. This is what makes + // the hit output format / identity well defined, as discussed. + fDetId = mOptions.detID; +} + +ExternalDetector::ExternalDetector() + : o2::base::DetImpl("EXTDET", true), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +ExternalDetector::ExternalDetector(const ExternalDetector& rhs) + : o2::base::DetImpl(rhs), + mOptions(rhs.mOptions), + mSensitiveVolumeNames(rhs.mSensitiveVolumeNames), + mSensitiveVolIDs(rhs.mSensitiveVolIDs), + mVolID2SensorID(rhs.mVolID2SensorID), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +ExternalDetector::~ExternalDetector() +{ + if (mHits) { + o2::utils::freeSimVector(mHits); + } +} + +void ExternalDetector::collectSensitiveVolumeNames(TGeoVolume* vol, std::set& visited) +{ + if (!vol || visited.count(vol)) { + return; + } + visited.insert(vol); + + bool sensitive = false; + // match by volume name + const std::string volname = vol->GetName(); + for (const auto& token : mOptions.sensitiveVolumes) { + if (!token.empty() && volname.find(token) != std::string::npos) { + sensitive = true; + break; + } + } + // otherwise match by medium name + if (!sensitive) { + if (auto medium = vol->GetMedium()) { + const std::string medname = medium->GetName(); + for (const auto& token : mOptions.sensitiveMedia) { + if (!token.empty() && medname.find(token) != std::string::npos) { + sensitive = true; + break; + } + } + } + } + if (sensitive) { + mSensitiveVolumeNames.emplace_back(volname); + } + + const int nd = vol->GetNdaughters(); + for (int i = 0; i < nd; ++i) { + if (auto node = vol->GetNode(i)) { + collectSensitiveVolumeNames(node->GetVolume(), visited); + } + } +} + +void ExternalDetector::ConstructGeometry() +{ + // build the CAD geometry and obtain its top volume + auto module_top = o2::base::buildCADVolumeFromMacro(mOptions.root_macro_file, GetName()); + if (!module_top) { + LOG(error) << "No geometry could be built for external detector " << GetName(); + return; + } + + // bring the CAD media under O2's MaterialManager + o2::base::remapCADMedia(module_top, GetName()); + + // determine which volumes should become sensitive (selected by medium name) + mSensitiveVolumeNames.clear(); + std::set visited; + collectSensitiveVolumeNames(module_top, visited); + if (mSensitiveVolumeNames.empty()) { + LOG(warning) << "External detector " << GetName() << ": no volume matched the configured sensitive media; " + << "no hits will be produced"; + } else { + LOG(info) << "External detector " << GetName() << ": " << mSensitiveVolumeNames.size() + << " sensitive volume(s) selected"; + } + + // place it into the provided anchor volume (needs to exist) + auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str()); + if (!anchor) { + LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting"; + return; + } + anchor->AddNode(module_top, 1, const_cast(mOptions.placement)); +} + +void ExternalDetector::InitializeO2Detector() +{ + // resolve the MC volume IDs of the sensitive volumes and register them with FairRoot + mSensitiveVolIDs.clear(); + mVolID2SensorID.clear(); + int sensorID = 0; + for (const auto& name : mSensitiveVolumeNames) { + const int volID = registerSensitiveVolumeAndGetVolID(name); + if (volID <= 0) { + continue; + } + mSensitiveVolIDs.insert(volID); + mVolID2SensorID[volID] = sensorID++; + LOG(info) << "External detector " << GetName() << ": registered sensitive volume '" << name + << "' (MC volID " << volID << ", sensor " << mVolID2SensorID[volID] << ")"; + } + + // optionally load a user-provided sensitive action from a ROOT macro (same mechanism as + // generator/stepping hooks). When given, it fully replaces the built-in action. + if (!mOptions.sensitiveMacro.empty()) { + const auto file = o2::utils::expandShellVarsInFileName(mOptions.sensitiveMacro); + const auto func = mOptions.sensitiveFunction.empty() ? std::string("sensitiveAction()") : mOptions.sensitiveFunction; + const auto unique = std::string("o2ext_sensitive_action_") + GetName(); + mSensitiveAction = o2::conf::GetFromMacro(file, func, "o2::ext::ExternalDetector::SensitiveFcn", unique); + if (mSensitiveAction) { + LOG(info) << "External detector " << GetName() << ": using sensitive action '" << func + << "' from macro '" << file << "'"; + } else { + LOG(fatal) << "External detector " << GetName() << ": could not load sensitive action '" << func + << "' from macro '" << file << "'"; + } + } +} + +Bool_t ExternalDetector::ProcessHits(FairVolume* vol) +{ + // This method is called from the MC stepping for the registered sensitive volumes. + // Remember the current volume so the action helpers (currentSensorID()) can resolve it, + // then either run the user-provided action or the built-in one. + mCurrentVolume = vol; + ++mStepCount; // probe: count stepping calls inside our sensitive volumes + if (mSensitiveAction) { + return mSensitiveAction(this) ? kTRUE : kFALSE; + } + return defaultProcessHits(); +} + +Bool_t ExternalDetector::defaultProcessHits() +{ + if (!(fMC->TrackCharge())) { + return kFALSE; + } + + const int sensorID = currentSensorID(); + if (sensorID < 0) { + return kFALSE; // not one of our sensitive volumes + } + + bool startHit = false, stopHit = false; + unsigned char status = 0; + if (fMC->IsTrackEntering()) { + status |= o2::ext::Hit::kTrackEntering; + } + if (fMC->IsTrackInside()) { + status |= o2::ext::Hit::kTrackInside; + } + if (fMC->IsTrackExiting()) { + status |= o2::ext::Hit::kTrackExiting; + } + if (fMC->IsTrackOut()) { + status |= o2::ext::Hit::kTrackOut; + } + if (fMC->IsTrackStop()) { + status |= o2::ext::Hit::kTrackStopped; + } + if (fMC->IsTrackAlive()) { + status |= o2::ext::Hit::kTrackAlive; + } + + // track is entering or created in the volume + if ((status & o2::ext::Hit::kTrackEntering) || (status & o2::ext::Hit::kTrackInside && !mTrackData.mHitStarted)) { + startHit = true; + } else if ((status & (o2::ext::Hit::kTrackExiting | o2::ext::Hit::kTrackOut | o2::ext::Hit::kTrackStopped))) { + stopHit = true; + } + + // increment energy loss at all steps except entrance + if (!startHit) { + mTrackData.mEnergyLoss += fMC->Edep(); + } + if (!(startHit | stopHit)) { + return kFALSE; // do nothing + } + + if (startHit) { + mTrackData.mEnergyLoss = 0.; + fMC->TrackMomentum(mTrackData.mMomentumStart); + fMC->TrackPosition(mTrackData.mPositionStart); + mTrackData.mTrkStatusStart = status; + mTrackData.mHitStarted = true; + } + if (stopHit) { + TLorentzVector positionStop; + fMC->TrackPosition(positionStop); + addHit(currentTrackID(), sensorID, mTrackData.mPositionStart.Vect(), positionStop.Vect(), + mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), + mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status, fMC->TrackPid(), fMC->TrackLength()); + mTrackData.mHitStarted = false; + } + return kTRUE; +} + +int ExternalDetector::currentSensorID() const +{ + const int volID = mCurrentVolume ? mCurrentVolume->getMCid() : -1; + auto it = mVolID2SensorID.find(volID); + return it == mVolID2SensorID.end() ? -1 : it->second; +} + +int ExternalDetector::currentTrackID() const +{ + return static_cast(fMC->GetStack())->GetCurrentTrackNumber(); +} + +o2::ext::Hit* ExternalDetector::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, float length) +{ + mHits->emplace_back(trackID, sensorID, startPos, endPos, startMom, startE, endTime, eLoss, + startStatus, endStatus, pdg, length); + // register that this track left a hit in our detector (sets the hit bit on the MCTrack) + static_cast(fMC->GetStack())->addHit(GetDetId()); + return &(mHits->back()); +} + +void ExternalDetector::Register() +{ + // Create a branch (named "Hit") holding the produced hits. + if (FairRootManager::Instance()) { + FairRootManager::Instance()->RegisterAny(addNameTo("Hit").data(), mHits, kTRUE); + } +} + +void ExternalDetector::Reset() +{ + if (!o2::utils::ShmManager::Instance().isOperational()) { + mHits->clear(); + } +} + +void ExternalDetector::EndOfEvent() +{ + // probe: report how often our sensitive volumes were stepped through and how many hits resulted + LOG(info) << "External detector " << GetName() << " EndOfEvent: " << mStepCount + << " sensitive step(s) -> " << (mHits ? mHits->size() : 0) << " hit(s)"; + mStepCount = 0; + Reset(); +} + +namespace +{ +// Build a TGeoCombiTrans from an optional JSON "placement" object carrying +// "translation":[x,y,z] (cm) and/or "rotation_deg":[rx,ry,rz] (deg, applied X,Y,Z). +TGeoMatrix* makePlacementFromJSON(const rapidjson::Value& placement) +{ + auto combi = new TGeoCombiTrans(); + if (placement.HasMember("rotation_deg") && placement["rotation_deg"].IsArray()) { + const auto& r = placement["rotation_deg"]; + if (r.Size() == 3) { + combi->RotateX(r[0].GetDouble()); + combi->RotateY(r[1].GetDouble()); + combi->RotateZ(r[2].GetDouble()); + } else { + LOG(warning) << "ExternalDetector placement 'rotation_deg' must have 3 entries; ignoring"; + } + } + if (placement.HasMember("translation") && placement["translation"].IsArray()) { + const auto& t = placement["translation"]; + if (t.Size() == 3) { + combi->SetDx(t[0].GetDouble()); + combi->SetDy(t[1].GetDouble()); + combi->SetDz(t[2].GetDouble()); + } else { + LOG(warning) << "ExternalDetector placement 'translation' must have 3 entries; ignoring"; + } + } + return combi; +} +} // namespace + +std::vector ExternalDetector::createFromJSON(const std::string& jsonfile) +{ + std::vector result; + + auto expanded = o2::utils::expandShellVarsInFileName(jsonfile); + std::ifstream fileStream(expanded, std::ios::in); + if (!fileStream.is_open()) { + LOG(error) << "Cannot open external geometry config file '" << expanded << "'"; + return result; + } + + rapidjson::IStreamWrapper isw(fileStream); + rapidjson::Document doc; + doc.ParseStream(isw); + if (doc.HasParseError()) { + LOG(error) << "Error parsing external geometry JSON '" << expanded << "': " + << rapidjson::GetParseError_En(doc.GetParseError()) + << " (offset " << doc.GetErrorOffset() << ")"; + return result; + } + // the array of sensitive external detectors is optional (the same file may only + // configure passive external modules) + if (!doc.HasMember("externalDetectors")) { + return result; + } + if (!doc["externalDetectors"].IsArray()) { + LOG(error) << "External geometry JSON '" << expanded << "': 'externalDetectors' must be an array"; + return result; + } + + auto getString = [](const rapidjson::Value& v, const char* key) -> std::string { + if (v.HasMember(key) && v[key].IsString()) { + return v[key].GetString(); + } + return std::string(); + }; + + for (const auto& entry : doc["externalDetectors"].GetArray()) { + if (!entry.IsObject()) { + LOG(error) << "Skipping non-object entry in 'externalDetectors'"; + continue; + } + const auto name = getString(entry, "name"); + if (name.empty()) { + LOG(error) << "Skipping external detector entry without 'name'"; + continue; + } + ExternalDetectorOptions options; + options.root_macro_file = getString(entry, "macro"); + options.anchor_volume = getString(entry, "anchor"); + if (options.root_macro_file.empty() || options.anchor_volume.empty()) { + LOG(error) << "External detector '" << name << "' requires both 'macro' and 'anchor'; skipping"; + continue; + } + + if (entry.HasMember("sensitiveMedia") && entry["sensitiveMedia"].IsArray()) { + for (const auto& m : entry["sensitiveMedia"].GetArray()) { + if (m.IsString()) { + options.sensitiveMedia.emplace_back(m.GetString()); + } + } + } + if (entry.HasMember("sensitiveVolumes") && entry["sensitiveVolumes"].IsArray()) { + for (const auto& v : entry["sensitiveVolumes"].GetArray()) { + if (v.IsString()) { + options.sensitiveVolumes.emplace_back(v.GetString()); + } + } + } + if (options.sensitiveMedia.empty() && options.sensitiveVolumes.empty()) { + LOG(error) << "External detector '" << name + << "' requires a non-empty 'sensitiveMedia' or 'sensitiveVolumes' array; skipping"; + continue; + } + + const auto detIDName = getString(entry, "detID"); + if (!detIDName.empty()) { + const auto did = o2::detectors::DetID::nameToID(detIDName.c_str()); + if (did < 0 || did >= o2::detectors::DetID::nDetectors) { + LOG(error) << "External detector '" << name << "': unknown detID '" << detIDName << "'; skipping"; + continue; + } + options.detID = did; + } + + if (entry.HasMember("placement") && entry["placement"].IsObject()) { + options.placement = makePlacementFromJSON(entry["placement"]); + } + + // optional user-provided sensitive action (a ROOT macro). When absent, the built-in + // generic entrance/exit hit action is used. + options.sensitiveMacro = getString(entry, "sensitiveMacro"); + options.sensitiveFunction = getString(entry, "sensitiveFunction"); + + auto title = getString(entry, "title"); + if (title.empty()) { + title = name; + } + LOG(info) << "Configured external detector '" << name << "' from macro '" << options.root_macro_file + << "' anchored to '" << options.anchor_volume << "', tied to DetID '" + << o2::detectors::DetID::getName(options.detID) << "'"; + result.push_back(new ExternalDetector(name.c_str(), title.c_str(), options)); + } + return result; +} + +} // namespace o2::ext + +ClassImp(o2::ext::ExternalDetector); diff --git a/Detectors/External/src/ExternalDetectorsLinkDef.h b/Detectors/External/src/ExternalDetectorsLinkDef.h new file mode 100644 index 0000000000000..f6e13b70200fc --- /dev/null +++ b/Detectors/External/src/ExternalDetectorsLinkDef.h @@ -0,0 +1,23 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::ext::Hit + ; +#pragma link C++ class std::vector < o2::ext::Hit> + ; +#pragma link C++ class o2::ext::ExternalDetector + ; +#pragma link C++ class o2::base::DetImpl < o2::ext::ExternalDetector> + ; + +#endif diff --git a/Detectors/Passive/CMakeLists.txt b/Detectors/Passive/CMakeLists.txt index a24954ad10539..e3bc45f8e8153 100644 --- a/Detectors/Passive/CMakeLists.txt +++ b/Detectors/Passive/CMakeLists.txt @@ -24,7 +24,8 @@ o2_add_library(DetectorsPassive src/HallSimParam.cxx src/PassiveBase.cxx src/ExternalModule.cxx - PUBLIC_LINK_LIBRARIES O2::Field O2::DetectorsBase O2::SimConfig) + PUBLIC_LINK_LIBRARIES O2::Field O2::DetectorsBase O2::SimConfig + RapidJSON::RapidJSON) o2_target_root_dictionary(DetectorsPassive HEADERS include/DetectorsPassive/Absorber.h diff --git a/Detectors/Passive/include/DetectorsPassive/ExternalModule.h b/Detectors/Passive/include/DetectorsPassive/ExternalModule.h index 155870ae42a6d..01c6dfea16947 100644 --- a/Detectors/Passive/include/DetectorsPassive/ExternalModule.h +++ b/Detectors/Passive/include/DetectorsPassive/ExternalModule.h @@ -14,6 +14,8 @@ #include "DetectorsPassive/PassiveBase.h" // base class of passive modules #include "Rtypes.h" // for Pipe::Class, ClassDef, Pipe::Streamer +#include +#include class TGeoVolume; class TGeoTransformation; @@ -41,22 +43,23 @@ class ExternalModule : public PassiveBase ~ExternalModule() override = default; void ConstructGeometry() override; + /// Build a list of external (passive) modules from a JSON description file. + /// The file must contain an "externalModules" array; each entry needs at least + /// "name", "macro" and "anchor"; an optional "placement" object may carry + /// "translation":[x,y,z] (cm) and "rotation_deg":[rx,ry,rz] (degrees). + /// Ownership of the returned modules is transferred to the caller. + static std::vector createFromJSON(const std::string& jsonfile); + /// Clone this object (used in MT mode only) FairModule* CloneModule() const override { return nullptr; } - typedef std::function GeomBuilderFcn; // function hook for external geometry builder - private: // void createMaterials(); ExternalModule(const ExternalModule& orig); ExternalModule& operator=(const ExternalModule&); - GeomBuilderFcn mGeomHook; ExternalModuleOptions mOptions; - bool initGeomBuilderHook(); // function to load/JIT Geometry builder hook - void remapMedia(TGeoVolume* vol); // performs a remapping of materials/media IDs after registration with VMC - // ClassDefOverride(ExternalModule, 0); }; } // namespace passive diff --git a/Detectors/Passive/src/ExternalModule.cxx b/Detectors/Passive/src/ExternalModule.cxx index fc6bd6953b82d..ebfa405d877de 100644 --- a/Detectors/Passive/src/ExternalModule.cxx +++ b/Detectors/Passive/src/ExternalModule.cxx @@ -12,16 +12,15 @@ // Sandro Wenzel (CERN), 2026 #include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include // ClassImp(o2::passive::ExternalModule) @@ -32,121 +31,17 @@ ExternalModule::ExternalModule(const char* name, const char* long_title, Externa { } -void ExternalModule::remapMedia(TGeoVolume* top_volume) -{ - std::unordered_map medium_ptr_mapping; - std::unordered_set volumes_already_treated; - int counter = 1; - - auto modulename = GetName(); - - // The transformer function - auto transform_media = [&](TGeoVolume* vol_) { - if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) { - // this volume was already transformed - return; - } - volumes_already_treated.insert(vol_); - - if (dynamic_cast(vol_)) { - // do nothing for assemblies (they don't have a medium) - return; - } - - auto medium = vol_->GetMedium(); - if (!medium) { - return; - } - - auto iter = medium_ptr_mapping.find(medium); - if (iter != medium_ptr_mapping.end()) { - // This medium has already been transformed, so - // we just update the volume - vol_->SetMedium(iter->second); - return; - } else { - std::cout << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName() << "\n"; - - // we found a medium, not yet treated - auto curr_mat = medium->GetMaterial(); - auto& matmgr = o2::base::MaterialManager::Instance(); - - matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen()); - // TGeo medium params are stored in a flat array with the following convention - // fParams[0] = isvol; - // fParams[1] = ifield; - // fParams[2] = fieldm; - // fParams[3] = tmaxfd; - // fParams[4] = stemax; - // fParams[5] = deemax; - // fParams[6] = epsil; - // fParams[7] = stmin; - const auto isvol = medium->GetParam(0); - const auto isxfld = medium->GetParam(1); - const auto sxmgmx = medium->GetParam(2); - const auto tmaxfd = medium->GetParam(3); - const auto stemax = medium->GetParam(4); - const auto deemax = medium->GetParam(5); - const auto epsil = medium->GetParam(6); - const auto stmin = medium->GetParam(7); - - matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin); - - // there will be new Material and Medium objects; fetch them - auto new_med = matmgr.getTGeoMedium(modulename, counter); - - // insert into cache - medium_ptr_mapping[medium] = new_med; - vol_->SetMedium(new_med); - counter++; - } - }; // end transformer lambda - - // a generic volume walker - std::function visit_volume; - visit_volume = [&](TGeoVolume* vol) -> void { - if (!vol) { - return; - } - - // call the transformer - transform_media(vol); - - // Recurse into daughters - const int nd = vol->GetNdaughters(); - for (int i = 0; i < nd; ++i) { - TGeoNode* node = vol->GetNode(i); - if (!node) { - continue; - } - TGeoVolume* child = node->GetVolume(); - if (!child) { - continue; - } - - visit_volume(child); - } - }; - - visit_volume(top_volume); -} - void ExternalModule::ConstructGeometry() { - // JIT the geom builder hook - if (!initGeomBuilderHook()) { - LOG(error) << " Could not load geometry builder hook"; - return; - } - - // otherwise execute it and obtain pointer to top most module volume - auto module_top = mGeomHook(); + // JIT the geom builder macro and obtain the top most module volume + auto module_top = o2::base::buildCADVolumeFromMacro(mOptions.root_macro_file, GetName()); if (!module_top) { - LOG(error) << "No module found\n"; + LOG(error) << "No module geometry could be built from " << mOptions.root_macro_file; return; } - remapMedia(const_cast(module_top)); + // bring the CAD media under O2's MaterialManager + o2::base::remapCADMedia(module_top, GetName()); // place it into the provided anchor volume (needs to exist) auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str()); @@ -154,22 +49,101 @@ void ExternalModule::ConstructGeometry() LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting"; return; } - anchor->AddNode(const_cast(module_top), 1, const_cast(mOptions.placement)); + anchor->AddNode(module_top, 1, const_cast(mOptions.placement)); +} + +namespace +{ +// Build a TGeoCombiTrans from an optional JSON "placement" object carrying +// "translation":[x,y,z] (cm) and/or "rotation_deg":[rx,ry,rz] (deg, applied X,Y,Z). +TGeoMatrix* makePlacementFromJSON(const rapidjson::Value& placement) +{ + auto combi = new TGeoCombiTrans(); + if (placement.HasMember("rotation_deg") && placement["rotation_deg"].IsArray()) { + const auto& r = placement["rotation_deg"]; + if (r.Size() == 3) { + combi->RotateX(r[0].GetDouble()); + combi->RotateY(r[1].GetDouble()); + combi->RotateZ(r[2].GetDouble()); + } else { + LOG(warning) << "ExternalModule placement 'rotation_deg' must have 3 entries; ignoring"; + } + } + if (placement.HasMember("translation") && placement["translation"].IsArray()) { + const auto& t = placement["translation"]; + if (t.Size() == 3) { + combi->SetDx(t[0].GetDouble()); + combi->SetDy(t[1].GetDouble()); + combi->SetDz(t[2].GetDouble()); + } else { + LOG(warning) << "ExternalModule placement 'translation' must have 3 entries; ignoring"; + } + } + return combi; } +} // namespace -bool ExternalModule::initGeomBuilderHook() +std::vector ExternalModule::createFromJSON(const std::string& jsonfile) { - if (mOptions.root_macro_file.size() > 0) { - LOG(info) << "Initializing the hook for geometry module building"; - auto expandedHookFileName = o2::utils::expandShellVarsInFileName(mOptions.root_macro_file); - if (std::filesystem::exists(expandedHookFileName)) { - // if this file exists we will compile the hook on the fly (the last one is an identifier --> maybe make it dependent on this class) - mGeomHook = o2::conf::GetFromMacro(mOptions.root_macro_file, "get_builder_hook_unchecked()", "function", "o2_passive_extmodule_builder"); - LOG(info) << "Hook initialized from file " << expandedHookFileName; - return true; + std::vector result; + + auto expanded = o2::utils::expandShellVarsInFileName(jsonfile); + std::ifstream fileStream(expanded, std::ios::in); + if (!fileStream.is_open()) { + LOG(error) << "Cannot open external geometry config file '" << expanded << "'"; + return result; + } + + rapidjson::IStreamWrapper isw(fileStream); + rapidjson::Document doc; + doc.ParseStream(isw); + if (doc.HasParseError()) { + LOG(error) << "Error parsing external geometry JSON '" << expanded << "': " + << rapidjson::GetParseError_En(doc.GetParseError()) + << " (offset " << doc.GetErrorOffset() << ")"; + return result; + } + if (!doc.HasMember("externalModules") || !doc["externalModules"].IsArray()) { + LOG(error) << "External geometry JSON '" << expanded << "' must contain an 'externalModules' array"; + return result; + } + + auto getString = [](const rapidjson::Value& v, const char* key) -> std::string { + if (v.HasMember(key) && v[key].IsString()) { + return v[key].GetString(); + } + return std::string(); + }; + + for (const auto& entry : doc["externalModules"].GetArray()) { + if (!entry.IsObject()) { + LOG(error) << "Skipping non-object entry in 'externalModules'"; + continue; + } + const auto name = getString(entry, "name"); + if (name.empty()) { + LOG(error) << "Skipping external module entry without 'name'"; + continue; + } + ExternalModuleOptions options; + options.root_macro_file = getString(entry, "macro"); + options.anchor_volume = getString(entry, "anchor"); + if (options.root_macro_file.empty() || options.anchor_volume.empty()) { + LOG(error) << "External module '" << name << "' requires both 'macro' and 'anchor'; skipping"; + continue; + } + if (entry.HasMember("placement") && entry["placement"].IsObject()) { + options.placement = makePlacementFromJSON(entry["placement"]); + } + auto title = getString(entry, "title"); + if (title.empty()) { + title = name; } + LOG(info) << "Configured external module '" << name << "' from macro '" << options.root_macro_file + << "' anchored to volume '" << options.anchor_volume << "'"; + result.push_back(new ExternalModule(name.c_str(), title.c_str(), options)); } - return false; + return result; } } // namespace o2::passive \ No newline at end of file diff --git a/macro/build_geometry.C b/macro/build_geometry.C index b4924ebd35d5d..5538e5500e373 100644 --- a/macro/build_geometry.C +++ b/macro/build_geometry.C @@ -63,6 +63,7 @@ #endif #include +#include using Return = o2::base::Detector*; @@ -183,16 +184,18 @@ void build_geometry(FairRunSim* run = nullptr) } #endif - if (isActivated("EXT")) { - // EXAMPLE!! how to pick geometry generated from external (CAD) module via `O2_CADtoTGeo.py` - o2::passive::ExternalModuleOptions options; - options.root_macro_file = "PATH_TO_EXTERNAL_GEOM_MODULE/geom.C"; - options.anchor_volume = "barrel"; // hook this into barrel - auto rot = new TGeoCombiTrans(); - rot->RotateX(90); - rot->SetDy(30); // we need to compensate for a shift of barrel with respect to zero - options.placement = rot; - run->AddModule(new o2::passive::ExternalModule("FOO", "BAR", options)); + // external (e.g. CAD-derived) geometry modules are injected from the outside via a JSON + // description file given with `--extGeomFile` (geometry generated via `O2_CADtoTGeo.py`). + // Each module is added when its 'name' is part of the active module list (so it can be + // switched on/off via the detector list, like any other module). + if (auto extGeomFile = confref.getExtGeomFilename(); !extGeomFile.empty()) { + for (auto* extmod : o2::passive::ExternalModule::createFromJSON(extGeomFile)) { + if (isActivated(extmod->GetName())) { + run->AddModule(extmod); + } else { + delete extmod; // not requested in the active module list + } + } } // the absorber @@ -225,6 +228,19 @@ void build_geometry(FairRunSim* run = nullptr) } }; + // sensitive external (CAD-derived) detectors, injected from the same JSON used for passive + // external modules (entries under "externalDetectors"). These derive from o2::base::Detector, + // so they produce hits and participate in the regular hit forwarding/merging machinery. + if (auto extGeomFile = confref.getExtGeomFilename(); !extGeomFile.empty()) { + for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) { + if (isActivated(extdet->GetName())) { + addReadoutDetector(extdet); + } else { + delete extdet; // not requested in the active module list + } + } + } + if (isActivated("TOF")) { // TOF addReadoutDetector(new o2::tof::Detector(isReadout("TOF"))); diff --git a/run/CMakeLists.txt b/run/CMakeLists.txt index 7dae47b4a742a..3302eab2fe724 100644 --- a/run/CMakeLists.txt +++ b/run/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(allsim FairMQ::FairMQ O2::CPVSimulation O2::DetectorsPassive + O2::ExternalDetectors O2::EMCALSimulation O2::FDDSimulation O2::Field diff --git a/run/O2HitMerger.h b/run/O2HitMerger.h index 20280294fbb81..9794c17b62b5f 100644 --- a/run/O2HitMerger.h +++ b/run/O2HitMerger.h @@ -59,6 +59,7 @@ #include #include #include +#include #include "CommonUtils/ShmManager.h" #include @@ -889,6 +890,7 @@ class O2HitMerger : public fair::mq::Device int mPipeToDriver = -1; std::vector> mDetectorInstances; //! + std::vector mExternalDetIDs; //! DetID slots occupied by external (CAD) detectors // output folder configuration std::string mInitialOutputDir; // initial output folder of the process (initialized during construction) @@ -899,6 +901,7 @@ class O2HitMerger : public fair::mq::Device // init detector instances void initDetInstances(); + void initExternalDetInstances(); void initHitFiles(std::string prefix); }; @@ -920,6 +923,12 @@ void O2HitMerger::initHitFiles(std::string prefix) // init the detector specific output files initHitTreeAndOutFile(prefix, i); } + + // external (CAD) detectors are not part of the readout-detector list (their module names + // are not DetID names); their slots were determined in initDetInstances() + for (auto detID : mExternalDetIDs) { + initHitTreeAndOutFile(prefix, detID); + } } // init detector instances used to write hit data to a TTree @@ -1047,6 +1056,57 @@ void O2HitMerger::initDetInstances() if (counter != DetID::nDetectors) { LOG(warning) << " O2HitMerger: Some Detectors are potentially missing in this initialization "; } + + // also register external (CAD-derived) sensitive detectors so their hits are persisted + // in parallel (multi-worker) mode + initExternalDetInstances(); +} + +// init detector instances for external (CAD-derived) sensitive detectors. +// These are not part of the hard-coded DetID switch above: they are described in the +// external geometry JSON (the same file used by build_geometry.C on the worker side) and +// tied to an existing (free) DetID. The merger only needs an instance able to interpret the +// generic o2::ext::Hit wire format and write the "Hit" branch; no geometry is built here. +void O2HitMerger::initExternalDetInstances() +{ + using o2::detectors::DetID; + + auto& simConfig = o2::conf::SimConfig::Instance(); + const auto extGeomFile = simConfig.getExtGeomFilename(); + if (extGeomFile.empty()) { + return; + } + + // mirror the worker-side activation: an external detector participates when its module + // name is part of the active module list + auto const& activeModules = simConfig.getActiveModules(); + auto isActivated = [&activeModules](std::string const& s) -> bool { + return std::find(activeModules.begin(), activeModules.end(), s) != activeModules.end(); + }; + + for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) { + const std::string name = extdet->GetName(); + if (!isActivated(name)) { + delete extdet; // not requested in the active module list + continue; + } + const int detID = extdet->GetDetId(); + if (detID < DetID::First || detID > DetID::Last) { + LOG(error) << "O2HitMerger: external detector " << name << " has invalid DetID " << detID << "; skipping"; + delete extdet; + continue; + } + if (mDetectorInstances[detID]) { + LOG(error) << "O2HitMerger: DetID " << DetID::getName(detID) << " requested by external detector " << name + << " is already occupied; its hits will not be persisted. Assign a free DetID."; + delete extdet; + continue; + } + mDetectorInstances[detID].reset(extdet); + mExternalDetIDs.emplace_back(detID); + LOG(info) << "O2HitMerger: registered external detector " << name << " on DetID " << DetID::getName(detID) + << " (branch " << name << "Hit)"; + } } } // namespace devices diff --git a/run/SimExamples/External_Sensitive_Detectors/README.md b/run/SimExamples/External_Sensitive_Detectors/README.md new file mode 100644 index 0000000000000..d36a6efa5e297 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/README.md @@ -0,0 +1,54 @@ +# External sensitive detectors + +A minimal example showing how to add **sensitive** detectors to `o2-sim` without compiling +anything into O2. Two artificial detectors are injected purely from data: + +| name | geometry (runtime macro) | DetID slot | sensitive action | +|---------|-------------------------------|------------|---------------------------------| +| `ACYL` | `geometry_innerCylinder.macro`| `ITS` | built-in entrance/exit action | +| `BDISK` | `geometry_outerDisk.macro` | `TST` | custom action `sensitive_action.macro` | + +Both are instances of the single compiled `o2::ext::ExternalDetector` class and produce the +single generic hit type `o2::ext::Hit`. They differ only in their data: geometry, the DetID +slot they occupy, and (optionally) a sensitive-action macro. + +## Run + +```bash +./run.sh +``` + +This transports a few `boxgen` events and prints the per-detector hit counts, e.g. + +``` +External sensitive detector hits: + ACYLHit : ~430 hits over 5 events, mean radius 20.0 cm [o2sim_HitsITS.root] + BDISKHit : ~120 hits over 5 events, mean radius ... cm [o2sim_HitsTST.root] +``` + +## How it works + +* **Geometry** — each `"macro"` is a ROOT macro exporting `get_builder_hook_unchecked()`, the + same symbol `O2_CADtoTGeo.py` emits when converting CAD (STEP) files to TGeo. The macros here + are hand-written so the example is self-contained (no CAD binaries). The volumes whose names + match `"sensitiveVolumes"` are registered as sensitive. + +* **Sensitive action** — `ACYL` has none, so it uses the built-in action that records an + entrance/exit hit per track. `BDISK` points `"sensitiveMacro"` at a macro whose + `sensitiveAction()` returns the per-step callable; it is JIT-compiled at runtime via + `o2::conf::GetFromMacro` (the same mechanism as the generator / stepping hooks — no + recompilation, no ACLIC). The callable has the full `TVirtualMC` singleton and a few helpers + (`currentSensorID()`, `currentTrackID()`, `addHit()`). + +* **Persistence** — each detector is tied to a free **DetID** slot (`ITS`, `TST` here, chosen + because no real detector of that name is active). The DetID is the scarce resource: it fixes + the hit-file name (`o2sim_Hits.root`) and lets the hit merger instantiate a matching + receiver, so hits are written in parallel multi-worker mode just like for any built-in + detector. The branch keeps the detector's own name (`ACYLHit`, `BDISKHit`). + +## Adding more detectors + +Append entries to `externalDetectors.json` (each on a different free DetID slot) and list their +names in `detectorlist.json`. The mechanism is fully data-driven; nothing needs to be rebuilt. + +See also `Detectors/External` and `scripts/geometry/O2_CADtoTGeo.py`. diff --git a/run/SimExamples/External_Sensitive_Detectors/detectorlist.json b/run/SimExamples/External_Sensitive_Detectors/detectorlist.json new file mode 100644 index 0000000000000..2284e67f19748 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/detectorlist.json @@ -0,0 +1,6 @@ +{ + "EXTEXAMPLE": [ + "ACYL", + "BDISK" + ] +} diff --git a/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json b/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json new file mode 100644 index 0000000000000..abccc209c4819 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json @@ -0,0 +1,23 @@ +{ + "externalDetectors": [ + { + "name": "ACYL", + "title": "artificial barrel cylinder - built-in entrance/exit action", + "macro": "geometry_innerCylinder.macro", + "anchor": "barrel", + "detID": "ITS", + "sensitiveVolumes": ["ACYL_SENS"], + "placement": { "translation": [0, 0, 0] } + }, + { + "name": "BDISK", + "title": "artificial endcap disk - custom JITed sensitive action", + "macro": "geometry_outerDisk.macro", + "anchor": "barrel", + "detID": "TST", + "sensitiveVolumes": ["BDISK_SENS"], + "sensitiveMacro": "sensitive_action.macro", + "placement": { "translation": [0, 0, 0] } + } + ] +} diff --git a/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro b/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro new file mode 100644 index 0000000000000..78bec83f42213 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro @@ -0,0 +1,35 @@ +// Geometry builder for the "ACYL" artificial detector: a thin silicon cylindrical shell. +// +// This is a hand-written stand-in for the macros that O2_CADtoTGeo.py produces from CAD +// files. It exports the same builder-hook symbol (get_builder_hook_unchecked) that +// o2::base::buildCADVolumeFromMacro expects, so it is injected exactly like a CAD module +// through the external-geometry JSON. Media are placeholders here; remapCADMedia() +// re-registers them under O2's MaterialManager at construction time. +#include +#include +#include +#include +#include +#include +#include + +TGeoVolume* build(bool /*check*/ = true) +{ + if (!gGeoManager) { + throw std::runtime_error("gGeoManager is null when building ACYL"); + } + auto* mat = new TGeoMaterial("SiACYL", 28.0855, 14., 2.33, 9.37, 45.5); + double params[8] = {1., 0., 0., 0., 0., 0., 0., 0.}; // isvol=1, no field, defaults + auto* med = new TGeoMedium("SiACYL", 1, mat, params); + + // 2 mm thick shell, R = 20 cm, half-length 40 cm + auto* tube = new TGeoTube("ACYL_SENS", 20.0, 20.2, 40.0); + auto* sens = new TGeoVolume("ACYL_SENS", tube, med); + + auto* top = new TGeoVolumeAssembly("ACYL"); + top->AddNode(sens, 1, nullptr); + return top; +} + +std::function get_builder_hook_checked() { return []() { return build(true); }; } +std::function get_builder_hook_unchecked() { return []() { return build(false); }; } diff --git a/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro b/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro new file mode 100644 index 0000000000000..13907ebb94a5b --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro @@ -0,0 +1,35 @@ +// Geometry builder for the "BDISK" artificial detector: a thin silicon endcap disk. +// +// Hand-written stand-in for a CAD-exported macro (see geometry_innerCylinder.macro). Exports the +// same builder-hook symbol consumed by o2::base::buildCADVolumeFromMacro. Together with ACYL +// this gives two geometrically distinct artificial detectors (a barrel shell and an endcap +// disk) sharing one generic o2::ext::Hit type but writing into separate DetID slots. +#include +#include +#include +#include +#include +#include +#include +#include + +TGeoVolume* build(bool /*check*/ = true) +{ + if (!gGeoManager) { + throw std::runtime_error("gGeoManager is null when building BDISK"); + } + auto* mat = new TGeoMaterial("SiBDISK", 28.0855, 14., 2.33, 9.37, 45.5); + double params[8] = {1., 0., 0., 0., 0., 0., 0., 0.}; // isvol=1, no field, defaults + auto* med = new TGeoMedium("SiBDISK", 1, mat, params); + + // a 2 mm thick disk: inner r = 5 cm, outer r = 40 cm, placed at z = +70 cm (endcap) + auto* disk = new TGeoTube("BDISK_SENS", 5.0, 40.0, 0.1); + auto* sens = new TGeoVolume("BDISK_SENS", disk, med); + + auto* top = new TGeoVolumeAssembly("BDISK"); + top->AddNode(sens, 1, new TGeoTranslation(0., 0., 70.0)); + return top; +} + +std::function get_builder_hook_checked() { return []() { return build(true); }; } +std::function get_builder_hook_unchecked() { return []() { return build(false); }; } diff --git a/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro b/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro new file mode 100644 index 0000000000000..caebe1496b851 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro @@ -0,0 +1,50 @@ +// Inspect the hits produced by the two artificial external detectors of this example. +// +// In parallel mode (o2-sim) the hit merger writes one file per detector, named after the +// DetID slot the detector borrows: ACYL -> o2sim_HitsITS.root, BDISK -> o2sim_HitsTST.root. +// The branch inside keeps the detector's own name: "ACYLHit" / "BDISKHit". +// +// Run: root -l -b -q inspect_hits.macro +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/Hit.h" +#include +#include +#include +#include +#endif + +void inspectOne(const char* fname, const char* branch) +{ + auto* f = TFile::Open(fname); + if (!f || f->IsZombie()) { + printf(" %-22s : file not found (%s)\n", branch, fname); + return; + } + auto* t = (TTree*)f->Get("o2sim"); + if (!t || !t->GetBranch(branch)) { + printf(" %-22s : no '%s' branch in %s\n", branch, branch, fname); + delete f; + return; + } + std::vector* hits = nullptr; + t->SetBranchAddress(branch, &hits); + long total = 0; + double rsum = 0.; + for (Long64_t e = 0; e < t->GetEntries(); ++e) { + t->GetEntry(e); + for (auto& h : *hits) { + ++total; + rsum += std::hypot(h.GetX(), h.GetY()); + } + } + printf(" %-22s : %ld hits over %lld events, mean radius %.1f cm [%s]\n", + branch, total, t->GetEntries(), total ? rsum / total : 0., fname); + delete f; +} + +void inspect_hits() +{ + printf("External sensitive detector hits:\n"); + inspectOne("o2sim_HitsITS.root", "ACYLHit"); // barrel cylinder, built-in action + inspectOne("o2sim_HitsTST.root", "BDISKHit"); // endcap disk, custom JITed action +} diff --git a/run/SimExamples/External_Sensitive_Detectors/run.sh b/run/SimExamples/External_Sensitive_Detectors/run.sh new file mode 100755 index 0000000000000..2cd94aa49c8c9 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/run.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# +# Minimal example injecting two artificial *sensitive* external detectors into o2-sim. +# +# Neither detector is compiled into O2: both are described purely by data. The geometry of +# each is built at runtime from a ROOT macro (here hand-written stand-ins for what +# O2_CADtoTGeo.py produces from CAD files), and each is tied to a free DetID slot so its hits +# are written like those of any built-in detector -- including in parallel (multi-worker) mode, +# where the hit merger instantiates a matching receiver per detector. +# +# ACYL : thin silicon barrel cylinder, DetID slot ITS, built-in entrance/exit action +# BDISK : thin silicon endcap disk, DetID slot TST, custom JITed sensitive action +# +# Both share the single generic hit type o2::ext::Hit. Multiplicity is data-driven: add more +# entries (on more free DetID slots) to externalDetectors.json and detectorlist.json. + +set -x + +# run from this example's directory so the relative macro/JSON paths resolve +cd "$(dirname "$0")" + +NWORKERS=2 +EVENTS=5 + +o2-sim -j ${NWORKERS} -n ${EVENTS} -g boxgen \ + --detectorList EXTEXAMPLE:detectorlist.json \ + --extGeomFile externalDetectors.json \ + --configKeyValues 'BoxGun.number=50' \ + > sim.log 2>&1 + +# count and locate the produced hits (one file per detector / DetID slot) +root -l -b -q inspect_hits.macro diff --git a/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro b/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro new file mode 100644 index 0000000000000..29734a8aea9f9 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro @@ -0,0 +1,44 @@ +// Custom sensitive action for the "BDISK" artificial detector. +// +// The action is JIT-compiled at runtime via o2::conf::GetFromMacro (the same mechanism used +// for generator and stepping hooks) and fully replaces ExternalDetector's built-in +// entrance/exit action. It runs for every MC step inside one of the detector's sensitive +// volumes and has the full TVirtualMC singleton available, plus a few convenience helpers on +// the detector (currentSensorID(), currentTrackID(), addHit()). +// +// IMPORTANT: the return type must be spelled exactly as the typedef name passed to +// GetFromMacro ("o2::ext::ExternalDetector::SensitiveFcn"); the loader compares the function's +// return-type name textually, so std::function<...> would not match. +// +// This example records a single point-like hit whenever a charged particle enters a sensitive +// volume. +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/ExternalDetector.h" +#include "ExternalDetectors/Hit.h" +#include +#include +#endif + +o2::ext::ExternalDetector::SensitiveFcn sensitiveAction() +{ + return [](o2::ext::ExternalDetector* det) -> bool { + auto vmc = TVirtualMC::GetMC(); + if (vmc->TrackCharge() == 0) { + return false; // ignore neutral particles + } + if (!vmc->IsTrackEntering()) { + return false; // record only the entrance point + } + const int sensor = det->currentSensorID(); + if (sensor < 0) { + return false; // not one of our sensitive volumes + } + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + det->addHit(det->currentTrackID(), sensor, pos.Vect(), pos.Vect(), mom.Vect(), + mom.E(), pos.T(), 0. /*eLoss*/, o2::ext::Hit::kTrackEntering, o2::ext::Hit::kTrackEntering, + vmc->TrackPid(), vmc->TrackLength()); + return true; + }; +} diff --git a/scripts/geometry/README.md b/scripts/geometry/README.md index 4fb2d1ec610d4..8c02cadef51a4 100644 --- a/scripts/geometry/README.md +++ b/scripts/geometry/README.md @@ -1,27 +1,229 @@ -This is the tool O2_CADtoTGeo.py which translates from geometries in STEP format (CAD export) to -TGeo. +# CAD-to-TGeo geometry import -To use the tool, setup a conda environment with python-occ core installed. -The following should work on standard linux x86: +`O2_CADtoTGeo.py` converts CAD geometries exported as STEP files into ROOT TGeo geometry. +The converter emits a small ROOT macro plus compact binary facet payloads. The generated +macro can be loaded directly in ROOT, or injected into `o2-sim` as an external passive +module or as a sensitive external detector. +The current integration path is data-driven: the CAD geometry is converted once, then a +JSON file tells `o2-sim` which generated macro to load, where to anchor it in the existing +geometry, and, for sensitive detectors, which volumes or media should produce hits. + +## Software setup + +The preferred setup is the normal ALICE software environment. The `pythonOCC` package pulls +in OpenCascade and the Python bindings needed by the converter: + +```bash +alienv enter O2sim/latest,pythonOCC/latest +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py --help ``` -# -) download miniconda into $HOME/miniconda (if not already done) -if [ ! -d $HOME/miniconda ]; then - curl -fsSL https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -o miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda -fi -# -) source conda into the environment (in every shell you want to use this) -source $HOME/miniconda/etc/profile.d/conda.sh +If you are working from a local O2 checkout, `PATH_TO_ALICEO2_SOURCES` is the directory that +contains this `scripts/geometry` folder. -# -) Create an OCC environment (for OpenCacade) +For standalone studies outside the ALICE software stack, a conda environment with +`pythonocc-core` can also be used: + +```bash conda create -n occ python=3.10 -y conda activate occ - -# 3) Install OpenCascade Python bindings conda install -c conda-forge pythonocc-core -y -# 4) Run the tool, e.g. -conda activate occ python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py --help -``` \ No newline at end of file +``` + +## Convert a STEP file + +For a quick, robust geometry preview, convert leaves to bounding boxes: + +```bash +mkdir -p cad_out/mydet +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --step-unit auto +``` + +For a more detailed faceted representation, enable meshing: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --mesh-prec 0.05 \ + --step-unit auto +``` + +The output folder contains: + +- `geom.C`, a ROOT macro exporting `get_builder_hook_unchecked()` +- `facets_*.bin`, one compact triangle payload per leaf logical volume + +The generated macro is the file referenced from the external-geometry JSON examples below. +The macro and its facet binaries should stay together, because the macro loads the facet files +relative to its own location. + +## Optional material mapping + +The converter can use a BOM CSV to assign materials and, when part masses and CAD volumes are +available, derive effective densities. A Geant4 NIST material JSON dump enables richer material +and tracking-length information: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --materials-csv detector_bom.csv \ + --bom-mass-unit kg \ + --g4-nist-json g4_nist_materials.json +``` + +The expected BOM rows are mechanical part rows of the form: + +```text +CAD,Mechanical/Part,,,,,,... +``` + +Material names are matched to the NIST database when possible. If a match is ambiguous or not +available, the generated macro falls back to a simple material and leaves comments in `geom.C` +for follow-up. + +## Inject passive CAD geometry into `o2-sim` + +Passive external modules are configured under an `externalModules` array. Each entry needs a +module name, the generated macro, and an anchor volume already present in the O2 geometry. An +optional placement can translate and rotate the imported geometry inside the anchor volume: + +```json +{ + "externalModules": [ + { + "name": "IRIS", + "title": "IRIS support from CAD", + "macro": "cad_out/iris/geom.C", + "anchor": "barrel", + "placement": { + "translation": [0.0, 0.0, 0.0], + "rotation_deg": [0.0, 0.0, 15.0] + } + } + ] +} +``` + +The module is only added when its `name` is present in the active detector/module list. One +way to make such a list is a detector-list JSON file: + +```json +{ + "EXTCAD": ["IRIS"] +} +``` + +Run `o2-sim` with both files: + +```bash +o2-sim -n 1 -g boxgen \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json +``` + +Multiple passive modules can be listed in the same file. The CAD macro loader JIT-compiles each +macro into a unique namespace, so several `O2_CADtoTGeo.py` outputs can coexist even though they +export the same builder-hook symbol names. + +## Inject sensitive external CAD detectors + +Sensitive external detectors are configured under an `externalDetectors` array. They use the +same generated geometry macro, but additionally select sensitive volumes or media and bind the +detector to a free O2 `DetID` slot. All such detectors are instances of +`o2::ext::ExternalDetector` and write the generic `o2::ext::Hit` format. + +```json +{ + "externalDetectors": [ + { + "name": "ECYL", + "title": "External silicon cylinder", + "macro": "cad_out/ecyl/geom.C", + "anchor": "barrel", + "detID": "ITS", + "sensitiveVolumes": ["ECYL_SENSOR"], + "placement": { "translation": [0.0, 0.0, 0.0] } + }, + { + "name": "EDISK", + "title": "External endcap disk with custom action", + "macro": "cad_out/edisk/geom.C", + "anchor": "barrel", + "detID": "TST", + "sensitiveMedia": ["Silicon"], + "sensitiveMacro": "sensitive_action.macro", + "sensitiveFunction": "sensitiveAction()" + } + ] +} +``` + +Selection rules: + +- `sensitiveVolumes` matches substrings of TGeo volume names. +- `sensitiveMedia` matches substrings of TGeo medium names. +- At least one of the two arrays must be non-empty. + +The `detID` determines the hit-file identity, for example `o2sim_HitsITS.root` or +`o2sim_HitsTST.root`. Choose a DetID that is not already occupied by an active built-in detector. +The branch name keeps the external detector name, for example `ECYLHit` or `EDISKHit`. + +If no `sensitiveMacro` is provided, the built-in action records a charged-track entrance/exit hit. +With a custom action, the macro is JIT-compiled at runtime and must return an +`o2::ext::ExternalDetector::SensitiveFcn`. The action can query `TVirtualMC::GetMC()` and use +helpers such as `currentSensorID()`, `currentTrackID()`, and `addHit()`. + +Run the detectors just like passive modules, with their names in the detector-list JSON: + +```json +{ + "EXTCAD": ["ECYL", "EDISK"] +} +``` + +```bash +o2-sim -j 2 -n 5 -g boxgen \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json \ + --configKeyValues 'BoxGun.number=50' +``` + +In parallel mode, the hit merger reads the same `--extGeomFile`, registers the configured active +external detectors, and persists their generic external hits like built-in detector hits. + +## Complete runnable example + +A self-contained example is available in: + +```bash +run/SimExamples/External_Sensitive_Detectors +``` + +It defines two artificial sensitive detectors entirely from data: + +- `ACYL`, a silicon barrel cylinder using the built-in entrance/exit action +- `BDISK`, a silicon endcap disk using a custom JITed sensitive action + +The example uses hand-written geometry macros that mimic `O2_CADtoTGeo.py` output, so it does not +require CAD input files. Run it from its directory: + +```bash +cd run/SimExamples/External_Sensitive_Detectors +./run.sh +``` + +The script transports a few box-generator events and prints the hit counts for the produced +external-detector branches. \ No newline at end of file diff --git a/scripts/geometry/TODO.md b/scripts/geometry/TODO.md new file mode 100644 index 0000000000000..8add645e06357 --- /dev/null +++ b/scripts/geometry/TODO.md @@ -0,0 +1,4 @@ +- implement a BVHSurface solid as an exact representation of a CAD solid +- complete geometry configurable as JSON --> even the world volume ? + + \ No newline at end of file From 2eaaf76c0395b7c78c005c00ae2eb729467ef055 Mon Sep 17 00:00:00 2001 From: swenzel Date: Thu, 23 Jul 2026 11:02:08 +0200 Subject: [PATCH 07/22] Add CAD clip-box and name-based part selection to O2_CADtoTGeo Support culling/cutting the CAD model against an axis-aligned --clip-box (with --clip-deduplicate {none,intact}) and whitelisting/blacklisting parts via --include-name/--exclude-name regexes before TGeo conversion. Aided by GPT-5.5 (Copilot CERN pilot licence) --- scripts/geometry/O2_CADtoTGeo.py | 421 ++++++++++++++++++++++++++++--- scripts/geometry/README.md | 54 ++++ 2 files changed, 443 insertions(+), 32 deletions(-) diff --git a/scripts/geometry/O2_CADtoTGeo.py b/scripts/geometry/O2_CADtoTGeo.py index 3de2fd75973df..836488444c50e 100644 --- a/scripts/geometry/O2_CADtoTGeo.py +++ b/scripts/geometry/O2_CADtoTGeo.py @@ -50,11 +50,14 @@ import struct from dataclasses import dataclass from pathlib import Path as _Path -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Pattern, Tuple from OCC.Core.Bnd import Bnd_Box +from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common from OCC.Core.BRepBndLib import brepbndlib +from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh +from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.BRep import BRep_Tool from OCC.Core.TopLoc import TopLoc_Location from OCC.Core.TopAbs import TopAbs_REVERSED @@ -67,7 +70,7 @@ from OCC.Core.TDF import TDF_Label, TDF_LabelSequence, TDF_Tool from OCC.Core.TCollection import TCollection_AsciiString -from OCC.Core.gp import gp_Trsf +from OCC.Core.gp import gp_Pnt, gp_Trsf # volume properties for density calcs (may not be present in older pythonOCC builds) try: @@ -161,6 +164,61 @@ def detect_step_length_unit(step_path: str) -> str: return "mm" +@dataclass(frozen=True) +class ClipBox: + xmin: float + ymin: float + zmin: float + xmax: float + ymax: float + zmax: float + + @classmethod + def from_values(cls, values: List[float]) -> "ClipBox": + if len(values) != 6: + raise ValueError("--clip-box expects 6 values: xmin ymin zmin xmax ymax zmax") + xmin, ymin, zmin, xmax, ymax, zmax = (float(v) for v in values) + if not (xmin < xmax and ymin < ymax and zmin < zmax): + raise ValueError("--clip-box requires xmin Tuple[float, float, float, float, float, float]: + return (self.xmin, self.ymin, self.zmin, self.xmax, self.ymax, self.zmax) + + +@dataclass(frozen=True) +class NameFilter: + include: Tuple[Pattern[str], ...] + exclude: Tuple[Pattern[str], ...] + + @classmethod + def from_patterns(cls, include: List[str], exclude: List[str], case_sensitive: bool = False) -> "NameFilter": + flags = 0 if case_sensitive else re.IGNORECASE + return cls( + tuple(re.compile(pattern, flags) for pattern in include), + tuple(re.compile(pattern, flags) for pattern in exclude), + ) + + @property + def active(self) -> bool: + return bool(self.include or self.exclude) + + @property + def has_include(self) -> bool: + return bool(self.include) + + def _text(self, lid: str, name: str) -> str: + return f"{name} {lid}".strip() + + def matches_include(self, lid: str, name: str) -> bool: + text = self._text(lid, name) + return any(pattern.search(text) for pattern in self.include) + + def matches_exclude(self, lid: str, name: str) -> bool: + text = self._text(lid, name) + return any(pattern.search(text) for pattern in self.exclude) + + # ------------------------------- # Triangulation helpers # ------------------------------- @@ -921,6 +979,113 @@ def emit_assembly_cpp(lid: str, asm_display_name: str) -> str: return f' TGeoVolumeAssembly *asm_{safe} = new TGeoVolumeAssembly("{name}");' +# ------------------------------- +# CAD clipping helpers +# ------------------------------- + +def make_clip_box_shape(clip_box: ClipBox): + return BRepPrimAPI_MakeBox( + gp_Pnt(clip_box.xmin, clip_box.ymin, clip_box.zmin), + gp_Pnt(clip_box.xmax, clip_box.ymax, clip_box.zmax), + ).Shape() + + +def _compose_trsf(parent_to_world: gp_Trsf, local_to_parent: gp_Trsf) -> gp_Trsf: + return parent_to_world.Multiplied(local_to_parent) + + +def _shape_is_empty(shape) -> bool: + if shape is None: + return True + try: + if shape.IsNull(): + return True + except Exception: + pass + try: + for _ in TopologyExplorer(shape).faces(): + return False + return True + except Exception: + return False + + +def _transformed_bbox(shape, trsf: gp_Trsf) -> Optional[Tuple[float, float, float, float, float, float]]: + box = Bnd_Box() + brepbndlib.Add(shape, box) + try: + xmin, ymin, zmin, xmax, ymax, zmax = box.Get() + except Exception: + return None + + points = [] + for x in (xmin, xmax): + for y in (ymin, ymax): + for z in (zmin, zmax): + p = gp_Pnt(x, y, z) + p.Transform(trsf) + points.append((p.X(), p.Y(), p.Z())) + + return ( + min(p[0] for p in points), + min(p[1] for p in points), + min(p[2] for p in points), + max(p[0] for p in points), + max(p[1] for p in points), + max(p[2] for p in points), + ) + + +def _bbox_outside_clip_box(bbox: Tuple[float, float, float, float, float, float], clip_box: ClipBox) -> bool: + xmin, ymin, zmin, xmax, ymax, zmax = bbox + return ( + xmax < clip_box.xmin or xmin > clip_box.xmax or + ymax < clip_box.ymin or ymin > clip_box.ymax or + zmax < clip_box.zmin or zmin > clip_box.zmax + ) + + +def _bbox_inside_clip_box(bbox: Tuple[float, float, float, float, float, float], clip_box: ClipBox) -> bool: + xmin, ymin, zmin, xmax, ymax, zmax = bbox + return ( + xmin >= clip_box.xmin and xmax <= clip_box.xmax and + ymin >= clip_box.ymin and ymax <= clip_box.ymax and + zmin >= clip_box.zmin and zmax <= clip_box.zmax + ) + + +def _classify_shape_against_clip_box(shape, clip_box: ClipBox, local_to_world: gp_Trsf) -> Optional[str]: + world_bbox = _transformed_bbox(shape, local_to_world) + if world_bbox is None: + return None + if _bbox_outside_clip_box(world_bbox, clip_box): + return "outside" + if _bbox_inside_clip_box(world_bbox, clip_box): + return "inside" + return "overlap" + + +def clip_shape_to_box(shape, clip_box: ClipBox, clip_box_shape, local_to_world: gp_Trsf, lid: str): + clip_state = _classify_shape_against_clip_box(shape, clip_box, local_to_world) + if clip_state is None: + return None + if clip_state == "outside": + return None + if clip_state == "inside": + return shape + + local_clip = BRepBuilderAPI_Transform(clip_box_shape, local_to_world.Inverted(), True).Shape() + common = BRepAlgoAPI_Common(shape, local_clip) + common.Build() + if not common.IsDone(): + raise RuntimeError(f"Failed to clip CAD shape {lid} against --clip-box") + + clipped = common.Shape() + if _shape_is_empty(clipped): + return None + return clipped + + # ------------------------------- # Definition graph extraction # ------------------------------- @@ -939,61 +1104,188 @@ def cpp_var_for_def(lid: str) -> str: return f"asm_{safe}" if lid in assemblies else f"vol_{safe}" -def expand_definition(def_label: TDF_Label, shape_tool, meshparam=None, scale_to_cm: float = 1.0): - def_lid = label_id(def_label) - if def_lid in visited_defs: - return - visited_defs.add(def_lid) +def expand_definition( + def_label: TDF_Label, + shape_tool, + meshparam=None, + scale_to_cm: float = 1.0, + clip_box: Optional[ClipBox] = None, + clip_box_shape=None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, + include_subtree: bool = False, + world_trsf: Optional[gp_Trsf] = None, + occ_path: str = "r1", +) -> Optional[str]: + clip_enabled = clip_box_shape is not None + if world_trsf is None: + world_trsf = gp_Trsf() + def_lid = label_id(def_label) nm = label_name(def_label) - if nm and def_lid not in def_names: - def_names[def_lid] = nm - elif def_lid not in def_names: - def_names[def_lid] = "" + + subtree_included = include_subtree + if name_filter is not None: + if name_filter.matches_exclude(def_lid, nm): + return None + if name_filter.has_include and name_filter.matches_include(def_lid, nm): + subtree_included = True + + if clip_enabled and clip_box is not None: + try: + shape_for_clip = shape_tool.GetShape(def_label) + except Exception: + shape_for_clip = None + if shape_for_clip is not None: + clip_state = _classify_shape_against_clip_box(shape_for_clip, clip_box, world_trsf) + if clip_state == "outside": + return None + if clip_state == "inside" and clip_deduplicate == "intact": + return expand_definition( + def_label, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=None, + clip_box_shape=None, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + + def_key = f"{def_lid}@{occ_path}" if clip_enabled else def_lid + if not clip_enabled and def_lid in visited_defs: + return def_lid + if not clip_enabled: + visited_defs.add(def_lid) + + if nm and def_key not in def_names: + def_names[def_key] = nm + elif def_key not in def_names: + def_names[def_key] = "" children = TDF_LabelSequence() shape_tool.GetComponents(def_label, children) has_children = children.Length() > 0 if has_children or shape_tool.IsAssembly(def_label): - assemblies.add(def_lid) + assemblies.add(def_key) + kept_children = 0 for i in range(children.Length()): child = children.Value(i + 1) + child_occ_path = f"{occ_path}_{i + 1}" if shape_tool.IsReference(child): referred = TDF_Label() shape_tool.GetReferredShape(child, referred) - child_def_lid = label_id(referred) loc = shape_tool.GetLocation(child) trsf = loc.Transformation() - placements.append((def_lid, child_def_lid, trsf)) - - expand_definition(referred, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + if clip_enabled: + child_key = expand_definition( + referred, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + world_trsf=_compose_trsf(world_trsf, trsf), + occ_path=child_occ_path, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + else: + child_key = expand_definition( + referred, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) else: - child_def_lid = label_id(child) - placements.append((def_lid, child_def_lid, gp_Trsf())) - expand_definition(child, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) - return + trsf = gp_Trsf() + if clip_enabled: + child_key = expand_definition( + child, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + world_trsf=world_trsf, + occ_path=child_occ_path, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + else: + child_key = expand_definition( + child, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + kept_children += 1 + + if (clip_enabled or (name_filter is not None and name_filter.has_include)) and kept_children == 0: + assemblies.discard(def_key) + return None + return def_key if shape_tool.IsSimpleShape(def_label): - if def_lid not in logical_volumes: + if name_filter is not None and name_filter.has_include and not subtree_included: + return None + + if def_key not in logical_volumes: shape = shape_tool.GetShape(def_label) # store volume (for density estimation) try: - def_volumes_cm3[def_lid] = volume_cm3_of_shape(shape, scale_to_cm=scale_to_cm) + volume_cm3 = volume_cm3_of_shape(shape, scale_to_cm=scale_to_cm) except Exception: - def_volumes_cm3[def_lid] = 0.0 + volume_cm3 = 0.0 + + if clip_enabled: + shape = clip_shape_to_box(shape, clip_box, clip_box_shape, world_trsf, def_lid) + if shape is None: + return None + + def_volumes_cm3[def_key] = volume_cm3 do_meshing = (meshparam is not None) and meshparam.get("do_meshing", None) is True - logical_volumes[def_lid] = triangulate_CAD_solid(shape, meshparam=meshparam, scale_to_cm=scale_to_cm) if do_meshing else triangulate_asbbox(shape, scale_to_cm=scale_to_cm) - return + logical_volumes[def_key] = triangulate_CAD_solid(shape, meshparam=meshparam, scale_to_cm=scale_to_cm) if do_meshing else triangulate_asbbox(shape, scale_to_cm=scale_to_cm) + return def_key - assemblies.add(def_lid) + assemblies.add(def_key) + return def_key -def extract_graph(step_path: str, meshparam=None, scale_to_cm: float = 1.0): +def extract_graph( + step_path: str, + meshparam=None, + scale_to_cm: float = 1.0, + clip_box: Optional[ClipBox] = None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, +): global logical_volumes, def_names, def_volumes_cm3, assemblies, placements, top_defs, visited_defs logical_volumes = {} def_names = {} @@ -1004,20 +1296,42 @@ def extract_graph(step_path: str, meshparam=None, scale_to_cm: float = 1.0): visited_defs = set() doc, shape_tool = load_step_with_xcaf(step_path) + clip_box_shape = make_clip_box_shape(clip_box) if clip_box is not None else None roots = TDF_LabelSequence() shape_tool.GetFreeShapes(roots) for i in range(roots.Length()): root = roots.Value(i + 1) + root_occ_path = f"r{i + 1}" if shape_tool.IsReference(root): ref = TDF_Label() shape_tool.GetReferredShape(root, ref) - top_defs.add(label_id(ref)) - expand_definition(ref, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + top = expand_definition( + ref, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + occ_path=root_occ_path, + ) else: - top_defs.add(label_id(root)) - expand_definition(root, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + top = expand_definition( + root, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + occ_path=root_occ_path, + ) + if top is not None: + top_defs.add(top) return doc, shape_tool @@ -1074,6 +1388,9 @@ def emit_root_macro( out_folder: _Path, meshparam=None, step_unit: str = "auto", + clip_box: Optional[ClipBox] = None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, materials_csv: Optional[str] = None, bom_mass_unit: str = "kg", g4_nist_json: Optional[str] = None, @@ -1087,7 +1404,21 @@ def emit_root_macro( scale_to_cm = step_unit_scale_to_cm(step_unit) print(f"Using overridden STEP length unit: {step_unit} (scale to cm = {scale_to_cm})") - extract_graph(step_path, meshparam=meshparam, scale_to_cm=scale_to_cm) + if clip_box is not None: + print(f"Clipping CAD geometry to STEP-coordinate bounding box: {clip_box.as_tuple()}") + print(f"Clip deduplication mode: {clip_deduplicate}") + + if name_filter is not None and name_filter.active: + print(f"CAD name filters: {len(name_filter.include)} include regex(es), {len(name_filter.exclude)} exclude regex(es)") + + extract_graph( + step_path, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + ) out_folder = out_folder.expanduser().resolve() out_folder.mkdir(parents=True, exist_ok=True) @@ -1278,6 +1609,11 @@ def main(): ap.add_argument("--print-tree", action="store_true", help="Just prints the geometry tree") ap.add_argument("--mesh-prec", default=0.1, help="meshing precision. lower --> slower") ap.add_argument("--step-unit", default="auto", choices=["auto", "mm", "cm", "m", "in", "ft"], help="STEP length unit override (default: auto-detect); TGeo expects cm") + ap.add_argument("--clip-box", nargs=6, type=float, metavar=("XMIN", "YMIN", "ZMIN", "XMAX", "YMAX", "ZMAX"), default=None, help="Clip CAD geometry to this axis-aligned bounding box before meshing (coordinates in STEP file units, before conversion to cm)") + ap.add_argument("--clip-deduplicate", default="intact", choices=["none", "intact"], help="When clipping, reuse original logical definitions for subtrees fully inside the clip box (default: intact); use 'none' for one volume per surviving occurrence") + ap.add_argument("--include-name", action="append", default=[], help="Only convert CAD labels whose XCAF name or label entry matches this regex; may be repeated. Matching an assembly includes its subtree.") + ap.add_argument("--exclude-name", action="append", default=[], help="Skip CAD labels/subtrees whose XCAF name or label entry matches this regex; may be repeated.") + ap.add_argument("--name-filter-case-sensitive", action="store_true", help="Make --include-name/--exclude-name matching case-sensitive (default: case-insensitive)") # NEW: BOM / material support ap.add_argument("--materials-csv", default=None, help="BOM CSV file providing material + mass per part (optional)") @@ -1304,6 +1640,24 @@ def main(): if args.out_path is not None: out_folder = _Path(args.out_path) + clip_box = None + if args.clip_box is not None: + try: + clip_box = ClipBox.from_values(args.clip_box) + except ValueError as exc: + ap.error(str(exc)) + + name_filter = None + if args.include_name or args.exclude_name: + try: + name_filter = NameFilter.from_patterns( + args.include_name, + args.exclude_name, + case_sensitive=args.name_filter_case_sensitive, + ) + except re.error as exc: + ap.error(f"Invalid CAD name filter regex: {exc}") + meshparam = {"do_meshing": args.mesh, "lin_defl": args.mesh_prec, "ang_defl": args.mesh_prec} @@ -1325,6 +1679,9 @@ def main(): out_folder, meshparam=meshparam, step_unit=args.step_unit, + clip_box=clip_box, + clip_deduplicate=args.clip_deduplicate, + name_filter=name_filter, materials_csv=args.materials_csv, bom_mass_unit=args.bom_mass_unit, g4_nist_json=args.g4_nist_json, diff --git a/scripts/geometry/README.md b/scripts/geometry/README.md index 8c02cadef51a4..a59da13c12716 100644 --- a/scripts/geometry/README.md +++ b/scripts/geometry/README.md @@ -67,6 +67,60 @@ The generated macro is the file referenced from the external-geometry JSON examp The macro and its facet binaries should stay together, because the macro loads the facet files relative to its own location. +## Restrict the converted region with a clip box + +Large CAD assemblies often contain far more than the region of interest. The `--clip-box` +option restricts the conversion to an axis-aligned bounding box, so only the geometry inside +(or overlapping) that box is written out: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --clip-box XMIN YMIN ZMIN XMAX YMAX ZMAX +``` + +Notes on the coordinates: + +- The six values are `xmin ymin zmin xmax ymax zmax` and must satisfy `xmin < xmax`, + `ymin < ymax`, and `zmin < zmax`. +- Coordinates are given in the **STEP file units** (before conversion to cm), and are applied + in the global/world coordinate system of the assembly. + +Each solid is classified against the box before meshing: + +- Solids fully outside the box are dropped. +- Solids fully inside the box are kept unchanged. +- Solids straddling the box boundary are cut against it (a boolean intersection), so only the + part inside the box is meshed. + +Assemblies that end up with no surviving children are removed from the output tree. + +### Deduplication mode + +The `--clip-deduplicate` option controls how subtrees that fall entirely inside the box are +emitted: + +- `intact` (default): subtrees fully inside the box reuse their original shared logical + definitions, keeping the output compact. +- `none`: every surviving occurrence becomes its own volume, which is useful when you need a + flat, per-instance representation. + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --clip-box -50 -50 -20 50 50 20 \ + --clip-deduplicate none +``` + +Clipping can be combined with the name-based selection options (`--include-name` / +`--exclude-name`) to further narrow down which parts are converted. + ## Optional material mapping The converter can use a BOM CSV to assign materials and, when part masses and CAD volumes are From e02ff4d44742e95a74255ddba6f481ba4dcb2573 Mon Sep 17 00:00:00 2001 From: swenzel Date: Tue, 4 Aug 2026 15:20:14 +0200 Subject: [PATCH 08/22] fix linking issue --- macro/CMakeLists.txt | 2 ++ macro/build_geometry.C | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/macro/CMakeLists.txt b/macro/CMakeLists.txt index d6ffa9d4e8399..aacd68ad8082c 100644 --- a/macro/CMakeLists.txt +++ b/macro/CMakeLists.txt @@ -124,6 +124,7 @@ endif() o2_add_test_root_macro(build_geometry.C PUBLIC_LINK_LIBRARIES O2::SimConfig O2::DetectorsPassive + O2::ExternalDetectors O2::Field O2::MFTSimulation O2::MCHSimulation @@ -183,6 +184,7 @@ if(Geant4_FOUND AND BUILD_SIMULATION) o2_add_test_root_macro(o2sim.C PUBLIC_LINK_LIBRARIES O2::Generators O2::DetectorsPassive + O2::ExternalDetectors O2::Field O2::MFTSimulation O2::MCHSimulation diff --git a/macro/build_geometry.C b/macro/build_geometry.C index 5538e5500e373..c5bec12b703e1 100644 --- a/macro/build_geometry.C +++ b/macro/build_geometry.C @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -63,7 +64,6 @@ #endif #include -#include using Return = o2::base::Detector*; From 1769109be3d026fa4b318dfb7645f10ef262f63e Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 4 Aug 2026 13:08:35 +0200 Subject: [PATCH 09/22] Attach the magnetic field to the VMC engine before media creation Fixing a long standing error message about "No magnetic field found" when initializing tracking media. Fixed by reordering of initializations. --- Steer/src/O2MCApplication.cxx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index 1e3f925042d01..b241768af6611 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -35,6 +35,9 @@ #include #include #include "SimConfig/GlobalProcessCutSimParam.h" +#include +#include // full type: FairField derives from TVirtualMagField +#include #include "DetectorsBase/GeometryManagerParam.h" #include #include @@ -124,6 +127,23 @@ void O2MCApplicationBase::PreTrack() void O2MCApplicationBase::ConstructGeometry() { + // The transport engine constructs the geometry from inside its own + // constructor, long before FairMCApplication::InitMC() attaches the magnetic + // field to it. The media built below read the field through + // Detector::initFieldTrackingParams(), so without this they all silently fall + // back to hardcoded defaults. The run has known the field since + // build_geometry.C, which runs before Init() -- hand it over now. + if (auto* vmc = TVirtualMC::GetMC(); vmc != nullptr && vmc->GetMagField() == nullptr) { + auto* run = FairRunSim::Instance(); + if (run != nullptr && run->GetField() != nullptr) { + vmc->SetMagField(run->GetField()); + LOG(info) << "Magnetic field attached to the engine before media creation"; + } else { + LOG(warn) << "No magnetic field available at geometry construction; media " + "will be initialised with default tracking parameters"; + } + } + // fill the mapping mModIdToName.clear(); o2::detectors::DetID::mask_t dmask{}; From 905c309bc88817ff87bb0f6e267061bf3ca9699c Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Tue, 4 Aug 2026 14:36:27 +0200 Subject: [PATCH 10/22] Make per-track random seeding work for Geant4 SimCutParams.trackSeed was so far a no-op with Geant4 because seeding only occurred in the Geant3 stack-pop path. Add the missing seeding hook in O2MCApplicationBase::PreTrack(), which Geant4 invokes for both primary and secondary tracks on their first step. Geant3 continues to seed at stack-pop time. This preserves existing behaviour and avoids moving seeding to a later hook, which measurably reduces reproducibility. The PreTrack seed is derived from the engine track state rather than Stack::GetCurrentTrack(), which is only reliable for Geant4 primaries. Also add diagnostics to detect missing seed propagation by counting successful seed applications and warning at the end of the event if track seeding was requested but never performed. Verified on both Geant3 and Geant4: per-track seeding now works as intended and existing Geant3 behaviour remains unchanged. --- .../include/DetectorsBase/VMCSeederService.h | 6 +- Detectors/Base/src/VMCSeederService.cxx | 1 + Steer/include/Steer/O2MCApplicationBase.h | 5 ++ Steer/src/O2MCApplication.cxx | 84 ++++++++++++++++++- 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/Detectors/Base/include/DetectorsBase/VMCSeederService.h b/Detectors/Base/include/DetectorsBase/VMCSeederService.h index 1669c73b39620..1d35a78a1f0c6 100644 --- a/Detectors/Base/include/DetectorsBase/VMCSeederService.h +++ b/Detectors/Base/include/DetectorsBase/VMCSeederService.h @@ -35,13 +35,17 @@ class VMCSeederService void setSeed() const; // will propagate seed to the VMC engines + /// how often a seed was propagated; lets callers detect a silent no-op + unsigned long long getSeedCount() const { return mSeedCount; } + typedef std::function SeederFcn; private: VMCSeederService(); void initSeederFunction(TVirtualMC const*); - SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + mutable unsigned long long mSeedCount{0}; // number of setSeed() calls }; } // namespace base diff --git a/Detectors/Base/src/VMCSeederService.cxx b/Detectors/Base/src/VMCSeederService.cxx index 5bf4e1ed5641b..8fc36d9074fab 100644 --- a/Detectors/Base/src/VMCSeederService.cxx +++ b/Detectors/Base/src/VMCSeederService.cxx @@ -50,4 +50,5 @@ void VMCSeederService::setSeed() const // This is ok since in any case gRandom->SetSeed(seed); gRandom->GetSeed() != seed; gRandom->Rndm(); mSeederFcn(); + ++mSeedCount; } diff --git a/Steer/include/Steer/O2MCApplicationBase.h b/Steer/include/Steer/O2MCApplicationBase.h index d61199baba0ae..bd730c0f2fcb2 100644 --- a/Steer/include/Steer/O2MCApplicationBase.h +++ b/Steer/include/Steer/O2MCApplicationBase.h @@ -68,6 +68,11 @@ class O2MCApplicationBase : public FairMCApplication // keeping track of volumeIds and volume names double mLongestTrackTime = 0; + bool mTrackSeedWarned{false}; // whether we already complained that seeding never fired + + /// whether this engine needs per-track seeding in PreTrack (Geant3 seeds at + /// stack-pop time instead, see O2MCApplicationBase::seedsInPreTrack) + bool seedsInPreTrack() const; /// some common parts of finishEvent void finishEventCommon(); TrackRefFcn mTrackRefFcn; // a function hook that gets (optionally) called during Stepping diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index b241768af6611..3a39b7d15be8b 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -46,6 +46,10 @@ #include #include #include "SimConfig/G4Params.h" +#include "DetectorsBase/VMCSeederService.h" // per-track seeding of the engine +#include +#include +#include namespace o2 { @@ -119,9 +123,76 @@ void O2MCApplicationBase::Stepping() FairMCApplication::Stepping(); } +namespace +{ +// Hash of a track's initial state (vertex, global time, momentum, PDG). Used as +// the random seed for that track, so that a track's random stream depends only +// on the track itself and not on how many randoms earlier tracks happened to +// consume. +// +// The values are read from the transport engine, not from +// o2::data::Stack::GetCurrentTrack(): under Geant4 the stack's "current track" +// is only meaningful for primaries -- Stack::SetCurrentTrack() falls back to +// mCurrentParticle0 (the last particle *pushed*) for anything beyond the +// primary array, so every secondary would hash the wrong particle. Both engines +// have the track's initial state loaded by the time PreTrack is called (Geant4 +// sets the step to kVertex first; Geant3 calls GLTRAC before GUTRAK). +ULong_t hashCurrentTrack(TVirtualMC* vmc) +{ + auto asLong = [](double x) { + ULong_t l; + std::memcpy(&l, &x, sizeof(l)); + return l; + }; + + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + + ULong_t hash = asLong(pos.X()); + hash ^= asLong(pos.Y()); + hash ^= asLong(pos.Z()); + hash ^= asLong(pos.T()); + hash ^= asLong(mom.Px()); + hash ^= asLong(mom.Py()); + hash ^= asLong(mom.Pz()); + hash += (ULong_t)vmc->TrackPid(); + return hash; +} +} // namespace + +bool O2MCApplicationBase::seedsInPreTrack() const +{ + // Geant3 seeds at stack-pop time, in o2::data::Stack::PopNextTrack(). That is + // strictly earlier than its PreTrack hook (gutrak) and measurably stronger: + // with the TOF module removed from an otherwise identical setup, pop-time + // seeding keeps all 603 ITS hits bit-identical, PreTrack seeding only 68 %. + // Do not seed Geant3 here as well -- it is already covered, and reseeding a + // second time mid-track would undo the first. + static const bool inPreTrack = [this]() { + const char* name = (fMC != nullptr) ? fMC->GetName() : ""; + return strncmp(name, "TGeant3", 7) != 0; + }(); + return inPreTrack; +} + void O2MCApplicationBase::PreTrack() { - // dispatch first to function in FairRoot + if (mCutParams.trackSeed && seedsInPreTrack()) { + // Per-track seeding for engines that do not go through + // o2::data::Stack::PopNextTrack(). Geant4 is one: it takes primaries via + // PopPrimaryForTracking and keeps secondaries internally, so the stack hook + // never fires and this is the only per-track hook available. It is called + // for primaries and secondaries alike + // (TG4TrackingAction::PreUserTrackingAction), and only on a track's first + // step, so a suspended track is not reseeded mid-flight. + auto hash = hashCurrentTrack(fMC); + // TRandom::SetSeed(0) means "seed from the clock" -- never let that happen. + gRandom->SetSeed(hash == 0 ? 1 : hash); + o2::base::VMCSeederService::instance().setSeed(); + } + + // dispatch now to function in FairRoot FairMCApplication::PreTrack(); } @@ -309,6 +380,17 @@ void O2MCApplicationBase::finishEventCommon() header->setDetId2HitBitLUT(o2::base::Detector::getDetId2HitBitIndex()); static_cast(GetStack())->updateEventStats(); + + // Per-track seeding used to be wired to a stack callback that one of the two + // engines never invoked, and it failed silently. Never again: if it was asked + // for and nothing was seeded, say so. + if (mCutParams.trackSeed && o2::base::VMCSeederService::instance().getSeedCount() == 0 && + !mTrackSeedWarned) { + mTrackSeedWarned = true; + LOG(warn) << "Per-track seeding (SimCutParams.trackSeed) was requested but not a single track " + "was seeded -- neither the stack nor the PreTrack hook fired for this engine. " + "Seeding is NOT active."; + } } void O2MCApplicationBase::FinishEvent() From 02a19a3da80ec6be424bca9bbceebcbf07fda142 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Tue, 4 Aug 2026 15:53:13 +0000 Subject: [PATCH 11/22] Please consider the following formatting changes --- Detectors/Base/include/DetectorsBase/VMCSeederService.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Detectors/Base/include/DetectorsBase/VMCSeederService.h b/Detectors/Base/include/DetectorsBase/VMCSeederService.h index 1d35a78a1f0c6..5f8f70f48840f 100644 --- a/Detectors/Base/include/DetectorsBase/VMCSeederService.h +++ b/Detectors/Base/include/DetectorsBase/VMCSeederService.h @@ -44,7 +44,7 @@ class VMCSeederService VMCSeederService(); void initSeederFunction(TVirtualMC const*); - SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines mutable unsigned long long mSeedCount{0}; // number of setSeed() calls }; From 2ef587611115e3edcab86963d11f250ac3a786f6 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Wed, 5 Aug 2026 14:35:42 +0200 Subject: [PATCH 12/22] Shorten comment Removed redundant comments about Geant3 seeding comparison. --- Steer/src/O2MCApplication.cxx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index 3a39b7d15be8b..fb501b3fd5611 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -164,9 +164,7 @@ ULong_t hashCurrentTrack(TVirtualMC* vmc) bool O2MCApplicationBase::seedsInPreTrack() const { // Geant3 seeds at stack-pop time, in o2::data::Stack::PopNextTrack(). That is - // strictly earlier than its PreTrack hook (gutrak) and measurably stronger: - // with the TOF module removed from an otherwise identical setup, pop-time - // seeding keeps all 603 ITS hits bit-identical, PreTrack seeding only 68 %. + // strictly earlier than its PreTrack hook (gutrak). // Do not seed Geant3 here as well -- it is already covered, and reseeding a // second time mid-track would undo the first. static const bool inPreTrack = [this]() { From cd5048628405cfd1d06f567ce81ed5cb14a24e7f Mon Sep 17 00:00:00 2001 From: Francesco Mazzaschi Date: Mon, 3 Aug 2026 14:46:19 +0200 Subject: [PATCH 13/22] Add double-omega decay information --- .../SimulationDataFormat/O2DatabasePDG.h | 4 +-- Steer/src/O2MCApplication.cxx | 31 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h index 9111f548bb9d4..4f6d1fb89d850 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h +++ b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h @@ -408,13 +408,13 @@ inline void O2DatabasePDG::addALICEParticles(TDatabasePDG* db) ionCode = 1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("OmegaOmega", "OmegaOmega", 3.229, kFALSE, + db->AddParticle("OmegaOmega", "OmegaOmega", 3.334, kFALSE, 2.5e-15, 6, "Special", ionCode); } ionCode = -1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.229, kFALSE, + db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.334, kFALSE, 2.5e-15, 6, "Special", ionCode); } diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index fb501b3fd5611..77b59ed305b6a 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -510,10 +510,10 @@ void addSpecialParticles() TVirtualMC::GetMC()->DefineParticle(-1030010020, "AntiOmegaNeutron", kPTHadron, 2.472, 1.0, 2.190e-22, "Hadron", 0.0, 2, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Omega-Omega - TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.229, 2.0, 2.632e-10, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.334, -2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Anti-Omega-Omega - TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.229, 2.0, 2.632e-10, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.334, 2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Lambda(1405)-Proton TVirtualMC::GetMC()->DefineParticle(1010010021, "Lambda1405Proton", kPTHadron, 2.295, 1.0, 1.316e-23, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); @@ -1283,6 +1283,7 @@ void addSpecialParticles() TVirtualMC::GetMC()->SetDecayMode(-1030010020, abratio8, amode8); // Define the 3-body phase space decay for the Omega-Omega + // Assuming that one of the Omegas decays freely inside the nucleus Int_t mode9[6][3]; Float_t bratio9[6]; @@ -1292,9 +1293,18 @@ void addSpecialParticles() mode9[kz][1] = 0; mode9[kz][2] = 0; } - bratio9[0] = 100.; + bratio9[0] = 68.; mode9[0][0] = 3334; // negative Omega - mode9[0][1] = 3312; // negative Xi + mode9[0][1] = 3122; // Lambda + mode9[0][2] = -321; // negative Kaon + bratio9[1] = 24; + mode9[1][0] = 3334; // negative Omega + mode9[1][1] = 3322; // neutral Xi + mode9[1][2] = -211; // negative pion + bratio9[2] = 8.; + mode9[2][0] = 3334; // negative Omega + mode9[2][1] = 3312; // negative Xi + mode9[2][2] = 111; // neutral pion TVirtualMC::GetMC()->SetDecayMode(1060020020, bratio9, mode9); @@ -1308,9 +1318,18 @@ void addSpecialParticles() amode9[kz][1] = 0; amode9[kz][2] = 0; } - abratio9[0] = 100.; + abratio9[0] = 68.; amode9[0][0] = -3334; // positive Omega - amode9[0][1] = -3312; // positive Xi + amode9[0][1] = -3122; // anti-Lambda + amode9[0][2] = 321; // positive Kaon + abratio9[1] = 24.; + amode9[1][0] = -3334; // positive Omega + amode9[1][1] = -3322; // anti-neutral Xi + amode9[1][2] = 211; // positive pion + abratio9[2] = 8.; + amode9[2][0] = -3334; // positive Omega + amode9[2][1] = -3312; // positive Xi + amode9[2][2] = 111; // neutral pion TVirtualMC::GetMC()->SetDecayMode(-1060020020, abratio9, amode9); From 43f7fd181a85b5ce3872efaef51a0657340e0673 Mon Sep 17 00:00:00 2001 From: Francesco Mazzaschi Date: Tue, 4 Aug 2026 17:22:50 +0200 Subject: [PATCH 14/22] Fix mass value and width --- .../include/SimulationDataFormat/O2DatabasePDG.h | 8 ++++---- Steer/src/O2MCApplication.cxx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h index 4f6d1fb89d850..7a19798674bf6 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h +++ b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h @@ -408,14 +408,14 @@ inline void O2DatabasePDG::addALICEParticles(TDatabasePDG* db) ionCode = 1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("OmegaOmega", "OmegaOmega", 3.334, kFALSE, - 2.5e-15, 6, "Special", ionCode); + db->AddParticle("OmegaOmega", "OmegaOmega", 3.343, kFALSE, + 8.01e-15, 6, "Special", ionCode); } ionCode = -1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.334, kFALSE, - 2.5e-15, 6, "Special", ionCode); + db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.343, kFALSE, + 8.01e-15, 6, "Special", ionCode); } ionCode = 1010010021; diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index 77b59ed305b6a..7f37fd4bccf03 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -510,10 +510,10 @@ void addSpecialParticles() TVirtualMC::GetMC()->DefineParticle(-1030010020, "AntiOmegaNeutron", kPTHadron, 2.472, 1.0, 2.190e-22, "Hadron", 0.0, 2, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Omega-Omega - TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.334, -2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.343, -2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Anti-Omega-Omega - TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.334, 2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.343, 2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Lambda(1405)-Proton TVirtualMC::GetMC()->DefineParticle(1010010021, "Lambda1405Proton", kPTHadron, 2.295, 1.0, 1.316e-23, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); From 1183f52fe824b395b27997bd829cf741c5dd9eac Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Thu, 6 Aug 2026 13:12:12 +0200 Subject: [PATCH 15/22] DPL: use C++26 extension to expand packs where available (#15631) O(1) in both CPU and time rather than O(N) when compiling the pack expansion. Removes the limit to 100 elements in a struct. Requires XCode 26.4 --- .../Core/include/Framework/AnalysisTask.h | 4 +- .../Core/include/Framework/TableBuilder.h | 19 +++ .../include/Framework/StructToTuple.h | 140 +++++++++--------- .../Foundation/test/test_StructToTuple.cxx | 29 ++++ 4 files changed, 122 insertions(+), 70 deletions(-) diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index 1e483d017cc88..aa86525df1721 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -317,7 +317,7 @@ struct AnalysisDataProcessorBuilder { groupingTable.setPointerReconstructor(pointerReconstructor); } #endif - constexpr const int numElements = nested_brace_constructible_size>() / 10; + constexpr const int numElements = homogeneous_apply_refs_size>(); // set filtered tables for partitions with grouping homogeneous_apply_refs_sized([&groupingTable](auto& element) { @@ -546,7 +546,7 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) newOrigin.runtimeInit(newOriginStr.c_str(), std::min(newOriginStr.size(), 4UL)); } - constexpr const int numElements = nested_brace_constructible_size>() / 10; + constexpr const int numElements = homogeneous_apply_refs_size>(); /// make sure options and configurables are set before expression infos are created homogeneous_apply_refs_sized([&options](auto& element) { return analysis_task_parsers::appendOption(options, element); }, *task.get()); diff --git a/Framework/Core/include/Framework/TableBuilder.h b/Framework/Core/include/Framework/TableBuilder.h index 41f6d4ea5dc86..3b2d81bc04b00 100644 --- a/Framework/Core/include/Framework/TableBuilder.h +++ b/Framework/Core/include/Framework/TableBuilder.h @@ -558,6 +558,23 @@ constexpr auto tuple_to_pack(std::tuple&&) /// Helper function to convert a brace-initialisable struct to /// a tuple. +#ifdef DPL_STRUCTURED_BINDING_PACKS +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++26-extensions" +#endif +template +auto constexpr to_tuple(T&& object) noexcept +{ + auto&& [... members] = object; + return std::make_tuple(members...); +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else // DPL_STRUCTURED_BINDING_PACKS + template auto constexpr to_tuple(T&& object) noexcept { @@ -579,6 +596,8 @@ auto constexpr to_tuple(T&& object) noexcept } } +#endif // DPL_STRUCTURED_BINDING_PACKS + template constexpr auto makeHolderTypes() { diff --git a/Framework/Foundation/include/Framework/StructToTuple.h b/Framework/Foundation/include/Framework/StructToTuple.h index 1c7aa62260bd3..e06df1bab984a 100644 --- a/Framework/Foundation/include/Framework/StructToTuple.h +++ b/Framework/Foundation/include/Framework/StructToTuple.h @@ -14,6 +14,14 @@ #include #include +// Structured binding packs (P1061) are C++26, but clang implements them as an +// extension in every language mode and advertises them via the feature test +// macro, so we can use them while still compiling as C++20. +#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 202411L +#define DPL_STRUCTURED_BINDING_PACKS 1 +#endif + +#ifndef DPL_STRUCTURED_BINDING_PACKS namespace { template @@ -24,9 +32,11 @@ template std::false_type brace_test(...); } // namespace +#endif namespace o2::framework { +#ifndef DPL_STRUCTURED_BINDING_PACKS struct any_type { template constexpr operator T(); // non explicit @@ -35,19 +45,67 @@ struct any_type { template struct is_braces_constructible : decltype(brace_test(0)) { }; +#endif -#define DPL_REPEAT_0(x) -#define DPL_REPEAT_1(x) x -#define DPL_REPEAT_2(x) x, x -#define DPL_REPEAT_3(x) x, x, x -#define DPL_REPEAT_4(x) x, x, x, x -#define DPL_REPEAT_5(x) x, x, x, x, x -#define DPL_REPEAT_6(x) x, x, x, x, x, x -#define DPL_REPEAT_7(x) x, x, x, x, x, x, x -#define DPL_REPEAT_8(x) x, x, x, x, x, x, x, x -#define DPL_REPEAT_9(x) x, x, x, x, x, x, x, x, x -#define DPL_REPEAT_10(x) x, x, x, x, x, x, x, x, x, x -#define DPL_REPEAT(x, d, u) DPL_REPEAT_##d(DPL_REPEAT_10(x)), DPL_REPEAT_##u(x) +struct UniversalType { + template + operator T() + { + } +}; + +template +consteval auto brace_constructible_size(auto... Members) +{ + if constexpr (requires { T{Members...}; } == false) { + static_assert(sizeof...(Members) != 0, "You need to make sure that you have implicit constructors or that you call the explicit constructor correctly."); + return sizeof...(Members) - 1; + } else { + return brace_constructible_size(Members..., UniversalType{}); + } +} + +template +consteval int nested_brace_constructible_size() +{ + using type = std::decay_t; + constexpr int nesting = B ? 1 : 0; + return brace_constructible_size() - nesting; +} + +/// The size to be passed to homogeneous_apply_refs_sized for T. Structured +/// binding packs do not need to know how many members T has, so we can skip +/// counting them altogether. +template +consteval int homogeneous_apply_refs_size() +{ +#ifdef DPL_STRUCTURED_BINDING_PACKS + return 0; +#else + return nested_brace_constructible_size() / 10; +#endif +} + +#ifdef DPL_STRUCTURED_BINDING_PACKS +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++26-extensions" +#endif +template +constexpr auto homogeneous_apply_refs(L l, T&& object) +{ + auto&& [... members] = object; + if constexpr (sizeof...(members) == 0) { + return std::array(); + } else { + return std::array{l(members)...}; + } +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else // DPL_STRUCTURED_BINDING_PACKS #define DPL_ENUM_0(pre, post) #define DPL_ENUM_1(pre, post) pre##0##post @@ -97,54 +155,6 @@ struct is_braces_constructible : decltype(brace_test(0)) { #define DPL_FENUM(f, pre, post, d, u) DPL_FENUM_##d##0(f, pre, post), DPL_FENUM_##u(f, pre##d, post) -#define DPL_10_As DPL_REPEAT_10(A) -#define DPL_20_As DPL_10_As, DPL_10_As -#define DPL_30_As DPL_20_As, DPL_10_As -#define DPL_40_As DPL_30_As, DPL_10_As -#define DPL_50_As DPL_40_As, DPL_10_As -#define DPL_60_As DPL_50_As, DPL_10_As -#define DPL_70_As DPL_60_As, DPL_10_As -#define DPL_80_As DPL_70_As, DPL_10_As -#define DPL_90_As DPL_80_As, DPL_10_As -#define DPL_100_As DPL_90_As, DPL_10_As - -#define DPL_0_9(pre, po) pre##0##po, pre##1##po, pre##2##po, pre##3##po, pre##4##po, pre##5##po, pre##6##po, pre##7##po, pre##8##po, pre##9##po - -#define BRACE_CONSTRUCTIBLE_ENTRY_LOW(u) \ - constexpr(is_braces_constructible{}) \ - { \ - return u; \ - } -#define BRACE_CONSTRUCTIBLE_ENTRY(d, u) \ - constexpr(is_braces_constructible{}) \ - { \ - return d##u; \ - } - -#define BRACE_CONSTRUCTIBLE_ENTRY_TENS(d) \ - constexpr(is_braces_constructible{}) \ - { \ - return d##0; \ - } - -struct UniversalType { - template - operator T() - { - } -}; - -template -consteval auto brace_constructible_size(auto... Members) -{ - if constexpr (requires { T{Members...}; } == false) { - static_assert(sizeof...(Members) != 0, "You need to make sure that you have implicit constructors or that you call the explicit constructor correctly."); - return sizeof...(Members) - 1; - } else { - return brace_constructible_size(Members..., UniversalType{}); - } -} - #define DPL_HOMOGENEOUS_APPLY_ENTRY_LOW(u) \ constexpr(numElements == u) \ { \ @@ -166,14 +176,6 @@ consteval auto brace_constructible_size(auto... Members) return std::array{DPL_FENUM_##d##0(l, p, )}; \ } -template -consteval int nested_brace_constructible_size() -{ - using type = std::decay_t; - constexpr int nesting = B ? 1 : 0; - return brace_constructible_size() - nesting; -} - template () / 10, typename L> requires(D == 9) constexpr auto homogeneous_apply_refs(L l, T&& object) @@ -373,6 +375,8 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) // clang-format on } +#endif // DPL_STRUCTURED_BINDING_PACKS + template constexpr auto homogeneous_apply_refs_sized(L l, T&& object) { diff --git a/Framework/Foundation/test/test_StructToTuple.cxx b/Framework/Foundation/test/test_StructToTuple.cxx index 59685a5f1d598..4b8a69e837d6a 100644 --- a/Framework/Foundation/test/test_StructToTuple.cxx +++ b/Framework/Foundation/test/test_StructToTuple.cxx @@ -164,3 +164,32 @@ TEST_CASE("TestStructToTuple") REQUIRE(t6.size() == 3); REQUIRE(t6[0] == true); } + +/// Empty base class, mirroring o2::framework::ConfigurableGroup: structs +/// deriving from it must decompose to their own members only, and the empty +/// base must not be counted or bound. Exercised with B=true, as the option +/// group handling in AnalysisManagers.h does. +struct EmptyBase { +}; + +struct DerivedGroup : EmptyBase { + int a = 3; + int b = 30; + int c = 300; +}; + +TEST_CASE("EmptyBaseDestructuring") +{ + DerivedGroup g; + auto t = o2::framework::homogeneous_apply_refs([](auto i) -> bool { return i > 20; }, g); + REQUIRE(t.size() == 3); + REQUIRE(t[0] == false); + REQUIRE(t[1] == true); + REQUIRE(t[2] == true); + + // The binding must reach the derived members, not the base. + o2::framework::homogeneous_apply_refs([](auto& i) { i += 1; return true; }, g); + REQUIRE(g.a == 4); + REQUIRE(g.b == 31); + REQUIRE(g.c == 301); +} From 9c055906d13bca7d5d204ba4fc9eda09eb33d9cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 09:28:33 +0200 Subject: [PATCH 16/22] Bump actions/stale from 10 to 11 (#15661) Bumps [actions/stale](https://github.com/actions/stale) from 10 to 11. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v10...v11) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '11' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 23f454aaca950..44e9072aabf6f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v11 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: 'This PR did not have any update in the last 30 days. Is it still needed? Unless further action in will be closed in 5 days.' From e4d1882bf5fdcc9d821593b867313e67a56fc629 Mon Sep 17 00:00:00 2001 From: Mario Ciacco Date: Fri, 7 Aug 2026 11:26:12 +0200 Subject: [PATCH 17/22] [ALICE3] TF3: add passive edge around each pixel (#15654) * add passive edge to each pixel * set default to 0 * revert linkdef --- .../IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h | 10 ++++++++++ .../ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h | 4 ++-- .../include/IOTOFSimulation/Segmentation.h | 12 +++++++++++- .../ALICE3/IOTOF/simulation/src/Segmentation.cxx | 10 +++++----- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h index 7d3dbdc1bd1dc..f81f3f9484475 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h @@ -28,6 +28,8 @@ struct ChipSpecifics { float PassiveEdgeReadOut = 0.; float PassiveEdgeTop = 0.; float PassiveEdgeSide = 0.; + float PixelPassiveEdgeX = 0.; + float PixelPassiveEdgeZ = 0.; float SensorLayerThicknessEff = 0.; float SensorLayerThickness = 0.; @@ -45,6 +47,11 @@ struct ITOFChipSpecifics : ChipSpecifics { NRows = 271; PitchCol = 250.00e-4; PitchRow = 100.00e-4; + PassiveEdgeReadOut = 0.; + PassiveEdgeTop = 0.; + PassiveEdgeSide = 0.; + PixelPassiveEdgeX = 0.; + PixelPassiveEdgeZ = 0.; SensorLayerThicknessEff = 50.e-4; SensorLayerThickness = 50.e-4; } @@ -59,6 +66,9 @@ struct OTOFChipSpecifics : ChipSpecifics { PitchRow = 100.00e-4; PassiveEdgeTop = 50.e-4; PassiveEdgeSide = 115.8e-4; + PassiveEdgeReadOut = 50.e-4; + PixelPassiveEdgeX = 0.; + PixelPassiveEdgeZ = 0.; SensorLayerThicknessEff = 50.e-4; SensorLayerThickness = 50.e-4; } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h index 5cbff299d78c1..e83f4fb51b130 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h @@ -15,8 +15,8 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::iotof::GeometryTGeo + +#pragma link C++ class o2::iotof::GeometryTGeo + ; #pragma link C++ class o2::iotof::IOTOFBaseParam + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::iotof::IOTOFBaseParam> + ; -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h index cd0ab55bd03d7..4425676b5fdcb 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Segmentation.h @@ -42,7 +42,7 @@ class Segmentation ~Segmentation() = default; void configChip(const int nCols, const int nRows, const float pitchCol, const float pitchRow, const float passiveEdgeReadOut, const float passiveEdgeTop, - const float passiveEdgeSide, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID); + const float passiveEdgeSide, const float PixelPassiveEdgeX, const float PixelPassiveEdgeZ, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID); void configChip(const ChipSpecifics& specsConfig, const int subDetectorID); /// Transformation from Geant detector centered local coordinates (cm) to @@ -181,6 +181,11 @@ inline void Segmentation::localToDetectorUnchecked(float xRow, float zCol, int& zCol += 0.5 * specsConfig.ActiveMatrixSizeCols(); // coordinate wrt left edge of Active matrix iRow = int(xRow / specsConfig.PitchRow); iCol = int(zCol / specsConfig.PitchCol); + // check pixel passive region + if (std::abs(xRow - (iRow + 0.5) * specsConfig.PitchRow) > (0.5 * specsConfig.PitchRow - specsConfig.PixelPassiveEdgeX) || std::abs(zCol - (iCol + 0.5) * specsConfig.PitchCol) > (0.5 * specsConfig.PitchCol - specsConfig.PixelPassiveEdgeZ)) { + iRow = iCol = -1; + return; + } if (xRow < 0) { iRow -= 1; } @@ -206,6 +211,11 @@ inline bool Segmentation::localToDetector(float xRow, float zCol, int& iRow, int } iRow = int(xRow / specsConfig.PitchRow); iCol = int(zCol / specsConfig.PitchCol); + // check pixel passive region + if (std::abs(xRow - (iRow + 0.5) * specsConfig.PitchRow) > (0.5 * specsConfig.PitchRow - specsConfig.PixelPassiveEdgeX) || std::abs(zCol - (iCol + 0.5) * specsConfig.PitchCol) > (0.5 * specsConfig.PitchCol - specsConfig.PixelPassiveEdgeZ)) { + iRow = iCol = -1; + return false; + } return true; } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx index ea03b3d317cdc..6e4624294a60e 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx @@ -39,8 +39,8 @@ Segmentation::Segmentation() } else { auto& itofPars = ITOFChipSpecificParam::Instance(); auto& otofPars = OTOFChipSpecificParam::Instance(); - const ChipSpecifics mITofChipPars(itofPars.NCols, itofPars.NRows, itofPars.PitchCol, itofPars.PitchRow, itofPars.PassiveEdgeReadOut, itofPars.PassiveEdgeTop, itofPars.PassiveEdgeSide, itofPars.SensorLayerThicknessEff, itofPars.SensorLayerThickness); - const ChipSpecifics mOTofChipPars(otofPars.NCols, otofPars.NRows, otofPars.PitchCol, otofPars.PitchRow, otofPars.PassiveEdgeReadOut, otofPars.PassiveEdgeTop, otofPars.PassiveEdgeSide, otofPars.SensorLayerThicknessEff, otofPars.SensorLayerThickness); + const ChipSpecifics mITofChipPars(itofPars.NCols, itofPars.NRows, itofPars.PitchCol, itofPars.PitchRow, itofPars.PassiveEdgeReadOut, itofPars.PassiveEdgeTop, itofPars.PassiveEdgeSide, itofPars.PixelPassiveEdgeX, itofPars.PixelPassiveEdgeZ, itofPars.SensorLayerThicknessEff, itofPars.SensorLayerThickness); + const ChipSpecifics mOTofChipPars(otofPars.NCols, otofPars.NRows, otofPars.PitchCol, otofPars.PitchRow, otofPars.PassiveEdgeReadOut, otofPars.PassiveEdgeTop, otofPars.PassiveEdgeSide, otofPars.PixelPassiveEdgeX, otofPars.PixelPassiveEdgeZ, otofPars.SensorLayerThicknessEff, otofPars.SensorLayerThickness); configChip(mITofChipPars, 0 /* subDetectorID for iTOF */); configChip(mOTofChipPars, 1 /* subDetectorID for oTOF */); @@ -48,12 +48,12 @@ Segmentation::Segmentation() } void Segmentation::configChip(const int nCols, const int nRows, const float pitchCol, const float pitchRow, const float passiveEdgeReadOut, - const float passiveEdgeTop, const float passiveEdgeSide, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID) + const float passiveEdgeTop, const float passiveEdgeSide, const float pixelPassiveEdgeX, const float pixelPassiveEdgeZ, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID) { if (subDetectorID == 0) { - mITofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + mITofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, pixelPassiveEdgeX, pixelPassiveEdgeZ, sensorLayerThicknessEff, sensorLayerThickness); } else if (subDetectorID == 1) { - mOTofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + mOTofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, pixelPassiveEdgeX, pixelPassiveEdgeZ, sensorLayerThicknessEff, sensorLayerThickness); } else { printf("Invalid subDetectorID %d. Must be 0 (iTOF) or 1 (oTOF). No configuration applied.\n", subDetectorID); } From 5d1f5199b8e690a0fa257cb152ec2943e0071bea Mon Sep 17 00:00:00 2001 From: Mahi Islam Date: Fri, 7 Aug 2026 17:11:28 +0200 Subject: [PATCH 18/22] Add riscv64 support in x9 and rANS (#15669) - x9.c: add a riscv64 branch to the arch guards, emitting the PAUSE hint (Zihintpause) as a raw .4byte encoding so it assembles regardless of -march and is a NOP on cores without the extension. - rANS defines.h: add riscv64 (64-bit) to the coder-detection guard so the existing scalar Compat/SingleStream coders are selected; no SIMD. Refs #15664 --- Framework/Foundation/3rdparty/x9/x9.c | 3 +++ Utilities/rANS/include/rANS/internal/common/defines.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Framework/Foundation/3rdparty/x9/x9.c b/Framework/Foundation/3rdparty/x9/x9.c index 2ca4bb80237b3..28684183cc954 100644 --- a/Framework/Foundation/3rdparty/x9/x9.c +++ b/Framework/Foundation/3rdparty/x9/x9.c @@ -38,6 +38,7 @@ #if defined(__x86_64__) || defined(__i386__) #include /* _mm_pause */ #elif defined(__aarch64__) +#elif defined(__riscv) #else #error Not supported architecture #endif @@ -370,6 +371,8 @@ void x9_read_from_inbox_spin(x9_inbox* const inbox, _mm_pause(); #elif defined(__aarch64__) __asm__ __volatile__ ("yield"); +#elif defined(__riscv) + __asm__ __volatile__ (".4byte 0x0100000F"); /* PAUSE hint (Zihintpause); NOP if unsupported */ #else #error Not supported architecture #endif diff --git a/Utilities/rANS/include/rANS/internal/common/defines.h b/Utilities/rANS/include/rANS/internal/common/defines.h index 21afb4ff01750..d053a2e67dab3 100644 --- a/Utilities/rANS/include/rANS/internal/common/defines.h +++ b/Utilities/rANS/include/rANS/internal/common/defines.h @@ -40,7 +40,7 @@ #error RANS_FMA cannot be directly set #endif -#if (defined(__x86_64__) || defined(__aarch64__)) +#if (defined(__x86_64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64)) #define RANS_COMPAT #if defined(__SIZEOF_INT128__) #define RANS_SINGLE_STREAM From 6fb748ee1ca4733ff7f1abe582d65716126f8d57 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sat, 8 Aug 2026 08:28:35 +0200 Subject: [PATCH 19/22] DPL: split dictionary for test classes (#15567) --- Framework/Core/CMakeLists.txt | 23 +++++++++++++++++++---- Framework/Core/src/StepTHnLinkDef.h | 20 ++++++++++++++++++++ Framework/Core/test/TestClassesLinkDef.h | 21 +++++++++++++++++++++ Framework/Utils/CMakeLists.txt | 6 +++--- 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 Framework/Core/src/StepTHnLinkDef.h create mode 100644 Framework/Core/test/TestClassesLinkDef.h diff --git a/Framework/Core/CMakeLists.txt b/Framework/Core/CMakeLists.txt index 45af3ad6c59cc..d74eb45e49b92 100644 --- a/Framework/Core/CMakeLists.txt +++ b/Framework/Core/CMakeLists.txt @@ -160,7 +160,6 @@ o2_add_library(Framework src/DPLWebSocket.cxx src/StatusWebSocketHandler.cxx src/TimerParamSpec.cxx - test/TestClasses.cxx TARGETVARNAME targetName PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src PUBLIC_LINK_LIBRARIES AliceO2::Configuration @@ -189,9 +188,16 @@ o2_add_library(Framework target_include_directories(${targetName} PUBLIC $) o2_target_root_dictionary(Framework + HEADERS include/Framework/StepTHn.h + LINKDEF src/StepTHnLinkDef.h) + +# o2::test::* support classes for unit tests, kept out of production libO2Framework. +o2_add_library(FrameworkTestSupport + SOURCES test/TestClasses.cxx + PUBLIC_LINK_LIBRARIES O2::Framework) +o2_target_root_dictionary(FrameworkTestSupport HEADERS test/TestClasses.h - include/Framework/StepTHn.h - LINKDEF test/FrameworkCoreTestLinkDef.h) + LINKDEF test/TestClassesLinkDef.h) add_executable(o2-test-framework-core test/test_AlgorithmSpec.cxx @@ -268,6 +274,7 @@ add_executable(o2-test-framework-core test/unittest_DataSpecUtils.cxx ) target_link_libraries(o2-test-framework-core PRIVATE O2::Framework) +target_link_libraries(o2-test-framework-core PRIVATE O2::FrameworkTestSupport) target_link_libraries(o2-test-framework-core PRIVATE O2::Catch2) get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) @@ -374,7 +381,6 @@ foreach(w RegionInfoCallbackService DanglingInputs DanglingOutputs - DataAllocator StaggeringWorkflow Forwarding ParallelPipeline @@ -403,6 +409,15 @@ foreach(w COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run --shm-segment-size 20000000) endforeach() +o2_add_test(DataAllocator NAME test_Framework_test_DataAllocator + SOURCES test/test_DataAllocator.cxx + COMPONENT_NAME Framework + LABELS framework workflow + PUBLIC_LINK_LIBRARIES O2::Framework O2::FrameworkTestSupport + TIMEOUT 30 + NO_BOOST_TEST + COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run --shm-segment-size 20000000) + if (BUILD_TESTING) # TODO: DanglingInput test not working for the moment [ERROR] Unable to relay # part. [WARN] Incoming data is already obsolete, not relaying. diff --git a/Framework/Core/src/StepTHnLinkDef.h b/Framework/Core/src/StepTHnLinkDef.h new file mode 100644 index 0000000000000..550daa56a8b9d --- /dev/null +++ b/Framework/Core/src/StepTHnLinkDef.h @@ -0,0 +1,20 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class StepTHn + ; +#pragma link C++ class StepTHnT < TArrayF> + ; +#pragma link C++ class StepTHnT < TArrayD> + ; +#pragma link C++ typedef StepTHnF; +#pragma link C++ typedef StepTHnD; diff --git a/Framework/Core/test/TestClassesLinkDef.h b/Framework/Core/test/TestClassesLinkDef.h new file mode 100644 index 0000000000000..c3cfb448621fb --- /dev/null +++ b/Framework/Core/test/TestClassesLinkDef.h @@ -0,0 +1,21 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::test::TriviallyCopyable + ; +#pragma link C++ class o2::test::Base + ; +#pragma link C++ class o2::test::Polymorphic + ; +#pragma link C++ class o2::test::SimplePODClass + ; +#pragma link C++ class std::vector < o2::test::TriviallyCopyable> + ; +#pragma link C++ class std::vector < o2::test::Polymorphic> + ; diff --git a/Framework/Utils/CMakeLists.txt b/Framework/Utils/CMakeLists.txt index fcbc53ef0e6f0..486c3a42e6b16 100644 --- a/Framework/Utils/CMakeLists.txt +++ b/Framework/Utils/CMakeLists.txt @@ -34,7 +34,7 @@ o2_add_executable(output-proxy o2_add_test(RootTreeWriterWorkflow NO_BOOST_TEST SOURCES test/test_RootTreeWriterWorkflow.cxx - PUBLIC_LINK_LIBRARIES O2::DPLUtils + PUBLIC_LINK_LIBRARIES O2::DPLUtils O2::FrameworkTestSupport COMPONENT_NAME DPLUtils LABELS dplutils COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run) @@ -42,7 +42,7 @@ o2_add_test(RootTreeWriterWorkflow o2_add_test(RootTreeReader NO_BOOST_TEST SOURCES test/test_RootTreeReader.cxx - PUBLIC_LINK_LIBRARIES O2::DPLUtils + PUBLIC_LINK_LIBRARIES O2::DPLUtils O2::FrameworkTestSupport COMPONENT_NAME DPLUtils LABELS dplutils COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run) @@ -53,7 +53,7 @@ add_executable(o2-test-framework-utils test/test_DPLRawParser.cxx test/test_DPLRawPageSequencer.cxx ) -target_link_libraries(o2-test-framework-utils PRIVATE O2::Framework O2::DPLUtils O2::DetectorsRaw) +target_link_libraries(o2-test-framework-utils PRIVATE O2::Framework O2::DPLUtils O2::DetectorsRaw O2::FrameworkTestSupport) target_link_libraries(o2-test-framework-utils PRIVATE O2::Catch2) get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) From b248548a373f257ff9e397c34edd4a48e99f074c Mon Sep 17 00:00:00 2001 From: shahor02 Date: Sat, 8 Aug 2026 22:09:38 +0400 Subject: [PATCH 20/22] Split ALICE3 TRK and FT3 layout (#15657) * Split ALICE3 TRK and FT3 layout Restructure the ALICE3 upgrade detectors so TRK and FT3 live under a shared Detectors/Upgrades/ALICE3/TRKFT3 top-level directory. TRK-specific code is kept in TRKFT3/TRK, FT3-specific code is kept in TRKFT3/FT3, and shared reconstruction, workflow, and simulation code is placed under TRKFT3/common. The data formats follow the same split with DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common providing shared TRKFT3 objects. Move the common hit, digit, cluster, and ROF data formats to DataFormatsTRKFT3. The TRKFT3 Hit is a standalone clone of the ITSMFT-style hit rather than deriving from ITSMFT. The Digit is cloned into the TRKFT3 data formats so digitization no longer relies on ITSMFT digits. The Cluster type is now template, with TRKCluster and FT3Cluster aliases, preserving subDetID for geometry lookups while replacing the previous disk/dist split with a single layer field. The MC2ROF data format and related digitizer/cluster writer paths are removed. Move common digitization code to TRKFT3/common/simulation and template the digitizer stack by DetID. DigiParams, DPLDigitizerParam, and Digitizer are now instantiated separately for TRK and FT3. DigiParams uses std::array storage with detector-specific compile-time layer capacities, and the DPL digitizer workflow can instantiate independent TRK and FT3 digitizers using the shared TRKFT3 digitizer implementation. Split GeometryTGeo into TRK and FT3-specific classes. TRK keeps the barrel/vertex geometry implementation, while FT3 has its own FT3Base GeometryTGeo and parameters. Both detector simulations use the shared TRKFT3 Hit type. Global reconstruction, digitizer workflow, reconstruction, workflow, and validation macros were updated to consume DataFormatsTRKFT3 types and the new TRKFT3 layout. Validation: this change was iterated against the O2 build, fixing configure, compile, and link errors reported during the build, including stale DataFormatsTRK references, missing TRKFT3 includes, clusterer template namespace usage, removed cluster disk access, and the FT3 GeometryTGeo Print symbol. * Re-introduce mapping functions for FT3 digitization The log files indicate that digits are produced, but they are not properly written out. Probably because the DigitWriterSpec still needs work. * Please consider the following formatting changes * Please consider the following formatting changes * Fix digitizer and digit/cluster writers * Guard ALICE3 test macros by ENABLE_UPGRADES ifdef --------- Co-authored-by: Marco van Leeuwen Co-authored-by: ALICE Action Bot --- .../DetectorsCommonDataFormats/SimTraits.h | 8 +- .../Detectors/Upgrades/ALICE3/CMakeLists.txt | 2 +- .../ALICE3/{TRK => TRKFT3}/CMakeLists.txt | 14 +- .../ALICE3/TRKFT3/common/CMakeLists.txt | 24 + .../include/DataFormatsTRKFT3}/Cluster.h | 26 +- .../common/include/DataFormatsTRKFT3/Digit.h | 60 ++ .../common/include/DataFormatsTRKFT3/Hit.h | 111 +++ .../include/DataFormatsTRKFT3}/ROFRecord.h | 22 +- .../common/src/DataFormatsTRKFT3LinkDef.h | 29 + .../common/src/Digit.cxx} | 23 +- .../Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx | 41 ++ .../{TRK => TRKFT3/common}/src/ROFRecord.cxx | 9 +- Detectors/Upgrades/ALICE3/CMakeLists.txt | 2 +- .../macros/CMakeLists.txt | 2 +- .../macros/CheckTracksALICE3.C | 20 +- .../reconstruction/CMakeLists.txt | 2 +- .../TimeFrameMixin.h | 24 +- .../workflow/CMakeLists.txt | 2 +- .../TrackerSpecImpl.h | 18 +- .../workflow/src/TrackerSpec.cxx | 6 +- Detectors/Upgrades/ALICE3/README.md | 11 +- .../TRKSimulation/ChipDigitsContainer.h | 47 -- .../simulation/src/ChipDigitsContainer.cxx | 64 -- .../Upgrades/ALICE3/TRKFT3/CMakeLists.txt | 17 + .../Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt | 13 + .../Upgrades/ALICE3/TRKFT3/FT3/README.md | 34 + .../ALICE3/TRKFT3/FT3/base/CMakeLists.txt | 19 + .../FT3/base/include/FT3Base/FT3BaseParam.h | 61 ++ .../FT3/base/include/FT3Base/GeometryTGeo.h | 120 ++++ .../FT3/base/src/FT3BaseLinkDef.h} | 21 +- .../FT3/base/src/FT3BaseParam.cxx} | 6 +- .../TRKFT3/FT3/base/src/GeometryTGeo.cxx | 411 +++++++++++ .../TRKFT3/FT3/simulation/CMakeLists.txt | 29 + .../TRKFT3/FT3/simulation/data/simcuts.dat | 0 .../include/FT3Simulation/Detector.h | 169 +++++ .../include/FT3Simulation}/FT3Layer.h | 11 +- .../include/FT3Simulation}/FT3Module.h | 4 +- .../FT3Simulation}/FT3ModuleConstants.h | 4 +- .../TRKFT3/FT3/simulation/src/Detector.cxx | 674 ++++++++++++++++++ .../FT3}/simulation/src/FT3Layer.cxx | 99 ++- .../FT3}/simulation/src/FT3Module.cxx | 20 +- .../FT3/simulation/src/FT3SimulationLinkDef.h | 11 +- .../Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt | 14 + .../ALICE3/{ => TRKFT3}/TRK/README.md | 38 +- .../{ => TRKFT3}/TRK/base/CMakeLists.txt | 0 .../TRK/base/include/TRKBase/AlmiraParam.h | 0 .../TRK/base/include/TRKBase/GeometryTGeo.h | 41 +- .../base/include/TRKBase/SegmentationChip.h | 0 .../TRK/base/include/TRKBase/Specs.h | 0 .../TRK/base/include/TRKBase/TRKBaseParam.h | 11 - .../{ => TRKFT3}/TRK/base/src/AlmiraParam.cxx | 0 .../TRK/base/src/GeometryTGeo.cxx | 199 +----- .../TRK/base/src/SegmentationChip.cxx | 0 .../TRK/base/src/TRKBaseLinkDef.h | 0 .../TRK/base/src/TRKBaseParam.cxx | 0 .../{ => TRKFT3}/TRK/macros/CMakeLists.txt | 0 .../TRK/macros/test/CMakeLists.txt | 4 +- .../TRK/macros/test/CheckBandwidth.C | 8 +- .../TRK/macros/test/CheckClusters.C | 40 +- .../TRK/macros/test/CheckDigitsTRK.C | 16 +- .../TRK/macros/test/CheckTracksCA.C | 4 +- .../TRK/macros/test/postClusterSizeVsEta.C | 0 .../{ => TRKFT3}/TRK/macros/test/run_test.sh | 0 .../TRK/simulation/CMakeLists.txt | 24 +- .../include/TRKSimulation/Detector.h | 40 +- .../include/TRKSimulation/TRKLayer.h | 0 .../include/TRKSimulation/TRKServices.h | 0 .../include/TRKSimulation/VDGeometryBuilder.h | 0 .../include/TRKSimulation/VDLayer.h | 0 .../include/TRKSimulation/VDSensorRegistry.h | 0 .../TRK/simulation/src/Detector.cxx | 187 +---- .../TRK/simulation/src/TRKLayer.cxx | 0 .../TRK/simulation/src/TRKServices.cxx | 0 .../TRK/simulation/src/TRKSimulationLinkDef.h | 11 - .../TRK/simulation/src/VDGeometryBuilder.cxx | 0 .../TRK/simulation/src/VDLayer.cxx | 0 .../{TRK => TRKFT3/common}/CMakeLists.txt | 2 - .../common}/reconstruction/CMakeLists.txt | 2 +- .../include/TRKReconstruction/Clusterer.h | 40 +- .../include/TRKReconstruction/ClustererACTS.h | 10 +- .../common}/reconstruction/src/Clusterer.cxx | 100 +-- .../reconstruction/src/ClustererACTS.cxx | 21 +- .../TRKFT3/common/simulation/CMakeLists.txt | 31 + .../TRKFT3Simulation/ChipDigitsContainer.h | 92 +++ .../TRKFT3Simulation}/ChipSimResponse.h | 4 +- .../TRKFT3Simulation}/DPLDigitizerParam.h | 4 +- .../include/TRKFT3Simulation}/DigiParams.h | 49 +- .../include/TRKFT3Simulation}/Digitizer.h | 101 ++- .../simulation/src/ChipDigitsContainer.cxx | 17 + .../simulation/src/ChipSimResponse.cxx | 4 +- .../simulation/src/DPLDigitizerParam.cxx | 10 +- .../common}/simulation/src/DigiParams.cxx | 31 +- .../common}/simulation/src/Digitizer.cxx | 54 +- .../simulation/src/TRKFT3SimulationLinkDef.h | 27 + .../common}/workflow/CMakeLists.txt | 2 +- .../{TRK => TRKFT3/common}/workflow/README.md | 0 .../include/TRKWorkflow/ClusterWriterSpec.h | 2 + .../include/TRKWorkflow/ClustererSpec.h | 2 +- .../include/TRKWorkflow/DigitReaderSpec.h | 9 +- .../include/TRKWorkflow/DigitWriterSpec.h | 1 + .../include/TRKWorkflow/RecoWorkflow.h | 0 .../workflow/src/ClusterWriterSpec.cxx | 55 +- .../common}/workflow/src/ClustererSpec.cxx | 12 +- .../common}/workflow/src/DigitReaderSpec.cxx | 0 .../common}/workflow/src/DigitWriterSpec.cxx | 42 +- .../common}/workflow/src/RecoWorkflow.cxx | 0 .../workflow/src/trk-reco-workflow.cxx | 0 .../src/SimpleDigitizerWorkflow.cxx | 9 +- .../src/TRKDigitizerSpec.cxx | 165 +++-- .../DigitizerWorkflow/src/TRKDigitizerSpec.h | 5 +- macro/CMakeLists.txt | 1 + macro/build_geometry.C | 6 + run/CMakeLists.txt | 3 +- run/O2HitMerger.h | 5 + 114 files changed, 2748 insertions(+), 1097 deletions(-) rename DataFormats/Detectors/Upgrades/ALICE3/{TRK => TRKFT3}/CMakeLists.txt (57%) create mode 100644 DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt rename DataFormats/Detectors/Upgrades/ALICE3/{TRK/include/DataFormatsTRK => TRKFT3/common/include/DataFormatsTRKFT3}/Cluster.h (50%) create mode 100644 DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h create mode 100644 DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h rename DataFormats/Detectors/Upgrades/ALICE3/{TRK/include/DataFormatsTRK => TRKFT3/common/include/DataFormatsTRKFT3}/ROFRecord.h (78%) create mode 100644 DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h rename DataFormats/Detectors/Upgrades/ALICE3/{TRK/src/Cluster.cxx => TRKFT3/common/src/Digit.cxx} (56%) create mode 100644 DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx rename DataFormats/Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/src/ROFRecord.cxx (85%) delete mode 100644 Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h delete mode 100644 Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation/Hit.h => TRKFT3/FT3/base/src/FT3BaseLinkDef.h} (65%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/src/Hit.cxx => TRKFT3/FT3/base/src/FT3BaseParam.cxx} (85%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/data/simcuts.dat create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/FT3/simulation/include/FT3Simulation}/FT3Layer.h (92%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/FT3/simulation/include/FT3Simulation}/FT3Module.h (97%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/FT3/simulation/include/FT3Simulation}/FT3ModuleConstants.h (99%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/FT3}/simulation/src/FT3Layer.cxx (84%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/FT3}/simulation/src/FT3Module.cxx (99%) rename DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h => Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h (60%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/README.md (91%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/CMakeLists.txt (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/include/TRKBase/AlmiraParam.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/include/TRKBase/GeometryTGeo.h (82%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/include/TRKBase/SegmentationChip.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/include/TRKBase/Specs.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/include/TRKBase/TRKBaseParam.h (89%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/src/AlmiraParam.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/src/GeometryTGeo.cxx (83%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/src/SegmentationChip.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/src/TRKBaseLinkDef.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/base/src/TRKBaseParam.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/CMakeLists.txt (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/CMakeLists.txt (95%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/CheckBandwidth.C (99%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/CheckClusters.C (95%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/CheckDigitsTRK.C (98%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/CheckTracksCA.C (99%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/postClusterSizeVsEta.C (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/macros/test/run_test.sh (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/CMakeLists.txt (56%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/Detector.h (68%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/TRKLayer.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/TRKServices.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/VDLayer.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/Detector.cxx (71%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/TRKLayer.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/TRKServices.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/TRKSimulationLinkDef.h (70%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/VDGeometryBuilder.cxx (100%) rename Detectors/Upgrades/ALICE3/{ => TRKFT3}/TRK/simulation/src/VDLayer.cxx (100%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/CMakeLists.txt (92%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/reconstruction/CMakeLists.txt (96%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/reconstruction/include/TRKReconstruction/Clusterer.h (84%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/reconstruction/include/TRKReconstruction/ClustererACTS.h (76%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/reconstruction/src/Clusterer.cxx (80%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/reconstruction/src/ClustererACTS.cxx (94%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/common/simulation/include/TRKFT3Simulation}/ChipSimResponse.h (96%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/common/simulation/include/TRKFT3Simulation}/DPLDigitizerParam.h (98%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/common/simulation/include/TRKFT3Simulation}/DigiParams.h (72%) rename Detectors/Upgrades/ALICE3/{TRK/simulation/include/TRKSimulation => TRKFT3/common/simulation/include/TRKFT3Simulation}/Digitizer.h (62%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/simulation/src/ChipSimResponse.cxx (90%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/simulation/src/DPLDigitizerParam.cxx (70%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/simulation/src/DigiParams.cxx (72%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/simulation/src/Digitizer.cxx (93%) create mode 100644 Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/CMakeLists.txt (96%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/README.md (100%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/include/TRKWorkflow/ClusterWriterSpec.h (85%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/include/TRKWorkflow/ClustererSpec.h (97%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/include/TRKWorkflow/DigitReaderSpec.h (91%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/include/TRKWorkflow/DigitWriterSpec.h (88%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/include/TRKWorkflow/RecoWorkflow.h (100%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/ClusterWriterSpec.cxx (67%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/ClustererSpec.cxx (94%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/DigitReaderSpec.cxx (100%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/DigitWriterSpec.cxx (80%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/RecoWorkflow.cxx (100%) rename Detectors/Upgrades/ALICE3/{TRK => TRKFT3/common}/workflow/src/trk-reco-workflow.cxx (100%) diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h index 37c4b790d181b..72782d4ed7bdf 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h @@ -124,6 +124,10 @@ namespace itsmft { class Hit; } +namespace trkft3 +{ +class Hit; +} namespace tof { class HitType; @@ -246,11 +250,11 @@ struct DetIDToHitTypes { }; template <> struct DetIDToHitTypes { - using HitType = o2::itsmft::Hit; + using HitType = o2::trkft3::Hit; }; template <> struct DetIDToHitTypes { - using HitType = o2::itsmft::Hit; + using HitType = o2::trkft3::Hit; }; template <> struct DetIDToHitTypes { diff --git a/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt index 360b50d442d7d..3914b8c7ede8d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -10,4 +10,4 @@ # or submit itself to any jurisdiction. add_subdirectory(FD3) -add_subdirectory(TRK) +add_subdirectory(TRKFT3) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt similarity index 57% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt index c239a2a36845d..fd6e02c44a6b6 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt @@ -9,16 +9,4 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -o2_add_library(DataFormatsTRK - SOURCES src/Cluster.cxx - src/ROFRecord.cxx - PUBLIC_LINK_LIBRARIES O2::CommonDataFormat - O2::DataFormatsITSMFT - O2::SimulationDataFormat -) - -o2_target_root_dictionary(DataFormatsTRK - HEADERS include/DataFormatsTRK/Cluster.h - include/DataFormatsTRK/ROFRecord.h - LINKDEF src/DataFormatsTRKLinkDef.h -) +add_subdirectory(common) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt new file mode 100644 index 0000000000000..d2a8b73da3455 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright 2019-2026 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(DataFormatsTRKFT3 + SOURCES src/Digit.cxx + src/Hit.cxx + src/ROFRecord.cxx + PUBLIC_LINK_LIBRARIES O2::CommonDataFormat + O2::SimulationDataFormat) + +o2_target_root_dictionary(DataFormatsTRKFT3 + HEADERS include/DataFormatsTRKFT3/Cluster.h + include/DataFormatsTRKFT3/Digit.h + include/DataFormatsTRKFT3/Hit.h + include/DataFormatsTRKFT3/ROFRecord.h + LINKDEF src/DataFormatsTRKFT3LinkDef.h) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h similarity index 50% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h index ec68191b3c43f..c3517f289d505 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h @@ -9,30 +9,44 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef ALICEO2_DATAFORMATSTRK_CLUSTER_H -#define ALICEO2_DATAFORMATSTRK_CLUSTER_H +#ifndef ALICEO2_DATAFORMATSTRKFT3_CLUSTER_H +#define ALICEO2_DATAFORMATSTRKFT3_CLUSTER_H +#include "DetectorsCommonDataFormats/DetID.h" #include #include +#include #include -namespace o2::trk +namespace o2::trkft3 { +template struct Cluster { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 clusters are supported"); + uint16_t chipID = 0; uint16_t row = 0; uint16_t col = 0; uint16_t size = 1; int16_t subDetID = -1; int16_t layer = -1; - int16_t disk = -1; - std::string asString() const; + std::string asString() const + { + std::ostringstream stream; + stream << o2::detectors::DetID(DetID).getName() << " cluster chip=" << chipID + << " row=" << row << " col=" << col << " size=" << size + << " subDet=" << subDetID << " layer=" << layer; + return stream.str(); + } ClassDefNV(Cluster, 1); }; -} // namespace o2::trk +using TRKCluster = Cluster; +using FT3Cluster = Cluster; + +} // namespace o2::trkft3 #endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h new file mode 100644 index 0000000000000..41aa774a2ea06 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h @@ -0,0 +1,60 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_DATAFORMATSTRKFT3_DIGIT_H +#define ALICEO2_DATAFORMATSTRKFT3_DIGIT_H + +#include "Rtypes.h" +#include +#include + +namespace o2::trkft3 +{ + +class Digit +{ + public: + Digit(UShort_t chipindex = 0, UShort_t row = 0, UShort_t col = 0, Int_t charge = 0); + ~Digit() = default; + + UShort_t getChipIndex() const { return mChipIndex; } + UShort_t getColumn() const { return mCol; } + UShort_t getRow() const { return mRow; } + Int_t getCharge() const { return mCharge; } + + void setChipIndex(UShort_t index) { mChipIndex = index; } + void setPixelIndex(UShort_t row, UShort_t col) + { + mRow = row; + mCol = col; + } + void setCharge(Int_t charge) { mCharge = charge < USHRT_MAX ? charge : USHRT_MAX; } + void addCharge(int charge) { setCharge(charge + int(mCharge)); } + + std::ostream& print(std::ostream& output) const; + friend std::ostream& operator<<(std::ostream& output, const Digit& digi) + { + digi.print(output); + return output; + } + + private: + UShort_t mChipIndex = 0; + UShort_t mRow = 0; + UShort_t mCol = 0; + UShort_t mCharge = 0; + + ClassDefNV(Digit, 1); +}; + +} // namespace o2::trkft3 + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h new file mode 100644 index 0000000000000..bcc51828436f7 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h @@ -0,0 +1,111 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_DATAFORMATSTRKFT3_HIT_H +#define ALICEO2_DATAFORMATSTRKFT3_HIT_H + +#include +#include + +#include "CommonUtils/ShmAllocator.h" +#include "SimulationDataFormat/BaseHits.h" +#include "Rtypes.h" +#include "TVector3.h" + +namespace o2::trkft3 +{ + +class Hit : public o2::BasicXYZEHit +{ + public: + enum HitStatus_t { + kTrackEntering = 0x1, + kTrackInside = 0x1 << 1, + kTrackExiting = 0x1 << 2, + kTrackOut = 0x1 << 3, + kTrackStopped = 0x1 << 4, + kTrackAlive = 0x1 << 5 + }; + + Hit() = default; + + Hit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, const TVector3& startMom, + double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus); + + math_utils::Point3D GetPosStart() const { return mPosStart; } + Float_t GetStartX() const { return mPosStart.X(); } + Float_t GetStartY() const { return mPosStart.Y(); } + Float_t GetStartZ() const { return mPosStart.Z(); } + template + void GetStartPosition(F& x, F& y, F& z) const + { + x = GetStartX(); + y = GetStartY(); + z = GetStartZ(); + } + + math_utils::Vector3D GetMomentum() const { return mMomentum; } + math_utils::Vector3D& GetMomentum() { return mMomentum; } + Float_t GetPx() const { return mMomentum.X(); } + Float_t GetPy() const { return mMomentum.Y(); } + Float_t GetPz() const { return mMomentum.Z(); } + Float_t GetE() const { return mE; } + Float_t GetTotalEnergy() const { return GetE(); } + + UChar_t GetStatusEnd() const { return mTrackStatusEnd; } + UChar_t GetStatusStart() const { return mTrackStatusStart; } + + Bool_t IsEntering() const { return mTrackStatusEnd & kTrackEntering; } + Bool_t IsInside() const { return mTrackStatusEnd & kTrackInside; } + Bool_t IsExiting() const { return mTrackStatusEnd & kTrackExiting; } + Bool_t IsOut() const { return mTrackStatusEnd & kTrackOut; } + Bool_t IsStopped() const { return mTrackStatusEnd & kTrackStopped; } + Bool_t IsAlive() const { return mTrackStatusEnd & kTrackAlive; } + + Bool_t IsEnteringStart() const { return mTrackStatusStart & kTrackEntering; } + Bool_t IsInsideStart() const { return mTrackStatusStart & kTrackInside; } + Bool_t IsExitingStart() const { return mTrackStatusStart & kTrackExiting; } + Bool_t IsOutStart() const { return mTrackStatusStart & kTrackOut; } + Bool_t IsStoppedStart() const { return mTrackStatusStart & kTrackStopped; } + Bool_t IsAliveStart() const { return mTrackStatusStart & kTrackAlive; } + + void SetPosStart(const math_utils::Point3D& p) { mPosStart = p; } + + void Print(const Option_t* opt) const; + friend std::ostream& operator<<(std::ostream& of, const Hit& point) + { + of << "-I- Hit: O2 trkft3 point for track " << point.GetTrackID() << " in detector " << point.GetDetectorID() << std::endl; + return of; + } + + private: + math_utils::Vector3D mMomentum; ///< momentum at entrance + math_utils::Point3D mPosStart; ///< position at entrance, base position is at exit + Float_t mE = 0.f; ///< total energy at entrance + UChar_t mTrackStatusEnd = 0; ///< MC status flag at exit + UChar_t mTrackStatusStart = 0; ///< MC status at starting point + + ClassDefNV(Hit, 3); +}; + +} // namespace o2::trkft3 + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; +} // namespace std +#endif + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h similarity index 78% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h index 86ee31389fd5f..633ad7e4af24d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h @@ -9,8 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef ALICEO2_DATAFORMATSTRK_ROFRECORD_H -#define ALICEO2_DATAFORMATSTRK_ROFRECORD_H +#ifndef ALICEO2_DATAFORMATSTRKFT3_ROFRECORD_H +#define ALICEO2_DATAFORMATSTRKFT3_ROFRECORD_H #include "CommonDataFormat/InteractionRecord.h" #include "CommonDataFormat/RangeReference.h" @@ -18,7 +18,7 @@ #include #include -namespace o2::trk +namespace o2::trkft3 { class ROFRecord @@ -56,20 +56,6 @@ class ROFRecord ClassDefNV(ROFRecord, 1); }; -struct MC2ROFRecord { - using ROFtype = unsigned int; - - int eventRecordID = -1; - int rofRecordID = 0; - ROFtype minROF = 0; - ROFtype maxROF = 0; - - MC2ROFRecord() = default; - MC2ROFRecord(int evID, int rofRecID, ROFtype mnrof, ROFtype mxrof) : eventRecordID(evID), rofRecordID(rofRecID), minROF(mnrof), maxROF(mxrof) {} - - ClassDefNV(MC2ROFRecord, 1); -}; - -} // namespace o2::trk +} // namespace o2::trkft3 #endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h new file mode 100644 index 0000000000000..046f680e42e8a --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h @@ -0,0 +1,29 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::trkft3::Digit + ; +#pragma link C++ class std::vector < o2::trkft3::Digit> + ; +#pragma link C++ class o2::trkft3::Hit + ; +#pragma link C++ class std::vector < o2::trkft3::Hit> + ; +#pragma link C++ class o2::trkft3::Cluster < o2::detectors::DetID::TRK> + ; +#pragma link C++ class std::vector < o2::trkft3::Cluster < o2::detectors::DetID::TRK>> + ; +#pragma link C++ class o2::trkft3::Cluster < o2::detectors::DetID::FT3> + ; +#pragma link C++ class std::vector < o2::trkft3::Cluster < o2::detectors::DetID::FT3>> + ; +#pragma link C++ class o2::trkft3::ROFRecord + ; +#pragma link C++ class std::vector < o2::trkft3::ROFRecord> + ; + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx similarity index 56% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx index 6c96692ea5a9e..f01d98d1e005d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx @@ -9,20 +9,21 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsTRK/Cluster.h" -#include +#include "DataFormatsTRKFT3/Digit.h" +#include -ClassImp(o2::trk::Cluster); +ClassImp(o2::trkft3::Digit); -namespace o2::trk -{ +using namespace o2::trkft3; -std::string Cluster::asString() const +Digit::Digit(UShort_t chipindex, UShort_t row, UShort_t col, Int_t charge) + : mChipIndex(chipindex), mRow(row), mCol(col) { - std::ostringstream stream; - stream << "chip=" << chipID << " row=" << row << " col=" << col << " size=" << size - << " subDet=" << subDetID << " layer=" << layer << " disk=" << disk; - return stream.str(); + setCharge(charge); } -} // namespace o2::trk +std::ostream& Digit::print(std::ostream& output) const +{ + output << "TRKFT3Digit chip [" << mChipIndex << "] R:" << mRow << " C:" << mCol << " Q: " << mCharge; + return output; +} diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx new file mode 100644 index 0000000000000..ff99214cbd994 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx @@ -0,0 +1,41 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "DataFormatsTRKFT3/Hit.h" + +#include + +ClassImp(o2::trkft3::Hit); + +namespace o2::trkft3 +{ + +Hit::Hit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, const TVector3& startMom, + double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus) + : BasicXYZEHit(endPos.X(), endPos.Y(), endPos.Z(), endTime, eLoss, trackID, detID), + mMomentum(startMom.Px(), startMom.Py(), startMom.Pz()), + mPosStart(startPos.X(), startPos.Y(), startPos.Z()), + mE(startE), + mTrackStatusEnd(endStatus), + mTrackStatusStart(startStatus) +{ +} + +void Hit::Print(const Option_t* opt) const +{ + printf( + "Det: %5d Track: %6d E.loss: %.3e P: %+.3e %+.3e %+.3e\n" + "PosIn: %+.3e %+.3e %+.3e PosOut: %+.3e %+.3e %+.3e\n", + GetDetectorID(), GetTrackID(), GetEnergyLoss(), GetPx(), GetPy(), GetPz(), + GetStartX(), GetStartY(), GetStartZ(), GetX(), GetY(), GetZ()); +} + +} // namespace o2::trkft3 diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx similarity index 85% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx index 79745f9854eb7..9b2808653cf99 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx @@ -9,13 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include -ClassImp(o2::trk::ROFRecord); -ClassImp(o2::trk::MC2ROFRecord); +ClassImp(o2::trkft3::ROFRecord); -namespace o2::trk +namespace o2::trkft3 { std::string ROFRecord::asString() const @@ -26,4 +25,4 @@ std::string ROFRecord::asString() const return stream.str(); } -} // namespace o2::trk +} // namespace o2::trkft3 diff --git a/Detectors/Upgrades/ALICE3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/CMakeLists.txt index 301a82322636d..f587772a1885b 100644 --- a/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -10,7 +10,7 @@ # or submit itself to any jurisdiction. add_subdirectory(Passive) -add_subdirectory(TRK) +add_subdirectory(TRKFT3) add_subdirectory(GlobalReconstruction) add_subdirectory(ECal) add_subdirectory(FD3) diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt index 8295e490f4d7d..834f11f2cce16 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt @@ -11,7 +11,7 @@ o2_add_test_root_macro(CheckTracksALICE3.C PUBLIC_LINK_LIBRARIES O2::DataFormatsITS - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::ITStracking O2::SimulationDataFormat O2::DetectorsBase diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C index 836327507018c..3e849171e8757 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C @@ -12,6 +12,20 @@ /// \file CheckTracksALICE3.C /// \brief Quality assurance macro for TRK tracking +#ifndef ENABLE_UPGRADES +#include +#include + +void CheckTracksALICE3(std::string = "o2trac_trk.root", + std::string = "o2sim", + std::string = "o2clus_trk.root", + std::string = "trk_qa_output.root") +{ + std::cerr << "CheckTracksALICE3 requires a build with ENABLE_UPGRADES" << std::endl; +} + +#else + #if !defined(__CLING__) || defined(__ROOTCLING__) #include #include @@ -30,7 +44,7 @@ #include #include "DataFormatsITS/TrackITS.h" -#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRKFT3/Cluster.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTrack.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -130,7 +144,7 @@ void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", std::unordered_map particleClusterMap; static constexpr int nTRKLayers = 11; - std::array*, nTRKLayers> clustersPerLayer{}; + std::array*, nTRKLayers> clustersPerLayer{}; std::array*, nTRKLayers> clusterLabelsPerLayer{}; for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { @@ -617,3 +631,5 @@ void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", delete clustersFile; delete tracFile; } + +#endif diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt index 9cc7222d413e7..68afd31835999 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -22,7 +22,7 @@ o2_add_library(ALICE3GlobalReconstruction Microsoft.GSL::GSL O2::CommonConstants O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::ITSBase O2::ITSReconstruction diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h index 6e95be32dd0e1..da1a80b77772b 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -17,8 +17,8 @@ #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H #include "CommonDataFormat/InteractionRecord.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "ITStracking/ROFLookupTables.h" #include "ITStracking/TimeFrame.h" #include "SimulationDataFormat/MCCompLabel.h" @@ -27,7 +27,7 @@ #include "SimulationDataFormat/DigitizationContext.h" #include "Steer/MCKinematicsReader.h" #include "TRKReconstruction/Clusterer.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "Framework/Logger.h" @@ -58,8 +58,8 @@ class TimeFrameMixin : public Base int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - int loadROFrameData(const std::array, nLayers>& layerROFs, - const std::array, nLayers>& layerClusters, + int loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, const std::array, nLayers>& layerPatterns, const std::array*, nLayers>* mcLabels = nullptr, float yPlaneMLOT = 0.f); @@ -68,7 +68,7 @@ class TimeFrameMixin : public Base void addTruthSeedingVertices(); - void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } @@ -118,7 +118,7 @@ void TimeFrameMixin::initTimingTables(const std::array -void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) { if (mTimingTablesInitialised) { return; @@ -180,7 +180,7 @@ int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, Geometry gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; @@ -313,8 +313,8 @@ int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, Geometry } template -int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, - const std::array, nLayers>& layerClusters, +int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, const std::array, nLayers>& layerPatterns, const std::array*, nLayers>* mcLabels, float yPlaneMLOT) @@ -391,7 +391,7 @@ int TimeFrameMixin::loadROFrameData(const std::array 1 || c.disk != -1) { + if (c.subDetID < 0 || c.subDetID > 1) { continue; } @@ -403,7 +403,7 @@ int TimeFrameMixin::loadROFrameData(const std::arraygetMatrixL2G(c.chipID) * locXYZ; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt index 6a4994e11467b..7b5cd0e735802 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt @@ -18,7 +18,7 @@ o2_add_library(ALICE3GlobalReconstructionWorkflow O2::GPUWorkflow O2::SimConfig O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::DPLUtils O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h index f6221e485f369..8a2f162019de0 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h @@ -15,8 +15,8 @@ #include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include "CommonDataFormat/IRFrame.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" #include "Field/MagFieldParam.h" #include "Field/MagneticField.h" @@ -26,7 +26,7 @@ #include "SimulationDataFormat/MCEventHeader.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/GeometryTGeo.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include #include @@ -59,7 +59,7 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); TTree* hitsTree = hitsFile.Get("o2sim"); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); @@ -92,16 +92,16 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF TGeoGlobalMagField::Instance()->Lock(); constexpr int nLayers{11}; - std::array, nLayers> layerClusters; + std::array, nLayers> layerClusters; std::array, nLayers> layerPatterns; - std::array, nLayers> layerROFs; + std::array, nLayers> layerROFs; std::array*, nLayers> layerLabels{}; size_t nInputRofs{0}; for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); + layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); layerPatterns[iLayer] = pc.inputs().get>(std::format("patterns_{}", iLayer)); - layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); nInputRofs = std::max(nInputRofs, layerROFs[iLayer].size()); if (mIsMC) { layerLabels[iLayer] = pc.inputs().get*>(std::format("trkmclabels_{}", iLayer)).release(); @@ -173,7 +173,7 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF highestROF = std::max(highestROF, static_cast(clockLayer.getROF(vtx.getTimeStamp().lower()))); } - std::vector allTrackROFs(highestROF); + std::vector allTrackROFs(highestROF); for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { auto& rof = allTrackROFs[iROF]; o2::InteractionRecord ir; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 0b7ab97d44ee6..f588b07c598bf 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -19,8 +19,8 @@ #include "CommonUtils/DLLoaderBase.h" #include "CommonDataFormat/IRFrame.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/Configuration.h" @@ -35,7 +35,7 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "ALICE3GlobalReconstruction/TimeFrame.h" #include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" diff --git a/Detectors/Upgrades/ALICE3/README.md b/Detectors/Upgrades/ALICE3/README.md index f491ae316e046..b7b712d8f5df6 100644 --- a/Detectors/Upgrades/ALICE3/README.md +++ b/Detectors/Upgrades/ALICE3/README.md @@ -68,21 +68,22 @@ Configurables for various sub-detectors are presented in the following Table: | Available options | Link to options | | ----------------- | ---------------------------------------------------------------- | -| TRK | [Link to TRK options](./TRK/README.md#specific-detector-setup) | +| TRK | [Link to TRK options](./TRKFT3/TRK/README.md#specific-detector-setup) | +| FT3 | [Link to FT3 options](./TRKFT3/FT3/README.md#specific-detector-setup) | | TOF | [Link to TOF options](./IOTOF/README.md#specific-detector-setup) | Example O2 command to create a geometry with **segmented layers for TRK (expect for VD), FT3 and TOF:** ```bash -o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK TF3 \ ---configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kSegmented;TRKBase.layoutFT3=kSegmentedFT3;IOTOFBase.segmentedInnerTOF=true;IOTOFBase.segmentedOuterTOF=true" +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ +--configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kSegmented;FT3Base.layoutFT3=kSegmented;IOTOFBase.segmentedInnerTOF=true;IOTOFBase.segmentedOuterTOF=true" ``` Example O2 command to create a geometry with **simple (non-segmented) layers for TRK, FT3 and TOF**: ```bash -o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK TF3 \ ---configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kCylindrical;TRKBase.layoutFT3=kTrapezoidal;IOTOFBase.segmentedInnerTOF=false;IOTOFBase.segmentedOuterTOF=false" +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ +--configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kCylindrical;FT3Base.layoutFT3=kTrapezoidal;IOTOFBase.segmentedInnerTOF=false;IOTOFBase.segmentedOuterTOF=false" ``` ### Output of the simulation diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h deleted file mode 100644 index bf28ace0724bc..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -#ifndef ALICEO2_TRK_CHIPDIGITSCONTAINER_ -#define ALICEO2_TRK_CHIPDIGITSCONTAINER_ - -#include "ITSMFTBase/SegmentationAlpide.h" -#include "ITSMFTSimulation/ChipDigitsContainer.h" -#include "TRKBase/SegmentationChip.h" -#include "TRKBase/Specs.h" -#include "TRKSimulation/DigiParams.h" -#include - -namespace o2::trk -{ - -class ChipDigitsContainer : public o2::itsmft::ChipDigitsContainer -{ - public: - explicit ChipDigitsContainer(UShort_t idx = 0); - - using Segmentation = SegmentationChip; - - /// Get global ordering key made of readout frame, column and row - static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col) - { - return (static_cast(roframe) << (8 * sizeof(UInt_t))) + (static_cast(col) << (8 * sizeof(Short_t))) + row; - } - - /// Adds noise digits, deleted the one using the itsmft::DigiParams interface - void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::itsmft::DigiParams* params, int maxRows = o2::itsmft::SegmentationAlpide::NRows, int maxCols = o2::itsmft::SegmentationAlpide::NCols) = delete; - void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer); - - ClassDefNV(ChipDigitsContainer, 1); -}; - -} // namespace o2::trk - -#endif // ALICEO2_TRK_CHIPDIGITSCONTAINER_ diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx deleted file mode 100644 index d8e6df8b6099c..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -#include "TRKSimulation/ChipDigitsContainer.h" - -using namespace o2::trk; - -ChipDigitsContainer::ChipDigitsContainer(UShort_t idx) - : o2::itsmft::ChipDigitsContainer(idx) {} - -//______________________________________________________________________ -void ChipDigitsContainer::addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer) -{ - UInt_t row = 0; - UInt_t col = 0; - Int_t nhits = 0; - constexpr float ns2sec = 1e-9; - float mean = 0.f; - int nel = 0; - int maxRows = 0; - int maxCols = 0; - - // TODO: set different noise and threshold for VD and MLOT - if (subDetID == 0) { // VD - maxRows = constants::VD::petal::layer::nRows[layer]; // TODO: get the layer from the geometry - maxCols = constants::VD::petal::layer::nCols; - mean = params->getNoisePerPixel() * maxRows * maxCols; - nel = static_cast(params->getChargeThreshold() * 1.1); - } else { // ML/OT - maxRows = constants::moduleMLOT::chip::nRows; - maxCols = constants::moduleMLOT::chip::nCols; - mean = params->getNoisePerPixel() * maxRows * maxCols; - nel = static_cast(params->getChargeThreshold() * 1.1); - } - - LOG(debug) << "Adding noise for chip " << mChipIndex << " with mean " << mean << " and charge " << nel; - - for (UInt_t rof = rofMin; rof <= rofMax; rof++) { - nhits = gRandom->Poisson(mean); - for (Int_t i = 0; i < nhits; ++i) { - row = gRandom->Integer(maxRows); - col = gRandom->Integer(maxCols); - LOG(debug) << "Generated noise hit at ROF " << rof << ", row " << row << ", col " << col; - if (mNoiseMap && mNoiseMap->isNoisy(mChipIndex, row, col)) { - continue; - } - if (mDeadChanMap && mDeadChanMap->isNoisy(mChipIndex, row, col)) { - continue; - } - auto key = getOrderingKey(rof, row, col); - if (!findDigit(key)) { - addDigit(key, rof, row, col, nel, o2::MCCompLabel(true)); - } - } - } -} diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt new file mode 100644 index 0000000000000..3f9a281e64480 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(FT3/base) +add_subdirectory(TRK/base) +add_subdirectory(common) +add_subdirectory(FT3/simulation) +add_subdirectory(TRK/macros) +add_subdirectory(TRK/simulation) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt new file mode 100644 index 0000000000000..3dde618f9d57a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(base) +add_subdirectory(simulation) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md new file mode 100644 index 0000000000000..c11352607db85 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md @@ -0,0 +1,34 @@ + + +# ALICE 3 Tracker Endcaps + +This is top page for the FT3 detector documentation. + +## Specific detector setup + + +Configuration of the endcap disks can be done by setting values for the `FT3Base.layoutFT3` configurable, +the available options are presented in the following Table: + +| Option | Comments | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `kSegmentedStave` | Segmentation of ML and OT disks: Modules are placed on staggered staves with user defined constants | +| `kSegmentedStaveOTOnly` (default) | Only OT disks are contain staves with modules, ML layers are segmented with strips of modules on front/back | +| `kSegmented` | Segmentation of ML and OT disk with strips of modules of chips on the front and back of a layer | +| `kTrapezoidal` | Simple trapezoidal disks (in both ML and OT), with `FT3Base.nTrapezoidalSegments=32` | +| `kCylindrical` | Simplest possible disks as TGeoTubes (ML and OT), bad for ACTS (wrong digi due to polar coorinates on disk sides) | + +Furthermore, there are more options in the case of stave segmentation -- for only OT or both. The user can set to cut the staves exactly on the nominal inner radii (true by default), and outer radii (false by default) of the disks. This exists since (planned) placements of sensors & staves often protrude out of the nominal radii to be more able to cover the nominal disk area. In addition, it is possible to draw reference circles in root for the stave segmented layouts for both the inner (red) and outer (blue) radii. This is off by default, yet can be toggled if the user wants to see how tight the tiling is to the nominal radii -- for visualisation purposes only. + +[ [Link to definitions](./base/include/FT3Base/FT3BaseParam.h) ] + +For example, see the command below to generate a geometry with the endcaps only, all layers with the stave geometry, and including reference circles of nominal radii for visualisation. +```bash +o2-sim-serial-run5 -n 1 -g pythia8hi -m FT3 \ + --configKeyValues "FT3Base.layoutFT3=kSegmented; FT3Base.drawReferenceCircles=true" +``` + + diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt new file mode 100644 index 0000000000000..1cfb57c4beb84 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(FT3Base + SOURCES src/GeometryTGeo.cxx + SOURCES src/FT3BaseParam.cxx + PUBLIC_LINK_LIBRARIES O2::DetectorsBase) + +o2_target_root_dictionary(FT3Base + HEADERS include/FT3Base/GeometryTGeo.h + HEADERS include/FT3Base/FT3BaseParam.h) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h new file mode 100644 index 0000000000000..f4619c608c099 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_FT3_BASEPARAM_H_ +#define ALICEO2_FT3_BASEPARAM_H_ + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +namespace o2 +{ +namespace ft3 +{ +// Parameters for FT3 (ML and OT disks) +enum eFT3Layout { + kCylindrical = 0, + kTrapezoidal, + kSegmented, + kSegmentedStave, + kSegmentedStaveOTOnly +}; +struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { + // Geometry Builder parameters + eFT3Layout layoutFT3 = kSegmentedStave; + int nTrapezoidalSegments = 32; // for the simple trapezoidal disks + + // FT3Geometry::Telescope parameters + Int_t nLayers = 10; + Float_t z0 = -16.0; // First layer z position + Float_t zLength = 263.0; // Distance between first and last layers + Float_t etaIn = 4.5; + Float_t etaOut = 1.5; + Float_t Layerx2X0 = 0.01; + + // define tolerance allowed for staves to go outside nominal radii + double staveTolMLInner = 0.; + double staveTolMLOuter = 0.; + double staveTolOTInner = 0.; + double staveTolOTOuter = 0.; + + // What to place over x=0 line in case of full outer-outer stave: Gap or Module + bool placeSensorStackInMiddleOfStave = false; + + // Draw reference circles at inner and outer radius of stave layer, for visualisation + bool drawReferenceCircles = false; + + O2ParamDef(FT3BaseParam, "FT3Base"); +}; + +} // end namespace ft3 +} // end namespace o2 + +#endif // ALICEO2_FT3_BASEPARAM_H_ diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h new file mode 100644 index 0000000000000..2415da33c976e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h @@ -0,0 +1,120 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GeometryTGeo.h +/// \brief Definition of the GeometryTGeo class +/// \author cvetan.cheshkov@cern.ch - 15/02/2007 +/// \author ruben.shahoyan@cern.ch - adapted to ITSupg 18/07/2012 +/// \author rafael.pezzi@cern.ch - adapted to PostLS4EndCaps 25/06/2020 + +#ifndef ALICEO2_FT3_GEOMETRYTGEO_H_ +#define ALICEO2_FT3_GEOMETRYTGEO_H_ + +#include +#include "DetectorsCommonDataFormats/DetMatrixCache.h" +#include "DetectorsCommonDataFormats/DetID.h" +// #include "MathUtils/Utils.h" +// #include "Rtypes.h" // for Int_t, Double_t, Bool_t, UInt_t, etc + +namespace o2 +{ +namespace ft3 +{ +class GeometryTGeo : public o2::detectors::DetMatrixCache +{ + public: + using Mat3D = o2::math_utils::Transform3D; + using DetMatrixCache::getMatrixL2G; + using DetMatrixCache::getMatrixT2GRot; + using DetMatrixCache::getMatrixT2L; + // this method is not advised for ITS: for barrel detectors whose tracking frame is just a rotation + // it is cheaper to use T2GRot + using DetMatrixCache::getMatrixT2G; + GeometryTGeo(bool build = false, int loadTrans = 0); + ~GeometryTGeo(); + void Build(int loadTrans); + void fillMatrixCache(int mask); + + static GeometryTGeo* Instance() + { + // get (create if needed) a unique instance of the object + if (!sInstance) { + sInstance = std::unique_ptr(new GeometryTGeo(true, 0)); + } + return sInstance.get(); + } + + // adopt the unique instance from external raw pointer (to be used only to read saved instance from file) + static void adopt(GeometryTGeo* raw); + + int extractNumberOfDiscs(int dir); + int extractNumberOfChips(int dir, int layer); + int extractChipId(std::string const volName); + void extractStaveChipId(std::string const volName, int& stave, int& chip); + void extractChipIds(std::string const volName, int& direction, int& layer, int& stave, int& chip); + + int getChipIndex(int dir, int disc, int stave, int chip) const; + // int getDisk(int index) const {return -1;} // TODO: implement this + int getLayer(int chipIdx) const; + std::string getMatrixPath(int direction, int layer, int stave, int chip) const; + int getNumberOfChips() const { return mSize; } + int getNumberOfLayers() const { return mNumberOfDiscs[0] + mNumberOfDiscs[1]; } + int getNumberOfStaves(int absDisc) const { return mNumberOfStavesPerDisc[absDisc]; } + int getSubDetID(int) const { return 2; } + int getStave(int chipIdx) const; + int getChipOnStave(int chipIdx) const; + int getStaveIdxDisc(int absDisc) const { return mStaveIdxDisc[absDisc]; } + int getChipIdxStave(int absStave) const { return mChipIdxStave[absStave]; } + /// Exract FT3 parameters from TGeo + + bool isOwner() const { return mOwner; } + void setOwner(bool v) { mOwner = v; } + + void Print(Option_t* opt = "") const; + static const char* getFT3VolPattern() { return sVolumeName.c_str(); } + static const char* getFT3InnerVolPattern() { return sInnerVolumeName.c_str(); } + static const char* getFT3LayerPattern() { return sLayerName.c_str(); } + static const char* getFT3ChipPattern() { return sChipName.c_str(); } + static const char* getFT3SensorPattern() { return sSensorName.c_str(); } + static const char* getFT3PassivePattern() { return sPassiveName.c_str(); } + + static const char* composeSymNameFT3(Int_t d) { return Form("%s_%d", o2::detectors::DetID(o2::detectors::DetID::FT3).getName(), d); } + static const char* composeSymNameLayer(Int_t d, Int_t lr); + static const char* composeSymNameChip(Int_t d, Int_t lr); + static const char* composeSymNameSensor(Int_t d, Int_t lr); + + protected: + static std::string sInnerVolumeName; ///< Mother inner volume name + static std::string sVolumeName; ///< Mother volume name + static std::string sLayerName; ///< Layer name + static std::string sChipName; ///< Chip name + static std::string sSensorName; ///< Sensor name + static std::string sPassiveName; ///< Passive material name + + std::vector mCacheRefXDiscs; /// cache for X of ML and OT + std::vector mCacheRefAlphaDiscs; /// cache for sensor ref alpha ML and OT + std::vector mNumberOfDiscs; ///< Number Discs per direction + std::vector mNumberOfStavesPerDisc; /// TODO; in principle redundant? + std::vector mStaveIdxDisc; /// Index of first global stave Id for each disc + std::vector mChipIdxStave; /// Index of first chup for each global stave + std::vector mNumberOfChipsPerDisc; /// + // std::vector mChipIndexLayer; ///< ID of first chip in the layer + // std::vector mChipStaveIds; + + bool mOwner = true; //! is it owned by the singleton? + + private: + static std::unique_ptr sInstance; ///< singleton instance +}; + +} // namespace ft3 +} // namespace o2 +#endif \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseLinkDef.h similarity index 65% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseLinkDef.h index 402a343ead472..0a732ea1ec39b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseLinkDef.h @@ -9,21 +9,14 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file Hit.h -/// \brief Definition of the TRK Hit class +#ifdef __CLING__ -#ifndef ALICEO2_TRK_HIT_H_ -#define ALICEO2_TRK_HIT_H_ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -#include "ITSMFTSimulation/Hit.h" - -namespace o2::trk -{ -class Hit : public o2::itsmft::Hit -{ - public: - using o2::itsmft::Hit::Hit; // Inherit constructors -}; -} // namespace o2::trk +#pragma link C++ class o2::ft3::GeometryTGeo; +#pragma link C++ class o2::ft3::FT3BaseParam + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::ft3::FT3BaseParam> + ; #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseParam.cxx similarity index 85% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseParam.cxx index 1f49b84114b9d..a5179299531a2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseParam.cxx @@ -9,7 +9,5 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file Hit.cxx -/// \brief Implementation of the Hit class - -#include "TRKSimulation/Hit.h" +#include "FT3Base/FT3BaseParam.h" +O2ParamImpl(o2::ft3::FT3BaseParam); diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx new file mode 100644 index 0000000000000..9bfc465805777 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx @@ -0,0 +1,411 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// TODO: clean up includes +#include +#include "MathUtils/Cartesian.h" + +#include // for LOG + +#include // for TGeoBBox +#include // for gGeoManager, TGeoManager +#include // for TGeoPNEntry, TGeoPhysicalNode +#include // for TGeoShape +#include // for Nint, ATan2, RadToDeg +#include // for TString, Form +#include "TClass.h" // for TClass +#include "TGeoMatrix.h" // for TGeoHMatrix +#include "TGeoNode.h" // for TGeoNode, TGeoNodeMatrix +#include "TGeoVolume.h" // for TGeoVolume +#include "TMathBase.h" // for Max +#include "TObjArray.h" // for TObjArray +#include "TObject.h" // for TObject + +#include // for isdigit +#include // for snprintf, NULL, printf +#include // for strstr, strlen + +using namespace TMath; +using namespace o2::detectors; + +namespace o2 +{ +namespace ft3 +{ +std::unique_ptr GeometryTGeo::sInstance; + +std::string GeometryTGeo::sVolumeName = "FT3V"; ///< Mother volume name +std::string GeometryTGeo::sInnerVolumeName = "FT3Inner"; ///< Mother inner volume name +std::string GeometryTGeo::sLayerName = "FT3Layer"; ///< Layer name +std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Chip name +// TODO: this is now only used for the not-segmented version; synchronise? +std::string GeometryTGeo::sSensorName = "FT3Sensor"; ///< Sensor name +std::string GeometryTGeo::sPassiveName = "Passive"; ///< Passive material name + +GeometryTGeo::~GeometryTGeo() +{ + if (!mOwner) { + mOwner = true; + sInstance.release(); + } +} +//__________________________________________________________________________ +GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache(detectors::DetID::FT3) +{ + // default c-tor, if build is true, the structures will be filled and the transform matrices + // will be cached + if (sInstance) { + LOG(fatal) << "Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"; + // throw std::runtime_error("Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"); + } + + if (build) { + Build(loadTrans); + } +} + +//__________________________________________________________________________ +void GeometryTGeo::Build(int loadTrans) +{ + if (isBuilt()) { + LOG(warning) << "Already built"; + return; // already initialized + } + + if (!gGeoManager) { + // RSTODO: in future there will be a method to load matrices from the CDB + LOG(fatal) << "Geometry is not loaded"; + } + + // Forward discs part + // int sensIdx = 0; + int totDiscs = 0; + int absStaveIdx = 0; + mSize = 0; + // TODO: clean up initialisation + if (mChipIdxStave.size() == 0) { + mChipIdxStave.push_back(0); + } + if (mStaveIdxDisc.size() == 0) { + mStaveIdxDisc.push_back(0); + } + for (int iDir = 0; iDir < 2; iDir++) { + mNumberOfDiscs.push_back(extractNumberOfDiscs(iDir)); + LOG(info) << "direction " << iDir << " has " << mNumberOfDiscs[iDir] << " discs"; + totDiscs += mNumberOfDiscs[iDir]; + + for (int iDisc = 0; iDisc < mNumberOfDiscs[iDir]; iDisc++) { + TGeoVolume* ft3V = gGeoManager->GetVolume(getFT3VolPattern()); + if (ft3V == nullptr) { + LOG(fatal) << getName() << " volume " << getFT3VolPattern() << " is not in the geometry"; + } + auto layerNode = ft3V->GetNode(Form("%s_1", composeSymNameLayer(iDir, iDisc))); + if (layerNode == nullptr) + LOG(fatal) << "Could not find layer node " << Form("%s_1", composeSymNameLayer(iDir, iDisc)); + auto layerVol = layerNode->GetVolume(); + if (layerVol == nullptr) + LOG(fatal) << "Could not find layer volume " << Form("%s_1", composeSymNameLayer(iDir, iDisc)); + TObjArray* nodes = layerVol->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + int nStaves = 0; + int nSensor = 0; + std::vector chipsPerStave; + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); + const char* name = nd->GetName(); + if (strstr(name, "FT3Sensor") != nullptr && strstr(name, "Inactive") == nullptr) { + int direction = 0, layer = 0; + int stave = 0, chip = 0; + extractChipIds(name, direction, layer, stave, chip); + if (stave >= chipsPerStave.size()) { + chipsPerStave.resize(stave + 1, 0); + nStaves = stave + 1; + } + if (chip + 1 >= chipsPerStave[stave]) { + chipsPerStave[stave] = chip + 1; + } + nSensor++; + } + } + LOG(info) << "direction " << iDir << " disc " << iDisc << " has " << nNodes << " nodes of which " << nSensor << " sensors in " << chipsPerStave.size() << " staves"; + + if (nStaves != chipsPerStave.size()) + LOG(info) << "Inconsistency in stave count " << nStaves << " " << chipsPerStave.size(); + mChipIdxStave.resize(absStaveIdx + chipsPerStave.size() + 1); + mNumberOfStavesPerDisc.push_back(chipsPerStave.size()); // TODO: remove this? Or remove StaveIdxDisc + int totSensor = 0; + for (int nChips : chipsPerStave) { + LOG(debug) << "Absolute Stave ID " << absStaveIdx << " : " << nChips << " sensors"; + totSensor += nChips; + if (absStaveIdx) + mChipIdxStave[absStaveIdx + 1] = mChipIdxStave[absStaveIdx] + nChips; + absStaveIdx++; + } + if (totSensor != nSensor) + LOG(info) << "Inconsistency in sensor count " << nSensor << " " << totSensor; + LOG(debug) << " adding stave Idx " << absStaveIdx << " to disc array; element " << mStaveIdxDisc.size(); + mStaveIdxDisc.push_back(absStaveIdx); + mNumberOfChipsPerDisc.push_back(totSensor); + mSize += totSensor; + LOG(info) << "Total sensors so far " << mSize; + } + } + // mSize = mChipStaveIds.size(); + LOG(info) << "Total sensors " << mSize; + LOG(info) << "Length of stave-disc array " << mStaveIdxDisc.size(); + fillMatrixCache(loadTrans); // Check whether this causes trouble +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameLayer(int direction, int layerNumber) +{ + return Form("%s%d_%d", GeometryTGeo::getFT3LayerPattern(), direction, layerNumber); +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameChip(Int_t d, Int_t lr) +{ + return Form("%s/%s%d", composeSymNameLayer(d, lr), getFT3ChipPattern(), lr); +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameSensor(Int_t d, Int_t lr) +{ + return Form("%s/%s%d", composeSymNameChip(d, lr), getFT3SensorPattern(), lr); +} + +//__________________________________________________________________________ +int GeometryTGeo::extractNumberOfDiscs(int dir) +{ + int numDiscs = 0; + while (gGeoManager->GetVolume(composeSymNameLayer(dir, numDiscs))) { + numDiscs++; + } // Check maybe subvolume? + return numDiscs; // Assume same # layers on both sides +} +//__________________________________________________________________________ +int GeometryTGeo::extractNumberOfChips(int dir, int layer) +{ + int numSensors = 0; + TGeoVolume* ft3V = gGeoManager->GetVolume(getFT3VolPattern()); + if (ft3V == nullptr) { + LOG(fatal) << getName() << " volume " << getFT3VolPattern() << " is not in the geometry"; + } + auto layerVol = ft3V->GetNode(Form("%s_1", composeSymNameLayer(dir, layer)))->GetVolume(); + TObjArray* nodes = layerVol->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + int nSensor = 0; + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); + const char* name = nd->GetName(); + if (strstr(name, "FT3Sensor") != nullptr && strstr(name, "Inactive") == nullptr) { + nSensor++; + } + } + LOG(info) << "direction " << dir << " layer " << layer << " has " << nNodes << " nodes of which " << nSensor << " sensors"; + return nSensor; +} +//__________________________________________________________________________ +int GeometryTGeo::extractChipId(std::string const volName) +{ + if (volName.find("FT3Sensor_Active") == 0) { + return std::stoi(volName.substr(volName.rfind('_') + 1)); + } + LOG(error) << "Not a sensor volume " << volName; + return -1; +} +void GeometryTGeo::extractStaveChipId(std::string const volName, int& stave, int& chip) +{ + if (volName.find("FT3Sensor_Active") == 0) { + int idx = volName.rfind('_'); + chip = std::stoi(volName.substr(idx + 1)); + idx = volName.rfind('_', idx); + stave = std::stoi(volName.substr(idx + 1)); + } else { + LOG(error) << "Not a sensor volume " << volName; + stave = -1; + chip = -1; + } +} +void GeometryTGeo::extractChipIds(std::string const volName, int& direction, int& layer, int& stave, int& chip) +{ + if (volName.find("FT3Sensor_Active") == 0) { + int idx = volName.find('_') + 1; + idx = volName.find('_', idx) + 1; + direction = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + layer = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + stave = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + chip = std::stoi(volName.substr(idx)); + } else { + LOG(error) << "Not a sensor volume " << volName; + direction = -1; + } +} + +int GeometryTGeo::getChipIndex(int dir, int layer, int stave, int chip) const +{ + int absDisc = layer; + if (dir == 1) + absDisc += mNumberOfDiscs[0]; + return mChipIdxStave[mStaveIdxDisc[absDisc] + stave] + chip; +} + +int GeometryTGeo::getLayer(int chipIdx) const +{ + int lay = mNumberOfDiscs[0] + mNumberOfDiscs[1] - 1; + while (chipIdx < mChipIdxStave[mStaveIdxDisc[lay]] && lay > 0) { + lay--; + } + return lay; +} + +// retrieve local stave number from chip ID +int GeometryTGeo::getStave(int chipIdx) const +{ + int lay = getLayer(chipIdx); + int absStave = mStaveIdxDisc[lay]; + while (chipIdx >= mChipIdxStave[absStave] && absStave < mStaveIdxDisc[lay + 1]) { + absStave++; + } + return absStave - 1 - mStaveIdxDisc[lay]; +} + +// retrieve local chip number on stave from chip ID +int GeometryTGeo::getChipOnStave(int chipIdx) const +{ + int lay = getLayer(chipIdx); + int stave = getStave(chipIdx); + return chipIdx - mChipIdxStave[mStaveIdxDisc[lay] + stave]; +} + +std::string GeometryTGeo::getMatrixPath(int direction, int layer, int stave, int chip) const +{ + + // PrintChipID(index, subDetID, petalcase, disk, layer, stave, halfstave, mod, chip); + + std::string path = Form("/cave_1/barrel_1/%s_2/", GeometryTGeo::getFT3VolPattern()); + + // Stave name: std::string stave_volume_name = + // "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + // "_" + std::to_string(direction); + // Sensors directly placed in layer volume? + + path += Form("%s%d_%d_1/", getFT3LayerPattern(), direction, layer); // TRKLayerx_1 + // std::string sensorName = std::string("FT3Sensor_") + std::to_string(layer) + "_" + std::to_string(direction) + "_" + std::to_string(mChipStaveIds[index]) + "_" + index; + path += Form("FT3Sensor_Active_%d_%d_%d_%d_%d", direction, layer, stave, chip, chip); + /* + if (mLayoutMLOT == FT3Layout::kCylindrical) { + // TODO: fix this caser? + path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 + } else { + path += Form("%s%d_%d/", getFT3StavePattern(), layer, stave); + path += Form("%s%d_%d/", getFT3ModulePattern(), layer, mod); + path += Form("%s%d_%d_1", getFT3ChipPattern(), layer, chipID); + } + */ + return path; +} + +//__________________________________________________________________________ +void GeometryTGeo::fillMatrixCache(int mask) +{ + // populate matrix cache for requested transformations + // + if (mSize < 1) { + LOG(warning) << "The method Build was not called yet"; + Build(mask); + return; + } + + // build matrices + if ((mask & o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)) && !getCacheL2G().isFilled()) { + // Matrices for Local (Sensor!!! rather than the full chip) to Global frame transformation + LOGP(info, "Loading {} L2G matrices from TGeo; there are {} matrices", getName(), mSize); + auto& cacheL2G = getCacheL2G(); + cacheL2G.setSize(mSize); + auto& cacheT2L = getCacheT2L(); + cacheT2L.setSize(mSize); + mCacheRefAlphaDiscs.resize(mSize, 0); + + double locA[3] = {-100., 0., 0.}, locB[3] = {100., 0., 0.}, gloA[3], gloB[3]; + double xp{0}, yp{0}; + + gGeoManager->PushPath(); + LOG(info) << " Number of directions " << mNumberOfDiscs.size(); + int nTotDisc = mNumberOfDiscs[0] + mNumberOfDiscs[1]; + for (int absDisc = 0; absDisc < nTotDisc; absDisc++) { + int direction = 0; + int layer = absDisc; + if (absDisc >= mNumberOfDiscs[0]) { + direction = 1; + layer = absDisc - mNumberOfDiscs[0]; + } + LOG(info) << "Direction " << direction << " layer " << layer; + if (absDisc >= mNumberOfStavesPerDisc.size()) + LOG(fatal) << "Not enough entries in mNumberOfStavesPerDisc " << absDisc << " " << mNumberOfStavesPerDisc.size(); + for (int stave = 0; stave < mNumberOfStavesPerDisc[absDisc]; stave++) { + int absStave = mStaveIdxDisc[absDisc] + stave; + if (absStave + 1 >= mChipIdxStave.size()) + LOG(fatal) << "Attempting to get absStave + 1 from index array size " << mChipIdxStave.size(); + int nChip = mChipIdxStave[absStave + 1] - mChipIdxStave[absStave]; // TODO: this is too often == 0 + LOG(debug) << "Getting matrices for direction " << direction << " layer " << layer << " stave " << stave << " : " << nChip << " chips"; + for (int chip = 0; chip < nChip; chip++) { + int chipIdx = getChipIndex(direction, layer, stave, chip); + if (!gGeoManager->cd(getMatrixPath(direction, layer, stave, chip).c_str())) + LOG(fatal) << "Geometry path not found " << getMatrixPath(direction, layer, stave, chip); + const TGeoHMatrix* matL2G = gGeoManager->GetCurrentMatrix(); + if (chipIdx >= mSize) + LOG(fatal) << "ChipIdx " << chipIdx << " out of range " << mSize; + cacheL2G.setMatrix(Mat3D(*matL2G), chipIdx); + + matL2G->LocalToMaster(locA, gloA); + matL2G->LocalToMaster(locB, gloB); + double dx = gloB[0] - gloA[0], dy = gloB[1] - gloA[1]; + double t = (gloB[0] * dx + gloB[1] * dy) / (dx * dx + dy * dy); + xp = gloB[0] - dx * t; + yp = gloB[1] - dy * t; + float alp = std::atan2(yp, xp); + mCacheRefXDiscs.push_back(std::hypot(xp, yp)); + o2::math_utils::bringTo02Pi(alp); + mCacheRefAlphaDiscs[chipIdx] = alp; + + static TGeoHMatrix t2l; + t2l.Clear(); + t2l.RotateZ(mCacheRefAlphaDiscs[chipIdx] * TMath::RadToDeg()); // TODO: do we need this cache? + const TGeoHMatrix& matL2Gi = matL2G->Inverse(); + t2l.MultiplyLeft(&matL2Gi); + cacheT2L.setMatrix(Mat3D(t2l), chipIdx); // TODO: may need deref with * + } + } + } + gGeoManager->PopPath(); + } +} + +//__________________________________________________________________________ +void GeometryTGeo::Print(Option_t*) const +{ + if (!isBuilt()) { + LOGF(info, "Geometry not built yet!"); + return; + } + std::cout << "Detector ID: " << sInstance.get()->getDetID() << std::endl; + + LOGF(info, "Summary of GeometryTGeo: %s", getName()); + LOGF(info, "Number of disks: %d + %d", mNumberOfDiscs[0], mNumberOfDiscs[1]); + LOGF(info, "Total number of sensors: %d", mSize); +} + +} // namespace ft3 +} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt new file mode 100644 index 0000000000000..98adea7c6124a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(FT3Simulation + SOURCES + src/FT3Module.cxx + src/FT3Layer.cxx + src/Detector.cxx + PUBLIC_LINK_LIBRARIES O2::FT3Base + O2::TRKFT3Simulation + O2::DataFormatsTRKFT3 + O2::ITSMFTSimulation + ROOT::Physics) + +o2_target_root_dictionary(FT3Simulation + HEADERS + include/FT3Simulation/FT3Module.h + include/FT3Simulation/Detector.h + include/FT3Simulation/FT3Layer.h) + +o2_data_file(COPY data DESTINATION Detectors/FT3/simulation) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/data/simcuts.dat b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/data/simcuts.dat new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h new file mode 100644 index 0000000000000..3779587b0f7a6 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h @@ -0,0 +1,169 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file Detector.h +/// \brief Definition of the Detector class + +#ifndef ALICEO2_FT3_DETECTOR_H_ +#define ALICEO2_FT3_DETECTOR_H_ + +#include "Rtypes.h" // for Int_t, Double_t, Float_t, Bool_t, etc + +#include "DetectorsBase/Detector.h" // for Detector +#include "DetectorsBase/GeometryManager.h" // for getSensID +#include "DetectorsCommonDataFormats/DetID.h" // for Detector +#include "DataFormatsTRKFT3/Hit.h" // for Hit + +#include "TArrayD.h" // for TArrayD +#include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) +#include "TLorentzVector.h" // for TLorentzVector +#include "TVector3.h" // for TVector3 + +#include +#include + +class FairVolume; +class TGeoVolume; + +class TParticle; + +class TString; + +namespace o2::ft3 +{ +class GeometryTGeo; +class FT3BaseParam; +class FT3Layer; + +class Detector : public o2::base::DetImpl +{ + public: + /// Name : Detector Name + /// Active: kTRUE for active detectors (ProcessHits() will be called) + /// kFALSE for inactive detectors + Detector(Bool_t active); + + /// Default constructor + Detector(); + + /// Default destructor + ~Detector() override; + + /// Initialization of the detector is done here + void InitializeO2Detector() override; + + /// This method is called for each step during simulation (see FairMCApplication::Stepping()) + Bool_t ProcessHits(FairVolume* v = nullptr) override; + + /// Registers the produced collections in FAIRRootManager + void Register() override; + + /// Gets the produced collections + std::vector* getHits(Int_t iColl) const + { + if (iColl == 0) { + return mHits; + } + return nullptr; + } + + /// Has to be called after each event to reset the containers + void Reset() override; + + /// Base class to create the detector geometry + void ConstructGeometry() override; + + /// This method is an example of how to add your own point of type Hit to the clones array + o2::trkft3::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus); + + Int_t chipVolUID(Int_t id) const { return o2::base::GeometryManager::getSensID(o2::detectors::DetID::FT3, id); } + + void EndOfEvent() override; + + void FinishPrimary() override { ; } + virtual void finishRun() { ; } + void BeginPrimary() override { ; } + void PostTrack() override { ; } + void PreTrack() override { ; } + + static constexpr int IdxForwardDisks = 0; + static constexpr int IdxBackwardDisks = 1; + /// Returns the number of layers + size_t getNumberOfLayers() const + { + if (mLayerName[IdxBackwardDisks].size() != mLayerName[IdxForwardDisks].size()) { + LOG(fatal) << "Number of layers in the two directions are different! Returning 0."; + } + return mLayerName[IdxBackwardDisks].size(); + } + + void buildBasicFT3(const FT3BaseParam& param); + void buildFT3V1(); + void buildFT3V3b(); + void buildFT3Scoping(); + void buildFT3NewVacuumVessel(); + void buildFT3ScopingV3(); + + protected: + std::array, 2> mLayerName; // Two sets of layer names, one per direction (forward/backward) + + private: + /// this is transient data about track passing the sensor + struct TrackData { // this is transient + bool mHitStarted; //! hit creation started + unsigned char mTrkStatusStart; //! track status flag + TLorentzVector mPositionStart; //! position at entrance + TLorentzVector mMomentumStart; //! momentum + double mEnergyLoss; //! energy loss + } mTrackData; //! + + /// Container for hit data + std::vector* mHits; + + /// Create the detector materials + virtual void createMaterials(); + + /// Create the detector geometry + void createGeometry(); + + /// Define the sensitive volumes of the geometry + void defineSensitiveVolumes(); + + Detector(const Detector&); + + Detector& operator=(const Detector&); + + std::array, 2> mLayers; // Two sets of layers, one per direction (forward/backward) + bool mIsPipeActivated = true; //! If Alice 3 pipe is present append inner disks to vacuum volume to avoid overlaps + + template + friend class o2::base::DetImpl; + ClassDefOverride(Detector, 2); +}; + +} // namespace o2::ft3 + +#ifdef USESHM +namespace o2 +{ +namespace base +{ +template <> +struct UseShm { + static constexpr bool value = true; +}; +} // namespace base +} // namespace o2 +#endif + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h similarity index 92% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Layer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h index 5ccfc406009a1..282f8fd274ec0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -15,15 +15,16 @@ #ifndef ALICEO2_FT3_UPGRADEV3LAYER_H_ #define ALICEO2_FT3_UPGRADEV3LAYER_H_ -#include // for gGeoManager -#include "Rtypes.h" // for Double_t, Int_t, Bool_t, etc -#include "TRKSimulation/FT3Module.h" +#include // for gGeoManager +#include "Rtypes.h" // for Double_t, Int_t, Bool_t, etc +#include "FT3Simulation/Detector.h" // for Detector, Detector::Model +#include "FT3Simulation/FT3Module.h" class TGeoVolume; namespace o2 { -namespace trk +namespace ft3 { /// This class defines the Geometry for the FT3 Layer TGeo. This is a work class used @@ -91,7 +92,7 @@ class FT3Layer : public TObject ClassDefOverride(FT3Layer, 0); // ALICE 3 EndCaps geometry }; -} // namespace trk +} // namespace ft3 } // namespace o2 #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Module.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Module.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h index f71bab0a1e882..75c1cfb7210e3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3Module.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -19,7 +19,7 @@ #include #include -#include "TRKSimulation/FT3ModuleConstants.h" +#include "FT3Simulation/FT3ModuleConstants.h" // define types for y positions, second element is the stack height using PositionType = std::pair; @@ -27,7 +27,7 @@ using PositionTypes = std::vector; using PosNegPositionTypes = std::pair; // define type of the y position range: First pair is (min, max) for positive y using PositionRangeType = std::pair, std::pair>; -namespace Constants = o2::trk::FT3ModuleConstants; +namespace Constants = o2::ft3::ModuleConstants; class FT3Module { diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3ModuleConstants.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h index 9069e4cf906bd..4f2bfce5c3f1d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/FT3ModuleConstants.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -20,7 +20,7 @@ #include #include -namespace o2::trk::FT3ModuleConstants +namespace o2::ft3::ModuleConstants { /* CURRENT STATUS: * 25x29mm sensors, 2mm inactive on one side @@ -215,6 +215,6 @@ inline StaveConfig getStaveConfig(bool isInnerDisk) } } -} // namespace o2::trk::FT3ModuleConstants +} // namespace o2::ft3::ModuleConstants #endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx new file mode 100644 index 0000000000000..7fe43975934f4 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx @@ -0,0 +1,674 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file Detector.cxx +/// \brief Implementation of the Detector class + +#include "FT3Simulation/Detector.h" + +#include "DetectorsBase/Stack.h" +#include "SimulationDataFormat/TrackReference.h" + +#include "FT3Base/FT3BaseParam.h" +#include "FT3Base/GeometryTGeo.h" +#include "FT3Simulation/FT3Layer.h" + +// FairRoot includes +#include "FairDetector.h" // for FairDetector +#include "FairRootManager.h" // for FairRootManager +#include "FairRootManager.h" +#include "FairRun.h" // for FairRun +#include "FairRuntimeDb.h" // for FairRuntimeDb +#include "FairVolume.h" // for FairVolume + +#include "TGeoManager.h" // for TGeoManager, gGeoManager +#include "TGeoPcon.h" // for TGeoPcon +#include "TGeoTube.h" // for TGeoTube +#include "TGeoVolume.h" // for TGeoVolume, TGeoVolumeAssembly +#include "TString.h" // for TString, operator+ +#include "TVirtualMC.h" // for gMC, TVirtualMC +#include "TVirtualMCStack.h" // for TVirtualMCStack + +#include // for LOG, LOG_IF + +#include // for NULL, snprintf + +#define MAX_SENSORS 2000 + +class FairModule; + +class TGeoMedium; + +class TParticle; + +using namespace o2::ft3; +using o2::trkft3::Hit; + +//_________________________________________________________________________________________________ +Detector::Detector() + : o2::base::DetImpl("FT3", kTRUE), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +//_________________________________________________________________________________________________ +void Detector::buildBasicFT3(const FT3BaseParam& param) +{ + // Build a basic parametrized FT3 detector with nLayers equally spaced between z_first and z_first+z_length + // Covering pseudo rapidity [etaIn,etaOut]. Silicon thinkness computed to match layer x/X0 + + LOG(info) << "Building FT3 Detector: Conical Telescope"; + + const int numberOfLayers = param.nLayers; + const auto z_first = param.z0; + const auto z_length = param.zLength; + const auto etaIn = param.etaIn; + const auto etaOut = param.etaOut; + const auto Layerx2X0 = param.Layerx2X0; + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string layerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(layerNumber + numberOfLayers * direction); + mLayerName[direction][layerNumber] = layerName; + + // Adds evenly spaced layers + const float layerZ = z_first + (layerNumber * z_length / numberOfLayers) * std::copysign(1, z_first); + const float rIn = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaIn)))); + const float rOut = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaOut)))); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, layerZ, rIn, rOut, Layerx2X0, isMiddleLayer); + } + } +} + +//_________________________________________________________________________________________________ +void Detector::buildFT3V1() +{ + // Build FT3 detector according to + // https://indico.cern.ch/event/992488/contributions/4174473/attachments/2168881/3661331/tracker_parameters_werner_jan_11_2021.pdf + + LOG(info) << "Building FT3 Detector: V1"; + + const int numberOfLayers = 10; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfig{ + {26., .5, 3., 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} + {30., .5, 3., 0.1f * layersx2X0}, + {34., .5, 3., 0.1f * layersx2X0}, + {77., 3.5, 35., layersx2X0}, + {100., 3.5, 35., layersx2X0}, + {122., 3.5, 35., layersx2X0}, + {150., 3.5, 80.f, layersx2X0}, + {180., 3.5, 80.f, layersx2X0}, + {220., 3.5, 80.f, layersx2X0}, + {279., 3.5, 80.f, layersx2X0}}; + + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string directionName = std::to_string(direction); + std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction][layerNumber] = layerName; + auto& z = layersConfig[layerNumber][0]; + + auto& rIn = layersConfig[layerNumber][1]; + auto& rOut = layersConfig[layerNumber][2]; + auto& x0 = layersConfig[layerNumber][3]; + + LOG(info) << "Adding Layer " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +//_________________________________________________________________________________________________ +void Detector::buildFT3V3b() +{ + // Build FT3 detector according to + // https://www.overleaf.com/project/6051acc870e39aaeb4653621 + + LOG(info) << "Building FT3 Detector: V3b"; + + const int numberOfLayers = 12; + float sensorThickness = 30.e-4; + float layersx2X0 = 1.e-2; + std::vector> layersConfig{ + {26., .5, 3., 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} + {30., .5, 3., 0.1f * layersx2X0}, + {34., .5, 3., 0.1f * layersx2X0}, + {77., 5.0, 35., layersx2X0}, + {100., 5.0, 35., layersx2X0}, + {122., 5.0, 35., layersx2X0}, + {150., 5.5, 80.f, layersx2X0}, + {180., 6.6, 80.f, layersx2X0}, + {220., 8.1, 80.f, layersx2X0}, + {279., 10.2, 80.f, layersx2X0}, + {340., 12.5, 80.f, layersx2X0}, + {400., 14.7, 80.f, layersx2X0}}; + + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string directionName = std::to_string(direction); + std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction][layerNumber] = layerName; + auto& z = layersConfig[layerNumber][0]; + + auto& rIn = layersConfig[layerNumber][1]; + auto& rOut = layersConfig[layerNumber][2]; + auto& x0 = layersConfig[layerNumber][3]; + + LOG(info) << "Adding Layer " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +void Detector::buildFT3NewVacuumVessel() +{ + // Build the FT3 detector according to changes proposed during + // https://indico.cern.ch/event/1407704/ + // to adhere to the changes that were presented at the ALICE 3 Upgrade days in March 2024 + // Inner radius at C-side to 7 cm + // Inner radius at A-side stays at 5 cm + // 06.02.2025 update: IRIS layers are now in TRK + + LOG(info) << "Building FT3 Detector: After Upgrade Days March 2024 version"; + + const int numberOfLayers = 9; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfigCSide{ + {77., 7.0, 35., layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} + {100., 7.0, 35., layersx2X0}, + {122., 7.0, 35., layersx2X0}, + {150., 7.0, 68.f, layersx2X0}, + {180., 7.0, 68.f, layersx2X0}, + {220., 7.0, 68.f, layersx2X0}, + {260., 7.0, 68.f, layersx2X0}, + {300., 7.0, 68.f, layersx2X0}, + {350., 7.0, 68.f, layersx2X0}}; + + const std::vector> layersConfigASide{ + {77., 5.0, 35., layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} + {100., 5.0, 35., layersx2X0}, + {122., 5.0, 35., layersx2X0}, + {150., 5.0, 68.f, layersx2X0}, + {180., 5.0, 68.f, layersx2X0}, + {220., 5.0, 68.f, layersx2X0}, + {260., 5.0, 68.f, layersx2X0}, + {300., 5.0, 68.f, layersx2X0}, + {350., 5.0, 68.f, layersx2X0}}; + + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string directionName = std::to_string(direction); + std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction][layerNumber] = layerName; + float z, rIn, rOut, x0; + if (direction == 0) { // C-Side + z = layersConfigCSide[layerNumber][0]; + rIn = layersConfigCSide[layerNumber][1]; + rOut = layersConfigCSide[layerNumber][2]; + x0 = layersConfigCSide[layerNumber][3]; + } else if (direction == 1) { // A-Side + z = layersConfigASide[layerNumber][0]; + rIn = layersConfigASide[layerNumber][1]; + rOut = layersConfigASide[layerNumber][2]; + x0 = layersConfigASide[layerNumber][3]; + } + + LOG(info) << "Adding Layer " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +void Detector::buildFT3ScopingV3() +{ + // Build the FT3 detector according to v3 layout + // https://indico.cern.ch/event/1596309/contributions/6728167/attachments/3190117/5677220/2025-12-10-AW-ALICE3planning.pdf + // Middle disks inner radius 10 cm + // Outer disks inner radius 20 cm + + LOG(info) << "Building FT3 Detector: v3 scoping version"; + + const int numberOfLayers = 6; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + using LayerConfig = std::array; // {z_layer, r_in, r_out, Layerx2X0} + const std::array layersConfigCSide{LayerConfig{77., 10.0, 35., layersx2X0}, + LayerConfig{100., 10.0, 35., layersx2X0}, + LayerConfig{122., 10.0, 35., layersx2X0}, + LayerConfig{150., 20.0, 68.f, layersx2X0}, + LayerConfig{180., 20.0, 68.f, layersx2X0}, + LayerConfig{220., 20.0, 68.f, layersx2X0}}; + + const std::array layersConfigASide{LayerConfig{77., 10.0, 35., layersx2X0}, + LayerConfig{100., 10.0, 35., layersx2X0}, + LayerConfig{122., 10.0, 35., layersx2X0}, + LayerConfig{150., 20.0, 68.f, layersx2X0}, + LayerConfig{180., 20.0, 68.f, layersx2X0}, + LayerConfig{220., 20.0, 68.f, layersx2X0}}; + const std::array enabled{true, true, true, true, true, true}; // To enable or disable layers for debug purpose + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + mLayerName[direction].clear(); + const std::array& layerConfig = (direction == IdxBackwardDisks) ? layersConfigCSide : layersConfigASide; + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + if (!enabled[layerNumber]) { + continue; + } + const std::string directionName = std::to_string(direction); + const std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction].push_back(layerName.c_str()); + const float z = layerConfig[layerNumber][0]; + const float rIn = layerConfig[layerNumber][1]; + const float rOut = layerConfig[layerNumber][2]; + const float x0 = layerConfig[layerNumber][3]; + LOG(info) << "buildFT3ScopingV3 -> Adding Layer " << layerNumber << "/" << numberOfLayers << " " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +//_________________________________________________________________________________________________ +void Detector::buildFT3Scoping() +{ + // Build FT3 detector according to the scoping document + + LOG(info) << "Building FT3 Detector: Scoping document version"; + + const int numberOfLayers = 12; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfig{ + {26., .5, 2.5, 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} + {30., .5, 2.5, 0.1f * layersx2X0}, + {34., .5, 2.5, 0.1f * layersx2X0}, + {77., 5.0, 35., layersx2X0}, + {100., 5.0, 35., layersx2X0}, + {122., 5.0, 35., layersx2X0}, + {150., 5.0, 68.f, layersx2X0}, + {180., 5.0, 68.f, layersx2X0}, + {220., 5.0, 68.f, layersx2X0}, + {260., 5.0, 68.f, layersx2X0}, + {300., 5.0, 68.f, layersx2X0}, + {350., 5.0, 68.f, layersx2X0}}; + + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string directionName = std::to_string(direction); + std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction][layerNumber] = layerName; + auto& z = layersConfig[layerNumber][0]; + auto& rIn = layersConfig[layerNumber][1]; + auto& rOut = layersConfig[layerNumber][2]; + auto& x0 = layersConfig[layerNumber][3]; + + LOG(info) << "Adding Layer " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +//_________________________________________________________________________________________________ +Detector::Detector(bool active) + : o2::base::DetImpl("FT3", active), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ + buildFT3ScopingV3(); // v3 Dec 25 +} + +//_________________________________________________________________________________________________ +Detector::Detector(const Detector& rhs) + : o2::base::DetImpl(rhs), + mTrackData(), + /// Container for data points + mHits(o2::utils::createSimVector()) +{ + mLayerName = rhs.mLayerName; +} + +//_________________________________________________________________________________________________ +Detector::~Detector() +{ + + if (mHits) { + // delete mHits; + o2::utils::freeSimVector(mHits); + } +} + +//_________________________________________________________________________________________________ +Detector& Detector::operator=(const Detector& rhs) +{ + // The standard = operator + // Inputs: + // Detector &h the sourse of this copy + // Outputs: + // none. + // Return: + // A copy of the sourse hit h + + if (this == &rhs) { + return *this; + } + + // base class assignment + base::Detector::operator=(rhs); + + mLayerName = rhs.mLayerName; + mLayers = rhs.mLayers; + mTrackData = rhs.mTrackData; + + /// Container for data points + mHits = nullptr; + + return *this; +} + +//_________________________________________________________________________________________________ +void Detector::InitializeO2Detector() +{ + // Define the list of sensitive volumes + LOG(info) << "Initialize FT3 O2Detector"; + + defineSensitiveVolumes(); +} + +//_________________________________________________________________________________________________ +bool Detector::ProcessHits(FairVolume* vol) +{ + // This method is called from the MC stepping + if (!(fMC->TrackCharge())) { + return kFALSE; + } + + int volID = vol->getMCid(); + + auto stack = (o2::data::Stack*)fMC->GetStack(); + + bool startHit = false, stopHit = false; + unsigned char status = 0; + if (fMC->IsTrackEntering()) { + status |= Hit::kTrackEntering; + } + if (fMC->IsTrackInside()) { + status |= Hit::kTrackInside; + } + if (fMC->IsTrackExiting()) { + status |= Hit::kTrackExiting; + } + if (fMC->IsTrackOut()) { + status |= Hit::kTrackOut; + } + if (fMC->IsTrackStop()) { + status |= Hit::kTrackStopped; + } + if (fMC->IsTrackAlive()) { + status |= Hit::kTrackAlive; + } + + // track is entering or created in the volume + if ((status & Hit::kTrackEntering) || (status & Hit::kTrackInside && !mTrackData.mHitStarted)) { + startHit = true; + } else if ((status & (Hit::kTrackExiting | Hit::kTrackOut | Hit::kTrackStopped))) { + stopHit = true; + } + + // increment energy loss at all steps except entrance + if (!startHit) { + mTrackData.mEnergyLoss += fMC->Edep(); + } + if (!(startHit | stopHit)) { + return kFALSE; // do noting + } + if (startHit) { + mTrackData.mEnergyLoss = 0.; + fMC->TrackMomentum(mTrackData.mMomentumStart); + fMC->TrackPosition(mTrackData.mPositionStart); + mTrackData.mTrkStatusStart = status; + mTrackData.mHitStarted = true; + } + static auto* geom = GeometryTGeo::Instance(); + if (stopHit) { + TLorentzVector positionStop; + fMC->TrackPosition(positionStop); + // Retrieve the chip index from the volume name + int chipindex = 0; + std::string volName = fMC->CurrentVolName(); + int direction = -1, layer = -1, stave = -1, chip = -1; + geom->extractChipIds(volName, direction, layer, stave, chip); + chipindex = geom->getChipIndex(direction, layer, stave, chip); + + Hit* p = addHit(stack->GetCurrentTrackNumber(), chipindex, mTrackData.mPositionStart.Vect(), positionStop.Vect(), + mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), + mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status); + // p->SetTotalEnergy(vmc->Etot()); + + // RS: not sure this is needed + // Increment number of Detector det points in TParticle + stack->addHit(GetDetId()); + } + + return kTRUE; +} + +//_________________________________________________________________________________________________ +void Detector::createMaterials() +{ + int ifield = 2; + float fieldm = 10.0; + o2::base::Detector::initFieldTrackingParams(ifield, fieldm); + + float tmaxfdSi = 0.1; // .10000E+01; // Degree + float stemaxSi = 0.0075; // .10000E+01; // cm + float deemaxSi = 0.1; // 0.30000E-02; // Fraction of particle's energy 0RegisterAny(addNameTo("Hit").data(), mHits, kTRUE); + } +} + +//_________________________________________________________________________________________________ +void Detector::Reset() +{ + if (!o2::utils::ShmManager::Instance().isOperational()) { + mHits->clear(); + } +} + +//_________________________________________________________________________________________________ +void Detector::ConstructGeometry() +{ + // Create detector materials + createMaterials(); + + // Construct the detector geometry + createGeometry(); +} + +//_________________________________________________________________________________________________ +void Detector::createGeometry() +{ + + TGeoVolume* volFT3 = new TGeoVolumeAssembly(GeometryTGeo::getFT3VolPattern()); + TGeoVolume* volIFT3 = new TGeoVolumeAssembly(GeometryTGeo::getFT3InnerVolPattern()); + + LOG(info) << "FT3: createGeometry volume name = " << GeometryTGeo::getFT3VolPattern(); + + TGeoVolume* vALIC = gGeoManager->GetVolume("barrel"); + if (!vALIC) { + LOG(fatal) << "Could not find the top volume"; + } + + TGeoVolume* A3IPvac = gGeoManager->GetVolume("OUT_PIPEVACUUM"); + if (!A3IPvac) { + LOG(info) << "Running simulation with no beam pipe."; + } + + // This will need to adapt to the new scheme + if (!A3IPvac) { + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { // Backward layers at mLayers[0]; Forward layers at mLayers[1] + const std::string directionString = direction ? "Forward" : "Backward"; + LOG(info) << " Creating FT3 without beampipe " << directionString << " layers:"; + for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { + mLayers[direction][iLayer].createLayer(volFT3); + } + } + vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); + } else { // If beampipe is enabled append inner disks to beampipe filling volume, this should be temporary. + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + const std::string directionString = direction ? "Forward" : "Backward"; + LOG(info) << " Creating FT3 " << directionString << " layers:"; + for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { + LOG(info) << " Creating " << directionString << " layer " << iLayer; + if (mLayers[direction][iLayer].getIsInMiddleLayer()) { // ML disks + mLayers[direction][iLayer].createLayer(volIFT3); + } else { + mLayers[direction][iLayer].createLayer(volFT3); + } + } + } + A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); + vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); + } +} + +//_________________________________________________________________________________________________ +void Detector::defineSensitiveVolumes() +{ + TGeoManager* geoManager = gGeoManager; + + // Get the flat list of ALL volumes present in the geometry + TObjArray* allVolumes = geoManager->GetListOfVolumes(); + int nVolumes = allVolumes->GetEntriesFast(); + + LOG(info) << "Adding FT3 Sensitive Volumes by iterating over all geometry volumes..."; + static auto* geom = GeometryTGeo::Instance(); + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int iLayer = 0; iLayer < getNumberOfLayers(); iLayer++) { + int iSens = 0; + + // Build the "signatures" (prefixes) of the names for the various layouts for this specific layer and direction: + + // 1. Trapezoidal/Cylindrical (format: FT3Sensor__) + std::string sig1 = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer); + + // 2. Segmented front/back (format: FT3Sensor_front___...) + std::string sig2 = "FT3Sensor_front_" + std::to_string(iLayer) + "_" + std::to_string(direction); + std::string sig3 = "FT3Sensor_back_" + std::to_string(iLayer) + "_" + std::to_string(direction); + + // 3. SegmentedStave (format: FT3Sensor___...) + // Add the trailing underscore to avoid confusing it with sig1 + std::string sig4 = "FT3Sensor_Active_" + std::to_string(direction) + "_" + std::to_string(iLayer) + "_"; + + // Iterate over all existing volumes to find matches + for (int i = 0; i < nVolumes; ++i) { + TGeoVolume* v = (TGeoVolume*)allVolumes->At(i); + std::string vName = v->GetName(); + + // Explicitly exclude the inactive silicon regions created in FT3Module + if (vName.find("Inactive") != std::string::npos || vName.find("inactive") != std::string::npos) { + continue; + } + + // Check if the volume name matches one of our active sensors + bool isMatch = false; + if (vName == sig1) { + isMatch = true; // Exact match for Trapezoidal/Cylindrical layouts + } else if (vName.find(sig2) == 0 || vName.find(sig3) == 0 || vName.find(sig4) == 0) { + isMatch = true; // Prefix match for Segmented and SegmentedStave layouts + } + + if (isMatch) { + AddSensitiveVolume(v); + iSens++; + } + } + + if (iSens == 0) { + LOG(error) << "NO sensitive volume found for direction " << direction << ", layer " << iLayer; + } else { + LOG(info) << iSens << " sensitive volume(s) added for direction " << direction << " layer " << iLayer; + } + } + } +} + +//_________________________________________________________________________________________________ +Hit* Detector::addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, + unsigned char endStatus) +{ + mHits->emplace_back(trackID, detID, startPos, endPos, startMom, startE, endTime, eLoss, startStatus, endStatus); + return &(mHits->back()); +} + +ClassImp(o2::ft3::Detector); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx similarity index 84% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Layer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx index f14393dd866d8..7fce06e029764 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx @@ -14,10 +14,10 @@ /// \author Mario Sitta /// \author Chinorat Kobdaj (kobdaj@g.sut.ac.th) -#include "TRKSimulation/FT3Layer.h" -#include "TRKBase/GeometryTGeo.h" -#include "TRKBase/TRKBaseParam.h" -#include "TRKSimulation/FT3ModuleConstants.h" +#include "FT3Simulation/FT3Layer.h" +#include "FT3Base/GeometryTGeo.h" +#include "FT3Base/FT3BaseParam.h" +#include "FT3Simulation/FT3ModuleConstants.h" #include // for TGeoManager, gGeoManager #include // for TGeoCombiTrans, TGeoRotation, etc @@ -31,7 +31,7 @@ class TGeoMedium; using namespace TMath; -using namespace o2::trk; +using namespace o2::ft3; ClassImp(FT3Layer); @@ -234,9 +234,9 @@ void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::strin TGeoTube* outerCircle = new TGeoTube(mOuterRadius - 0.1, mOuterRadius + 0.1, 0.01); TGeoTube* outerCircleEdge = new TGeoTube(mOuterRadius + 3.3, mOuterRadius + 3.5, 0.01); - TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("TRK_AIR$")); - TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("TRK_AIR$")); - TGeoVolume* outerCircleEdgeVol = new TGeoVolume((mLayerName + "_OuterCircleEdge").c_str(), outerCircleEdge, gGeoManager->GetMedium("TRK_AIR$")); + TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* outerCircleEdgeVol = new TGeoVolume((mLayerName + "_OuterCircleEdge").c_str(), outerCircleEdge, gGeoManager->GetMedium("FT3_AIR$")); innerCircleVol->SetLineColor(kRed); outerCircleVol->SetLineColor(kBlue); @@ -251,36 +251,36 @@ void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::strin void FT3Layer::createLayer(TGeoVolume* motherVolume) { - auto& trkParams = TRKBaseParam::Instance(); + auto& ft3Params = FT3BaseParam::Instance(); if (mLayerNumber < 0) { LOG(fatal) << "Invalid layer number " << mLayerNumber << " for FT3 layer."; } - LOG(info) << "FT3: TRKParams.layoutFT3 = " << trkParams.layoutFT3 + LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3 << " Creating Layer " << mLayerNumber << " at z=" << mZ << " with direction " << mDirection; // ### options for ML and OT disk layout - if (trkParams.layoutFT3 == kTrapezoidal /*|| (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)*/) { + if (ft3Params.layoutFT3 == kTrapezoidal /*|| (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)*/) { // trapezoidal ML+OT disks // (disks with TGeoTubes doesn'n work properly in ACTS, due to polar coordinates on TGeoTube sides) // (!) Currently (March 12, 2026), only OT disks are segmented --> use Trapezoidal option for ML disks as a simplified segmentation // To be changed to "true" paving with modules, as for the OT disks - std::string chipName = GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber); + std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber); std::string sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); - std::string passiveName = GeometryTGeo::getFT3PassivePattern() + std::to_string(mLayerNumber); + std::string passiveName = o2::ft3::GeometryTGeo::getFT3PassivePattern() + std::to_string(mLayerNumber); - TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + TGeoMedium* medSi = gGeoManager->GetMedium("FT3_SILICON$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); layerVol->SetLineColor(kGray); - const int NtrapezoidalSegments = trkParams.nTrapezoidalSegments; + const int NtrapezoidalSegments = ft3Params.nTrapezoidalSegments; const double dz = mChipThickness / 2; const double dzSensor = mSensorThickness / 2; @@ -375,16 +375,47 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) auto* diskRotation = new TGeoRotation("TrapezoidalDiskRotation", 0, 0, 0); auto* diskCombiTrans = new TGeoCombiTrans(0, 0, mZ, diskRotation); motherVolume->AddNode(layerVol, 1, diskCombiTrans); - } else if (trkParams.layoutFT3 == kSegmentedFT3 || - (trkParams.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { + } else if (ft3Params.layoutFT3 == kCylindrical) { + // cylindrical ML+OT disks + + std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber), + sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); + TGeoTube* sensor = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); + TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); + + TGeoMedium* medSi = gGeoManager->GetMedium("FT3_SILICON$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + + TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); + sensVol->SetLineColor(kYellow); + TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); + chipVol->SetLineColor(kYellow); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow); + + LOG(info) << "Inserting " << sensVol->GetName() << " inside " << chipVol->GetName(); + chipVol->AddNode(sensVol, 1, nullptr); + + LOG(info) << "Inserting " << chipVol->GetName() << " inside " << layerVol->GetName(); + layerVol->AddNode(chipVol, 1, nullptr); + + // Finally put everything in the mother volume + auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ, FwdDiskRotation); + + LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else if (ft3Params.layoutFT3 == kSegmented || + (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { FT3Module module; // layer structure - std::string frontLayerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; - std::string backLayerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoVolume* layerVol = nullptr; // Add a little additional room in radius TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); @@ -402,16 +433,16 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " (Rmin=" << mInnerRadius << ", Rmax=" << mOuterRadius << ", z=" << mZ << "cm) inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); - } else if (trkParams.layoutFT3 == kSegmentedStave || - trkParams.layoutFT3 == kSegmentedStaveOTOnly) { + } else if (ft3Params.layoutFT3 == kSegmentedStave || + ft3Params.layoutFT3 == kSegmentedStaveOTOnly) { FT3Module module; // layer structure - std::string frontLayerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; - std::string backLayerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); TGeoVolume* layerVol = nullptr; // set up stave config, differs between ML and OT disks @@ -421,12 +452,12 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs double z_layer_thickness = // need to shift internally with this - o2::trk::FT3ModuleConstants::staveTriangleHeight + - o2::trk::FT3ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + - o2::trk::FT3ModuleConstants::siliconThickness + - o2::trk::FT3ModuleConstants::copperThickness + - o2::trk::FT3ModuleConstants::kaptonThickness + - o2::trk::FT3ModuleConstants::epoxyThickness * 2 + + o2::ft3::ModuleConstants::staveTriangleHeight + + o2::ft3::ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + + o2::ft3::ModuleConstants::siliconThickness + + o2::ft3::ModuleConstants::copperThickness + + o2::ft3::ModuleConstants::kaptonThickness + + o2::ft3::ModuleConstants::epoxyThickness * 2 + 0.5; // add some extra room to ensure all volumes are encapsulated // shift stave volumes into layer volume, since nominal z_{stave face} = 0 @@ -436,7 +467,7 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 3.49, z_layer_thickness / 2); layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - if (trkParams.drawReferenceCircles) { + if (ft3Params.drawReferenceCircles) { std::string referenceCirclesName = "ReferenceCircles_Dir" + std::to_string(mDirection) + "_Layer" + std::to_string(mLayerNumber); createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes } @@ -454,6 +485,6 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); } else { - LOG(fatal) << "Unknown FT3 layout option: " << static_cast(trkParams.layoutFT3); + LOG(fatal) << "Unknown FT3 layout option: " << static_cast(ft3Params.layoutFT3); } } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Module.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx index 4e89d5c0f7c72..d91ca5f83168e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx @@ -12,8 +12,8 @@ /// \file FT3Module.cxx /// \brief Implementation of the FT3Module class -#include "TRKSimulation/FT3Module.h" -#include "TRKBase/TRKBaseParam.h" +#include "FT3Simulation/FT3Module.h" +#include "FT3Base/FT3BaseParam.h" #include #include #include @@ -132,8 +132,8 @@ std::pair calculate_y_range( } /* - * This function is a helper function to determine the positions of senors on the stave - * by padding out the stave with sensors until there is no more space available. + * This function is a helper function to determine the positions of sensors on the stave + * by adding sensors until there is no more space available. * * Arguments: * y_positions: a pair of vectors, where each vector contains pairs of @@ -500,7 +500,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction << direction << ", Layer " << layerNumber; FT3Module::initialize_materials(); - auto& trkParams = o2::trk::TRKBaseParam::Instance(); + auto& ft3Params = o2::ft3::FT3BaseParam::Instance(); // First let's define some constants used throughout /* @@ -562,7 +562,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // default positive and negative starting points has a gap around x-axis for symmetry double stave_half_length = staveConfig.y_lengths[i_stave] / 2; PositionRangeType y_ranges; - if (trkParams.placeSensorStackInMiddleOfStave) { + if (ft3Params.placeSensorStackInMiddleOfStave) { /* * We want a sensor stack to cross over the x-axis for coverage at y=0 * N.B. not necessarily exactly mirrored, only if stack gap is the same @@ -597,11 +597,11 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction // Define tolerances for cutting staves and placing sensors double tolerance_inner, tolerance_outer; if (staveConfig.isML) { - tolerance_inner = trkParams.staveTolFT3MLInner; - tolerance_outer = trkParams.staveTolFT3MLOuter; + tolerance_inner = ft3Params.staveTolMLInner; + tolerance_outer = ft3Params.staveTolMLOuter; } else { - tolerance_inner = trkParams.staveTolFT3OTInner; - tolerance_outer = trkParams.staveTolFT3OTOuter; + tolerance_inner = ft3Params.staveTolOTInner; + tolerance_outer = ft3Params.staveTolOTOuter; } // cut staves on nominal inner radius if specified if (tolerance_inner > staveConfig.maxToleranceInner) { diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h similarity index 60% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h index 36528d9dd2c46..3908f9aa71e5e 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h @@ -1,4 +1,4 @@ -// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. // See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. // All rights not expressly granted are reserved. // @@ -15,11 +15,8 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::Cluster + ; -#pragma link C++ class std::vector < o2::trk::Cluster> + ; -#pragma link C++ class o2::trk::ROFRecord + ; -#pragma link C++ class std::vector < o2::trk::ROFRecord> + ; -#pragma link C++ class o2::trk::MC2ROFRecord + ; -#pragma link C++ class std::vector < o2::trk::MC2ROFRecord> + ; +#pragma link C++ class o2::ft3::FT3Layer + ; +#pragma link C++ class o2::ft3::Detector + ; +#pragma link C++ class o2::base::DetImpl < o2::ft3::Detector> + ; #endif diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt new file mode 100644 index 0000000000000..a099bce5e022d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(base) +add_subdirectory(macros) +add_subdirectory(simulation) diff --git a/Detectors/Upgrades/ALICE3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md similarity index 91% rename from Detectors/Upgrades/ALICE3/TRK/README.md rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index b2880517a66c9..8cf4053fd325b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -48,15 +48,15 @@ When `TRKBase.layoutMLOT=kCylindrical` is used, each layer requires a minimum of **Example for `kCylindrical`:** ```text / Configuration for kCylindrical layout - ALICE3 TRK -/ rInn length thick [optional_mode] -7.0 127.985 0.1 -9.0 127.985 0.1 -12.0 127.985 0.1 -20.0 127.985 0.1 -30.0 127.985 0.1 -45.0 255.9 0.1 -60.0 255.9 0.1 -80.0 255.9 0.1 +/ rInn length thick [optional_mode] +7.0 127.985 0.1 +9.0 127.985 0.1 +12.0 127.985 0.1 +20.0 127.985 0.1 +30.0 127.985 0.1 +45.0 255.9 0.1 +60.0 255.9 0.1 +80.0 255.9 0.1 ``` ### 2. Segmented Layout (`kSegmented`) @@ -78,19 +78,19 @@ From the 6th valid line onwards, lines are parsed as `TRKOTLayer` objects. These ```text / Configuration for kSegmented layout - ALICE3 TRK / --- ML LAYERS (Indices 0 to 4) --- -/ rInn thick tilt nStaves nMods stagOffset [optional_mode] -7.0 0.01 11.2 10 11 0.0 1 -9.0 0.01 11.9 14 11 0.0 1 -12.0 0.01 11.4 18 11 0.0 1 -20.0 0.01 0.0 26 11 1.17 1 -30.0 0.01 0.0 38 11 0.89 1 +/ rInn thick tilt nStaves nMods stagOffset [optional_mode] +7.0 0.01 11.2 10 11 0.0 1 +9.0 0.01 11.9 14 11 0.0 1 +12.0 0.01 11.4 18 11 0.0 1 +20.0 0.01 0.0 26 11 1.17 1 +30.0 0.01 0.0 38 11 0.89 1 / / --- OT LAYERS (Indices 5 to 7) --- / Outer layers do NOT have stagOffset. -/ rInn thick tilt nStaves nMods [optional_mode] -45.0 0.01 0.0 32 22 1 -60.0 0.01 0.0 42 22 1 -80.0 0.01 0.0 56 22 1 +/ rInn thick tilt nStaves nMods [optional_mode] +45.0 0.01 0.0 32 22 1 +60.0 0.01 0.0 42 22 1 +80.0 0.01 0.0 56 22 1 ``` ## Additional options for forward disks diff --git a/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/CMakeLists.txt similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/CMakeLists.txt diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/AlmiraParam.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/AlmiraParam.h diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/GeometryTGeo.h similarity index 82% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/GeometryTGeo.h index f4a28f233ae9c..53ad7662cbfcd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/GeometryTGeo.h @@ -42,13 +42,6 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache } return sInstance.get(); }; - - static const char* getFT3VolPattern() { return sFT3VolumeName.c_str(); } - static const char* getFT3InnerVolPattern() { return sFT3InnerVolumeName.c_str(); } - static const char* getFT3LayerPattern() { return sFT3LayerName.c_str(); } - static const char* getFT3ChipPattern() { return sFT3ChipName.c_str(); } - static const char* getFT3PassivePattern() { return sFT3PassiveName.c_str(); } - static const char* getFT3SensorPattern() { return sFT3SensorName.c_str(); } static const char* getTRKVolPattern() { return sVolumeName.c_str(); } static const char* getTRKServiceVolPattern() { return sServiceVolName.c_str(); } static const char* getTRKLayerPattern() { return sLayerName.c_str(); } @@ -69,7 +62,6 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache int getNumberOfChips() const { return mSize; } /// Determines the number of active parts in the Geometry - int extractNumberOfDisksMLOT(int dir) const; int extractNumberOfLayersMLOT(); int extractNumberOfLayersVD() const; int extractNumberOfPetalsVD() const; @@ -82,15 +74,12 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache int extractNumberOfChipsMLOT(int lay) const; /// Extract number following the prefix in the name string - void extractChipIdsFT3(std::string const volName, int& layer, int& stave, int& chip) const; int extractVolumeCopy(const char* name, const char* prefix) const; int getNumberOfLayersMLOT() const { return mNumberOfLayersMLOT; } int getNumberOfActivePartsVD() const { return mNumberOfActivePartsVD; } int getNumberOfHalfStaves(int lay) const { return mNumberOfHalfStaves[lay]; } - int getNumberOfDisksMLOT() const { return mNumberOfDisksMLOT; } - int getNumberOfStavesInDisk(int lay) const { return mFirstStaveIndexDisc[lay + 1] - mFirstStaveIndexDisc[lay]; } bool isOwner() const { return mOwner; } void setOwner(bool v) { mOwner = v; } @@ -144,10 +133,9 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache /// This routine computes the chip index number from the subDetID, petal, disk, layer, stave /// TODO: retrieve also from chip when chips will be available /// This routine computes the chip index number from the subDetID, petal, disk, layer, stave, half stave, module, chip - /// The subdetectors are numbers as follows: 0: VD; 1: ML+OT barrels; 2: ML+OT discs /// \param int subDetID The subdetector ID, 0 for VD, 1 for MLOT /// \param int petalcase The petal case number for VD, from 0 to 3 - /// \param int disk The disk number for VD or OT (VD 0-6 if present; OT 0-12 (18 for V1 geometry) + /// \param int disk The disk number for VD, from 0 to 5 /// \param int lay The layer number. Starting from 0 both for VD and MLOT /// \param int stave The stave number for MLOT. Starting from 0 /// \param int halfstave The half stave number for MLOT. Can be 0 or 1 @@ -185,8 +173,6 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache return (petalcase == 0) ? 0 : mLastChipIndexVD[petalcase - 1] + 1; } else if (subDetID == 1) { // MLOT return mLastChipIndex[lay + mNumberOfPetalsVD - 1] + 1; - } else if (subDetID == 2) { - return mFirstChipIndexMLOTDisc[lay]; } return -1; // not found } @@ -205,14 +191,13 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache #endif static const char* composeSymNameLayer(int d, int layer); - static const char* composeSymNameLayerFT3(int dir, int layer); static const char* composeSymNameStave(int d, int layer); static const char* composeSymNameModule(int d, int layer); static const char* composeSymNameChip(int d, int layer); static const char* composeSymNameSensor(int d, int layer); protected: - static constexpr int MAXLAYERS = 25; ///< max number of active layers + static constexpr int MAXLAYERS = 20; ///< max number of active layers static std::string sVolumeName; static std::string sServiceVolName; @@ -231,38 +216,26 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static std::string sWrapperVolumeName; ///< Wrapper volume name, not implemented at the moment - static std::string sFT3InnerVolumeName; ///< Mother inner volume name - static std::string sFT3VolumeName; ///< Mother volume name - static std::string sFT3LayerName; ///< Layer name - static std::string sFT3ChipName; ///< Chip name - static std::string sFT3PassiveName; ///< Passive material name - static std::string sFT3SensorName; ///< Sensor name - Int_t mNumberOfLayersMLOT; ///< number of layers - Int_t mNumberOfDisksMLOT; ///< number of ML/OT disks (12 for v3) Int_t mNumberOfActivePartsVD; ///< number of layers Int_t mNumberOfLayersVD; ///< number of layers Int_t mNumberOfPetalsVD; ///< number of Petals = chip in each VD layer Int_t mNumberOfDisksVD; ///< number of Disks = 6 - std::vector mNumberOfStaves; ///< Number Of Staves per layer in ML/OT barrels - std::vector mNumberOfStavesMLOTDDiscs; ///< Number Of Staves per layer in ML/OT discs + std::vector mNumberOfStaves; ///< Number Of Staves per layer in ML/OT std::vector mNumberOfHalfStaves; ///< Number Of Half staves in each stave of the layer in ML/OT std::vector mNumberOfModules; ///< Number Of Modules per stave (half stave) in ML/OT std::vector mNumberOfChips; ///< number of chips per module in ML/OT std::vector mNumberOfChipsPerLayerVD; ///< number of chips per layer VD ( = number of petals) std::vector mNumberOfChipsPerLayerMLOT; ///< number of chips per layer MLOT - std::vector mNumberOfChipPerDiskMLOT; ///< number of chips per disc in MLOT std::vector mNumbersOfChipPerDiskVD; ///< numbersOfChipPerDiskVD std::vector mNumberOfChipsPerPetalVD; ///< numbersOfChipPerPetalVD // std::vector mNumberOfChipsPerStave; ///< number of chips per stave in ML/OT // std::vector mNumberOfChipsPerHalfStave; ///< number of chips per half stave in ML/OT // std::vector mNumberOfChipsPerModule; ///< number of chips per module in ML/OT - std::vector mLastChipIndex; ///< max ID of the detector in the petal(VD) or layer(MLOT) - std::vector mLastChipIndexVD; ///< max ID of the detector in the layer for the VD - // std::vector mLastChipIndexMLOT; ///< max ID of the detector in the layer for the MLOT - std::vector mFirstChipIndexMLOTDisc; ///< ID of the first sensor chip in the layer for the MLOT; array size is one larger than the number of disks; last element equals nChips+1 - std::vector mFirstStaveIndexDisc; ///< Index of first stave (abs ID) in each MLOT Disc - std::vector mFirstChipIndexStave; ///< Index of first chip on stave (Discs) + std::vector mLastChipIndex; ///< max ID of the detctor in the petal(VD) or layer(MLOT) + std::vector mLastChipIndexVD; ///< max ID of the detctor in the layer for the VD + std::vector mLastChipIndexMLOT; ///< max ID of the detctor in the layer for the MLOT + std::array mLayerToWrapper; ///< Layer to wrapper correspondence, not implemented yet bool mOwner = true; //! is it owned by the singleton? diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/SegmentationChip.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/SegmentationChip.h diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h similarity index 89% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h index 7a9f0b5dbb769..1b5efef11fc2a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h @@ -32,13 +32,6 @@ enum eMLOTLayout { kSegmented, }; -enum eFT3Layout { - kTrapezoidal = 0, - kSegmentedFT3, - kSegmentedStave, - kSegmentedStaveOTOnly // TODO: remove this? -}; - enum eSrvLayout { kPeacockv1 = 0, kLOISymm, @@ -50,8 +43,6 @@ struct TRKBaseParam : public o2::conf::ConfigurableParamHelper { bool irisOpen = false; bool includeLowServices = false; - bool disableFT3 = false; - // Options for forward disks (FT3) int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // Forward discs: define tolerance allowed for staves to go outside nominal radii @@ -68,12 +59,10 @@ struct TRKBaseParam : public o2::conf::ConfigurableParamHelper { eVDLayout layoutVD = kIRIS4; // VD detector layout design eMLOTLayout layoutMLOT = kSegmented; // ML and OT detector layout design - eFT3Layout layoutFT3 = kSegmentedStave; eSrvLayout layoutSRV = kPeacockv1; // Layout of services eVDLayout getLayoutVD() const { return layoutVD; } eMLOTLayout getLayoutMLOT() const { return layoutMLOT; } - eFT3Layout getLayoutFT3() const { return layoutFT3; } eSrvLayout getLayoutSRV() const { return layoutSRV; } O2ParamDef(TRKBaseParam, "TRKBase"); diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/AlmiraParam.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/AlmiraParam.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/GeometryTGeo.cxx similarity index 83% rename from Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/GeometryTGeo.cxx index 7b4959f43716c..ddfc844cc964d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/GeometryTGeo.cxx @@ -43,15 +43,6 @@ std::string GeometryTGeo::sMetalStackName = "TRKMetalStack"; std::string GeometryTGeo::sWrapperVolumeName = "TRKUWrapVol"; ///< Wrapper volume name, not implemented at the moment -std::string GeometryTGeo::sFT3VolumeName = "FT3V"; ///< Mother volume name -std::string GeometryTGeo::sFT3InnerVolumeName = "FT3Inner"; ///< Mother inner volume name -std::string GeometryTGeo::sFT3LayerName = "FT3Layer"; ///< Layer name -// TODO: chip and passive are only used by trapezoidal geom; use same for all? -std::string GeometryTGeo::sFT3ChipName = "FT3Chip"; ///< Chip name -std::string GeometryTGeo::sFT3PassiveName = "Passive"; ///< Passive material name -// TODO: this is now only used for the not-segmented version; synchronise? -std::string GeometryTGeo::sFT3SensorName = "FT3Sensor"; ///< Sensor name - o2::trk::GeometryTGeo::~GeometryTGeo() { if (!mOwner) { @@ -96,7 +87,6 @@ void GeometryTGeo::Build(int loadTrans) mNumberOfActivePartsVD = extractNumberOfActivePartsVD(); mNumberOfLayersVD = extractNumberOfLayersVD(); mNumberOfDisksVD = extractNumberOfDisksVD(); - mNumberOfDisksMLOT = extractNumberOfDisksMLOT(0) + extractNumberOfDisksMLOT(1); mNumberOfStaves.resize(mNumberOfLayersMLOT); mNumberOfHalfStaves.resize(mNumberOfLayersMLOT); @@ -110,7 +100,7 @@ void GeometryTGeo::Build(int loadTrans) mLastChipIndex.resize(mNumberOfPetalsVD + mNumberOfLayersMLOT); mLastChipIndexVD.resize(mNumberOfPetalsVD); - // mLastChipIndexMLOT.resize(mNumberOfLayersMLOT); /// ML and OT are part of TRK as the same detector, without disks + mLastChipIndexMLOT.resize(mNumberOfLayersMLOT); /// ML and OT are part of TRK as the same detector, without disks for (int i = 0; i < mNumberOfLayersMLOT; i++) { if (mLayoutMLOT == eMLOTLayout::kCylindrical) { @@ -141,99 +131,22 @@ void GeometryTGeo::Build(int loadTrans) mNumberOfChipsPerLayerMLOT[i] = mNumberOfStaves[i] * mNumberOfHalfStaves[i] * mNumberOfModules[i] * mNumberOfChips[i]; numberOfChipsTotal += mNumberOfChipsPerLayerMLOT[i]; mLastChipIndex[i + mNumberOfPetalsVD] = numberOfChipsTotal - 1; + mLastChipIndexMLOT[i] = numberOfChipsTotal - 1; } - // Forward discs (FT3) part - int totDiscs = 0; - int absStaveIdx = 0; - - if (mFirstChipIndexStave.size() == 0) { - mFirstChipIndexStave.push_back(numberOfChipsTotal); - mFirstChipIndexMLOTDisc.push_back(numberOfChipsTotal); - } - if (mFirstStaveIndexDisc.size() == 0) { - mFirstStaveIndexDisc.push_back(0); - } - std::vector numberOfDiscs; - for (int iDir = 0; iDir < 2; iDir++) { - numberOfDiscs.push_back(extractNumberOfDisksMLOT(iDir)); - totDiscs += numberOfDiscs[iDir]; - LOG(debug) << "direction " << iDir << "; disk total " << totDiscs; - - for (int iDisc = 0; iDisc < numberOfDiscs[iDir]; iDisc++) { - TGeoVolume* trkV = gGeoManager->GetVolume(getTRKVolPattern()); - if (trkV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; - } - auto layerNode = trkV->GetNode(Form("%s_1", composeSymNameLayerFT3(iDir, iDisc))); - if (layerNode == nullptr) { - LOG(info) << "Could not find layer node " << Form("%s_1", composeSymNameLayerFT3(iDir, iDisc)); - continue; - } - auto layerVol = layerNode->GetVolume(); - if (layerVol == nullptr) { - LOG(fatal) << "Could not find layer volume " << Form("%s_1", composeSymNameLayerFT3(iDir, iDisc)); - } - TObjArray* nodes = layerVol->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - int nStaves = 0; - int nSensor = 0; - std::vector chipsPerStave; - for (int j = 0; j < nNodes; j++) { - auto nd = dynamic_cast(nodes->At(j)); - const char* name = nd->GetName(); - if (strstr(name, "FT3Sensor") != nullptr && strstr(name, "Inactive") == nullptr) { - int direction = 0, layer = 0; - int stave = 0, chip = 0; - extractChipIdsFT3(name, layer, stave, chip); - if (stave >= chipsPerStave.size()) { - chipsPerStave.resize(stave + 1, 0); - nStaves = stave + 1; - } - // if (chip + 1 > mChipStaveIds.size()) mChipStaveIds.resize(chip+1); - if (chip + 1 >= chipsPerStave[stave]) { - chipsPerStave[stave] = chip + 1; - } - nSensor++; - } - } - LOG(debug) << "direction " << iDir << " disc " << iDisc << " has " << nNodes << " nodes of which " << nSensor << " sensors in " << chipsPerStave.size() << " staves"; - - if (nStaves != chipsPerStave.size()) { - LOG(info) << "Inconsistency in stave count " << nStaves << " " << chipsPerStave.size(); - } - mFirstChipIndexStave.resize(absStaveIdx + chipsPerStave.size() + 1, -1); - for (int nChips : chipsPerStave) { - LOG(debug) << "Absolute Stave ID " << absStaveIdx << " : " << nChips << " sensors, setting first chip ID for next stave to " << mFirstChipIndexStave[absStaveIdx] + nChips; - numberOfChipsTotal += nChips; - mFirstChipIndexStave[absStaveIdx + 1] = mFirstChipIndexStave[absStaveIdx] + nChips; - absStaveIdx++; - } - mFirstStaveIndexDisc.push_back(absStaveIdx); - mFirstChipIndexMLOTDisc.push_back(numberOfChipsTotal); - LOG(debug) << "Total sensors so far " << numberOfChipsTotal; - } - } setSize(numberOfChipsTotal); - if (numberOfChipsTotal > std::numeric_limits::max()) { - LOG(fatal) << "Too many sensor chips in TRK: " << numberOfChipsTotal; - } - // TODO: add corresponding info for FT3 defineMLOTSensors(); fillTrackingFramesCacheMLOT(); fillMatrixCache(loadTrans); - LOG(info) << "Build done"; } //__________________________________________________________________________ int GeometryTGeo::getSubDetID(int index) const { - if (index >= 0 && index <= mLastChipIndexVD[mLastChipIndexVD.size() - 1]) { + if (index <= mLastChipIndexVD[mLastChipIndexVD.size() - 1]) { return 0; - } else if (index <= mLastChipIndex[mLastChipIndex.size() - 1]) { + } else if (index > mLastChipIndexVD[mLastChipIndexVD.size() - 1]) { return 1; - } else if (index < mFirstChipIndexMLOTDisc[mFirstChipIndexMLOTDisc.size() - 1]) { - return 2; } return -1; /// not found } @@ -274,10 +187,10 @@ int GeometryTGeo::getDisk(int index) const int GeometryTGeo::getLayer(int index) const { int subDetID = getSubDetID(index); + int petalcase = getPetalCase(index); int lay = 0; if (subDetID == 0) { /// VD - int petalcase = getPetalCase(index); if (index % mNumberOfChipsPerPetalVD[petalcase] >= mNumberOfLayersVD) { return -1; /// disks } @@ -287,12 +200,6 @@ int GeometryTGeo::getLayer(int index) const lay++; } return lay - mNumberOfPetalsVD; /// numeration of MLOT layers starting from 0 - } else if (subDetID == 2) { - lay = mNumberOfDisksMLOT - 1; - while (index < mFirstChipIndexMLOTDisc[lay] && lay > 0) { - lay--; - } - return lay; } return -1; /// -1 if not found } @@ -303,18 +210,7 @@ int GeometryTGeo::getLayerTRK(int index) const return -1; /// disks do not have a global layer index } int subDetID = getSubDetID(index); - int firstDetLayer = 0; - - // NOTE: taking these from o2::trk::constants, instead - // of the geometry that is constructed in the 'Build' function - // risks inconsistencies... - - if (subDetID == 1) { - firstDetLayer = o2::trk::constants::VD::petal::nLayers; - } else if (subDetID == 2) { - firstDetLayer = o2::trk::constants::VD::petal::nLayers + o2::trk::constants::ML::nLayers + o2::trk::constants::OT::nLayers; - } - return firstDetLayer + getLayer(index); + return subDetID * o2::trk::constants::VD::petal::nLayers + getLayer(index); // MLOT: offset by number of VD layers } //__________________________________________________________________________ int GeometryTGeo::getStave(int index) const @@ -343,13 +239,6 @@ int GeometryTGeo::getStave(int index) const int chipsPerStave = Nmod * chipsPerModule; return index / chipsPerStave; } - } else if (subDetID == 2) { // Disks FT3 - int lay = getLayer(index); - int absStave = mFirstStaveIndexDisc[lay]; - while (index >= mFirstChipIndexStave[absStave] && absStave < mFirstStaveIndexDisc[lay + 1]) { - absStave++; - } - return absStave - 1 - mFirstStaveIndexDisc[lay]; } return -1; } @@ -437,10 +326,6 @@ int GeometryTGeo::getChip(int index) const int chipsPerModule = Nchip; return index % chipsPerModule; } - } else if (subDetID == 2) { // Forward disks (FT3) - int lay = getLayer(index); - int stave = getStave(index); - return index - mFirstChipIndexStave[mFirstStaveIndexDisc[lay] + stave]; } return -1; } @@ -471,7 +356,7 @@ unsigned short GeometryTGeo::getChipIndex(int subDetID, int petalcase, int disk, } } - LOGP(warning, "Chip index not found for subDetID {}, petalcase {}, disk {}, layer {}, stave {}, halfstave {}, module {}, chip {}, returning numeric limit", subDetID, petalcase, disk, lay, stave, halfstave, mod, chip); + LOGP(warning, "Chip index not found for subDetID %d, petalcase %d, disk %d, layer %d, stave %d, halfstave %d, module %d, chip %d, returning numeric limit", subDetID, petalcase, disk, lay, stave, halfstave, mod, chip); return std::numeric_limits::max(); // not found } @@ -496,12 +381,9 @@ unsigned short GeometryTGeo::getChipIndex(int subDetID, int volume, int lay, int int chipsPerStave = Nmod * chipsPerModule; return getFirstChipIndex(lay, -1, subDetID) + stave * chipsPerStave + mod * chipsPerModule + chip; } - } else if (subDetID == 2) { // FT3 - if (lay < mFirstStaveIndexDisc.size() && mFirstStaveIndexDisc[lay] + stave < mFirstChipIndexStave.size()) { - return mFirstChipIndexStave[mFirstStaveIndexDisc[lay] + stave] + chip; - } } - LOGP(warning, "Chip index not found for subDetID {}, volume {}, layer {}, stave {}, halfstave {}, module {}, chip {}, returning numeric limit", subDetID, volume, lay, stave, halfstave, mod, chip); + + LOGP(warning, "Chip index not found for subDetID %d, volume %d, layer %d, stave %d, halfstave %d, module %d, chip %d, returning numeric limit", subDetID, volume, lay, stave, halfstave, mod, chip); return std::numeric_limits::max(); // not found } @@ -563,14 +445,6 @@ TString GeometryTGeo::getMatrixPath(int index) const path += Form("%s%d_%d/", getTRKChipPattern(), layer, chip); // TRKChipx_y path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 } - } else if (subDetID == 2) { - int direction = 0; - if (layer >= mNumberOfDisksMLOT / 2) { - direction = 1; - layer -= mNumberOfDisksMLOT / 2; - } - path += Form("%s%d_%d_1/", getFT3LayerPattern(), direction, layer); - path += Form("FT3Sensor_Active_%d_%d_%d_%d_%d", direction, layer, stave, chip, chip); } return path; } @@ -624,9 +498,10 @@ TGeoHMatrix* GeometryTGeo::extractMatrixSensor(int index) const void GeometryTGeo::defineMLOTSensors() { for (int i = 0; i < mSize; i++) { - if (getSubDetID(i) == 1) { - sensorsMLOT.push_back(i); // TODO: this is now a trivial array where each element is a sequence number (expect that the first elements are skipped) + if (getSubDetID(i) == 0) { + continue; } + sensorsMLOT.push_back(i); } } @@ -634,20 +509,14 @@ void GeometryTGeo::defineMLOTSensors() void GeometryTGeo::fillTrackingFramesCacheMLOT() { // fill for every sensor of ML & OT its tracking frame parameters - int nSensMLOT = sensorsMLOT.size(); - int nSensMLOTDisk = mFirstChipIndexMLOTDisc[mFirstChipIndexMLOTDisc.size() - 1] - mFirstChipIndexMLOTDisc[0]; if (!isTrackingFrameCachedMLOT() && !sensorsMLOT.empty()) { - size_t newSize = nSensMLOT + nSensMLOTDisk; + size_t newSize = sensorsMLOT.size(); mCacheRefXMLOT.resize(newSize); mCacheRefAlphaMLOT.resize(newSize); - for (int i = 0; i < nSensMLOT; i++) { + for (int i = 0; i < newSize; i++) { int sensorId = sensorsMLOT[i]; extractSensorXAlphaMLOT(sensorId, mCacheRefXMLOT[i], mCacheRefAlphaMLOT[i]); } - for (int i = nSensMLOT; i < newSize; i++) { - // LOG(info) << "Getting XAlpha for disk chip " << mFirstChipIndexMLOTDisc[0] + i; - extractSensorXAlphaMLOT(mFirstChipIndexMLOTDisc[0] + i, mCacheRefXMLOT[i], mCacheRefAlphaMLOT[i]); - } } } @@ -698,14 +567,8 @@ const char* GeometryTGeo::composeSymNameLayer(int d, int layer) { return Form("%s/%s%d", composeSymNameTRK(d), getTRKLayerPattern(), layer); } - #endif -const char* GeometryTGeo::composeSymNameLayerFT3(int dir, int layer) -{ - return Form("%s%d_%d", GeometryTGeo::getFT3LayerPattern(), dir, layer); -} - const char* GeometryTGeo::composeSymNameStave(int d, int layer) { return Form("%s/%s%d", composeSymNameLayer(d, layer), getTRKStavePattern(), layer); @@ -726,38 +589,6 @@ const char* GeometryTGeo::composeSymNameSensor(int d, int layer) return Form("%s/%s%d", composeSymNameChip(d, layer), getTRKSensorPattern(), layer); } -//__________________________________________________________________________ -void GeometryTGeo::extractChipIdsFT3(std::string const volName, int& layer, int& stave, int& chip) const -{ - if (volName.find("FT3Sensor_Active") == 0) { - int idx = volName.find('_') + 1; - idx = volName.find('_', idx) + 1; - int direction = std::stoi(volName.substr(idx)); - idx = volName.find('_', idx) + 1; - layer = std::stoi(volName.substr(idx)); - idx = volName.find('_', idx) + 1; - stave = std::stoi(volName.substr(idx)); - idx = volName.find('_', idx) + 1; - chip = std::stoi(volName.substr(idx)); - if (direction == 1) { - layer += mNumberOfDisksMLOT / 2; - } - } else { - LOG(error) << "extractChipIdsFT3: Not a sensor volume " << volName; - layer = -1; - } -} - -//__________________________________________________________________________ -int GeometryTGeo::extractNumberOfDisksMLOT(int dir) const -{ - int numDiscs = 0; - while (gGeoManager->GetVolume(composeSymNameLayerFT3(dir, numDiscs))) { - numDiscs++; - } // Check maybe subvolume? - return numDiscs; // Assume same # layers on both sides -} - //__________________________________________________________________________ int GeometryTGeo::extractVolumeCopy(const char* name, const char* prefix) const { @@ -1329,7 +1160,6 @@ void GeometryTGeo::Print(Option_t*) const } LOGF(info, "Total number of chips: %d", getNumberOfChips()); - /* std::cout << "mLastChipIndex = ["; for (int i = 0; i < mLastChipIndex.size(); i++) { std::cout << mLastChipIndex[i]; @@ -1338,7 +1168,6 @@ void GeometryTGeo::Print(Option_t*) const } } std::cout << "]" << std::endl; - */ std::cout << "mLastChipIndexVD = ["; for (int i = 0; i < mLastChipIndexVD.size(); i++) { std::cout << mLastChipIndexVD[i]; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/SegmentationChip.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/SegmentationChip.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/SegmentationChip.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/SegmentationChip.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseLinkDef.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseLinkDef.h diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseParam.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseParam.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/CMakeLists.txt similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/CMakeLists.txt diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt similarity index 95% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt index cdae7c9c379fd..6dcbfc8d65d6b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt @@ -43,7 +43,7 @@ o2_add_test_root_macro(CheckTracksCA.C LABELS trk COMPILE_ONLY) o2_add_test_root_macro(CheckClusters.C - PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::Framework O2::TRKBase @@ -51,7 +51,7 @@ o2_add_test_root_macro(CheckClusters.C LABELS trk COMPILE_ONLY) o2_add_test_root_macro(postClusterSizeVsEta.C - PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::Framework O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C index c071a06516d30..f92c161e682b3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C @@ -28,11 +28,11 @@ #include #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "MathUtils/Utils.h" #include "DetectorsBase/GeometryManager.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "CommonDataFormat/InteractionRecord.h" #include "SimulationDataFormat/DigitizationContext.h" @@ -169,8 +169,8 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe TTree* digTree = (TTree*)digFile->Get("o2sim"); const int nDigitTreeEntries = digTree->GetEntries(); - std::vector*> digArr(nTotalLayers, nullptr); - std::vector*> rofRecords(nTotalLayers, nullptr); + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecords(nTotalLayers, nullptr); for (int nDigitsLayer{0}; nDigitsLayer < nTotalLayers; ++nDigitsLayer) { if (!digTree->GetBranch(Form("TRKDigit_%i", nDigitsLayer))) { break; diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C similarity index 95% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C index 7b9365dbe2011..a6adf3c6ba6aa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C @@ -12,6 +12,22 @@ /// \file CheckClusters.C /// \brief Macro to check TRK clusters and compare cluster positions to MC hit positions +#ifndef ENABLE_UPGRADES +#include +#include + +void CheckClusters(const std::string& = "o2clus_trk.root", + const std::string& = "o2sim_HitsTRK.root", + const std::string& = "o2sim_geometry.root", + const std::string& = "http://alice-ccdb.cern.ch", + long = -1, + bool = false) +{ + std::cerr << "CheckClusters requires a build with ENABLE_UPGRADES" << std::endl; +} + +#else + #if !defined(__CLING__) || defined(__ROOTCLING__) #include #include @@ -29,12 +45,12 @@ #include #include -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/Hit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" #include "ITSMFTSimulation/AlpideSimResponse.h" #include "CCDB/BasicCCDBManager.h" #include "MathUtils/Cartesian.h" @@ -53,7 +69,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", { gROOT->SetBatch(batch); - using HitVec = std::vector; + using HitVec = std::vector; using MC2HITS_map = std::unordered_map>; // maps (trackID << 32) + chipID -> hit indices // ── Chip response (for hit-segment propagation to charge-collection plane) ── @@ -151,8 +167,8 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // Read per-layer cluster branches and accumulate static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; - std::vector*> clusArrPerLayer(nLayers, nullptr); - std::vector*> rofRecVecPerLayer(nLayers, nullptr); + std::vector*> clusArrPerLayer(nLayers, nullptr); + std::vector*> rofRecVecPerLayer(nLayers, nullptr); std::vector*> patternsPerLayer(nLayers, nullptr); std::vector*> clusLabArrPerLayer(nLayers, nullptr); std::vector> patternOffsetsPerLayer(nLayers); @@ -350,7 +366,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", float clLocX{0.f}, clLocZ{0.f}; o2::trk::SegmentationChip::detectorToLocalUnchecked( cluster.row, cluster.col, clLocX, clLocZ, - cluster.subDetID, cluster.layer, cluster.disk); + cluster.subDetID, cluster.layer, cluster.layer); const float pitchRow = (cluster.subDetID == 0) ? o2::trk::SegmentationChip::PitchRowVD : o2::trk::SegmentationChip::PitchRowMLOT; @@ -377,7 +393,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.layer, -1.f, (float)cluster.subDetID, (float)cluster.row, (float)cluster.col, -1.f}; nt.Fill(data.data()); continue; @@ -405,7 +421,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", nNoMCHit++; continue; } - auto projectHitToResponsePlane = [&](const o2::trk::Hit& hit, float& hitLocX, float& hitLocZ) { + auto projectHitToResponsePlane = [&](const o2::trkft3::Hit& hit, float& hitLocX, float& hitLocZ) { const auto& gloHend = hit.GetPos(); const auto& gloHsta = hit.GetPosStart(); o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G @@ -430,7 +446,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", } }; - const o2::trk::Hit* bestHit = nullptr; + const o2::trkft3::Hit* bestHit = nullptr; float hitLocX{0.f}, hitLocZ{0.f}; float bestDist2 = std::numeric_limits::max(); for (const auto ih : hitEntry->second) { @@ -470,7 +486,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.layer, -1.f, (float)cluster.subDetID, (float)cluster.row, (float)cluster.col, pt}; nt.Fill(data.data()); } @@ -523,3 +539,5 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(info, "Output saved to CheckClusters.root and PNG files"); } + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C index 400457fc98585..ee463f304405e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C @@ -24,8 +24,8 @@ #include "TRKBase/SegmentationChip.h" #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "MathUtils/Utils.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" @@ -34,7 +34,7 @@ #include "ITSMFTSimulation/AlpideSimResponse.h" #include "CCDB/BasicCCDBManager.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #endif @@ -82,8 +82,8 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = using namespace o2::base; using namespace o2::trk; - using o2::itsmft::Digit; - using o2::trk::Hit; + using o2::trkft3::Digit; + using o2::trkft3::Hit; using o2::trk::SegmentationChip; @@ -117,7 +117,7 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = TFile* hitFile = TFile::Open(hitfile.data()); TTree* hitTree = (TTree*)hitFile->Get("o2sim"); int nevH = hitTree->GetEntries(); // hits are stored as one event per entry - std::vector*> hitArray(nevH, nullptr); + std::vector*> hitArray(nevH, nullptr); std::vector> mc2hitVec(nevH); @@ -126,8 +126,8 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = TTree* digTree = (TTree*)digFile->Get("o2sim"); int nDigitLayers = 0; - std::vector*> digArr(nTotalLayers, nullptr); - std::vector*> rofRecordsArr(nTotalLayers, nullptr); + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecordsArr(nTotalLayers, nullptr); std::vector plabelsArr(nTotalLayers, nullptr); for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C index f7917ca4203f1..708701789f483 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C @@ -41,7 +41,7 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTrack.h" #include "Steer/MCKinematicsReader.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "DetectorsBase/GeometryManager.h" @@ -161,7 +161,7 @@ void CheckTracksCA(std::string trackfile = "o2trac_trk.root", o2::base::GeometryManager::loadGeometry(); auto* gman = o2::trk::GeometryTGeo::Instance(); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); Long64_t nHitsEntries = hitsTree->GetEntries(); diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/postClusterSizeVsEta.C similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/postClusterSizeVsEta.C diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/run_test.sh similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/run_test.sh diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt similarity index 56% rename from Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt index e8460abe71dc0..0760504c4cf3f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt @@ -10,36 +10,22 @@ # or submit itself to any jurisdiction. o2_add_library(TRKSimulation - SOURCES src/Hit.cxx - src/TRKLayer.cxx - src/ChipDigitsContainer.cxx - src/ChipSimResponse.cxx + SOURCES src/TRKLayer.cxx src/Detector.cxx - src/DigiParams.cxx - src/Digitizer.cxx - src/FT3Layer.cxx - src/FT3Module.cxx src/TRKServices.cxx - src/DPLDigitizerParam.cxx src/VDLayer.cxx src/VDGeometryBuilder.cxx PUBLIC_LINK_LIBRARIES O2::TRKBase + O2::TRKFT3Simulation + O2::DataFormatsTRKFT3 O2::ITSMFTSimulation O2::DetectorsRaw O2::SimulationDataFormat) o2_target_root_dictionary(TRKSimulation - HEADERS include/TRKSimulation/Hit.h - include/TRKSimulation/ChipDigitsContainer.h - include/TRKSimulation/ChipSimResponse.h - include/TRKSimulation/DigiParams.h - include/TRKSimulation/Digitizer.h - include/TRKSimulation/Detector.h - include/TRKSimulation/FT3Layer.h - include/TRKSimulation/FT3Module.h + HEADERS include/TRKSimulation/Detector.h include/TRKSimulation/TRKLayer.h include/TRKSimulation/TRKServices.h include/TRKSimulation/VDLayer.h include/TRKSimulation/VDGeometryBuilder.h - include/TRKSimulation/VDSensorRegistry.h - include/TRKSimulation/DPLDigitizerParam.h) + include/TRKSimulation/VDSensorRegistry.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h similarity index 68% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h index 444905a1bf18a..a7972d14191a6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h @@ -13,11 +13,10 @@ #define ALICEO2_TRK_DETECTOR_H #include "DetectorsBase/Detector.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKSimulation/TRKLayer.h" #include "TRKSimulation/TRKServices.h" -#include "TRKSimulation/FT3Layer.h" #include "TRKBase/GeometryTGeo.h" #include @@ -44,9 +43,9 @@ class Detector : public o2::base::DetImpl void ConstructGeometry() override; - o2::trk::Hit* addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, - unsigned char startStatus, unsigned char endStatus); + o2::trkft3::Hit* addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus); // Mandatory overrides void BeginPrimary() override { ; } @@ -60,7 +59,7 @@ class Detector : public o2::base::DetImpl void Reset() override; // Custom member functions - std::vector* getHits(int iColl) const + std::vector* getHits(int iColl) const { if (!iColl) { return mHits; @@ -69,7 +68,6 @@ class Detector : public o2::base::DetImpl } void configMLOT(); - void configFT3ScopingV3(); void configFromFile(std::string fileName = "alice3_TRK_layout.txt"); void configToFile(std::string fileName = "alice3_TRK_layout.txt"); @@ -77,23 +75,20 @@ class Detector : public o2::base::DetImpl void createMaterials(); void createGeometry(); - static constexpr int kForward = 0; - static constexpr int kBackward = 1; - private: int mNumberOfVolumes; int mNumberOfVolumesVD; // Transient data about track passing the sensor struct TrackData { - bool mHitStarted; // hit creation started - unsigned char mTrkStatusStart; // track status flag - TLorentzVector mPositionStart; // position at entrance - TLorentzVector mMomentumStart; // momentum - double mEnergyLoss; // energy loss - } mTrackData; //! transient data - GeometryTGeo* mGeometryTGeo; //! - std::vector* mHits; // Derived from ITSMFT + bool mHitStarted; // hit creation started + unsigned char mTrkStatusStart; // track status flag + TLorentzVector mPositionStart; // position at entrance + TLorentzVector mMomentumStart; // momentum + double mEnergyLoss; // energy loss + } mTrackData; //! transient data + GeometryTGeo* mGeometryTGeo; //! + std::vector* mHits; std::vector> mLayers; TRKServices mServices; // Houses the services of the TRK, but not the Iris tracker @@ -103,21 +98,12 @@ class Detector : public o2::base::DetImpl void defineSensitiveVolumes(); protected: - std::array, 2> mFT3LayerName; // Two sets of layer (disc) names, one per direction (forward/backward) std::vector mSensorID; //! layer identifiers std::vector mSensorName; //! layer names - std::array, 2> mFT3Layers; // Two sets of layers (discs), one per direction (forward/backward) public: static constexpr Int_t sNumberVDPetalCases = 4; //! Number of VD petals int getNumberOfLayers() const { return mLayers.size(); } //! Number of TRK layers - int getNumberOfFT3Layers() const - { - if (mFT3LayerName[kBackward].size() != mFT3LayerName[kForward].size()) { - LOG(fatal) << "Number of layers in the two directions are different! Returning 0."; - } - return mFT3LayerName[kBackward].size(); - } void Print(FairVolume* vol, int volume, int subDetID, int layer, int stave, int halfstave, int mod, int chip, int chipID) const; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDLayer.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDLayer.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx similarity index 71% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx index 493b6b024f5c0..eead9a7571fd3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx @@ -15,7 +15,7 @@ #include "TRKBase/Specs.h" #include "TRKBase/TRKBaseParam.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKSimulation/VDGeometryBuilder.h" #include "TRKSimulation/VDSensorRegistry.h" #include @@ -27,7 +27,7 @@ #include #include -using o2::trk::Hit; +using o2::trkft3::Hit; namespace o2 { @@ -42,14 +42,14 @@ float getDetLengthFromEta(const float eta, const float radius) Detector::Detector() : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } Detector::Detector(bool active) : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { auto& trkPars = TRKBaseParam::Instance(); @@ -57,9 +57,6 @@ Detector::Detector(bool active) configFromFile(trkPars.configFile); } else { configMLOT(); - if (!trkPars.disableFT3) { - configFT3ScopingV3(); - } configToFile(); configServices(); } @@ -73,7 +70,7 @@ Detector::Detector(bool active) Detector::Detector(const Detector& other) : o2::base::DetImpl(other), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } @@ -135,56 +132,6 @@ void Detector::configMLOT() } } -void Detector::configFT3ScopingV3() -{ - // Build the FT3 detector according to v3 layout - // https://indico.cern.ch/event/1596309/contributions/6728167/attachments/3190117/5677220/2025-12-10-AW-ALICE3planning.pdf - // Middle disks inner radius 10 cm - // Outer disks inner radius 20 cm - - LOG(info) << "Building FT3 Detector: v3 scoping version"; - - const int numberOfLayers = 6; - const float sensorThickness = 30.e-4; - const float layersx2X0 = 1.e-2; - using LayerConfig = std::array; // {z_layer, r_in, r_out, Layerx2X0} - const std::array layersConfigCSide{LayerConfig{77., 10.0, 35., layersx2X0}, - LayerConfig{100., 10.0, 35., layersx2X0}, - LayerConfig{122., 10.0, 35., layersx2X0}, - LayerConfig{150., 20.0, 68.f, layersx2X0}, - LayerConfig{180., 20.0, 68.f, layersx2X0}, - LayerConfig{220., 20.0, 68.f, layersx2X0}}; - - const std::array layersConfigASide{LayerConfig{77., 10.0, 35., layersx2X0}, - LayerConfig{100., 10.0, 35., layersx2X0}, - LayerConfig{122., 10.0, 35., layersx2X0}, - LayerConfig{150., 20.0, 68.f, layersx2X0}, - LayerConfig{180., 20.0, 68.f, layersx2X0}, - LayerConfig{220., 20.0, 68.f, layersx2X0}}; - const std::array enabled{true, true, true, true, true, true}; // To enable or disable layers for debug purpose - - for (int direction : {kBackward, kForward}) { - mFT3LayerName[direction].clear(); - const std::array& layerConfig = (direction == kBackward) ? layersConfigCSide : layersConfigASide; - for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { - if (!enabled[layerNumber]) { - continue; - } - const std::string directionName = std::to_string(direction); - const std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); - mFT3LayerName[direction].push_back(layerName.c_str()); - const float z = layerConfig[layerNumber][0]; - const float rIn = layerConfig[layerNumber][1]; - const float rOut = layerConfig[layerNumber][2]; - const float x0 = layerConfig[layerNumber][3]; - LOG(info) << "buildFT3ScopingV3 -> Adding Layer " << layerNumber << "/" << numberOfLayers << " " << layerName << " at z = " << z; - // Add layers - const bool isMiddleLayer = layerNumber < 3; - auto& thisLayer = mFT3Layers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); - } - } -} - void Detector::configFromFile(std::string fileName) { // Override the default geometry if config file provided @@ -430,40 +377,6 @@ void Detector::createGeometry() mServices.excavateFromVacuum("IRIS_CUTOUTsh"); mServices.registerVacuum(vTRK); - - // Place forward tracking discs - - TGeoVolume* A3IPvac = gGeoManager->GetVolume("OUT_PIPEVACUUM"); - if (!A3IPvac) { - LOG(info) << "Running simulation with no beam pipe."; - } - - // TODO: disambiquate layer/disk below - // This will need to adapt to the new scheme - if (!A3IPvac) { - for (int direction : {kBackward, kForward}) { // Backward layers at mLayers[0]; Forward layers at mLayers[1] - const std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << " Creating FT3 without beampipe " << directionString << " layers:"; - for (int iLayer = 0; iLayer < mFT3Layers[direction].size(); iLayer++) { - mFT3Layers[direction][iLayer].createLayer(vTRK); - } - } - } else { // If beampipe is enabled append inner disks to beampipe filling volume, this should be temporary. - TGeoVolume* volIFT3 = new TGeoVolumeAssembly(GeometryTGeo::getFT3InnerVolPattern()); - for (int direction : {kBackward, kForward}) { - const std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << " Creating FT3 " << directionString << " layers:"; - for (int iLayer = 0; iLayer < mFT3Layers[direction].size(); iLayer++) { - LOG(info) << " Creating " << directionString << " layer " << iLayer; - if (mFT3Layers[direction][iLayer].getIsInMiddleLayer()) { // ML disks - mFT3Layers[direction][iLayer].createLayer(volIFT3); - } else { - mFT3Layers[direction][iLayer].createLayer(vTRK); - } - } - } - A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); - } } void Detector::InitializeO2Detector() @@ -513,69 +426,6 @@ void Detector::defineSensitiveVolumes() LOGP(info, "Adding TRK Sensitive Volume {}", v->GetName()); AddSensitiveVolume(v); } - - // Add FT3 sensitive volumes - // TODO: do we need to loop over all volumes in our code, or can we use the geomanager? - // Get the flat list of ALL volumes present in the geometry - TObjArray* allVolumes = geoManager->GetListOfVolumes(); - int nVolumes = allVolumes->GetEntriesFast(); - - LOG(info) << "Adding FT3 Sensitive Volumes by iterating over all geometry volumes..."; - - for (int direction : {kBackward, kForward}) { - for (int iLayer = 0; iLayer < getNumberOfFT3Layers(); iLayer++) { - int iSens = 0; - - // Build the "signatures" (prefixes) of the names for the various layouts for this specific layer and direction: - - // 1. Trapezoidal/Cylindrical (format: FT3Sensor__) - std::string sig1 = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer); - - // 2. Segmented front/back (format: FT3Sensor_front___...) - std::string sig2 = "FT3Sensor_front_" + std::to_string(iLayer) + "_" + std::to_string(direction); - std::string sig3 = "FT3Sensor_back_" + std::to_string(iLayer) + "_" + std::to_string(direction); - - // 3. SegmentedStave (format: FT3Sensor___...) - // Add the trailing underscore to avoid confusing it with sig1 - std::string sig4 = "FT3Sensor_Active_" + std::to_string(direction) + "_" + std::to_string(iLayer) + "_"; - - // Iterate over all existing volumes to find matches - for (int i = 0; i < nVolumes; ++i) { - TGeoVolume* v = (TGeoVolume*)allVolumes->At(i); - std::string vName = v->GetName(); - - // Explicitly exclude the inactive silicon regions created in FT3Module - if (vName.find("Inactive") != std::string::npos || vName.find("inactive") != std::string::npos) { - continue; - } - - // Check if the volume name matches one of our active sensors - bool isMatch = false; - if (vName == sig1) { - isMatch = true; // Exact match for Trapezoidal/Cylindrical layouts - } else if (vName.find(sig2) == 0 || vName.find(sig3) == 0 || vName.find(sig4) == 0) { - isMatch = true; // Prefix match for Segmented and SegmentedStave layouts - } - - if (isMatch) { - AddSensitiveVolume(v); - /* - int volID = gMC ? TVirtualMC::GetMC()->VolId(vName.c_str()) : 0; - if (volID > 0) { - mActiveSensorMap[volID] = iLayer; - } - */ - iSens++; - } - } - - if (iSens == 0) { - LOG(error) << "NO sensitive volume found for FT3 direction " << direction << ", layer " << iLayer; - } else { - LOG(info) << iSens << " sensitive volume(s) added for FT3 direction " << direction << " layer " << iLayer; - } - } - } } void Detector::EndOfEvent() { Reset(); } @@ -627,6 +477,10 @@ bool Detector::ProcessHits(FairVolume* vol) ++volume; /// there are 44 volumes, 36 for the VD (1 for each sensing element) and 8 for the MLOT (1 for each layer) } + if (notSens) { + return kFALSE; // RS: can this happen? This method must be called for sensors only? + } + if (volume < mNumberOfVolumesVD) { subDetID = 0; // VD. For the moment each "chip" is a volume./// TODO: change this logic once the naming scheme is changed } else { @@ -634,21 +488,10 @@ bool Detector::ProcessHits(FairVolume* vol) layer = volume - mNumberOfVolumesVD; } - if (strstr(vol->GetName(), "FT3Sensor_Active") || strstr(vol->GetName(), "FT3Chip")) { - subDetID = 2; - notSens = false; - } - - // TODO: add corresponding logic for disks. I think Ruben is right; this is only called for active volumes! - if (notSens) { - LOG(info) << "ProcessHit called for insensitive volume " << vol->GetName(); - return kFALSE; // RS: can this happen? This method must be called for sensors only? - } - // Is it needed to keep a track reference when the outer ITS volume is encountered? auto stack = (o2::data::Stack*)fMC->GetStack(); // if (fMC->IsTrackExiting() && (lay == 0 || lay == mLayers.size() - 1)) { - if (fMC->IsTrackExiting() && subDetID < 2 && InsideFirstOrLastLayer(vol->GetName())) { + if (fMC->IsTrackExiting() && InsideFirstOrLastLayer(vol->GetName())) { // Keep the track refs for the innermost and outermost layers only o2::TrackReference tr(*fMC, GetDetId()); tr.setTrackID(stack->GetCurrentTrackNumber()); @@ -721,9 +564,7 @@ bool Detector::ProcessHits(FairVolume* vol) } } } /// if VD, for the moment the volume is the "chipID" so no need to retrieve other elments - else if (subDetID == 2) { - mGeometryTGeo->extractChipIdsFT3(vol->GetName(), layer, stave, chip); - } + unsigned short chipID = mGeometryTGeo->getChipIndex(subDetID, volume, layer, stave, halfstave, mod, chip); // Print(vol, volume, subDetID, layer, stave, halfstave, mod, chip, chipID); @@ -743,9 +584,9 @@ bool Detector::ProcessHits(FairVolume* vol) return true; } -o2::trk::Hit* Detector::addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, - unsigned char endStatus) +o2::trkft3::Hit* Detector::addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, + unsigned char endStatus) { mHits->emplace_back(trackID, detID, startPos, endPos, startMom, startE, endTime, eLoss, startStatus, endStatus); return &(mHits->back()); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h similarity index 70% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h index 7a1d5d6d00f94..fa929c590adc2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h @@ -15,11 +15,6 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::Hit + ; -#pragma link C++ class std::vector < o2::trk::Hit> + ; - -#pragma link C++ class o2::trk::FT3Layer + ; - #pragma link C++ class o2::trk::TRKCylindricalLayer + ; #pragma link C++ class o2::trk::TRKSegmentedLayer + ; #pragma link C++ class o2::trk::TRKMLLayer + ; @@ -28,10 +23,4 @@ #pragma link C++ class o2::trk::TRKServices + ; #pragma link C++ class o2::trk::Detector + ; #pragma link C++ class o2::base::DetImpl < o2::trk::Detector> + ; -#pragma link C++ class o2::trk::Digitizer + ; -#pragma link C++ class o2::trk::ChipSimResponse + ; - -#pragma link C++ class o2::trk::DPLDigitizerParam < o2::detectors::DetID::TRK> + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::DPLDigitizerParam < o2::detectors::DetID::TRK>> + ; - #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDGeometryBuilder.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDGeometryBuilder.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDLayer.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDLayer.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt similarity index 92% rename from Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt index 6e3437c9d841b..6cd471f6f74de 100644 --- a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt @@ -9,8 +9,6 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -add_subdirectory(base) -add_subdirectory(macros) add_subdirectory(simulation) add_subdirectory(reconstruction) add_subdirectory(workflow) diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt index 45ce53ba7c3a3..fab32edc7c819 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt @@ -20,7 +20,7 @@ o2_add_library(TRKReconstruction PUBLIC_LINK_LIBRARIES Microsoft.GSL::GSL O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::TRKBase nlohmann_json::nlohmann_json diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h similarity index 84% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h index 3d30eb5068efe..0e709476d09fd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h @@ -19,11 +19,11 @@ // | *| #define _ALLOW_DIAGONAL_TRK_CLUSTERS_ -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/Digit.h" #include "DataFormatsITSMFT/ClusterPattern.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "DetectorsCommonDataFormats/DetID.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -41,15 +41,18 @@ namespace o2::trk class GeometryTGeo; +template class Clusterer { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 clusterers are supported"); + public: static constexpr int MaxLabels = 10; static constexpr int MaxHugeClusWarn = 5; - using Digit = o2::itsmft::Digit; - using DigROFRecord = o2::itsmft::ROFRecord; - using DigMC2ROFRecord = o2::itsmft::MC2ROFRecord; + using Digit = o2::trkft3::Digit; + using DigROFRecord = o2::trkft3::ROFRecord; + using ClusterType = o2::trkft3::Cluster; using ClusterTruth = o2::dataformats::MCTruthContainer; using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; using Label = o2::MCCompLabel; @@ -87,7 +90,7 @@ class Clusterer //---------------------------------------------- struct ClustererThread { - Clusterer* parent = nullptr; + Clusterer* parent = nullptr; // column buffers (pre-cluster state); extra sentinel entries at [0] and [size-1] int* column1 = nullptr; int* column2 = nullptr; @@ -106,7 +109,7 @@ class Clusterer std::vector> pixArrBuff; ///< (row,col) pixel buffer for pattern // per-thread output (accumulated, then merged back by caller) - std::vector clusters; + std::vector clusters; std::vector patterns; ClusterTruth labels; @@ -144,19 +147,19 @@ class Clusterer const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, GeometryTGeo* geom); void processChip(gsl::span digits, int chipFirst, int chipN, - std::vector* clustersOut, std::vector* patternsOut, + std::vector* clustersOut, std::vector* patternsOut, const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, GeometryTGeo* geom); void streamCluster(const BBox& bbox, const std::vector>& pixbuf, uint32_t totalCharge, bool doLabels, int nlab, - uint16_t chipID, int subDetID, int layer, int disk); + uint16_t chipID, int subDetID, int layer); ~ClustererThread() { delete[] column1; delete[] column2; } - explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} + explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} ClustererThread(const ClustererThread&) = delete; ClustererThread& operator=(const ClustererThread&) = delete; }; @@ -164,15 +167,13 @@ class Clusterer virtual void process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr); + ClusterTruth* clusterLabels = nullptr); - static o2::math_utils::Point3D getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + static o2::math_utils::Point3D getClusterLocalCoordinates(const ClusterType& cluster, const uint8_t* patt, float yPlaneMLOT = 0.f) noexcept; protected: @@ -181,6 +182,9 @@ class Clusterer std::vector mSortIdx; ///< reusable per-ROF sort buffer }; +using TRKClusterer = Clusterer; +using FT3Clusterer = Clusterer; + } // namespace o2::trk #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h similarity index 76% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h index 37a148aa78afb..f207a6dc0e24c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -26,18 +26,16 @@ namespace o2::trk class GeometryTGeo; -class ClustererACTS : public Clusterer +class ClustererACTS : public TRKClusterer { public: void process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr) override; + ClusterTruth* clusterLabels = nullptr) override; private: }; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx similarity index 80% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx index d60d6900657ba..0bd64be5efbc4 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx @@ -23,8 +23,9 @@ namespace o2::trk { //__________________________________________________ -o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, - float yPlaneMLOT) noexcept +template +o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const typename Clusterer::ClusterType& cluster, const uint8_t* patt, + float yPlaneMLOT) noexcept { const uint8_t rowSpan = *patt++; const uint8_t colSpan = *patt++; @@ -49,7 +50,7 @@ o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Clust float x{0.f}, y{0.f}, z{0.f}; SegmentationChip::detectorToLocalUnchecked(cluster.row, cluster.col, x, z, - cluster.subDetID, cluster.layer, cluster.disk); + cluster.subDetID, cluster.layer, cluster.layer); const float pitchRow = (cluster.subDetID == 0) ? SegmentationChip::PitchRowVD : SegmentationChip::PitchRowMLOT; const float pitchCol = (cluster.subDetID == 0) ? SegmentationChip::PitchColVD : SegmentationChip::PitchColMLOT; @@ -68,15 +69,14 @@ o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Clust } //__________________________________________________ -void Clusterer::process(gsl::span digits, - gsl::span digitROFs, - std::vector& clusters, - std::vector& patterns, - std::vector& clusterROFs, - const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) +template +void Clusterer::process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels, + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); @@ -127,23 +127,17 @@ void Clusterer::process(gsl::span digits, clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, static_cast(clusters.size()) - outFirst); } - - if (clusterMC2ROFs && !digMC2ROFs.empty()) { - clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); - for (const auto& in : digMC2ROFs) { - clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); - } - } } //__________________________________________________ -void Clusterer::ClustererThread::processChip(gsl::span digits, - int chipFirst, int chipN, - std::vector* clustersOut, - std::vector* patternsOut, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::processChip(gsl::span digits, + int chipFirst, int chipN, + std::vector* clustersOut, + std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { // chipFirst and chipN are relative to mSortIdx (i.e. mSortIdx[chipFirst..chipFirst+chipN-1] // are the global digit indices for this chip, already sorted by col then row). @@ -176,7 +170,8 @@ void Clusterer::ClustererThread::processChip(gsl::span digits, } //__________________________________________________ -void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom) +template +void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom) { const uint16_t chipID = digits[first].getChipIndex(); @@ -213,7 +208,8 @@ void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_ } //__________________________________________________ -void Clusterer::ClustererThread::updateChip(gsl::span digits, uint32_t ip) +template +void Clusterer::ClustererThread::updateChip(gsl::span digits, uint32_t ip) { const auto& pix = digits[ip]; uint16_t row = pix.getRow(); @@ -268,10 +264,11 @@ void Clusterer::ClustererThread::updateChip(gsl::span digits, uint3 } //__________________________________________________ -void Clusterer::ClustererThread::finishChip(gsl::span digits, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::finishChip(gsl::span digits, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { const uint16_t chipID = digits[pixels[0].second].getChipIndex(); @@ -314,16 +311,15 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } // Determine geometry info - int subDetID = -1, layer = -1, disk = -1; + int subDetID = -1, layer = -1; if (geom) { subDetID = geom->getSubDetID(chipID); layer = geom->getLayer(chipID); - disk = geom->getDisk(chipID); } const bool doLabels = (labelsClusPtr != nullptr); if (bbox.isAcceptableSize()) { - streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer, disk); + streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer); } else { // Huge cluster: split into MaxRowSpan x MaxColSpan tiles (same as ITS3) auto warnLeft = MaxHugeClusWarn - parent->mNHugeClus; @@ -349,7 +345,7 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } } if (!subPix.empty()) { - streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer, disk); + streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer); } bboxT.rowMin = bboxT.rowMax + 1; } while (bboxT.rowMin <= bbox.rowMax); @@ -361,10 +357,11 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } //__________________________________________________ -void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, uint32_t hit, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, uint32_t hit, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { const auto& d = digits[hit]; const uint16_t chipID = d.getChipIndex(); @@ -385,7 +382,7 @@ void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span patterns.emplace_back(1); patterns.emplace_back(0x80); - Cluster cluster; + ClusterType cluster; cluster.chipID = chipID; cluster.row = row; cluster.col = col; @@ -393,17 +390,17 @@ void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span if (geom) { cluster.subDetID = geom->getSubDetID(chipID); cluster.layer = geom->getLayer(chipID); - cluster.disk = geom->getDisk(chipID); } clusters.emplace_back(cluster); } //__________________________________________________ -void Clusterer::ClustererThread::streamCluster(const BBox& bbox, - const std::vector>& pixbuf, - uint32_t totalCharge, - bool doLabels, int nlab, - uint16_t chipID, int subDetID, int layer, int disk) +template +void Clusterer::ClustererThread::streamCluster(const BBox& bbox, + const std::vector>& pixbuf, + uint32_t totalCharge, + bool doLabels, int nlab, + uint16_t chipID, int subDetID, int layer) { if (doLabels) { const auto cnt = static_cast(clusters.size()); @@ -427,19 +424,19 @@ void Clusterer::ClustererThread::streamCluster(const BBox& bbox, int nBytes = (rowSpanW * colSpanW + 7) / 8; patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes); - Cluster cluster; + ClusterType cluster; cluster.chipID = chipID; cluster.row = bbox.rowMin; cluster.col = bbox.colMin; cluster.size = static_cast(pixbuf.size()); cluster.subDetID = static_cast(subDetID); cluster.layer = static_cast(layer); - cluster.disk = static_cast(disk); clusters.emplace_back(cluster); } //__________________________________________________ -void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) +template +void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) { if (nfilled >= MaxLabels) { return; @@ -462,4 +459,7 @@ void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitT } } +template class Clusterer; +template class Clusterer; + } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx similarity index 94% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx index 30ab503b7e250..86ac9f508a042 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx @@ -158,13 +158,11 @@ Cluster2D gencluster(int x0, int y0, int x1, int y1, RNG& rng, //__________________________________________________ void ClustererACTS::process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); @@ -326,7 +324,7 @@ void ClustererACTS::process(gsl::span digits, } // Create O2 cluster for this tile - o2::trk::Cluster cluster; + o2::trkft3::TRKCluster cluster; cluster.chipID = chipID; cluster.row = tileRowMin; cluster.col = tileColMin; @@ -334,7 +332,6 @@ void ClustererACTS::process(gsl::span digits, if (geom) { cluster.subDetID = static_cast(geom->getSubDetID(chipID)); cluster.layer = static_cast(geom->getLayer(chipID)); - cluster.disk = static_cast(geom->getDisk(chipID)); } clusters.emplace_back(cluster); } @@ -367,7 +364,7 @@ void ClustererACTS::process(gsl::span digits, } // Create O2 cluster - o2::trk::Cluster cluster; + o2::trkft3::TRKCluster cluster; cluster.chipID = chipID; cluster.row = rowMin; cluster.col = colMin; @@ -375,7 +372,6 @@ void ClustererACTS::process(gsl::span digits, if (geom) { cluster.subDetID = static_cast(geom->getSubDetID(chipID)); cluster.layer = static_cast(geom->getLayer(chipID)); - cluster.disk = static_cast(geom->getDisk(chipID)); } clusters.emplace_back(cluster); } @@ -386,11 +382,4 @@ void ClustererACTS::process(gsl::span digits, clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, static_cast(clusters.size()) - outFirst); } - - // if (clusterMC2ROFs && !digMC2ROFs.empty()) { - // clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); - // for (const auto& in : digMC2ROFs) { - // clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); - // } - // } } diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt new file mode 100644 index 0000000000000..ce9b79217997e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright 2019-2026 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(TRKFT3Simulation + SOURCES src/ChipDigitsContainer.cxx + src/ChipSimResponse.cxx + src/DigiParams.cxx + src/Digitizer.cxx + src/DPLDigitizerParam.cxx + PUBLIC_LINK_LIBRARIES O2::TRKBase + O2::FT3Base + O2::DataFormatsTRKFT3 + O2::ITSMFTSimulation + O2::DetectorsRaw + O2::SimulationDataFormat) + +o2_target_root_dictionary(TRKFT3Simulation + HEADERS include/TRKFT3Simulation/ChipDigitsContainer.h + include/TRKFT3Simulation/ChipSimResponse.h + include/TRKFT3Simulation/DigiParams.h + include/TRKFT3Simulation/Digitizer.h + include/TRKFT3Simulation/DPLDigitizerParam.h + LINKDEF src/TRKFT3SimulationLinkDef.h) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h new file mode 100644 index 0000000000000..10c55e6163846 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_TRK_CHIPDIGITSCONTAINER_ +#define ALICEO2_TRK_CHIPDIGITSCONTAINER_ + +#include "ITSMFTBase/SegmentationAlpide.h" +#include "ITSMFTSimulation/ChipDigitsContainer.h" +#include "TRKBase/SegmentationChip.h" +#include "TRKBase/Specs.h" +#include "TRKFT3Simulation/DigiParams.h" +#include +#include + +namespace o2::trkft3 +{ + +class ChipDigitsContainer : public o2::itsmft::ChipDigitsContainer +{ + public: + explicit ChipDigitsContainer(UShort_t idx = 0); + + using Segmentation = o2::trk::SegmentationChip; + + /// Get global ordering key made of readout frame, column and row + static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col) + { + return (static_cast(roframe) << (8 * sizeof(UInt_t))) + (static_cast(col) << (8 * sizeof(Short_t))) + row; + } + + /// Adds noise digits, deleted the one using the itsmft::DigiParams interface + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::itsmft::DigiParams* params, int maxRows = o2::itsmft::SegmentationAlpide::NRows, int maxCols = o2::itsmft::SegmentationAlpide::NCols) = delete; + template + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trkft3::DigiParams* params, int subDetID, int layer); + + ClassDefNV(ChipDigitsContainer, 1); +}; + +} // namespace o2::trkft3 + +template +void o2::trkft3::ChipDigitsContainer::addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trkft3::DigiParams* params, int subDetID, int layer) +{ + UInt_t row = 0; + UInt_t col = 0; + Int_t nhits = 0; + float mean = 0.f; + int nel = 0; + int maxRows = 0; + int maxCols = 0; + + if (subDetID == 0) { + maxRows = o2::trk::constants::VD::petal::layer::nRows[layer]; + maxCols = o2::trk::constants::VD::petal::layer::nCols; + } else { + maxRows = o2::trk::constants::moduleMLOT::chip::nRows; + maxCols = o2::trk::constants::moduleMLOT::chip::nCols; + } + mean = params->getNoisePerPixel() * maxRows * maxCols; + nel = static_cast(params->getChargeThreshold() * 1.1); + + LOG(debug) << "Adding noise for chip " << mChipIndex << " with mean " << mean << " and charge " << nel; + + for (UInt_t rof = rofMin; rof <= rofMax; rof++) { + nhits = gRandom->Poisson(mean); + for (Int_t i = 0; i < nhits; ++i) { + row = gRandom->Integer(maxRows); + col = gRandom->Integer(maxCols); + LOG(debug) << "Generated noise hit at ROF " << rof << ", row " << row << ", col " << col; + if (mNoiseMap && mNoiseMap->isNoisy(mChipIndex, row, col)) { + continue; + } + if (mDeadChanMap && mDeadChanMap->isNoisy(mChipIndex, row, col)) { + continue; + } + auto key = getOrderingKey(rof, row, col); + if (!findDigit(key)) { + addDigit(key, rof, row, col, nel, o2::MCCompLabel(true)); + } + } + } +} + +#endif // ALICEO2_TRK_CHIPDIGITSCONTAINER_ diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h index 29147997f66bf..99f966f0c608a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h @@ -16,7 +16,7 @@ namespace o2 { -namespace trk +namespace trkft3 { class ChipSimResponse : public o2::itsmft::AlpideSimResponse @@ -31,7 +31,7 @@ class ChipSimResponse : public o2::itsmft::AlpideSimResponse ClassDef(ChipSimResponse, 1); }; -} // namespace trk +} // namespace trkft3 } // namespace o2 #endif // ALICEO2_TRKSIMULATION_CHIPSIMRESPONSE_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h index de839b27aefee..f4b37142b6019 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h @@ -19,7 +19,7 @@ namespace o2 { -namespace trk +namespace trkft3 { template struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper> { @@ -63,7 +63,7 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper DPLDigitizerParam DPLDigitizerParam::sInstance; -} // namespace trk +} // namespace trkft3 } // namespace o2 #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h similarity index 72% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h index d7d1ea28bfcf7..004bf6fb40759 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h @@ -16,13 +16,14 @@ #define ALICEO2_TRK_DIGIPARAMS_H #include +#include +#include #include +#include "DetectorsCommonDataFormats/DetID.h" #include "ITSMFTSimulation/AlpideSignalTrapezoid.h" #include "ITSMFTSimulation/AlpideSimResponse.h" #include "TRKBase/AlmiraParam.h" -#include "TRKBase/TRKBaseParam.h" -#include "TRKBase/GeometryTGeo.h" //////////////////////////////////////////////////////////// // // @@ -36,20 +37,41 @@ namespace o2 { -namespace trk +namespace trkft3 { class ChipSimResponse; +namespace detail +{ +template +struct DigiParamsLayerTraits; + +template <> +struct DigiParamsLayerTraits { + static constexpr size_t MaxLayers = o2::trk::AlmiraParam::getNLayers(); +}; + +template <> +struct DigiParamsLayerTraits { + static constexpr size_t MaxLayers = 20; // two FT3 sides with the default 10 layers per side +}; +} // namespace detail + +template class DigiParams { + static_assert(DetIDV == o2::detectors::DetID::TRK || DetIDV == o2::detectors::DetID::FT3, "only TRK and FT3 digit parameters are supported"); using SignalShape = o2::itsmft::AlpideSignalTrapezoid; + static constexpr size_t MaxLayers = detail::DigiParamsLayerTraits::MaxLayers; public: DigiParams(); ~DigiParams() = default; + static constexpr size_t getMaxLayers() { return MaxLayers; } + void setNoisePerPixel(float v) { mNoisePerPixel = v; } float getNoisePerPixel() const { return mNoisePerPixel; } @@ -92,7 +114,7 @@ class DigiParams bool isTimeOffsetSet() const { return mTimeOffset > -infTime; } - const o2::trk::ChipSimResponse* getResponse() const { return mResponse.get(); } + const o2::trkft3::ChipSimResponse* getResponse() const { return mResponse.get(); } void setResponse(const o2::itsmft::AlpideSimResponse*); const SignalShape& getSignalShape() const { return mSignalShape; } @@ -115,22 +137,25 @@ class DigiParams float mIBVbb = 0.0; ///< back bias absolute value for ITS Inner Barrel (in Volt) float mOBVbb = 0.0; ///< back bias absolute value for ITS Outter Barrel (in Volt) - std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer - std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer - std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer - std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer - std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer + std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer + std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer + std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer + std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer + std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer o2::itsmft::AlpideSignalTrapezoid mSignalShape; ///< signal timeshape parameterization - std::unique_ptr mResponse; //!< pointer on external response + std::unique_ptr mResponse; //!< pointer on external response // auxiliary precalculated parameters - std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer + std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer // ClassDef(DigiParams, 2); }; -} // namespace trk + +using TRKDigiParams = DigiParams; +using FT3DigiParams = DigiParams; +} // namespace trkft3 } // namespace o2 #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h similarity index 62% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h index a0e5902cc5cde..22c61352c03ee 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h @@ -10,52 +10,57 @@ // or submit itself to any jurisdiction. /// \file Digitizer.h -/// \brief Definition of the TRK digitizer -#ifndef ALICEO2_TRK_DIGITIZER_H -#define ALICEO2_TRK_DIGITIZER_H +/// \brief Definition of the TRK/FT3 digitizer +#ifndef ALICEO2_TRKFT3_DIGITIZER_H +#define ALICEO2_TRKFT3_DIGITIZER_H #include #include #include +#include #include "Rtypes.h" // for Digitizer::Class #include "TObject.h" // for TObject -#include "TRKSimulation/ChipSimResponse.h" -#include "TRKSimulation/ChipDigitsContainer.h" +#include "TRKFT3Simulation/ChipSimResponse.h" +#include "TRKFT3Simulation/ChipDigitsContainer.h" -#include "TRKSimulation/DigiParams.h" -#include "TRKSimulation/Hit.h" +#include "TRKFT3Simulation/DigiParams.h" +#include "DataFormatsTRKFT3/Hit.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "FT3Base/GeometryTGeo.h" #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "CommonDataFormat/InteractionRecord.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#endif -namespace o2::trk +namespace o2::trkft3 { +template class Digitizer { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 digitizers are supported"); + using GeometryTGeo = std::conditional_t; using ExtraDig = std::vector; ///< container for extra contributions to PreDigits public: - void setDigits(std::vector* dig) { mDigits = dig; } + void setDigits(std::vector* dig) { mDigits = dig; } void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } - void setROFRecords(std::vector* rec) { mROFRecords = rec; } + void setROFRecords(std::vector* rec) { mROFRecords = rec; } void setResponseName(const std::string& name) { mRespName = name; } - o2::trk::DigiParams& getParams() { return (o2::trk::DigiParams&)mParams; } - const o2::trk::DigiParams& getParams() const { return mParams; } + o2::trkft3::DigiParams& getParams() { return mParams; } + const o2::trkft3::DigiParams& getParams() const { return mParams; } void init(); - const o2::trk::ChipSimResponse* getChipResponse(int chipID); + const o2::trkft3::ChipSimResponse* getChipResponse(int chipID); /// Steer conversion of hits to digits - void process(const std::vector* hits, int evID, int srcID, int layer); + void process(const std::vector* hits, int evID, int srcID, int layer); void setEventTime(const o2::InteractionTimeRecord& irt, int layer); void fillOutputContainer(uint32_t maxFrame, int layer); @@ -69,12 +74,11 @@ class Digitizer mExtraBuff.clear(); } - const o2::trk::DigiParams& getDigitParams() const { return mParams; } + const o2::trkft3::DigiParams& getDigitParams() const { return mParams; } - // provide the common trk::GeometryTGeo to access matrices and segmentation - void setGeometry(const o2::trk::GeometryTGeo* gm) + void setGeometry(const GeometryTGeo* gm) { - LOG(info) << "trk::Digizer set geom"; + LOG(info) << "trkft3::Digitizer set geom"; mGeometry = gm; } @@ -89,8 +93,8 @@ class Digitizer void setDeadChannelsMap(const o2::itsmft::NoiseMap* mp) { mDeadChanMap = mp; } private: - void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); - void registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, + void processHit(const o2::trkft3::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); + void registerDigits(o2::trkft3::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer); ExtraDig* getExtraDigBuffer(uint32_t roFrame) @@ -112,9 +116,9 @@ class Digitizer int getNCols(int subDetID, int layer) { if (subDetID == 0) { // VD - return constants::VD::petal::layer::nCols; + return o2::trk::constants::VD::petal::layer::nCols; } else if (subDetID == 1 || subDetID == 2) { // ML/OT: the smallest element is a chip of 470 rows and 640 cols - return constants::moduleMLOT::chip::nCols; + return o2::trk::constants::moduleMLOT::chip::nCols; } return 0; } @@ -126,16 +130,34 @@ class Digitizer int getNRows(int subDetID, int layer) { if (subDetID == 0) { // VD - return constants::VD::petal::layer::nRows[layer]; + return o2::trk::constants::VD::petal::layer::nRows[layer]; } else if (subDetID == 1 || subDetID == 2) { // ML/OT - return constants::moduleMLOT::chip::nRows; + return o2::trk::constants::moduleMLOT::chip::nRows; } return 0; } + int getROFLayer(int chipID) const + { + if constexpr (DetID == o2::detectors::DetID::TRK) { + return mGeometry->getLayerTRK(chipID); + } else { + return mGeometry->getLayer(chipID); + } + } + + int getDisk(int chipID) const + { + if constexpr (DetID == o2::detectors::DetID::TRK) { + return mGeometry->getDisk(chipID); + } else { + return -1; + } + } + static constexpr float sec2ns = 1e9; - o2::trk::DigiParams mParams; ///< digitization parameters + o2::trkft3::DigiParams mParams; ///< digitization parameters o2::InteractionTimeRecord mEventTime; ///< global event time and interaction record o2::InteractionRecord mIRFirstSampledTF; ///< IR of the 1st sampled IR, noise-only ROFs will be inserted till this IR only double mCollisionTimeWrtROF{}; @@ -150,9 +172,9 @@ class Digitizer int mNumberOfChips = 0; - const o2::trk::ChipSimResponse* mChipSimResp = nullptr; // simulated response - const o2::trk::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips - const o2::trk::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips + const o2::trkft3::ChipSimResponse* mChipSimResp = nullptr; // simulated response + const o2::trkft3::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips + const o2::trkft3::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips std::string mRespName; /// APTS or ALICE3, depending on the response to be used @@ -166,16 +188,21 @@ class Digitizer float mSimRespVDScaleDepth{1.f}; // scale depth-local coordinate to response function depth-coordinate float mSimRespMLOTScaleDepth{1.f}; // scale depth-local coordinate to response function depth-coordinate - const o2::trk::GeometryTGeo* mGeometry = nullptr; ///< TRK geometry + const GeometryTGeo* mGeometry = nullptr; ///< TRK or FT3 geometry - std::vector mChips; ///< Array of chips digits containers - std::deque> mExtraBuff; ///< buffer (per roFrame) for extra digits + std::vector mChips; ///< Array of chips digits containers + std::deque> mExtraBuff; ///< buffer (per roFrame) for extra digits - std::vector* mDigits = nullptr; //! output digits - std::vector* mROFRecords = nullptr; //! output ROF records + std::vector* mDigits = nullptr; //! output digits + std::vector* mROFRecords = nullptr; //! output ROF records o2::dataformats::MCTruthContainer* mMCLabels = nullptr; //! output labels const o2::itsmft::NoiseMap* mDeadChanMap = nullptr; const o2::itsmft::NoiseMap* mNoiseMap = nullptr; }; -} // namespace o2::trk +} // namespace o2::trkft3 + +extern template class o2::trkft3::Digitizer; +extern template class o2::trkft3::Digitizer; + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx new file mode 100644 index 0000000000000..8917062923537 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx @@ -0,0 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "TRKFT3Simulation/ChipDigitsContainer.h" + +using namespace o2::trkft3; + +ChipDigitsContainer::ChipDigitsContainer(UShort_t idx) + : o2::itsmft::ChipDigitsContainer(idx) {} diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx similarity index 90% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx index 70c4f131b9724..e8a11fb1b15d0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx @@ -9,11 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "TRKSimulation/ChipSimResponse.h" +#include "TRKFT3Simulation/ChipSimResponse.h" #include #include -using namespace o2::trk; +using namespace o2::trkft3; void ChipSimResponse::initData(int tableNumber, std::string dataPath, const bool quiet) { diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx similarity index 70% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx index a13f2e58bd3a4..8d7f4d4b7c767 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx @@ -9,15 +9,15 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "TRKSimulation/DPLDigitizerParam.h" +#include "TRKFT3Simulation/DPLDigitizerParam.h" namespace o2 { -namespace trk +namespace trkft3 { // this makes sure that the constructor of the parameters is statically called // so that these params are part of the parameter database -static auto& sDigitizerParamITS = o2::trk::DPLDigitizerParam::Instance(); -static auto& sDigitizerParamMFT = o2::trk::DPLDigitizerParam::Instance(); -} // namespace trk +static auto& sDigitizerParamITS = o2::trkft3::DPLDigitizerParam::Instance(); +static auto& sDigitizerParamMFT = o2::trkft3::DPLDigitizerParam::Instance(); +} // namespace trkft3 } // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx similarity index 72% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx index 3558a6a87ce71..fd2acbe45411d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx @@ -14,18 +14,20 @@ #include #include "Framework/Logger.h" -#include "TRKSimulation/DigiParams.h" -#include "TRKSimulation/ChipSimResponse.h" +#include "TRKFT3Simulation/DigiParams.h" +#include "TRKFT3Simulation/ChipSimResponse.h" -using namespace o2::trk; +using namespace o2::trkft3; -DigiParams::DigiParams() +template +DigiParams::DigiParams() { // make sure the defaults are consistent setNSimSteps(mNSimSteps); } -void DigiParams::setROFrameLength(float lNS, int layer) +template +void DigiParams::setROFrameLength(float lNS, int layer) { // set ROFrame length in nanosecongs mROFrameLayerLength[layer] = lNS; @@ -33,14 +35,16 @@ void DigiParams::setROFrameLength(float lNS, int layer) mROFrameLayerLengthInv[layer] = 1. / mROFrameLayerLength[layer]; } -void DigiParams::setNSimSteps(int v) +template +void DigiParams::setNSimSteps(int v) { // set number of sampling steps in silicon mNSimSteps = v > 0 ? v : 1; mNSimStepsInv = 1.f / mNSimSteps; } -void DigiParams::setChargeThreshold(int v, float frac2Account) +template +void DigiParams::setChargeThreshold(int v, float frac2Account) { // set charge threshold for digits creation and its fraction to account // contribution from single hit @@ -55,10 +59,11 @@ void DigiParams::setChargeThreshold(int v, float frac2Account) } //______________________________________________ -void DigiParams::print() const +template +void DigiParams::print() const { // print settings - printf("TRK digitization params:\n"); + printf("%s digitization params:\n", o2::detectors::DetID::getName(DetIDV)); printf("Threshold (N electrons) : %d\n", mChargeThreshold); printf("Min N electrons to account : %d\n", mMinChargeToAccount); printf("Number of charge sharing steps : %d\n", mNSimSteps); @@ -68,7 +73,8 @@ void DigiParams::print() const mSignalShape.print(); } -void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) +template +void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) { LOG(debug) << "Response function data path: " << resp->getDataPath(); LOG(debug) << "Response function info: "; @@ -76,5 +82,8 @@ void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) if (!resp) { LOGP(fatal, "cannot set response function from null"); } - mResponse = std::make_unique(resp); + mResponse = std::make_unique(resp); } + +template class o2::trkft3::DigiParams; +template class o2::trkft3::DigiParams; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx similarity index 93% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx index 79642d6ec7b86..5e8faca2830fe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx @@ -11,11 +11,10 @@ /// \file Digitizer.cxx -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/DPLDigitizerParam.h" -#include "TRKSimulation/TRKLayer.h" -#include "TRKSimulation/Digitizer.h" +#include "TRKBase/Specs.h" +#include "TRKFT3Simulation/Digitizer.h" #include "DetectorsRaw/HBFUtils.h" #include @@ -26,15 +25,16 @@ #include #include // for LOG -using o2::itsmft::Digit; -using o2::trk::Hit; +using o2::trkft3::Digit; +using o2::trkft3::Hit; using Segmentation = o2::trk::SegmentationChip; -using namespace o2::trk; +using namespace o2::trkft3; using namespace o2::itsmft; // using namespace o2::base; //_______________________________________________________________________ -void Digitizer::init() +template +void Digitizer::init() { LOG(info) << "Initializing digitizer"; mNumberOfChips = mGeometry->getNumberOfChips(); @@ -88,10 +88,7 @@ void Digitizer::init() mSimRespMLOTShift = mChipSimRespMLOT->getDepthMax() - thicknessMLOT / 2.f; // the shift should be done considering the rescaling done to adapt to the wrong silicon thickness. TODO: remove the scaling factor for the depth when the silicon thickness match the simulated response - // importing the parameters from DPLDigitizerParam.h - auto& dOptTRK = DPLDigitizerParam::Instance(); - - LOGP(info, "TRK Digitizer is initialised."); + LOGP(info, "{} Digitizer is initialised.", o2::detectors::DetID::getName(DetID)); mParams.print(); LOGP(info, "VD shift = {} ; ML/OT shift = {} = {} - {}", mSimRespVDShift, mSimRespMLOTShift, mChipSimRespMLOT->getDepthMax(), thicknessMLOT / 2.f); LOGP(info, "VD pixel scale on x = {} ; z = {}", mSimRespVDScaleX, mSimRespVDScaleZ); @@ -101,7 +98,8 @@ void Digitizer::init() mIRFirstSampledTF = o2::raw::HBFUtils::Instance().getFirstSampledTFIR(); } -const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) +template +const o2::trkft3::ChipSimResponse* Digitizer::getChipResponse(int chipID) { if (mGeometry->getSubDetID(chipID) == 0) { /// VD return mChipSimRespVD; @@ -114,7 +112,8 @@ const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) }; //_______________________________________________________________________ -void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) +template +void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) { // digitize single event, the time must have been set beforehand @@ -144,14 +143,15 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID, int l if (layer < 0) { return true; } - return mGeometry->getLayerTRK((*hits)[idx].GetDetectorID()) == layer; + return getROFLayer((*hits)[idx].GetDetectorID()) == layer; })) { processHit((*hits)[i], mROFrameMax, evID, srcID, layer); } } //_______________________________________________________________________ -void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) +template +void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) { LOG(info) << "Setting event time to " << irt.getTimeNS() << " ns after orbit 0 bc 0"; // assign event time in ns @@ -187,7 +187,8 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) } //_______________________________________________________________________ -void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) +template +void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) { // // fill output with digits from min.cached up to requested frame, generating the noise beforehand if (frameLast > mROFrameMax) { @@ -198,7 +199,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) LOG(info) << "Filling " << mGeometry->getName() << " digits output for RO frames " << mROFrameMin << ":" << frameLast; - o2::itsmft::ROFRecord rcROF; /// using temporarly itsmft::ROFRecord + o2::trkft3::ROFRecord rcROF; /// using temporarly trkft3::ROFRecord // we have to write chips in RO increasing order, therefore have to loop over the frames here for (; mROFrameMin <= frameLast; mROFrameMin++) { @@ -207,7 +208,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) auto& extra = *(mExtraBuff.front().get()); for (auto& chip : mChips) { - if (chip.isDisabled() || (layer >= 0 && mGeometry->getLayerTRK(chip.getChipIndex()) != layer)) { + if (chip.isDisabled() || (layer >= 0 && getROFLayer(chip.getChipIndex()) != layer)) { continue; } chip.addNoise(mROFrameMin, mROFrameMin, &mParams, mGeometry->getSubDetID(chip.getChipIndex()), mGeometry->getLayer(chip.getChipIndex())); /// TODO: add noise @@ -251,13 +252,14 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) +template +void Digitizer::processHit(const o2::trkft3::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) { int chipID = hit.GetDetectorID(); //// the chip ID at the moment is not referred to the chip but to a wider detector element (e.g. quarter of layer or disk in VD, stave in ML, half stave in OT) int subDetID = mGeometry->getSubDetID(chipID); int layer = mGeometry->getLayer(chipID); // local layer nr for response - int disk = mGeometry->getDisk(chipID); + int disk = getDisk(chipID); if (disk != -1) { LOG(debug) << "Skipping VD disk " << disk; @@ -401,7 +403,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i int rowPrev = -1, colPrev = -1, row, col; float cRowPix = 0.f, cColPix = 0.f; // local coordinate of the current pixel center - const o2::trk::ChipSimResponse* resp = getChipResponse(chipID); + const o2::trkft3::ChipSimResponse* resp = getChipResponse(chipID); // std::cout << "Printing chip response:" << std::endl; // resp->print(); @@ -497,8 +499,9 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } //________________________________________________________________________________ -void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) +template +void Digitizer::registerDigits(o2::trkft3::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) { // Register digits for given pixel, accounting for the possible signal contribution to // multiple ROFrame. The signal starts at time tInROF wrt the start of provided roFrame @@ -551,3 +554,6 @@ void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFr } } } + +template class o2::trkft3::Digitizer; +template class o2::trkft3::Digitizer; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h new file mode 100644 index 0000000000000..e11378f471ec3 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h @@ -0,0 +1,27 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::trkft3::ChipDigitsContainer + ; +#pragma link C++ class o2::trkft3::ChipSimResponse + ; +#pragma link C++ class o2::trkft3::Digitizer < o2::detectors::DetID::TRK> + ; +#pragma link C++ class o2::trkft3::Digitizer < o2::detectors::DetID::FT3> + ; +#pragma link C++ class o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::TRK> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::TRK>> + ; +#pragma link C++ class o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::FT3> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::FT3>> + ; + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt index e3309d78f47ea..f437b53715149 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt @@ -20,7 +20,7 @@ o2_add_library(TRKWorkflow O2::GPUWorkflow O2::SimConfig O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::DPLUtils O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/README.md similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/README.md rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/README.md diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h similarity index 85% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h index 50d823b497bb9..3fcc4253fed7f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h @@ -17,6 +17,8 @@ namespace o2::trk { +framework::DataProcessorSpec getTRKClusterWriterSpec(bool useMC); +framework::DataProcessorSpec getFT3ClusterWriterSpec(bool useMC); framework::DataProcessorSpec getClusterWriterSpec(bool useMC); } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h index 9d072e85d574a..0166aa14462a8 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h @@ -34,7 +34,7 @@ class ClustererDPL : public o2::framework::Task static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; bool mUseMC = true; int mNThreads = 1; - o2::trk::Clusterer mClusterer; + o2::trk::TRKClusterer mClusterer; #ifdef O2_WITH_ACTS bool mUseACTS = false; o2::trk::ClustererACTS mClustererACTS; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h similarity index 91% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h index 92b64e0815cfb..de04358b227eb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h @@ -16,14 +16,13 @@ #include "TFile.h" #include "TTree.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DataFormatsITSMFT/GBTCalibData.h" -#include "DataFormatsITSMFT/ROFRecord.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "Headers/DataHeader.h" -#include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" #include "TRKBase/AlmiraParam.h" @@ -51,9 +50,9 @@ class DigitReader : public Task static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; - std::vector*> mDigits{nullptr}; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::vector*> mDigROFRec{nullptr}; + std::vector*> mDigROFRec{nullptr}; std::vector mPLabels{nullptr}; o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h similarity index 88% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h index 9c37d4318bb0f..e5184b132811e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h @@ -20,6 +20,7 @@ namespace trk { o2::framework::DataProcessorSpec getTRKDigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); +o2::framework::DataProcessorSpec getFT3DigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); } // namespace trk } // end namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx similarity index 67% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx index 863915bac0572..ae4407a5136fa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx @@ -21,9 +21,12 @@ #include "Framework/ConcreteDataMatcher.h" #include "Framework/DataRef.h" #include "TRKBase/AlmiraParam.h" +#include "TRKBase/Specs.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "Headers/DataHeader.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -34,16 +37,17 @@ namespace o2::trk template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; -using ClustersType = std::vector; using PatternsType = std::vector; -using ROFrameType = std::vector; +using ROFrameType = std::vector; using LabelsType = o2::dataformats::MCTruthContainer; -using ROFRecLblType = std::vector; -DataProcessorSpec getClusterWriterSpec(bool useMC) +template +DataProcessorSpec getClusterWriterSpecT(bool useMC) { - static constexpr o2::header::DataOrigin Origin{o2::header::gDataOriginTRK}; - static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 cluster writers are supported"); + using ClustersType = std::vector>; + static constexpr o2::header::DataOrigin Origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const int nLayers = DetID == o2::detectors::DetID::TRK ? o2::trk::AlmiraParam::kNLayers : o2::trk::constants::MLOTDisks::nLayers; const auto detName = Origin.as(); auto compClusterSizes = std::make_shared>(nLayers, 0); @@ -73,37 +77,52 @@ DataProcessorSpec getClusterWriterSpec(bool useMC) vecInpSpecROF.reserve(nLayers); vecInpSpecLbl.reserve(nLayers); for (int iLayer = 0; iLayer < nLayers; iLayer++) { - vecInpSpecClus.emplace_back(getName("compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); - vecInpSpecPatt.emplace_back(getName("patterns", iLayer), Origin, "PATTERNS", iLayer); - vecInpSpecROF.emplace_back(getName("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); - vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); + vecInpSpecClus.emplace_back(getName(detName + "compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); + vecInpSpecPatt.emplace_back(getName(detName + "patterns", iLayer), Origin, "PATTERNS", iLayer); + vecInpSpecROF.emplace_back(getName(detName + "ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); + vecInpSpecLbl.emplace_back(getName(detName + "labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); } return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), - "o2clus_trk.root", - MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with TRK clusters"}, + std::format("o2clus_{}.root", detNameLC).c_str(), + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with " + detName + " clusters"}, BranchDefinition{vecInpSpecClus, - "TRKClusterComp", "compact-cluster-branch", + detName + "ClusterComp", "compact-cluster-branch", nLayers, compClustersSizeGetter, getIndex, getName}, BranchDefinition{vecInpSpecPatt, - "TRKClusterPatt", "cluster-pattern-branch", + detName + "ClusterPatt", "cluster-pattern-branch", nLayers, getIndex, getName}, BranchDefinition{vecInpSpecROF, - "TRKClustersROF", "cluster-rof-branch", + detName + "ClustersROF", "cluster-rof-branch", nLayers, logger, getIndex, getName}, BranchDefinition{vecInpSpecLbl, - "TRKClusterMCTruth", "cluster-label-branch", + detName + "ClusterMCTruth", "cluster-label-branch", (useMC ? nLayers : 0), getIndex, getName})(); } +DataProcessorSpec getTRKClusterWriterSpec(bool useMC) +{ + return getClusterWriterSpecT(useMC); +} + +DataProcessorSpec getFT3ClusterWriterSpec(bool useMC) +{ + return getClusterWriterSpecT(useMC); +} + +DataProcessorSpec getClusterWriterSpec(bool useMC) +{ + return getTRKClusterWriterSpec(useMC); +} + } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx similarity index 94% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx index f91262e021a55..f299c581956a0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx @@ -11,8 +11,8 @@ #include "TRKWorkflow/ClustererSpec.h" #include "DetectorsBase/GeometryManager.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" @@ -36,8 +36,8 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) uint64_t totalClusters = 0; for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); - auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); + auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); gsl::span labelbuffer; if (mUseMC) { @@ -45,9 +45,9 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) } o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - std::vector clusters; + std::vector clusters; std::vector patterns; - std::vector clusterROFs; + std::vector clusterROFs; std::unique_ptr> clusterLabels; if (mUseMC) { clusterLabels = std::make_unique>(); diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx similarity index 80% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx index 591b084aee3ba..e1d5d3cbcf5f2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx @@ -15,18 +15,20 @@ #include "Framework/ConcreteDataMatcher.h" #include "Framework/DataRef.h" #include "TRKBase/AlmiraParam.h" +#include "TRKBase/Specs.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "Headers/DataHeader.h" #include "DetectorsCommonDataFormats/DetID.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include "SimulationDataFormat/MCCompLabel.h" #include #include #include +#include #include using namespace o2::framework; @@ -41,20 +43,26 @@ template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; using MCCont = o2::dataformats::ConstMCTruthContainer; -DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) +template +DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) { - static constexpr o2::header::DataOrigin Origin = o2::header::gDataOriginTRK; - const int mLayers = o2::trk::AlmiraParam::kNLayers; - std::string detStr = "TRK"; - std::string detStrL = dec ? "o2_trk" : "trk"; + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 digit writers are supported"); + static constexpr o2::header::DataOrigin Origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const int mLayers = DetID == o2::detectors::DetID::TRK ? o2::trk::AlmiraParam::kNLayers : o2::trk::constants::MLOTDisks::nLayers; + std::string detStr = o2::detectors::DetID(DetID).getName(); + auto detStrL = detStr; + std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), [](unsigned char c) { return std::tolower(c); }); + if (dec) { + detStrL = "o2_" + detStrL; + } auto digitSizes = std::make_shared>(mLayers, 0); - auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { + auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*digitSizes)[dh->subSpecification] = inDigits.size(); }; auto rofSizes = std::make_shared>(mLayers, 0); - auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { + auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*rofSizes)[dh->subSpecification] = inROFs.size(); }; @@ -110,17 +118,17 @@ DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) vecInpSpecLbl.emplace_back(getName(detStr + "_digitsMCTR", iLayer), Origin, "DIGITSMCTR", iLayer); } - return MakeRootTreeWriterSpec(("TRKDigitWriter" + std::string(dec ? "_dec" : "")).c_str(), + return MakeRootTreeWriterSpec((detStr + "DigitWriter" + std::string(dec ? "_dec" : "")).c_str(), (detStrL + "digits.root").c_str(), MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = detStr + " Digits tree"}, MakeRootTreeWriterSpec::CustomClose(finishWriting), - BranchDefinition>{vecInpSpecDig, + BranchDefinition>{vecInpSpecDig, detStr + "Digit", "digit-branch", mLayers, digitSizeGetter, getIndex, getName}, - BranchDefinition>{vecInpSpecROF, + BranchDefinition>{vecInpSpecROF, detStr + "DigitROF", "digit-rof-branch", mLayers, rofSizeGetter, @@ -137,5 +145,15 @@ DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) (calib ? 1 : 0)})(); } +DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) +{ + return getDigitWriterSpec(mctruth, dec, calib); +} + +DataProcessorSpec getFT3DigitWriterSpec(bool mctruth, bool dec, bool calib) +{ + return getDigitWriterSpec(mctruth, dec, calib); +} + } // end namespace trk } // end namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx diff --git a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx index bcc9a4ecddc37..7ba04725d928b 100644 --- a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx +++ b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx @@ -665,11 +665,18 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (isEnabled(o2::detectors::DetID::TRK)) { detList.emplace_back(o2::detectors::DetID::TRK); // connect the ALICE 3 TRK digitization - specs.emplace_back(o2::trk::getTRKDigitizerSpec(fanoutsize++, mctruth)); + specs.emplace_back(o2::trkft3::getTRKDigitizerSpec(fanoutsize++, mctruth)); // connect the ALICE 3 TRK digit writer specs.emplace_back(o2::trk::getTRKDigitWriterSpec(mctruth)); } + // the ALICE 3 FT3 part + if (isEnabled(o2::detectors::DetID::FT3)) { + detList.emplace_back(o2::detectors::DetID::FT3); + specs.emplace_back(o2::trkft3::getFT3DigitizerSpec(fanoutsize++, mctruth)); + specs.emplace_back(o2::trk::getFT3DigitWriterSpec(mctruth)); + } + // the ALICE 3 IOTOF part if (isEnabled(o2::detectors::DetID::TF3)) { detList.emplace_back(o2::detectors::DetID::TF3); diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx index 32018921c7af7..bf95164cecfd4 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx @@ -18,16 +18,18 @@ #include "Framework/Lifetime.h" #include "Framework/Task.h" #include "Steer/HitProcessingManager.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "DetectorsBase/BaseDPLDigitizer.h" #include "DetectorsRaw/HBFUtils.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsCommonDataFormats/SimTraits.h" #include "DataFormatsParameters/GRPObject.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "TRKSimulation/Digitizer.h" -#include "TRKSimulation/DPLDigitizerParam.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "TRKFT3Simulation/Digitizer.h" +#include "TRKFT3Simulation/DPLDigitizerParam.h" +#include "FT3Base/GeometryTGeo.h" #include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/Specs.h" @@ -45,14 +47,13 @@ using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; namespace { -std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth) +std::vector makeOutChannels(o2::header::DataOrigin detOrig, int nLayers, bool mctruth) { std::vector outputs; - for (uint32_t iLayer = 0; iLayer < o2::trk::AlmiraParam::getNLayers(); ++iLayer) { + for (uint32_t iLayer = 0; iLayer < static_cast(nLayers); ++iLayer) { outputs.emplace_back(detOrig, "DIGITS", iLayer, Lifetime::Timeframe); outputs.emplace_back(detOrig, "DIGITSROF", iLayer, Lifetime::Timeframe); if (mctruth) { - outputs.emplace_back(detOrig, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); outputs.emplace_back(detOrig, "DIGITSMCTR", iLayer, Lifetime::Timeframe); } } @@ -61,15 +62,30 @@ std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mct } } // namespace -namespace o2::trk +namespace o2::trkft3 { using namespace o2::base; -class TRKDPLDigitizerTask : BaseDPLDigitizer + +template +int getNLayers() +{ + if constexpr (N == o2::detectors::DetID::TRK) { + return o2::trk::AlmiraParam::getNLayers(); + } else { + return o2::trk::constants::MLOTDisks::nLayers; + } +} + +template +class TRKFT3DPLDigitizerTask : BaseDPLDigitizer { public: + static_assert(N == o2::detectors::DetID::TRK || N == o2::detectors::DetID::FT3, "only TRK and FT3 digitizers are supported"); + static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::TRK ? o2::detectors::DetID::TRK : o2::detectors::DetID::FT3}; + static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3}; using BaseDPLDigitizer::init; - TRKDPLDigitizerTask(bool mctruth = true) : BaseDPLDigitizer(InitServices::FIELD | InitServices::GEOM), mWithMCTruth(mctruth) {} + TRKFT3DPLDigitizerTask(bool mctruth = true) : BaseDPLDigitizer(InitServices::FIELD | InitServices::GEOM), mWithMCTruth(mctruth) {} void initDigitizerTask(framework::InitContext& ic) override { @@ -88,7 +104,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer // read collision context from input auto context = pc.inputs().get("collisioncontext"); - context->initSimChains(mID, mSimChains); + context->initSimChains(ID, mSimChains); const bool withQED = context->isQEDProvided() && !mDisableQED; auto& timesview = context->getEventRecords(withQED); LOG(info) << "GOT " << timesview.size() << " COLLISION TIMES"; @@ -100,7 +116,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer } TStopwatch timer; timer.Start(); - LOG(info) << " CALLING TRK DIGITIZATION "; + LOG(info) << " CALLING " << ID.getName() << " DIGITIZATION "; auto& eventParts = context->getEventParts(withQED); uint64_t nDigits{0}; @@ -111,7 +127,6 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer if (mWithMCTruth) { mLabels[iLayer].clear(); mLabelsAccum[iLayer].clear(); - mMC2ROFRecordsAccum[iLayer].clear(); } mDigitizer.setDigits(&mDigits[iLayer]); @@ -120,7 +135,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.resetROFrameBounds(); // digits are directly put into DPL owned resource - auto& digitsAccum = pc.outputs().make>(Output{mOrigin, "DIGITS", iLayer}); + auto& digitsAccum = pc.outputs().make>(Output{Origin, "DIGITS", iLayer}); const int roFrameLengthInBC = mDigitizer.getParams().getROFrameLengthInBC(iLayer); const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / roFrameLengthInBC; @@ -162,7 +177,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.resetEventROFrames(); for (auto& part : eventParts[collID]) { mHits.clear(); - context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[mID][0].c_str(), part.sourceID, part.entryID, &mHits); + context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[ID][0].c_str(), part.sourceID, part.entryID, &mHits); if (!mHits.empty()) { LOG(debug) << "For collision " << collID << " eventID " << part.entryID @@ -170,16 +185,13 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.process(&mHits, part.entryID, part.sourceID, iLayer); } } - if (mWithMCTruth) { - mMC2ROFRecordsAccum[iLayer].emplace_back(collID, -1, mDigitizer.getEventROFrameMin(), mDigitizer.getEventROFrameMax()); - } accumulate(); } mDigitizer.fillOutputContainer(0xffffffff, iLayer); accumulate(); nDigits += digitsAccum.size(); - std::vector expDigitRofVec(nROFsTF); + std::vector expDigitRofVec(nROFsTF); for (int iROF = 0; iROF < nROFsTF; ++iROF) { auto& rof = expDigitRofVec[iROF]; const int orb = iROF * roFrameLengthInBC / o2::constants::lhc::LHCMaxBunches + mFirstOrbitTF; @@ -213,36 +225,16 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer prevFirst = rof.getFirstEntry(); } - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", iLayer}, expDigitRofVec); + pc.outputs().snapshot(Output{Origin, "DIGITSROF", iLayer}, expDigitRofVec); if (mWithMCTruth) { - std::vector clippedMC2ROFRecords; - clippedMC2ROFRecords.reserve(mMC2ROFRecordsAccum[iLayer].size()); - for (auto mc2rof : mMC2ROFRecordsAccum[iLayer]) { - if (mc2rof.minROF >= static_cast(nROFsTF) || mc2rof.minROF > mc2rof.maxROF) { - mc2rof.rofRecordID = -1; - mc2rof.minROF = 0; - mc2rof.maxROF = 0; - } else { - mc2rof.maxROF = std::min(mc2rof.maxROF, nROFsTF - 1); - if (mc2rof.minROF > mc2rof.maxROF) { - mc2rof.rofRecordID = -1; - mc2rof.minROF = 0; - mc2rof.maxROF = 0; - } else { - mc2rof.rofRecordID = mc2rof.minROF; - } - } - clippedMC2ROFRecords.push_back(mc2rof); - } - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", iLayer}, clippedMC2ROFRecords); - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", iLayer}); + auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", iLayer}); mLabelsAccum[iLayer].flatten_to(sharedlabels); mLabels[iLayer].clear_andfreememory(); mLabelsAccum[iLayer].clear_andfreememory(); } } - LOG(info) << mID.getName() << ": Sending ROMode= " << mROMode << " to GRPUpdater"; - pc.outputs().snapshot(Output{mOrigin, "ROMode", 0}, mROMode); + LOG(info) << ID.getName() << ": Sending ROMode= " << mROMode << " to GRPUpdater"; + pc.outputs().snapshot(Output{Origin, "ROMode", 0}, mROMode); timer.Stop(); LOG(info) << "Digitization took " << timer.CpuTime() << "s"; @@ -270,32 +262,40 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer initOnce = true; auto& digipar = mDigitizer.getParams(); - // configure digitizer - o2::trk::GeometryTGeo* geom = o2::trk::GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); // make sure L2G matrices are loaded - geom->Print(); - mDigitizer.setGeometry(geom); - - const auto& dopt = o2::trk::DPLDigitizerParam::Instance(); - // pc.inputs().get("TRK_almiraparam"); + const auto& dopt = o2::trkft3::DPLDigitizerParam::Instance(); const auto& aopt = o2::trk::AlmiraParam::Instance(); - mLayers = constants::VD::petal::nLayers + geom->getNumberOfLayersMLOT() + geom->getNumberOfDisksMLOT(); + if constexpr (N == o2::detectors::DetID::TRK) { + auto* geom = o2::trk::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + geom->Print(); + mDigitizer.setGeometry(geom); + mLayers = o2::trk::AlmiraParam::getNLayers(); + } else { + auto* geom = o2::ft3::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + geom->Print(); + mDigitizer.setGeometry(geom); + mLayers = getNLayers(); + } + if (mLayers > static_cast(o2::trkft3::DigiParams::getMaxLayers())) { + LOGP(fatal, "{} geometry has {} layers, but DigiParams supports at most {}", ID.getName(), mLayers, o2::trkft3::DigiParams::getMaxLayers()); + } mDigits.resize(mLayers); mROFRecords.resize(mLayers); mROFRecordsAccum.resize(mLayers); mLabels.resize(mLayers); mLabelsAccum.resize(mLayers); - mMC2ROFRecordsAccum.resize(mLayers); for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - const auto roFrameLengthInBC = aopt.getROFLengthInBC(iLayer); + const int parLayer = std::min(iLayer, o2::trk::AlmiraParam::getNLayers() - 1); + const auto roFrameLengthInBC = aopt.getROFLengthInBC(parLayer); const auto frameNS = roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS; digipar.setROFrameLengthInBC(roFrameLengthInBC, iLayer); // ROF delay is treated as an additional bias from the digitizer point of view. - digipar.setROFrameBiasInBC(aopt.getROFBiasInBC(iLayer) + aopt.getROFDelayInBC(iLayer), iLayer); - digipar.setStrobeDelay(aopt.getStrobeDelay(iLayer), iLayer); - const auto strobeLengthCont = aopt.getStrobeLengthCont(iLayer); - digipar.setStrobeLength(strobeLengthCont > 0 ? strobeLengthCont : frameNS - aopt.getStrobeDelay(iLayer), iLayer); + digipar.setROFrameBiasInBC(aopt.getROFBiasInBC(parLayer) + aopt.getROFDelayInBC(parLayer), iLayer); + digipar.setStrobeDelay(aopt.getStrobeDelay(parLayer), iLayer); + const auto strobeLengthCont = aopt.getStrobeLengthCont(parLayer); + digipar.setStrobeLength(strobeLengthCont > 0 ? strobeLengthCont : frameNS - aopt.getStrobeDelay(parLayer), iLayer); digipar.setROFrameLength(frameNS, iLayer); } // parameters of signal time response: flat-top duration, max rise time and q @ which rise time is 0 @@ -306,12 +306,12 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer digipar.setNSimSteps(dopt.nSimSteps); mROMode = o2::parameters::GRPObject::CONTINUOUS; - LOG(info) << mID.getName() << " simulated in CONTINUOUS RO mode"; + LOG(info) << ID.getName() << " simulated in CONTINUOUS RO mode"; // if (oTRKParams::Instance().useDeadChannelMap) { // pc.inputs().get("TRK_dead"); // trigger final ccdb update // } - pc.inputs().get("TRK_aptsresp"); + pc.inputs().get((std::string(ID.getName()) + "_aptsresp").c_str()); // init digitizer mDigitizer.init(); @@ -321,8 +321,8 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) { - if (matcher == ConcreteDataMatcher(mOrigin, "ALMIRAPARAM", 0)) { - LOG(info) << mID.getName() << " Almira param updated"; + if (matcher == ConcreteDataMatcher(Origin, "ALMIRAPARAM", 0)) { + LOG(info) << ID.getName() << " Almira param updated"; const auto& par = o2::trk::AlmiraParam::Instance(); par.printKeyValues(); return; @@ -332,8 +332,8 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer // mDigitizer.setDeadChannelsMap((o2::itsmft::NoiseMap*)obj); // return; // } - if (matcher == ConcreteDataMatcher(mOrigin, "APTSRESP", 0)) { - LOG(info) << mID.getName() << " loaded APTSResponseData"; + if (matcher == ConcreteDataMatcher(Origin, "APTSRESP", 0)) { + LOG(info) << ID.getName() << " loaded APTSResponseData"; if (mLocalRespFile.empty()) { LOG(info) << "Using CCDB/APTS response file"; mDigitizer.getParams().setResponse((const o2::itsmft::AlpideSimResponse*)obj); @@ -352,18 +352,15 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer bool mDisableQED{false}; unsigned long mFirstOrbitTF = 0x0; std::string mLocalRespFile{""}; - const o2::detectors::DetID mID{o2::detectors::DetID::TRK}; - const o2::header::DataOrigin mOrigin{o2::header::gDataOriginTRK}; - o2::trk::Digitizer mDigitizer{}; + o2::trkft3::Digitizer mDigitizer{}; int mLayers{0}; - std::vector> mDigits{}; - std::vector> mROFRecords{}; - std::vector> mROFRecordsAccum{}; - std::vector mHits{}; - std::vector* mHitsP{&mHits}; + std::vector> mDigits{}; + std::vector> mROFRecords{}; + std::vector> mROFRecordsAccum{}; + std::vector mHits{}; + std::vector* mHitsP{&mHits}; std::vector> mLabels{}; std::vector> mLabelsAccum{}; - std::vector> mMC2ROFRecordsAccum{}; std::vector mSimChains{}; o2::parameters::GRPObject::ROMode mROMode = o2::parameters::GRPObject::PRESENT; // readout mode }; @@ -381,11 +378,27 @@ DataProcessorSpec getTRKDigitizerSpec(int channel, bool mctruth) inputs.emplace_back("TRK_aptsresp", "TRK", "APTSRESP", 0, Lifetime::Condition, ccdbParamSpec("IT3/Calib/APTSResponse")); return DataProcessorSpec{detStr + "Digitizer", - inputs, makeOutChannels(detOrig, mctruth), - AlgorithmSpec{adaptFromTask(mctruth)}, + inputs, makeOutChannels(detOrig, getNLayers(), mctruth), + AlgorithmSpec{adaptFromTask>(mctruth)}, + Options{ + {"disable-qed", o2::framework::VariantType::Bool, false, {"disable QED handling"}}, + {"local-response-file", o2::framework::VariantType::String, "", {"use response file saved locally at this path/filename"}}}}; +} + +DataProcessorSpec getFT3DigitizerSpec(int channel, bool mctruth) +{ + std::string detStr = o2::detectors::DetID::getName(o2::detectors::DetID::FT3); + auto detOrig = o2::header::gDataOriginFT3; + std::vector inputs; + inputs.emplace_back("collisioncontext", "SIM", "COLLISIONCONTEXT", static_cast(channel), Lifetime::Timeframe); + inputs.emplace_back("FT3_aptsresp", "FT3", "APTSRESP", 0, Lifetime::Condition, ccdbParamSpec("IT3/Calib/APTSResponse")); + + return DataProcessorSpec{detStr + "Digitizer", + inputs, makeOutChannels(detOrig, getNLayers(), mctruth), + AlgorithmSpec{adaptFromTask>(mctruth)}, Options{ {"disable-qed", o2::framework::VariantType::Bool, false, {"disable QED handling"}}, {"local-response-file", o2::framework::VariantType::String, "", {"use response file saved locally at this path/filename"}}}}; } -} // namespace o2::trk +} // namespace o2::trkft3 diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h index 5a1a59c3b9f5e..e28401fd14389 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h @@ -14,11 +14,12 @@ #include "Framework/DataProcessorSpec.h" -namespace o2::trk +namespace o2::trkft3 { o2::framework::DataProcessorSpec getTRKDigitizerSpec(int channel, bool mctruth = true); +o2::framework::DataProcessorSpec getFT3DigitizerSpec(int channel, bool mctruth = true); } -// namespace o2::trk +// namespace o2::trkft3 // end namespace o2 #endif diff --git a/macro/CMakeLists.txt b/macro/CMakeLists.txt index aacd68ad8082c..91a15af31c3b0 100644 --- a/macro/CMakeLists.txt +++ b/macro/CMakeLists.txt @@ -114,6 +114,7 @@ if(ENABLE_UPGRADES) set(upgradeTargets O2::Alice3DetectorsPassive O2::ITS3Simulation + O2::FT3Simulation O2::FCTSimulation O2::IOTOFSimulation O2::RICHSimulation diff --git a/macro/build_geometry.C b/macro/build_geometry.C index c5bec12b703e1..25349e5195727 100644 --- a/macro/build_geometry.C +++ b/macro/build_geometry.C @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -269,6 +270,11 @@ void build_geometry(FairRunSim* run = nullptr) "O2TRKSimulation", "create_detector_trk", isReadout("TRK"))); } + if (isActivated("FT3")) { + // ALICE 3 FT3 + addReadoutDetector(new o2::ft3::Detector(isReadout("FT3"))); + } + if (isActivated("FCT")) { // ALICE 3 FCT addReadoutDetector(new o2::fct::Detector(isReadout("FCT"))); diff --git a/run/CMakeLists.txt b/run/CMakeLists.txt index 3302eab2fe724..063745e757816 100644 --- a/run/CMakeLists.txt +++ b/run/CMakeLists.txt @@ -42,11 +42,12 @@ target_link_libraries(allsim $<$:O2::Alice3DetectorsPassive> $<$:O2::ITS3Simulation> $<$:O2::TRKSimulation> + $<$:O2::FT3Simulation> $<$:O2::FCTSimulation> $<$:O2::IOTOFSimulation> $<$:O2::RICHSimulation> $<$:O2::ECalSimulation> - $<$:O2::FD3Simulation> + $<$:O2::FD3Simulation> $<$:O2::MI3Simulation> O2::Generators) diff --git a/run/O2HitMerger.h b/run/O2HitMerger.h index 9794c17b62b5f..a0a79ac4fef96 100644 --- a/run/O2HitMerger.h +++ b/run/O2HitMerger.h @@ -74,6 +74,7 @@ #ifdef ENABLE_UPGRADES #include +#include #include #include #include @@ -1027,6 +1028,10 @@ void O2HitMerger::initDetInstances() mDetectorInstances[i] = std::move(std::make_unique(true)); counter++; } + if (i == DetID::FT3) { + mDetectorInstances[i] = std::move(std::make_unique(true)); + counter++; + } if (i == DetID::FCT) { mDetectorInstances[i] = std::move(std::make_unique(true)); counter++; From f4dbc085e1e1abd2d4514f6648276e9b1311009b Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:11:33 +0200 Subject: [PATCH 21/22] DPL: make benchmark ready for new ownership model (#15562) Rather than relying on a moveable container, move elements one by one. This works the same for both ownership models and since it's just some benchmark internal buffer, it does not advantage the owning model vs the non owning. --- Framework/Core/test/benchmark_DataRelayer.cxx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Framework/Core/test/benchmark_DataRelayer.cxx b/Framework/Core/test/benchmark_DataRelayer.cxx index e7df8fbb2fe9b..ca47b63193c1e 100644 --- a/Framework/Core/test/benchmark_DataRelayer.cxx +++ b/Framework/Core/test/benchmark_DataRelayer.cxx @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -140,7 +141,8 @@ static void BM_RelaySingleSlot(benchmark::State& state) auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); assert((result.at(0) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -196,7 +198,8 @@ static void BM_RelayMultipleSlots(benchmark::State& state) auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); assert((result.at(0) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -271,9 +274,11 @@ static void BM_RelayMultipleRoutes(benchmark::State& state) assert(result.size() == 2); assert((result.at(0) | count_parts{}) == 1); assert((result.at(1) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); - inflightMessages.emplace_back(std::move(result[1][0])); - inflightMessages.emplace_back(std::move(result[1][1])); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); + inflightMessages.insert(inflightMessages.end(), + std::make_move_iterator(result[1].begin()), + std::make_move_iterator(result[1].end())); } } @@ -333,7 +338,9 @@ static void BM_RelaySplitParts(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); + auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -387,7 +394,9 @@ static void BM_RelayMultiplePayloads(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); + auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } From a69fc0add83b7171eef4b59bed202d22a508c1a6 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:57:54 +0200 Subject: [PATCH 22/22] DPL: avoid multiple linear searches when handling command line options --- Framework/Core/src/DeviceSpecHelpers.cxx | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Framework/Core/src/DeviceSpecHelpers.cxx b/Framework/Core/src/DeviceSpecHelpers.cxx index 64d0f58938941..4c19e7a6ff17b 100644 --- a/Framework/Core/src/DeviceSpecHelpers.cxx +++ b/Framework/Core/src/DeviceSpecHelpers.cxx @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include "Framework/ChannelConfigurationPolicy.h" @@ -1596,11 +1598,30 @@ void DeviceSpecHelpers::prepareArguments(bool defaultQuiet, bool defaultStopped, } }; + // Fast path for an exact, unambiguously declared long name. An option can + // carry more than one long name, so index all of them. A name declared twice + // is mapped to nullptr, so that it falls back to find_nothrow() below and is + // reported as ambiguous, as it would be without this lookup table. Wildcard + // and short-only names simply miss and fall back as well. + std::unordered_map odescByName; + odescByName.reserve(odesc.options().size()); + for (auto const& optDesc : odesc.options()) { + auto [names, count] = optDesc->long_names(); + for (size_t ni = 0; ni < count; ++ni) { + auto [it, inserted] = odescByName.try_emplace(names[ni], optDesc.get()); + if (!inserted) { + it->second = nullptr; + } + } + } for (const auto& varit : varmap) { // find the option belonging to key, add if the option has been parsed // and is not defaulted - const auto* description = odesc.find_nothrow(varit.first, false); - if (description == nullptr || varmap.count(varit.first) == 0) { + auto descIt = odescByName.find(varit.first); + const auto* description = (descIt != odescByName.end() && descIt->second != nullptr) + ? descIt->second + : odesc.find_nothrow(varit.first, false); + if (description == nullptr) { continue; }