|
| 1 | +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. |
| 2 | +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 3 | +// All rights not expressly granted are reserved. |
| 4 | +// |
| 5 | +// This software is distributed under the terms of the GNU General Public |
| 6 | +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 7 | +// |
| 8 | +// In applying this license CERN does not waive the privileges and immunities |
| 9 | +// granted to it by virtue of its status as an Intergovernmental Organization |
| 10 | +// or submit itself to any jurisdiction. |
| 11 | + |
| 12 | +/// \file TPCDigitizerMTSpec.cxx |
| 13 | +/// \brief A single TPC digitizer device that digitizes the sectors with several threads |
| 14 | + |
| 15 | +#include "TPCDigitizerSpec.h" |
| 16 | +#include "TPCSectorDigitization.h" |
| 17 | +#include "Framework/ConfigParamRegistry.h" |
| 18 | +#include "Framework/DataProcessorSpec.h" |
| 19 | +#include "Framework/DataRefUtils.h" |
| 20 | +#include "Framework/Lifetime.h" |
| 21 | +#include "Framework/CCDBParamSpec.h" |
| 22 | +#include "Framework/Task.h" |
| 23 | +#include "Framework/Logger.h" |
| 24 | +#include "Headers/DataHeader.h" |
| 25 | +#include "DataFormatsParameters/GRPObject.h" |
| 26 | +#include "DataFormatsTPC/TPCSectorHeader.h" |
| 27 | +#include "DetectorsBase/BaseDPLDigitizer.h" |
| 28 | +#include "DetectorsBase/Detector.h" |
| 29 | +#include "SimConfig/DigiParams.h" |
| 30 | +#include "SimulationDataFormat/DigitizationContext.h" |
| 31 | +#include "TPCBaseRecSim/CDBInterface.h" |
| 32 | +#include "TPCBase/ParameterGEM.h" |
| 33 | +#include "TPCBase/ParameterGas.h" |
| 34 | +#include "TPCCalibration/VDriftHelper.h" |
| 35 | +#include "TPCSimulation/Digitizer.h" |
| 36 | +#include "TPCSpaceCharge/SpaceCharge.h" |
| 37 | +#include <TChain.h> |
| 38 | +#include <TFile.h> |
| 39 | +#include <TROOT.h> |
| 40 | +#include <TRandom.h> |
| 41 | +#include <TStopwatch.h> |
| 42 | +#include <TTree.h> |
| 43 | +#include <algorithm> |
| 44 | +#include <atomic> |
| 45 | +#include <exception> |
| 46 | +#include <filesystem> |
| 47 | +#include <memory> |
| 48 | +#include <mutex> |
| 49 | +#include <optional> |
| 50 | +#include <string> |
| 51 | +#include <thread> |
| 52 | +#include <utility> |
| 53 | +#include <vector> |
| 54 | + |
| 55 | +using namespace o2::framework; |
| 56 | +using SC = o2::tpc::SpaceCharge<float>; |
| 57 | + |
| 58 | +namespace o2 |
| 59 | +{ |
| 60 | +namespace tpc |
| 61 | +{ |
| 62 | + |
| 63 | +namespace |
| 64 | +{ |
| 65 | +/// The state of one worker thread: a digitizer, its own hit chains and its own digit file |
| 66 | +struct DigitizerWorker { |
| 67 | + int id = 0; |
| 68 | + Digitizer digitizer; |
| 69 | + std::vector<TChain*> chains; |
| 70 | + SectorFlushBuffers buffers; |
| 71 | + std::exception_ptr error; |
| 72 | + |
| 73 | + ~DigitizerWorker() |
| 74 | + { |
| 75 | + for (auto chain : chains) { |
| 76 | + delete chain; |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + std::string fileName() const { return "tpc_driftime_digits_lane" + std::to_string(id) + ".root"; } |
| 81 | + |
| 82 | + /// Digitize one sector and write it to the worker's digit file, in the format of the chunked writer of the lane spec |
| 83 | + void processSector(const steer::DigitizationContext& context, int sector, ULong64_t timeframe, bool withMCTruth, std::mutex& cdbMutex) |
| 84 | + { |
| 85 | + std::unique_ptr<TFile> file(TFile::Open(fileName().c_str(), "UPDATE")); |
| 86 | + if (!file || file->IsZombie()) { |
| 87 | + throw std::runtime_error("TPC: cannot open " + fileName()); |
| 88 | + } |
| 89 | + auto tree = new TTree(std::to_string(sector).c_str(), "o2sim"); |
| 90 | + tree->SetDirectory(file.get()); // owned by the file |
| 91 | + size_t flushCounter = 0; |
| 92 | + auto onFlush = [&]() { |
| 93 | + ++flushCounter; |
| 94 | + auto fill = [&tree](const std::string& name, auto* data) { |
| 95 | + auto br = o2::base::getOrMakeBranch(*tree, name.c_str(), data); |
| 96 | + br->Fill(); |
| 97 | + br->ResetAddress(); |
| 98 | + }; |
| 99 | + fill("TPCDigit_" + std::to_string(sector), &buffers.digits); |
| 100 | + if (withMCTruth) { |
| 101 | + fill("TPCDigitMCTruth_" + std::to_string(sector), &buffers.labels); |
| 102 | + } |
| 103 | + fill("TPCCommonMode_" + std::to_string(sector), &buffers.commonMode); |
| 104 | + }; |
| 105 | + digitizeSector(digitizer, context, chains, sector, timeframe, buffers, onFlush, &cdbMutex); |
| 106 | + tree->SetEntries(flushCounter); |
| 107 | + file->Write("", TObject::kOverwrite); |
| 108 | + file->Close(); |
| 109 | + } |
| 110 | +}; |
| 111 | +} // namespace |
| 112 | + |
| 113 | +class TPCDPLDigitizerMTTask : public o2::base::BaseDPLDigitizer |
| 114 | +{ |
| 115 | + public: |
| 116 | + TPCDPLDigitizerMTTask(int nThreads, int distortionType) : BaseDPLDigitizer(o2::base::InitServices::FIELD | o2::base::InitServices::GEOM), mNThreads(nThreads), mDistortionType(distortionType) {} |
| 117 | + |
| 118 | + void initDigitizerTask(framework::InitContext& ic) override |
| 119 | + { |
| 120 | + LOG(info) << "Initializing multithreaded TPC digitization with " << mNThreads << " threads"; |
| 121 | + |
| 122 | + mWithMCTruth = o2::conf::DigiParams::Instance().mctruth; |
| 123 | + const auto triggeredMode = ic.options().get<bool>("TPCtriggered"); |
| 124 | + mRecalcDistortions = !(ic.options().get<bool>("do-not-recalculate-distortions")); |
| 125 | + SC::setNThreads(ic.options().get<int>("n-threads-distortions")); |
| 126 | + mUseCalibrationsFromCCDB = ic.options().get<bool>("TPCuseCCDB"); |
| 127 | + mMeanLumiDistortions = ic.options().get<float>("meanLumiDistortions"); |
| 128 | + mMeanLumiDistortionsDerivative = ic.options().get<float>("meanLumiDistortionsDerivative"); |
| 129 | + LOG(info) << "TPC calibrations from CCDB: " << mUseCalibrationsFromCCDB; |
| 130 | + |
| 131 | + // the workers share no random numbers: each digitizer seeds every sector from DigiParams.seed |
| 132 | + const auto seed = o2::conf::DigiParams::Instance().seed; |
| 133 | + if (seed == 0) { |
| 134 | + LOG(warning) << "TPC sector seeding with DigiParams.seed=0 is not reproducible"; |
| 135 | + } |
| 136 | + // with seed 0, gRandom was seeded randomly in the base class |
| 137 | + const ULong64_t sectorSeed = seed != 0 ? seed : gRandom->Integer(kMaxUInt); |
| 138 | + |
| 139 | + for (int i = 0; i < mNThreads; ++i) { |
| 140 | + auto& worker = mWorkers.emplace_back(std::make_unique<DigitizerWorker>()); |
| 141 | + worker->id = i; |
| 142 | + worker->digitizer.setContinuousReadout(!triggeredMode); |
| 143 | + worker->digitizer.setDistortionScaleType(mDistortionType); |
| 144 | + worker->digitizer.setUseSectorSeeding(sectorSeed); |
| 145 | + } |
| 146 | + |
| 147 | + // the merger picks up every lane file in the directory, so files of an earlier run must not survive |
| 148 | + for (const auto& entry : std::filesystem::directory_iterator(".")) { |
| 149 | + const auto name = entry.path().filename().string(); |
| 150 | + if (name.rfind("tpc_driftime_digits_lane", 0) == 0 && entry.path().extension() == ".root") { |
| 151 | + std::filesystem::remove(entry.path()); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + ROOT::EnableThreadSafety(); |
| 156 | + } |
| 157 | + |
| 158 | + void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) |
| 159 | + { |
| 160 | + if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { |
| 161 | + return; |
| 162 | + } |
| 163 | + // the maps are set on the first digitizer; the others share them once they are final |
| 164 | + auto& digitizer = mWorkers[0]->digitizer; |
| 165 | + if (matcher == ConcreteDataMatcher(o2::header::gDataOriginTPC, "TPCDIST", 0)) { |
| 166 | + LOGP(info, "Updating distortion map"); |
| 167 | + digitizer.setUseSCDistortions(static_cast<SC*>(obj)); |
| 168 | + if (mMeanLumiDistortions >= 0) { |
| 169 | + digitizer.setMeanLumiDistortions(mMeanLumiDistortions); |
| 170 | + } |
| 171 | + } |
| 172 | + if (matcher == ConcreteDataMatcher(o2::header::gDataOriginTPC, "TPCDISTDERIV", 0)) { |
| 173 | + LOGP(info, "Updating reference distortion map"); |
| 174 | + digitizer.setSCDistortionsDerivative(static_cast<SC*>(obj)); |
| 175 | + if (mMeanLumiDistortionsDerivative >= 0) { |
| 176 | + digitizer.setMeanLumiDistortionsDerivative(mMeanLumiDistortionsDerivative); |
| 177 | + } |
| 178 | + } |
| 179 | + if (matcher == ConcreteDataMatcher(o2::header::gDataOriginTPC, "TPCGASPARAM", 0)) { |
| 180 | + LOGP(info, "TPC gas param updated"); |
| 181 | + ParameterGas::Instance().printKeyValues(true, true); |
| 182 | + } |
| 183 | + if (matcher == ConcreteDataMatcher(o2::header::gDataOriginTPC, "TPCGEMPARAM", 0)) { |
| 184 | + LOGP(info, "TPC GEM param updated"); |
| 185 | + ParameterGEM::Instance().printKeyValues(true, true); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + void run(framework::ProcessingContext& pc) |
| 190 | + { |
| 191 | + LOG(info) << "Processing multithreaded TPC digitization"; |
| 192 | + |
| 193 | + auto& cdb = o2::tpc::CDBInterface::instance(); |
| 194 | + cdb.setUseDefaults(!mUseCalibrationsFromCCDB); |
| 195 | + // whatever are global settings for CCDB usage, we have to extract the TPC vdrift from CCDB for anchored simulations |
| 196 | + mTPCVDriftHelper.extractCCDBInputs(pc); |
| 197 | + pc.inputs().get<ParameterGas*>("gasparam"); |
| 198 | + pc.inputs().get<ParameterGEM*>("gemparam"); |
| 199 | + auto& primary = mWorkers[0]->digitizer; |
| 200 | + if (mDistortionType) { |
| 201 | + pc.inputs().get<SC*>("tpcdistortions"); |
| 202 | + if (mDistortionType == 2) { |
| 203 | + pc.inputs().get<SC*>("tpcdistortionsderiv"); |
| 204 | + primary.setLumiScaleFactor(); |
| 205 | + if (mRecalcDistortions) { |
| 206 | + // done once, with the threads of SpaceCharge, for all workers |
| 207 | + primary.recalculateDistortions(); |
| 208 | + } |
| 209 | + } |
| 210 | + for (size_t i = 1; i < mWorkers.size(); ++i) { |
| 211 | + mWorkers[i]->digitizer.setSCDistortionsFrom(primary); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + if (mTPCVDriftHelper.isUpdated()) { |
| 216 | + const auto& vd = mTPCVDriftHelper.getVDriftObject(); |
| 217 | + LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} and DriftTimeOffset correction {} wrt {} from source {}", |
| 218 | + vd.corrFact, vd.refVDrift, vd.timeOffsetCorr, vd.refTimeOffset, mTPCVDriftHelper.getSourceName()); |
| 219 | + for (auto& worker : mWorkers) { |
| 220 | + worker->digitizer.setVDrift(vd.getVDrift()); |
| 221 | + worker->digitizer.setTDriftOffset(vd.getTimeOffset()); |
| 222 | + } |
| 223 | + mTPCVDriftHelper.acknowledgeUpdate(); |
| 224 | + } |
| 225 | + |
| 226 | + if (std::filesystem::exists("ThresholdMap.root")) { |
| 227 | + LOG(info) << "TPC: Using zero suppression map from 'ThresholdMap.root'"; |
| 228 | + cdb.setThresholdMapFromFile("ThresholdMap.root"); |
| 229 | + } |
| 230 | + |
| 231 | + if (std::filesystem::exists("GainMap.root")) { |
| 232 | + LOG(info) << "TPC: Using gain map from 'GainMap.root'"; |
| 233 | + cdb.setGainMapFromFile("GainMap.root"); |
| 234 | + } |
| 235 | + |
| 236 | + // the sectors of this timeframe; all inputs carry the same collision context |
| 237 | + std::vector<int> sectors; |
| 238 | + std::optional<DataRef> contextRef; |
| 239 | + for (auto it = pc.inputs().begin(), end = pc.inputs().end(); it != end; ++it) { |
| 240 | + for (auto const& inputref : it.parts()) { |
| 241 | + if (inputref.spec->lifetime == o2::framework::Lifetime::Condition) { |
| 242 | + continue; |
| 243 | + } |
| 244 | + auto const* sectorHeader = DataRefUtils::getHeader<TPCSectorHeader*>(inputref); |
| 245 | + if (sectorHeader == nullptr) { |
| 246 | + LOG(error) << "TPC: Sector header missing, skipping input"; |
| 247 | + continue; |
| 248 | + } |
| 249 | + const int sector = sectorHeader->sector(); |
| 250 | + if (sector < 0 || sector >= TPCSectorHeader::NSectors) { |
| 251 | + throw std::runtime_error("Digitizer can only work on single sectors"); |
| 252 | + } |
| 253 | + sectors.push_back(sector); |
| 254 | + if (!contextRef) { |
| 255 | + contextRef = inputref; |
| 256 | + } |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + if (mWriteGRP) { |
| 261 | + auto roMode = primary.isContinuousReadout() ? o2::parameters::GRPObject::CONTINUOUS : o2::parameters::GRPObject::PRESENT; |
| 262 | + LOG(info) << "TPC: Sending ROMode= " << (primary.isContinuousReadout() ? "Continuous" : "Triggered") << " to GRPUpdater"; |
| 263 | + pc.outputs().snapshot(Output{"TPC", "ROMode", 0}, roMode); |
| 264 | + mWriteGRP = false; |
| 265 | + } |
| 266 | + if (!contextRef) { |
| 267 | + return; |
| 268 | + } |
| 269 | + auto context = pc.inputs().get<o2::steer::DigitizationContext*>(*contextRef); |
| 270 | + const ULong64_t timeframe = DataRefUtils::getHeader<o2::header::DataHeader*>(*contextRef)->tfCounter; |
| 271 | + LOG(info) << "TPC: Processing " << sectors.size() << " sectors with " << context->getEventRecords().size() << " collisions"; |
| 272 | + |
| 273 | + TStopwatch timer; |
| 274 | + timer.Start(); |
| 275 | + |
| 276 | + const size_t nWorkers = std::min(mWorkers.size(), sectors.size()); |
| 277 | + for (size_t i = 0; i < nWorkers; ++i) { |
| 278 | + context->initSimChains(o2::detectors::DetID::TPC, mWorkers[i]->chains); |
| 279 | + } |
| 280 | + |
| 281 | + std::atomic<size_t> nextSector{0}; |
| 282 | + // serialises CDBInterface access; the CCDB timestamp must not change while the workers run |
| 283 | + std::mutex cdbMutex; |
| 284 | + std::vector<std::thread> threads; |
| 285 | + for (size_t i = 0; i < nWorkers; ++i) { |
| 286 | + threads.emplace_back([&, worker = mWorkers[i].get()]() { |
| 287 | + try { |
| 288 | + for (size_t is = nextSector++; is < sectors.size(); is = nextSector++) { |
| 289 | + LOG(info) << "TPC: Worker " << worker->id << " processing sector " << sectors[is]; |
| 290 | + worker->processSector(*context, sectors[is], timeframe, mWithMCTruth, cdbMutex); |
| 291 | + } |
| 292 | + } catch (...) { |
| 293 | + worker->error = std::current_exception(); |
| 294 | + nextSector = sectors.size(); // let the other workers stop early |
| 295 | + } |
| 296 | + }); |
| 297 | + } |
| 298 | + for (auto& thread : threads) { |
| 299 | + thread.join(); |
| 300 | + } |
| 301 | + for (size_t i = 0; i < nWorkers; ++i) { |
| 302 | + if (mWorkers[i]->error) { |
| 303 | + std::rethrow_exception(mWorkers[i]->error); |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + timer.Stop(); |
| 308 | + LOG(info) << "TPC: Digitization took " << timer.CpuTime() << "s CPU, " << timer.RealTime() << "s wall"; |
| 309 | + } |
| 310 | + |
| 311 | + private: |
| 312 | + std::vector<std::unique_ptr<DigitizerWorker>> mWorkers; |
| 313 | + o2::tpc::VDriftHelper mTPCVDriftHelper{}; |
| 314 | + int mNThreads = 1; |
| 315 | + int mDistortionType = 0; |
| 316 | + bool mWriteGRP = true; |
| 317 | + bool mWithMCTruth = true; |
| 318 | + bool mUseCalibrationsFromCCDB = false; |
| 319 | + bool mRecalcDistortions = false; |
| 320 | + float mMeanLumiDistortions = -1; |
| 321 | + float mMeanLumiDistortionsDerivative = -1; |
| 322 | +}; |
| 323 | + |
| 324 | +o2::framework::DataProcessorSpec getTPCDigitizerMTSpec(int nThreads, std::vector<int> const& sectors, int distortionType) |
| 325 | +{ |
| 326 | + std::vector<InputSpec> inputs; |
| 327 | + for (auto sector : sectors) { |
| 328 | + inputs.emplace_back("collisioncontext" + std::to_string(sector), "SIM", "COLLISIONCONTEXT", static_cast<o2::header::DataHeader::SubSpecificationType>(sector), Lifetime::Timeframe); |
| 329 | + } |
| 330 | + o2::tpc::VDriftHelper::requestCCDBInputs(inputs); |
| 331 | + inputs.emplace_back("gasparam", o2::header::gDataOriginTPC, "TPCGASPARAM", 0, Lifetime::Condition, ccdbParamSpec(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::ParGas))); |
| 332 | + inputs.emplace_back("gemparam", o2::header::gDataOriginTPC, "TPCGEMPARAM", 0, Lifetime::Condition, ccdbParamSpec(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::ParGEM))); |
| 333 | + if (distortionType) { |
| 334 | + inputs.emplace_back("tpcdistortions", o2::header::gDataOriginTPC, "TPCDIST", 0, Lifetime::Condition, ccdbParamSpec(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::DistortionMapMC), {}, 1)); // time-dependent |
| 335 | + if (distortionType == 2) { |
| 336 | + inputs.emplace_back("tpcdistortionsderiv", o2::header::gDataOriginTPC, "TPCDISTDERIV", 0, Lifetime::Condition, ccdbParamSpec(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::DistortionMapDerivMC), {}, 1)); // time-dependent |
| 337 | + } |
| 338 | + } |
| 339 | + std::vector<OutputSpec> outputs{{"TPC", "ROMode", 0, Lifetime::Timeframe}}; |
| 340 | + |
| 341 | + // the name must not match "TPCDigitizer.*", whose completion policy would process the sectors one by one |
| 342 | + return DataProcessorSpec{ |
| 343 | + "tpc-digitizer-mt", |
| 344 | + inputs, |
| 345 | + outputs, |
| 346 | + AlgorithmSpec{adaptFromTask<TPCDPLDigitizerMTTask>(nThreads, distortionType)}, |
| 347 | + Options{ |
| 348 | + {"TPCtriggered", VariantType::Bool, false, {"Impose triggered RO mode (default: continuous)"}}, |
| 349 | + {"TPCuseCCDB", VariantType::Bool, false, {"true: load calibrations from CCDB; false: use random calibratoins"}}, |
| 350 | + {"meanLumiDistortions", VariantType::Float, -1.f, {"override lumi of distortion object if >=0"}}, |
| 351 | + {"meanLumiDistortionsDerivative", VariantType::Float, -1.f, {"override lumi of derivative distortion object if >=0"}}, |
| 352 | + {"do-not-recalculate-distortions", VariantType::Bool, false, {"Do not recalculate the distortions"}}, |
| 353 | + {"n-threads-distortions", VariantType::Int, 4, {"Number of threads used for the calculation of the distortions"}}, |
| 354 | + }}; |
| 355 | +} |
| 356 | + |
| 357 | +} // namespace tpc |
| 358 | +} // namespace o2 |
0 commit comments