From 3c18d6adb831cd177bac2c7eb4e06be8471d6547 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 21 Sep 2026 10:34:37 +0200 Subject: [PATCH 1/2] Improve skill to migrate to the new CCDB fetcher New Analysis Framework features now supported. --- .claude/commands/migrate-ccdb.md | 170 ++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/.claude/commands/migrate-ccdb.md b/.claude/commands/migrate-ccdb.md index 48d0f49c971..4f6bce9ab5d 100644 --- a/.claude/commands/migrate-ccdb.md +++ b/.claude/commands/migrate-ccdb.md @@ -10,9 +10,20 @@ The old approach uses `Service` and calls `ccdb->get // In namespace o2::aod (or a sub-namespace): DECLARE_SOA_CCDB_COLUMN(StructName, getterName, ConcreteType, "CCDB/Object/Path"); +// ... or, when the object needs fixing up after deserialisation, the _FULL form, whose +// trailing argument is the finaliser (see "Objects needing post-deserialisation fixup"): +DECLARE_SOA_CCDB_COLUMN_FULL(StructName, "fStructName", getterName, ConcreteType, "CCDB/Object/Path", + [](ConcreteType* o) { return fixUp(o); }); + DECLARE_SOA_TIMESTAMPED_TABLE(TableName, aod::Timestamps, o2::aod::timestamp::Timestamp, 1, "TABLEDESC", ns::StructName, ns::OtherColumn); +// ... or, when the object is constant across something coarser than a timestamp, the +// uniform form (see "Uniformity: how often the object can change"): +DECLARE_SOA_UNIFORM_TABLE(TableName, aod::Timestamps, o2::aod::timestamp::Timestamp, + aod::BCs, o2::aod::bc::RunNumber, 1, "TABLEDESC", + ns::StructName); + // In the task — basic usage: using MyBCs = soa::Join; void process(MyBCs const& bcs) { @@ -84,6 +95,17 @@ DECLARE_SOA_TIMESTAMPED_TABLE(MyTaskCCDBObjects, aod::Timestamps, o2::aod::times } // namespace o2::aod ``` +Before writing the declaration, settle three things per column — each has its own section +below, and getting them wrong is silent rather than loud: + +1. **Does the object need fixing up after deserialisation?** If so use `DECLARE_SOA_CCDB_COLUMN_FULL` + with a finaliser — see "Objects needing post-deserialisation fixup". +2. **How often can the object change?** Timestamp (the default) or run — see "Uniformity: how + often the object can change". Choose from the object's validity, not from how the old code + happened to fetch it. +3. **Is the path the same for every run?** If it varies by period, declare the mapping in the + query string instead of porting the run-range `if/else` — see "Paths that vary by run". + Rules for naming: - `StructName` / `getterName`: derive from the type name, e.g. `GRPMagField` / `grpMagField`, `MeanVertex` / `meanVertex` - Table name: `CCDBObjects`, e.g. `SkimmerDalitzEECCDBObjects` @@ -141,8 +163,154 @@ After making changes: - **`getRunDuration()` calls**: these use `BasicCCDBManager` statically and are unrelated to per-BC fetching — do not touch them. - **`ctpRateFetcher` / other helpers**: out of scope. - **Multiple tasks in one file**: tasks can share a single CCDB table declaration if they need the same objects; otherwise each task gets its own with a unique `_Desc_`. -- **Non-BC timestamps**: if the timestamp comes from something other than a BC (e.g. computed manually), the migration is non-trivial — flag it instead of forcing it. +- **Non-BC timestamps**: if the timestamp comes from something other than a BC, the migration is non-trivial — flag it instead of forcing it. This is the single most common blocker in practice. `Common/Tools/EventSelectionModule.h:243` computes `ts = sorTimestamp / 2 + eorTimestamp / 2` (mid-run, from `getRunDuration` / `AggregatedRunInfo`) and fetches `EventSelectionParams`, `ITS/Config/AlpideParam`, `TriggerAliases` and `ITS/Calib/TimeDeadMap` at it. A BC-keyed column fetches at each BC's own timestamp instead, so migrating these silently changes which object version is served whenever an object is revised mid-run. They need a run-keyed table before they can move. - **Global/init-time fetches** (e.g. `efficiencyGlobal.cxx` style): not migratable — the timestamped-table mechanism requires a row in a BC-keyed table. - **Magnetic-field side effects**: tasks that compute `d_bz` from a fetched `GRPMagField` and seed a propagator can keep that logic, just sourcing the object from `bc.grpMagField()` instead of `ccdb->getForTimeStamp(...)`. +## Lessons learned (established in-tree, with references) + +### Why this migration matters beyond tidiness + +The per-task path Configurable is a silent-divergence trap. `propagationService` and `propagationServiceV2` share the identical `ccdb.lutPath` Configurable (`Common/Tools/StandardCCDBLoader.h:45`, default `GLO/Param/MatLUT`), but config JSONs key overrides by *device name*. Every config in the tree carries a `propagation-service` block setting `GLO/Param/MatLUTInner` and no `propagation-service-v2` block, so V2 silently fell back to the full LUT — different material corrections, no warning. After migration the path is one option on the fetcher device, and two tasks disagreeing produces a warning (`ArrowSupport.cxx:641-666`) instead of silence. + +### Objects needing post-deserialisation fixup + +Some objects are not usable straight out of the ROOT streamer. `MatLayerCylSet` is a `FlatObject`: its internal pointers are unfixed and its voxel lookup unbuilt until `MatLayerCylSet::rectifyPtrFromFile()` runs. Use the `_FULL` form, which carries the finaliser (the plain `DECLARE_SOA_CCDB_COLUMN` passes an identity one): + +```cpp +DECLARE_SOA_CCDB_COLUMN_FULL(MatLUT, "fMatLUT", matLUT, o2::base::MatLayerCylSet, "GLO/Param/MatLUT", //! + [](o2::base::MatLayerCylSet* lut) { return o2::base::MatLayerCylSet::rectifyPtrFromFile(lut); }); +``` + +The finaliser must be the **last** macro argument (commas in a lambda body are absorbed by `__VA_ARGS__`), has signature `T* (*)(T*)`, and runs on the receiving device once per (re)deserialisation, before the object is ever handed out. Ownership contract: whatever it returns is what the column cache later `delete`s, so a finaliser returning a *different* instance must dispose of the one it was given. + +Do **not** put this fixup in the task. There is no `finaliseCCDB` hook on the analysis path (`adaptAnalysisTask` wires only `EndOfStream`, `AnalysisTask.h:610-619`; grep confirms zero uses of `finaliseCCDB` in O2Physics), and even if there were, an opt-in hook means a task that forgets it gets a silently broken object. + +### Uniformity: how often the object can change + +Every CCDB table declares a *uniformity column*: rows sharing its value resolve to the same +object, so the fetcher queries once per distinct value instead of once per row. +`DECLARE_SOA_TIMESTAMPED_TABLE` defaults it to the timestamp column, which is the +pre-existing behaviour — every distinct timestamp may yield a different object. + +Pick it from the object's real validity, and only then: + +| Object changes ... | Uniformity | Declare with | +| --- | --- | --- | +| within a run (calibrations, drift velocity) | timestamp (default) | `DECLARE_SOA_TIMESTAMPED_TABLE` | +| per run or per period (geometry, material, per-period calibrations) | `aod::BCs` / `aod::bc::RunNumber` | `DECLARE_SOA_UNIFORM_TABLE` | + +Worked examples in the tree: `aod::TpcCalibCCDBObjects` keeps the timestamp default because +the TPC drift velocity genuinely varies within a run; `aod::GeomCCDBObjects` and +`aod::TrackTunerCCDBObjects` are run-uniform. + +Two consequences worth knowing before choosing: + +- The uniformity column may live in a **different table** from the timestamp — the run number + is on `aod::BCs`, the timestamp on `aod::Timestamps`. Both are handed to the fetcher + automatically (the table's `generateSources()` merges their originals) and read positionally. +- Positional reading is only sound if the two sources are **row-aligned**. ASoA encodes no + type-level relation between tables that merely have equal row counts, so this cannot be a + `static_assert`; the fetcher compares the two column lengths and fatals on a mismatch. + Anything joinable with the BCs is fine. + +### Paths that vary by run: declare a mapping, not code + +A column's path may be a plain path, or a mapping from uniformity value to path: + +``` +"520259-529691=…/pp2023/pass4/vsPhi;559348-559387=…/ppRef/polarity_positive;fallback" +``` + +Ranges are inclusive; either bound may be omitted (`-hi=path`, `lo-=path`); entries are +separated by `;`; an entry without `=` is an explicit fallback. **A value matching no range +is fatal**, deliberately — silently substituting another period's calibration is the failure +mode this whole mechanism exists to prevent. A string with no `=` is a plain path, so +existing columns are unaffected. + +The mapping is *data*, carried in the schema metadata. That matters: the CCDB fetcher is a +separate device and must not depend on code from the task that declared the column, so a +resolver lambda would not do. It also means the run ranges stop being compiled in — the whole +mapping is replaceable at runtime through the `ccdb:fXxx` option. + +This replaces hand-written run-range tables. `TrackTuner::getPathInputFileAutomaticFromCCDB()` +is the model case: ~50 lines of `else if (lo <= runNumber && runNumber <= hi)` became the +declaration in `Common/DataModel/TrackTunerCCDBObjects.h`. When porting one, **derive the +mapping mechanically and diff it against the source** — first-match-wins must reproduce the +`if/else` order, which matters whenever ranges overlap (in TrackTuner, one PbPb range sits +inside a pp range and must stay *after* it). + +### Serving migrated and un-migrated callers from one module + +Shared modules must keep working for tasks that have not migrated. Detect the capability +rather than adding a configuration flag: + +```cpp +auto const& bc = collision.template bc_as(); +if constexpr (requires { bc.vdriftTgl(); }) { + mVDriftMgr.update(bc.vdriftTgl()); // column path +} else { + mVDriftMgr.update(bc.timestamp()); // legacy CCDB query +} +``` + +The discarded branch is not instantiated, so an un-migrated caller compiles exactly as before +and a migrated one never references the CCDB manager. `strangenessBuilderModule::updateVDrift` +uses this. Where a whole function parameter falls away, add an overload of different arity +that forwards (see "Shared module signatures") and put a `static_assert` with a readable +message on the ccdb-free one, so calling it with an unjoined BC table names the missing table +instead of failing somewhere inside the template. + +### Two path settings must never both be live + +After migration the column is the single source of truth for a path. If the task still has an +old `Configurable` for the same object, **fail loudly when both are set** rather +than silently preferring one — that divergence is exactly the bug this migration exists to +kill. `TrackPropagationModule::init` fatals when `trackTuner.pathInputFile` is non-empty while +the calibrations come from columns, naming the option to use instead (`ccdb:fTrackTunerDca`). + +Caveat: this test only works for Configurables whose default is empty. One with a non-empty +default cannot be distinguished from an unset one, so that hole stays open until the framework +can report whether an option was explicitly set. + +### Grouping columns into tables + +One table per **family of objects used together with similar validity intervals** — not one per consuming task. Geometry and material description (`GLO/Param/MatLUT`, and later `GLO/Config/GeometryAligned`, `GLO/Config/Geometry`, `/Calib/Align`; see `GRPGeomRequest` in `O2/Detectors/Base/src/GRPGeomHelper.cxx:44-60`) is one family with essentially static validity. The GRP family changes per run, and `GRPMagField` is requested per timeframe in O2 (`GRPGeomHelper.cxx:72`). Splitting on that boundary keeps a task from fetching a multi-hundred-MB LUT it never asked for. + +**Several timestamped tables can be joined onto the same BCs.** `soa::Join` works: the duplicated `aod::Timestamps` is deduplicated when `originals` is merged (`ASoA.h:172-186`), giving 4 originals, and every accessor resolves. Do not invent per-use-case tables to work around a limitation that does not exist. + +### Global state is not a lookup + +Migrating removes CCDB *queries*, not side effects. Two things stay: + +- `Propagator::initFieldFromGRP()` rebuilds or rescales a `MagneticField`, attaches it to `TGeoGlobalMagField::Instance()` and locks it (`O2/Detectors/Base/src/Propagator.cxx:107-149`). Keep it guarded on run change. +- `Propagator::Instance()->setMatLUT()` is a pointer store, so it is cheaper to redo unconditionally every timeframe — and doing so picks up a relocated column buffer for free instead of dangling. + +Everything else (mean vertex, run number) should become a direct read at the point of use, with no cached member and no `initCCDB()` helper. A cached pointer plus a "did the buffer move?" check is strictly worse than reading the column fresh. + +`Propagator` cannot itself become a column value: private constructor, deleted copy/move, singleton `Instance()` (`Propagator.h:157-201`). + +### Shared module signatures + +If a shared module takes a `StandardCCDBLoader`, change it to take the values it actually uses (`int runNumber`, `MeanVertexObject const*`) and keep a thin forwarding overload for un-migrated callers, so V1 tasks stay byte-identical. `TrackPropagationModule::fillTrackTables` does this — the two overloads differ in arity, so overload resolution is unambiguous. + +### What the migration does and does not buy + +The fetcher downloads once into a shm cache and the column stores `(handle, segment, size)` (`AnalysisCCDBHelpers.cxx:213-222`). What is shared is the **serialised blob**; each consumer still streams its own heap copy in the column getter. So expect fewer downloads, one configuration point and cross-device consistency — but not a per-device RSS reduction. For a `FlatObject` like the LUT, real memory sharing needs a zero-copy path (`FlatObject::setActualBufferAddress`) that does not exist yet. + +### Known gaps in the mechanism + +- **Run-dependent objects are not served correctly.** The analysis fetcher still hardcodes `.runNumber = 1, .runDependent = 0` for every column, even though `CCDBFetcherHelper.cxx:189-195` implements the run-dependent query paths. `GLO/Config/GRPECS` is marked "Run dependent !!!" in O2 and already has a column — verify before relying on it. Now that a run-uniform table gives the fetcher a run number per row, wiring this through is small and worth doing. +- **`getForRun` is not the same query.** `BasicCCDBManager::getForRun` resolves the run duration and queries at *mid-run* (`BasicCCDBManager.h:364-374`); a column queries at each BC's timestamp. Identical for objects with one version per run, divergent otherwise. +- **Row cardinality, not query count.** The uniformity column already collapses the *queries* to one per distinct value, but the table still carries one row per BC per column — a `FixedSizeList`, 24 B, rebuilt every timeframe. Collapsing the rows too needs a non-extension table plus lookup by value at the consumer, which does not exist yet. So a run-uniform table costs the same arrow memory as before; what it saves is the fetching. +- **Multi-run dataframes.** Skimmed datasets can span runs. Every existing consumer configures from `bcs.begin()` and applies it to the whole DF (`propagationServiceV2.cxx`, `StandardCCDBLoader.h:70-77`, `strangenessBuilderModule.h:850`), which is wrong for such a DF. Migrating preserves this bug unless it is fixed deliberately — do not claim the migration fixes it. + +### Practical gotchas + +- `DECLARE_SOA_CCDB_COLUMN` expands to code using `TClass` and `TBufferFile`, but `ASoA.h` only sees them forward-declared. A translation unit that includes the column header without otherwise pulling in `` and `` fails to compile. Include them if needed. +- A failed fetch is fatal, not silent: if `extractCCDBPayload` returns null the getter aborts naming the type, the path and the `ccdb:` option to check. A mistyped path therefore stops the job rather than dereferencing null. +- Do not add a `sources` member to a table's metadata struct. It makes the struct satisfy both `soa::with_sources` and `soa::with_sources_generator`, and `getInputMetadata` becomes ambiguous. +- Device options are matched by device *name*. Never look a task's own option up by a hardcoded name (`device.name == "propagation-service"` silently matched nothing in `propagation-service-v2`); take the running device from `initContext.services().get()`. Spell the type out rather than using `auto`, or the pre-existing `option.defaultValue.get()` becomes a dependent name and needs `template`. +- Verify with the *control*: when changing a shared header, compile an un-migrated consumer too. A new error appearing in both is yours; the same errors in both means you changed nothing for them. + $ARGUMENTS From e646a8beb8b2b3b053135b4f6f62d3c8c738ae61 Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Mon, 21 Sep 2026 10:34:37 +0200 Subject: [PATCH 2/2] Improve propagationServiceV2 * Make sure all the CCDB related objects get retrieved via the new table mechanism. * No need anymore for a centralised CCDBLoader object * Get rid of all BasicCCDBManager instances --- Common/DataModel/GloCCDBObjects.h | 73 +++++++++++++- Common/DataModel/TpcCCDBObjects.h | 43 ++++++++ Common/DataModel/TrackTunerCCDBObjects.h | 59 +++++++++++ Common/TableProducer/propagationServiceV2.cxx | 99 +++++++++---------- 4 files changed, 218 insertions(+), 56 deletions(-) create mode 100644 Common/DataModel/TpcCCDBObjects.h create mode 100644 Common/DataModel/TrackTunerCCDBObjects.h diff --git a/Common/DataModel/GloCCDBObjects.h b/Common/DataModel/GloCCDBObjects.h index f97b684b0a1..4612f8ba02a 100644 --- a/Common/DataModel/GloCCDBObjects.h +++ b/Common/DataModel/GloCCDBObjects.h @@ -33,9 +33,19 @@ /// `DECLARE_SOA_TIMESTAMPED_TABLE` with the relevant subset of columns from /// the `o2::aod::ccdbGlo` namespace rather than joining `aod::GloCCDBObjects`. /// -/// Note: MatLayerCylSet is intentionally omitted — it requires -/// `MatLayerCylSet::rectifyPtrFromFile()` after deserialisation, which the -/// CCDB column mechanism does not perform. +/// The material LUT lives in `aod::GeomCCDBObjects` rather than here: it belongs to +/// the geometry/material family, whose validity is essentially static, and keeping it +/// out means joining `aod::GloCCDBObjects` does not drag in a multi-hundred-MB object +/// nobody asked for. Join whichever tables you need — the duplicated `aod::Timestamps` +/// is deduplicated: +/// \code +/// using BCsWithLUT = soa::Join; +/// // rectifyPtrFromFile() is applied by the column's finaliser, so the object +/// // handed back is ready to use. Installing it is a pointer store, so it costs +/// // nothing to redo every timeframe — and doing so picks up a relocated column +/// // buffer for free instead of dangling. +/// o2::base::Propagator::Instance()->setMatLUT(&bcs.begin().matLUT()); +/// \endcode #ifndef COMMON_DATAMODEL_GLOCCDBOBJECTS_H_ #define COMMON_DATAMODEL_GLOCCDBOBJECTS_H_ @@ -44,8 +54,14 @@ #include #include #include +#include #include #include +#include + +#include +#include +#include namespace o2::aod { @@ -55,11 +71,62 @@ DECLARE_SOA_CCDB_COLUMN(GRPMagField, grpMagField, o2::parameters::GRPMagField, " DECLARE_SOA_CCDB_COLUMN(MeanVertex, meanVertex, o2::dataformats::MeanVertexObject, "GLO/Calib/MeanVertex"); //! DECLARE_SOA_CCDB_COLUMN(GRPECSObject, grpECS, o2::parameters::GRPECSObject, "GLO/Config/GRPECS"); //! DECLARE_SOA_CCDB_COLUMN(GRPLHCIFData, grpLHCIF, o2::parameters::GRPLHCIFData, "GLO/Config/GRPLHCIF"); //! + +/// The material LUT is a FlatObject: straight out of the ROOT streamer its internal +/// pointers are unfixed and its voxel lookup is unbuilt, so it is finalised with +/// MatLayerCylSet::rectifyPtrFromFile() before ever being handed to a task. +DECLARE_SOA_CCDB_COLUMN_FULL(MatLUT, "fMatLUT", matLUT, o2::base::MatLayerCylSet, "GLO/Param/MatLUT", 0, //! + [](o2::base::MatLayerCylSet* lut) { return o2::base::MatLayerCylSet::rectifyPtrFromFile(lut); }); + +/// Returns the GRPLHCIF object for this BC: straight from the aod::GrpLHCIFCCDBObjects +/// column when the BC table carries it, else through a CCDB query at \p timestamp. Lets a +/// shared module serve migrated and un-migrated tasks from a single code path — the +/// discarded branch is not instantiated, so an un-migrated caller keeps its CCDB query and +/// a migrated one never names the CCDB manager. +template +inline o2::parameters::GRPLHCIFData* grpLHCIFFor(TBC const& bc, TCCDB& ccdb, std::string const& path, int64_t timestamp) +{ + if constexpr (requires { bc.grpLHCIF(); }) { + // The column is the single source of truth for the path; a caller that also configured + // one is telling us two different things, and silently honouring one is how conditions + // diverge unnoticed. + if (path != std::string_view{GRPLHCIFData::query}) { + LOGP(fatal, R"(A GRPLHCIF path "{}" is configured while the object is taken from the aod::GrpLHCIFCCDBObjects column, whose path is "{}". Set it through the "ccdb:{}" option instead.)", + path, GRPLHCIFData::query, GRPLHCIFData::mLabel); + } + return &bc.grpLHCIF(); + } else { + return ccdb->template getForTimeStamp(path, timestamp); + } +} } // namespace ccdbGlo /// Full table — join with aod::BCsWithTimestamps to obtain all four objects. DECLARE_SOA_TIMESTAMPED_TABLE(GloCCDBObjects, aod::Timestamps, o2::aod::timestamp::Timestamp, 1, "GLOCCDBOBJ", //! ccdbGlo::GRPMagField, ccdbGlo::MeanVertex, ccdbGlo::GRPECSObject, ccdbGlo::GRPLHCIFData); + +/// The LHC filling scheme and beam configuration on its own. Run-uniform: GRPLHCIF is set +/// per fill and a run lies within a fill, so the fetcher resolves it once per run rather +/// than once per BC. Kept apart from aod::GloCCDBObjects so that a task needing only this +/// does not also fetch GRPMagField, MeanVertex and GRPECS — GRPLHCIF is the most widely +/// used of the four (44 files reference the path) and is usually wanted alone. +DECLARE_SOA_UNIFORM_TABLE(GrpLHCIFCCDBObjects, aod::Timestamps, o2::aod::timestamp::Timestamp, + aod::BCs, o2::aod::bc::RunNumber, 1, "GRPLHCIFCCDB", //! + ccdbGlo::GRPLHCIFData); + +/// Geometry and material description: objects which describe where the detector material +/// is, and which share an essentially static interval of validity. Kept apart from the GRP +/// family above, which changes per run (and, for GRPMagField, per timeframe). +/// The aligned/ideal geometry and the per-detector alignment objects belong here too when +/// they get columns; see GRPGeomRequest in O2 (GLO/Config/GeometryAligned, GLO/Config/Geometry, +/// /Calib/Align) for the family. +/// Join it alongside aod::GloCCDBObjects when a task needs both — the duplicated +/// aod::Timestamps is deduplicated when the joined table's originals are merged. +/// Uniform in the run number: the geometry/material description does not change within a +/// run, so the fetcher queries once per distinct run rather than once per BC. +DECLARE_SOA_UNIFORM_TABLE(GeomCCDBObjects, aod::Timestamps, o2::aod::timestamp::Timestamp, + aod::BCs, o2::aod::bc::RunNumber, 1, "GEOMCCDBOBJ", //! + ccdbGlo::MatLUT); } // namespace o2::aod #endif // COMMON_DATAMODEL_GLOCCDBOBJECTS_H_ diff --git a/Common/DataModel/TpcCCDBObjects.h b/Common/DataModel/TpcCCDBObjects.h new file mode 100644 index 00000000000..19b455e0234 --- /dev/null +++ b/Common/DataModel/TpcCCDBObjects.h @@ -0,0 +1,43 @@ +// 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. + +/// \file TpcCCDBObjects.h +/// \brief Declarative CCDB columns for TPC calibration objects. +/// +/// Unlike the geometry/material family in GloCCDBObjects.h, the drift velocity +/// genuinely varies within a run, so the table keeps the default uniformity — one +/// object per distinct timestamp — rather than collapsing per run. +/// +/// Usage: +/// \code +/// using BCsWithVDrift = soa::Join; +/// vdriftManager.update(bc.vdriftTgl()); +/// \endcode + +#ifndef COMMON_DATAMODEL_TPCCCDBOBJECTS_H_ +#define COMMON_DATAMODEL_TPCCCDBOBJECTS_H_ + +#include +#include +#include + +namespace o2::aod +{ +namespace ccdbTpc +{ +DECLARE_SOA_CCDB_COLUMN(VDriftTgl, vdriftTgl, o2::tpc::VDriftCorrFact, "TPC/Calib/VDriftTgl"); //! +} // namespace ccdbTpc + +DECLARE_SOA_TIMESTAMPED_TABLE(TpcCalibCCDBObjects, aod::Timestamps, o2::aod::timestamp::Timestamp, 1, "TPCCALIBCCDB", //! + ccdbTpc::VDriftTgl); +} // namespace o2::aod + +#endif // COMMON_DATAMODEL_TPCCCDBOBJECTS_H_ diff --git a/Common/DataModel/TrackTunerCCDBObjects.h b/Common/DataModel/TrackTunerCCDBObjects.h new file mode 100644 index 00000000000..2e4817efff3 --- /dev/null +++ b/Common/DataModel/TrackTunerCCDBObjects.h @@ -0,0 +1,59 @@ +// 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. + +/// \file TrackTunerCCDBObjects.h +/// \brief Declarative CCDB columns for the TrackTuner DCA / Q-over-pt calibrations. +/// +/// The DCA calibration is published under a different path per data-taking period, so +/// the column declares a uniformity-value-to-path mapping rather than a single path: +/// the fetcher picks the entry whose run range contains the row's run number. This +/// replaces TrackTuner::getPathInputFileAutomaticFromCCDB(), whose run ranges these are. +/// A run matching no range is a fatal error, as it was before — there is deliberately no +/// fallback entry, since silently using another period's calibration is worse than stopping. +/// +/// The ranges are the column's *default*; the whole mapping can be replaced at runtime +/// through the "ccdb:fTrackTunerDca" option, so adding a period need not be a code change. + +#ifndef COMMON_DATAMODEL_TRACKTUNERCCDBOBJECTS_H_ +#define COMMON_DATAMODEL_TRACKTUNERCCDBOBJECTS_H_ + +#include +#include + +#include + +namespace o2::aod +{ +namespace ccdbTrackTuner +{ +DECLARE_SOA_CCDB_COLUMN(TrackTunerDca, trackTunerDca, TList, //! + "520259-529691=Users/m/mfaggin/test/inputsTrackTuner/pp2023/pass4/vsPhi;" + "534998-543113=Users/m/mfaggin/test/inputsTrackTuner/pp2023/pass4/vsPhi;" + "529397-529418=Users/m/mfaggin/test/inputsTrackTuner/PbPb2023/apass4/vsPhi;" + "543437-545367=Users/m/mfaggin/test/inputsTrackTuner/PbPb2023/apass4/vsPhi;" + "549559-558807=Users/m/mfaggin/test/inputsTrackTuner/pp2024/pass1_minBias/vsPhi;" + "564356-564445=Users/m/mfaggin/test/inputsTrackTuner/OO/LHC25ae;" + "564468-564472=Users/m/mfaggin/test/inputsTrackTuner/OO/LHC25af;" + "559348-559387=Users/m/mfaggin/test/inputsTrackTuner/pp2024/ppRef/polarity_positive;" + "559408-559456=Users/m/mfaggin/test/inputsTrackTuner/pp2024/ppRef/polarity_negative"); + +DECLARE_SOA_CCDB_COLUMN(TrackTunerQOverPt, trackTunerQOverPt, TList, //! + "Users/h/hsharma/qOverPtGraphs"); +} // namespace ccdbTrackTuner + +/// Uniform in the run number: one calibration per data-taking period, so the fetcher +/// resolves the path and queries once per distinct run rather than once per BC. +DECLARE_SOA_UNIFORM_TABLE(TrackTunerCCDBObjects, aod::Timestamps, o2::aod::timestamp::Timestamp, + aod::BCs, o2::aod::bc::RunNumber, 1, "TRKTUNERCCDB", //! + ccdbTrackTuner::TrackTunerDca, ccdbTrackTuner::TrackTunerQOverPt); +} // namespace o2::aod + +#endif // COMMON_DATAMODEL_TRACKTUNERCCDBOBJECTS_H_ diff --git a/Common/TableProducer/propagationServiceV2.cxx b/Common/TableProducer/propagationServiceV2.cxx index 14f47a4c96d..0fb3f09b6b4 100644 --- a/Common/TableProducer/propagationServiceV2.cxx +++ b/Common/TableProducer/propagationServiceV2.cxx @@ -10,7 +10,8 @@ // or submit itself to any jurisdiction. /// \file propagationServiceV2.cxx -/// \brief V2: GRPMagField and MeanVertexObject sourced from aod::GloCCDBObjects declarative CCDB table. +/// \brief V2: every conditions object sourced from declarative CCDB tables, so the task +/// itself performs no CCDB query. /// \author ALICE //=============================================================== @@ -28,26 +29,22 @@ #include "Common/DataModel/EventSelection.h" #include "Common/DataModel/GloCCDBObjects.h" #include "Common/DataModel/PIDResponseTPC.h" -#include "Common/Tools/StandardCCDBLoader.h" +#include "Common/DataModel/TpcCCDBObjects.h" +#include "Common/DataModel/TrackTunerCCDBObjects.h" #include "Common/Tools/TrackPropagationModule.h" #include "Common/Tools/TrackTuner.h" -#include -#include #include #include #include #include #include -#include #include #include #include #include -#include - using namespace o2; using namespace o2::framework; @@ -66,15 +63,21 @@ using TracksWithExtra = soa::Join; using TracksExtraWithPID = soa::Join; struct propagationServiceV2 { - // Service kept for MatLUT (rectifyPtrFromFile) and - // strangenessBuilderModule (V-drift via ccdb->instance()). - // GRPMagField and MeanVertex are sourced from CCDB columns instead. - o2::framework::Configurable ccdburl{"ccdburl", "http://alice-ccdb.cern.ch", "url of the ccdb repository"}; - Service ccdb; - - // propagation stuff — ccdbLoader used only for lut + mMeanVtx (set from column) + runNumber - o2::common::StandardCCDBLoaderConfigurables standardCCDBLoaderConfigurables; - o2::common::StandardCCDBLoader ccdbLoader; + // No CCDB client of any kind: every conditions object this task consumes — magnetic + // field, mean vertex, material LUT, TPC drift correction and the two TrackTuner + // calibrations — arrives as a declarative CCDB column, so the task issues no query. + // + // No ConfigurableCCDBPath is declared either: ArrowSupport already registers a + // "ccdb:fXxx" option per declared column on the CCDB fetcher device, defaulting to the + // column's own query string. A task-side ConfigurableCCDBPath would only re-supply that + // identical default. Override a path with --ccdb:fMatLUT (and friends). + + // Everything this task needs is read straight off the CCDB columns at the point of + // use; no StandardCCDBLoader, and no CCDB query of its own. The single piece of + // retained state is the run number, needed only to avoid re-installing the magnetic + // field: that one is not a lookup but global state in the Propagator / + // TGeoGlobalMagField singletons, and installing it rebuilds or rescales the field map. + int mRunNumber = -1; // boilerplate: strangeness builder stuff o2::pwglf::strangenessbuilder::products products; @@ -93,46 +96,32 @@ struct propagationServiceV2 { o2::common::TrackPropagationConfigurables trackPropagationConfigurables; o2::common::TrackPropagationModule trackPropagation; - using BCsWithCCDB = soa::Join; + using BCsWithCCDB = soa::Join; // registry HistogramRegistry histos{"histos"}; void init(o2::framework::InitContext& initContext) { - // Only needed for MatLUT fetch and strangenessBuilderModule V-drift - ccdb->setCaching(true); - ccdb->setLocalObjectValidityChecking(); - ccdb->setURL(ccdburl.value); - // task-specific - trackPropagation.init(trackPropagationConfigurables, trackTunerObj, histos, initContext); + trackPropagation.init(trackPropagationConfigurables, trackTunerObj, histos, initContext, /*calibFromCCDBColumns=*/true); strangenessBuilderModule.init(baseOpts, v0BuilderOpts, cascadeBuilderOpts, preSelectOpts, eventSelectOpts, histos, initContext); } - // Load MatLUT once (needs rectifyPtrFromFile, kept manual), set B-field and mean vertex - // once per run from GRPMagField/MeanVertex CCDB columns. + /// Install into the Propagator the two things which are global state rather than + /// values: the magnetic field and the material LUT. template - void initCCDB(TBC const& bc0) + void initPropagator(TBC const& bc0) { - if (ccdbLoader.runNumber != bc0.runNumber()) { + if (mRunNumber != bc0.runNumber()) { LOG(info) << "Setting B-field to current " << bc0.grpMagField().getL3Current() << " A for run " << bc0.runNumber() << " from GRPMagField CCDB column"; o2::base::Propagator::initFieldFromGRP(&bc0.grpMagField()); - ccdbLoader.mMeanVtx = &bc0.meanVertex(); - ccdbLoader.runNumber = bc0.runNumber(); - } else { - // Verify the CCDB column buffer has not been replaced mid-run. - // The deserialised pointer must be stable for the lifetime of a run. - if (&bc0.meanVertex() != ccdbLoader.mMeanVtx) { - LOG(fatal) << "MeanVertex CCDB column pointer changed within run " << bc0.runNumber() << " — unexpected buffer replacement"; - } - } - if (!ccdbLoader.lut) { - LOG(info) << "Loading material look-up table for run: " << bc0.runNumber(); - ccdbLoader.lut = o2::base::MatLayerCylSet::rectifyPtrFromFile( - ccdb->template getForRun(standardCCDBLoaderConfigurables.lutPath.value, bc0.runNumber())); - o2::base::Propagator::Instance()->setMatLUT(ccdbLoader.lut); + mRunNumber = bc0.runNumber(); } + // A pointer store, so it costs nothing to redo every timeframe — and doing so + // means a relocated column buffer is picked up for free instead of dangling. + // The column's finaliser has already run MatLayerCylSet::rectifyPtrFromFile. + o2::base::Propagator::Instance()->setMatLUT(&bc0.matLUT()); } void processRealData(soa::Join const& collisions, aod::V0s const& v0s, aod::Cascades const& cascades, aod::TrackedCascades const& trackedCascades, FullTracksExtIU const& tracks, BCsWithCCDB const& bcs) @@ -140,9 +129,10 @@ struct propagationServiceV2 { if (bcs.size() == 0) { return; } - initCCDB(bcs.begin()); - trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, ccdbLoader, collisions, tracks, trackPropagationProducts, histos); - strangenessBuilderModule.dataProcess(ccdb, histos, collisions, static_cast(nullptr), v0s, cascades, trackedCascades, tracks, bcs, static_cast(nullptr), products); + auto bc0 = bcs.begin(); + initPropagator(bc0); + trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, bc0.runNumber(), &bc0.meanVertex(), &bc0.trackTunerDca(), &bc0.trackTunerQOverPt(), collisions, tracks, trackPropagationProducts, histos); + strangenessBuilderModule.dataProcess(histos, collisions, static_cast(nullptr), v0s, cascades, trackedCascades, tracks, bcs, static_cast(nullptr), products); } void processMonteCarlo(soa::Join const& collisions, aod::McCollisions const& mccollisions, aod::V0s const& v0s, aod::Cascades const& cascades, aod::TrackedCascades const& trackedCascades, FullTracksExtLabeledIU const& tracks, BCsWithCCDB const& bcs, aod::McParticles const& mcParticles) @@ -150,9 +140,10 @@ struct propagationServiceV2 { if (bcs.size() == 0) { return; } - initCCDB(bcs.begin()); - trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, ccdbLoader, collisions, tracks, trackPropagationProducts, histos); - strangenessBuilderModule.dataProcess(ccdb, histos, collisions, mccollisions, v0s, cascades, trackedCascades, tracks, bcs, mcParticles, products); + auto bc0 = bcs.begin(); + initPropagator(bc0); + trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, bc0.runNumber(), &bc0.meanVertex(), &bc0.trackTunerDca(), &bc0.trackTunerQOverPt(), collisions, tracks, trackPropagationProducts, histos); + strangenessBuilderModule.dataProcess(histos, collisions, mccollisions, v0s, cascades, trackedCascades, tracks, bcs, mcParticles, products); } void processRealDataWithPID(soa::Join const& collisions, aod::V0s const& v0s, aod::Cascades const& cascades, aod::TrackedCascades const& trackedCascades, FullTracksExtIUWithPID const& tracks, BCsWithCCDB const& bcs) @@ -160,9 +151,10 @@ struct propagationServiceV2 { if (bcs.size() == 0) { return; } - initCCDB(bcs.begin()); - trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, ccdbLoader, collisions, tracks, trackPropagationProducts, histos); - strangenessBuilderModule.dataProcess(ccdb, histos, collisions, static_cast(nullptr), v0s, cascades, trackedCascades, tracks, bcs, static_cast(nullptr), products); + auto bc0 = bcs.begin(); + initPropagator(bc0); + trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, bc0.runNumber(), &bc0.meanVertex(), &bc0.trackTunerDca(), &bc0.trackTunerQOverPt(), collisions, tracks, trackPropagationProducts, histos); + strangenessBuilderModule.dataProcess(histos, collisions, static_cast(nullptr), v0s, cascades, trackedCascades, tracks, bcs, static_cast(nullptr), products); } void processMonteCarloWithPID(soa::Join const& collisions, aod::McCollisions const& mccollisions, aod::V0s const& v0s, aod::Cascades const& cascades, aod::TrackedCascades const& trackedCascades, FullTracksExtLabeledIUWithPID const& tracks, BCsWithCCDB const& bcs, aod::McParticles const& mcParticles) @@ -170,9 +162,10 @@ struct propagationServiceV2 { if (bcs.size() == 0) { return; } - initCCDB(bcs.begin()); - trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, ccdbLoader, collisions, tracks, trackPropagationProducts, histos); - strangenessBuilderModule.dataProcess(ccdb, histos, collisions, mccollisions, v0s, cascades, trackedCascades, tracks, bcs, mcParticles, products); + auto bc0 = bcs.begin(); + initPropagator(bc0); + trackPropagation.fillTrackTables(trackPropagationConfigurables, trackTunerObj, bc0.runNumber(), &bc0.meanVertex(), &bc0.trackTunerDca(), &bc0.trackTunerQOverPt(), collisions, tracks, trackPropagationProducts, histos); + strangenessBuilderModule.dataProcess(histos, collisions, mccollisions, v0s, cascades, trackedCascades, tracks, bcs, mcParticles, products); } PROCESS_SWITCH(propagationServiceV2, processRealData, "process real data", true);